-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathindex.html
More file actions
1301 lines (1201 loc) · 56.9 KB
/
index.html
File metadata and controls
1301 lines (1201 loc) · 56.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html>
<head>
<title>Pharmacokinetic Modeling and Visualization Tool</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<script type="text/javascript">
// set the pyodide files URL (packages.json, pyodide.asm.data etc)
window.languagePluginUrl = 'https://cdn.jsdelivr.net/pyodide/v0.16.1/full/';
</script>
<script src="https://cdn.jsdelivr.net/pyodide/v0.16.1/full/pyodide.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/8.1.0/math.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/numeric/1.2.6/numeric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
</head>
<body>
<h1>Pharmacokinetic Modeling and Visualization Tool</h1>
<h2 style="text-align: left;">Background</h2>
<p style="padding-bottom: 10px;"> Pharmacokinetics is the study of drug absorption, distribution, metabolism, and excretion over time.
We often want to apply pharmacokinetic principles to the safe and effective therapeutic management of drugs in patients, but it may be
difficult to measure drug concentrations in specific tissues. While we can easily measure drug concentrations in places like blood,
urine, saliva, it can be much harder or impossible to measure drug concentrations in other tissues like in the subcutaneous space.
However, easily obtained measurements can be leveraged to develop pharmacokinetic models to predict drug concentrations in other
tissues where we cannot feasibly collect data from. Compartment models can be generated by dividing the body into compartments that
describe the absorption, distribution and elimination. Pharmacokinetic models can be extremely valuable to drug research and
development, shedding light to how a drug candidate interacts with the target organism.
</p>
<p>
This application is designed to aid researchers who are currently developing and researching drugs for deployment in animal models
or other subjects. This tool consists of two parts that operate entirely independently, although both can be used at the same time.
<br>
<br>
For the full documentation, please visit the Github repository where a README has been uploaded to go into more detail
<a href="https://github.com/peytoncchen/PK-Visualization/blob/main/README.md">here.</a>
<br>
<br>
It should also be noted that supporting files for this application including a commented version of the Python code in this web application
and a sample Jupyter Notebook for download that mimics the functionality of this application (allowing for greater end-user customizability)
is available <a href="https://github.com/peytoncchen/PK-Py">here.</a>
<br>
<br>
<b>Note:</b> you will notice some stutters as packages load in as soon as you press the "Load-in Packages" button in part 2.
This is completely normal. Additionally, the "Calculate k-values" button in part 2 is grayed out until said packages load -
this can take up to 30 seconds. The Rate Constant Calculator runs <b>ONLY</b> in <b>Chrome</b> and <b>Firefox.</b>
<b>Safari is only compatible with the first part of the calculator.</b>
</p>
<h2 style="text-align: left;">Overview</h3>
<ul>
<li><b>Part 1</b> is the Pharmacokinetic Data Modeling Calculator and Visualizer
<ul>
<li>The user inputs rate constants (k values) and initial values for each compartment and the application solves
the system of differential equations and displays mass vs. time or concentration vs. time curves for each
compartment.
</li>
</ul>
</li>
<li>
<b>Part 2</b> is the Rate Constant Calculator
<ul>
<li>
The user uploads mass vs. time or concentration vs. time data in a CSV file and the application solves for
best fit rate constants (k-values) for the system of differential equations for the data.
</li>
</ul>
</li>
</ul>
<h2 style="text-align: left;">Compartment Model and Differential Equations</h4>
<p align="center">
<img src="examples/cpm.png" width="600">
</p>
<p>
This example system illustrates the model set-up for both parts of the application. In both parts, the user will be able to
define the number of compartments in their model system. Each compartment starts with an initial mass and the application
uses first-order kinetics to model flow in and out of each compartment. We have chosen to use a 3 compartment model as an
example for the inputs and corresponding system of differential equations. The flow is dictated by the rate constants labeled
as k<sub>1</sub>, k<sub>2</sub>, and k<sub>3</sub> in our example picture. Initial mass in each compartment is given by
X<sub>1,0</sub>, X<sub>2,0</sub>, X<sub>3,0</sub>. X<sub>1</sub>, X<sub>2</sub>, and X<sub>3</sub> correspond to the mass
in compartments 1, 2, and 3 at a given time t.
</p>
<p>
The differential equations used in the calculator, resemble the following, where each compartment's derivative is modified
by the incoming mass and outgoing mass, where *X* represents the mass of a compartment.
</p>
<p align="center">
<img src="examples/diffeq.png" width="200">
</p>
<h2 style="text-align: left;">Instructions</h3>
<h3 style="text-align: left;">Part 1: Pharmacokinetic Data Modeling Calculator and Visualizer</h3>
<p>
The Pharmacokinetic Data Modeling Calculator and Visualizer allows users to set up a system of ordinary differential equations
(ODEs) describing a simple compartment model with first-order kinetics and then solve and plot the drug profiles in each
compartment over time.
</p>
<h4 style="text-align: left;">Inputs:</h4>
<ul>
<li>
Number of Compartments:
<ul>
<li>The number of compartments that you have in your model.</li>
</ul>
</li>
<li>
Compartment Info:
<ul>
<li>
Name (optional): for use in downloadable CSV file of generated data.
</li>
<li>
Initial values: Mass at t = 0 in each compartment.
</li>
<li>
k-value: rate constant for each compartment. The k-value input field in row 1 represents
the k-value from Compartment 1 to 2 (k<sub>1</sub> in the compartment model example) and so on.
k-values should have consistent units of time with time range specified (below). k-values should
have units of 1/t.
</li>
</ul>
</li>
<li>
Time range and units:
<ul>
<li>
Specify the time range (0 to t) for the generated data and the associated units.
Default is 250 minutes.
</li>
</ul>
</li>
<li>
Number of steps:
<ul>
<li>
Specify the number of steps that you would like the integrator to take.
Default is 100,000. Warning: too many steps will result in a lot of RAM usage
and may take a long time.
</li>
</ul>
</li>
<li>
Mass or concentration:
<ul>
Specify whether you want the y-values generated to be in mass or concentration.
The default output is mass (of the same units as initial values). If you would like
to have outputs given as concentrations, another row will appear called the "Animal Model Constant"
prompting you to fill out a constant and select which compartments you want it to affect.
</ul>
</li>
</ul>
<p>
<b>Animal Model Constant:</b>
<br>
While modeling mass transfer between systems is typically easiest,
often to compare the amount of drug in the body to the efficacious therapeutic dose (or cytotoxic dose)
a concentration is required. The Animal Model Constant is a conversion factor that allows you to convert
mass to concentration (typically (blood volume)<sup>-1</sup> or (distribution volume)<sup>-1</sup>. This will vary between
animal models and so users will have to determine the conversion appropriate for their system.
At present this application only supports one conversion factor and so for systems where one would need
to use a different conversion factor for each compartment we recommend only converting to concentration
for the compartment of interest or keeping all compartments in terms of mass. You are allowed to put in
mathematical expressions or numbers here.
</p>
<p style="padding-bottom: 10px;">
After inputting the parameters for your system, you can press the Calculate & Graph button which will solve the
system of ODEs using a 4th order Runge-Kutta ODE solver and generate mass vs. time or concentration vs. time curves
for each compartment. The results will be graphed and can also be downloaded as a CSV to be plotted by the user.
</p>
<h3 style="text-align: left;">Part 2: Rate Constant Calculator</h3>
<p>
The Rate Constant Calculator allows users to set up a system of ordinary differential equations (ODEs) describing
a simple compartment model and input a CSV containing experimental data, then have an optimizing algorithm run to
fit the data to a set of k-values.
</p>
<h4 style="text-align: left;">Inputs:</h4>
<ul>
<li> Press the <b>"Load-in packages"</b> button! (to the right of the Rate Constant Calculator title)</li>
<li>
Choose CSV file to upload:
<ul>
<li>
Clicking this field will bring up an input dialog for you to upload a CSV. Please follow the format of
the CSV <a href="https://github.com/peytoncchen/PK-Visualization/blob/main/examples/myinput.csv">here.</a>
Essentially, you will want the first row to be headers, your first column to be time, and your second column
to be the dependent variable. Any additional columns are not supported currently and will produce errors.
Please ensure that you delete any data that you do not want to consider any entry (time, value) that
is missing either value will just result in the entire row being ignored. Please also note that you <b>must</b>
utilize normalized data.
</li>
</ul>
</li>
<li>
Number of Compartments, Mass or Concentration, Animal Model Constant:
<ul>
<li>
Same as part 1.
</li>
</ul>
</li>
<li>
CSV data represents:
<ul>
<li>
Please input what compartment your CSV data represents. In the example case, our data represented Compartment 3.
</li>
</ul>
</li>
<li>
Compartment Info:
<ul>
<li>
Please indicate the bounds for k-values that you are unsure of and the initial value that the compartment starts off at.
If you do know the value, please tick the "Constrain" box and fill in what the k-value is for that compartment.
</li>
</ul>
</li>
</ul>
<p>
Finally, you can press the "Calculate k-values" button which will parse your inputs and return final k-value results for each
compartment. For specifics as to how this is done check-out the Python files which the code from this app is based on and the
methodologies section in the full <a href="https://github.com/peytoncchen/PK-Visualization/blob/main/README.md">documentation</a>
for this web application. There is also an example Jupyter Notebook that can be customized to your
specific situation. The link to that repository is <a href="https://github.com/peytoncchen/PK-Py">here.</a> Enjoy and best of luck
with your research!
</p>
<hr class="solid">
<h2 style="padding-top: 10px;">Pharmacokinetic Data Modeling Calculator and Visualizer</h2>
<div style="display: flex;">
<div style="margin:auto;">
<form id="inputform">
<table cellspacing="10" cellpadding="10" id="input_table">
<tr>
<td>
<label for="comps">Number of Compartments</label>
</td>
<td>
<select id="comps" onchange="gen_k_fields('comps', 'kval_table', 'massconc')">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="kvalues">Compartment Info</label>
</td>
<td>
<table id="kval_table">
</table>
</td>
</tr>
<tr>
<td>
<label for="timerange">Time Range and Units</label>
</td>
<td>
<input type="number" id="timerange" value="250" onchange="reset_output()" style="width: 70px">
<input id="time-units" value="minutes" onchange="reset_output()" style="width: 70px">
</td>
</tr>
<tr>
<td>
<label for="nsteps">Number of steps</label>
</td>
<td>
<input type="number" id="nsteps" value="100000" onchange="reset_output()" style="width: 70px">
</td>
</tr>
<tr>
<td>
<label for="massconc">Mass or Concentration</label>
</td>
<td>
<select id="massconc" onchange="gen_conc_field('massconc', 'input_table', 5, 'comps')">
<option value="Mass">Mass</option>
<option value="Concentration">Concentration</option>
</select>
</td>
</tr>
<tr id="button_row">
<td>
<button type="button" id="calcgraph" onclick="ode()">Calculate & Graph</button>
</td>
<td id="dwnld_box">
<button type="button" id="dwnld" onclick="download()" disabled>Download</button>
<label for="filename" id="filename-label" style="font-size: 13px; opacity: 0.4;">Filename:</label>
<input id="filename" style="width: 100px" disabled>
<div id="select_dwnld"></div>
</td>
</tr>
</table>
</form>
</div>
<div style="margin:auto;">
<canvas style="width: 600px; height: 375px;"id="my_graph"></canvas>
<div style="align-items: center;">
<p id="output" style="padding-left: 15px;"></p>
</div>
</div>
</div>
<div>
<hr class="solid">
<div style="display: flex; justify-content: center; position: relative;">
<h2 style="padding-top: 10px; padding-right: 10px;">Rate Constant Calculator</h2>
<div style="position: relative; right: 0; padding-top: 10px;">
<label for="loadkpg" id="labelloadpkg" style="padding-right: 5px;">Activate it! →</label>
<button type="button" id="loadpkg" onclick="load_pkgs()">Load-in Packages</button>
</div>
</div>
<div style="display:flex; align-items:center; justify-content: center;">
<form id="inputform2">
<table style="float:left;" cellspacing="10" cellpadding="10" id="input_table2">
<tr>
<td>
<label for="file_csv">Choose CSV file to upload</label>
</td>
<td>
<input type="file" id="file_csv" accept=".csv,.txt">
</td>
</tr>
<tr>
<td>
<label for="comps2">Number of Compartments</label>
</td>
<td>
<select id="comps2" onchange="gen_k_fields('comps2', 'kval_table2', 'massconc2')">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="massconc2">Mass or Concentration</label>
</td>
<td>
<select id="massconc2" onchange="gen_conc_field('massconc2', 'input_table2', 3, 'comps2')">
<option value="Mass">Mass</option>
<option value="Concentration">Concentration</option>
</select>
</td>
</tr>
<tr>
<td>
<label>CSV data represents</label>
</td>
<td id="calck_cell">
</td>
</tr>
<tr>
<td>
<button type="button" id="calck" onclick="calculate_k()" disabled>Calculate k-values</button>
</td>
</tr>
</table>
<table style="float:left;" cellspacing="10" cellpadding="10" id="kval_table2">
</table>
</form>
<p id="finalresult" style="padding-left: 20px;"></p>
</div>
</div>
<hr class="solid">
<h2 style="text-align: left;">Troubleshooting?</h2>
<p style="padding-bottom: 10px;">
Visit the full <a href="https://github.com/peytoncchen/PK-Visualization/blob/main/README.md">documentation</a> page for tips
on troubleshooting.
</p>
<script>
/* On change of number of compartments, this function will create the needed input components depending on
* whether the change is detected in the part 1 calculator or part 2 calculator.
*/
function gen_k_fields(comps, k_table, conc) {
var num_fields = parseInt(document.getElementById(comps).value);
var table = document.getElementById(k_table);
// reset innerHTML
if (k_table == "kval_table") {
table.innerHTML = "<tr>\n<th>Number</th>\n<th>Name (optional)</th>\n<th>Initial value</th>\n<th>k-value</th></tr>";
} else {
table.innerHTML = `<tr>\n<th>Compartment</th>\n<th>Low Bound</th>\n<th>High Bound</th>\n<th>Initial value
</th><th>Constrain</th><th>k-value</th></tr>`;
}
for (var i = 0; i < num_fields; i++) {
var row = document.createElement("tr");
var cell_label = document.createElement("td");
var label = document.createElement("label");
label.innerHTML = (i + 1).toString();
cell_label.appendChild(label);
row.appendChild(cell_label);
if (k_table == "kval_table") {
var cell_name = document.createElement("td");
var name_field = document.createElement("input");
name_field.setAttribute("id", "name" + (i + 1));
name_field.setAttribute("style", "width: 100px");
name_field.setAttribute("onchange", "reset_output()");
cell_name.appendChild(name_field);
row.appendChild(cell_name);
var cell_ival = document.createElement("td");
var ival_field = document.createElement("input");
ival_field.setAttribute("id", "i_val" + (i + 1));
ival_field.setAttribute("style", "width: 70px");
ival_field.setAttribute("type", "number");
ival_field.setAttribute("onchange", "reset_output()");
cell_ival.appendChild(ival_field);
row.appendChild(cell_ival);
var cell_k = document.createElement("td");
var k_field = document.createElement("input");
k_field.setAttribute("id", "k_val" + (i + 1));
k_field.setAttribute("style", "width: 70px");
k_field.setAttribute("onchange", "reset_output()");
k_field.setAttribute("type", "number");
cell_k.appendChild(k_field);
row.appendChild(cell_k);
} else {
var cell_l_bou = document.createElement("td");
var l_bou_field = document.createElement("input");
l_bou_field.setAttribute("id", "l_bou" + (i + 1));
l_bou_field.setAttribute("style", "width: 70px");
l_bou_field.setAttribute("type", "number");
cell_l_bou.appendChild(l_bou_field);
row.appendChild(cell_l_bou);
var cell_h_bou = document.createElement("td");
var h_bou_field = document.createElement("input");
h_bou_field.setAttribute("id", "h_bou" + (i + 1));
h_bou_field.setAttribute("style", "width: 70px");
h_bou_field.setAttribute("type", "number");
cell_h_bou.appendChild(h_bou_field);
row.appendChild(cell_h_bou);
var cell_ival2 = document.createElement("td");
var ival2 = document.createElement("input");
ival2.setAttribute("id", "i_val" + (i + 1) + "2");
ival2.setAttribute("style", "width: 70px");
ival2.setAttribute("type", "number");
cell_ival2.appendChild(ival2);
row.appendChild(cell_ival2);
var cell_const = document.createElement("td");
var option = document.createElement("input");
option.setAttribute("type", "checkbox");
option.setAttribute("class", "constrain");
option.setAttribute("value", (i + 1).toString());
option.setAttribute("style", "padding: 5px;");
option.setAttribute("onchange", "bou_off()");
cell_const.appendChild(option);
row.appendChild(cell_const);
var cell_k_try = document.createElement("td");
var k_try = document.createElement("input");
k_try.setAttribute("id", "k_try" + (i + 1));
k_try.setAttribute("style", "width: 70px");
k_try.setAttribute("type", "number");
k_try.setAttribute("disabled", true);
cell_k_try.appendChild(k_try);
row.appendChild(cell_k_try);
}
table.appendChild(row);
}
if (document.getElementById(conc).value == "Concentration") {
var row = k_table == "kval_table" ? 5 : 3;
var inp_table = k_table == "kval_table" ? "input_table" : "input_table2";
document.getElementById(inp_table).deleteRow(row)
gen_conc_field(conc, inp_table, row, comps);
}
if (k_table == "kval_table") {
reset_output();
} else {
gen_sel_data(num_fields);
}
}
// For part 2, generates radio buttons for user to select which compartment their CSV data represents.
function gen_sel_data(num_fields) {
var cell = document.getElementById("calck_cell");
cell.innerHTML = "";
for (var i = 0; i < num_fields; i++) {
var radio_but = document.createElement("input");
radio_but.setAttribute("type", "radio");
radio_but.setAttribute("name", "select")
radio_but.setAttribute("value", (i + 1).toString());
if (i == 0) {
radio_but.setAttribute("checked", true)
}
cell.appendChild(radio_but);
var label = document.createElement("label");
label.setAttribute("style", "font-size: 13px; padding: 5px");
label.innerHTML = "Compartment " + (i + 1);
cell.appendChild(label);
if (num_fields > 3 && i == 2) {
var brk2 = document.createElement("br");
cell.appendChild(brk2);
}
}
}
/* Creates a group of check boxes for a variety of different functions including animal model constant selection
* for both parts and download compartment selection for part 1.
*/
function create_checks(div, class_nm, tb_row, cell_input, comps) {
var num_fields = parseInt(document.getElementById(comps).value);
for (var i = 0; i < num_fields; i++) {
var option = document.createElement("input");
option.setAttribute("type", "checkbox")
option.setAttribute("class", class_nm);
option.setAttribute("value", (i + 1).toString());
div.appendChild(option);
var opt_lbl = document.createElement("label");
opt_lbl.setAttribute("style", "font-size: 13px; padding: 5px;");
opt_lbl.innerHTML = "Compartment " + (i + 1);
div.appendChild(opt_lbl);
if (num_fields > 3 && i == 2) {
var brk2 = document.createElement("br");
div.appendChild(brk2);
}
}
cell_input.appendChild(div);
}
/* Creates concentration input field for both a concentration constant and compartment selection to modify with
* the concentration constant.
*/
function gen_conc_field(option, inp_table, row, comps) {
if (document.getElementById(option).value == "Concentration") {
var row = document.getElementById(inp_table).insertRow(row);
var cell_label = document.createElement("td");
var label = document.createElement("label");
label.innerHTML = "Animal Model Constant";
cell_label.appendChild(label);
row.appendChild(cell_label);
var cell_input = document.createElement("td");
var input_field = document.createElement("input");
input_field.setAttribute("id", "a_const");
input_field.setAttribute("style", "width: 70px");
input_field.setAttribute("onchange", "reset_output()");
cell_input.appendChild(input_field);
var div = document.createElement("div");
var chk_class = inp_table == "input_table" ? "checks" : "checks2"
create_checks(div, chk_class, row, cell_input, comps);
row.appendChild(cell_input);
} else {
document.getElementById(inp_table).deleteRow(row)
}
if (inp_table == "input_table") {
reset_output();
}
}
/* Depending on whether constrain box is checked, toggles whether bounds or k-value inputs are open or
* disabled.
*/
function bou_off() {
var num_fields = parseInt(document.getElementById("comps2").value);
var indexes = parse_checks("constrain", num_fields)
for (var i = 0; i < num_fields; i++) {
if (indexes.includes(i + 1)) {
var lelem = document.getElementById("l_bou" + (i + 1));
var helem = document.getElementById("h_bou" + (i + 1));
document.getElementById("k_try" + (i + 1)).disabled = false;
lelem.disabled = true;
lelem.value = "";
helem.disabled = true;
helem.value = "";
} else {
document.getElementById("h_bou" + (i + 1)).disabled = false;
document.getElementById("l_bou" + (i + 1)).disabled = false;
var k_try = document.getElementById("k_try" + (i + 1));
k_try.disabled = true;
k_try.value = "";
}
}
}
/* For part 1 resets output by clearing graph datasets and resetting all generated output and enabling/disabling
* necessary buttons and input fields.
*/
function reset_output() {
document.getElementById("dwnld").disabled = true;
document.getElementById("select_dwnld").innerHTML = "";
document.getElementById("filename").disabled = true;
document.getElementById("filename-label").style.opacity = 0.4;
document.getElementById("filename").value = "";
document.getElementById("calcgraph").disabled = false;
document.getElementById("output").innerHTML = "";
for (var i = 0; i < 6; i++) {
window.chart.data.datasets[i].data = [];
}
window.chart.update();
}
</script>
<script>
/* Adapted from Ricky Reusser (2015) ode-rk4. Integrates a system of ODEs using the
* Fourth Order Runge-Kutta (RK-4) Method.
*/
var Integrator = function Integrator(y0, deriv, t, dt) {
// Bind variables to this:
this.deriv = deriv;
this.y = y0;
this.n = this.y.length;
this.dt = dt;
this.t = t;
// Create array to store all the y-values for graphing:
this.y_collection = [];
// Create a scratch array into which we compute the derivative:
this._ctor = this.y.constructor;
this._w = new this._ctor(this.n);
this._k1 = new this._ctor(this.n);
this._k2 = new this._ctor(this.n);
this._k3 = new this._ctor(this.n);
this._k4 = new this._ctor(this.n)
}
Integrator.prototype.step = function() {
this.deriv(this._k1, this.y, this.t);
for (var i = 0; i < this.n; i++) {
this._w[i] = this.y[i] + this._k1[i] * this.dt * 0.5;
}
this.deriv(this._k2, this._w, this.t + this.dt * 0.5);
for (var i = 0; i < this.n; i++) {
this._w[i] = this.y[i] + this._k2[i] * this.dt * 0.5;
}
this.deriv(this._k3, this._w, this.t + this.dt * 0.5);
for (var i = 0; i < this.n; i++) {
this._w[i] = this.y[i] + this._k3[i] * this.dt;
}
this.deriv(this._k4, this._w, this.t + this.dt);
var dto6 = this.dt / 6.0;
for (var i = 0; i < this.n; i++) {
this.y[i] += dto6 * (this._k1[i] + 2*this._k2[i] + 2*this._k3[i] + this._k4[i]);
}
this.t += this.dt;
this.y_collection.push([...this.y]);
return this;
}
Integrator.prototype.steps = function( n ) {
for (var step = 0; step < n; step++) {
this.step();
}
return this;
}
</script>
<script>
/* --------------------- Part 1 PK Data Modeling Global Variables --------------------- */
var integrator;
var full_dataset;
/* --------------------------------- Part 1 functions --------------------------------- */
// Parses and returns an array representation of the k-value table labeled "Compartment Info".
function parse_k_table() {
var k_array = [];
var init_array = [];
var num_fields = parseInt(document.getElementById("comps").value);
for (var i = 0; i < num_fields; i++) {
k_array.push(parseFloat(document.getElementById("k_val" + (i + 1)).value));
init_array.push(parseFloat(document.getElementById("i_val" + (i + 1)).value));
}
return [k_array, init_array];
}
// Parses and returns time range, the units of the time range and the number of steps.
function parse_time_nsteps() {
var time = parseFloat(document.getElementById("timerange").value);
var t_units = document.getElementById("time-units").value
var nsteps = parseInt(document.getElementById("nsteps").value);
return [time, nsteps, t_units];
}
// Parses and returns array selected indexes given a class of check boxes.
function parse_checks(class_nm, num_fields) {
var checks = document.getElementsByClassName(class_nm);
var select_indexes = [];
for (var i = 0; i < num_fields; i++) {
if (checks[i].checked) {
select_indexes.push(parseInt(checks[i].value));
}
}
return select_indexes;
}
// Given array result and time range and units information, parses and returns an HTML string.
function process_out(array, time, t_units) {
var out_str = "<b><u>Final Results at " + time + " " + t_units + "</u></b><br>";
for (var i = 0; i < array.length; i++) {
out_str += ("Compartment " + (i + 1) + ": " + (isNaN(array[i]) ? "error": array[i].toPrecision(4))
+ "<br>");
}
return out_str;
}
// Global variables for the chart in part 1
var colors = ["#70B9B5", "#EB8138", "#6E6CC0", "#E64445", "#8E99A0", "#38A9C7"] // These colors are the bomb
var chart_data = {
datasets: [
{
id: "comp_1_data",
label: "Compartment 1",
borderColor: colors[0],
showLine: true,
data: [],
pointRadius: 0,
},
{
id: "comp_2_data",
label: "Compartment 2",
borderColor: colors[1],
showLine: true,
data: [],
pointRadius: 0,
},
{
id: "comp_3_data",
label: "Compartment 3",
borderColor: colors[2],
showLine: true,
data: [],
pointRadius: 0,
},
{
id: "comp_4_data",
label: "Compartment 4",
borderColor: colors[3],
showLine: true,
data: [],
pointRadius: 0,
},
{
id: "comp_5_data",
label: "Compartment 5",
borderColor: colors[4],
showLine: true,
data: [],
pointRadius: 0,
},
{
id: "comp_6_data",
label: "Compartment 6",
borderColor: colors[5],
showLine: true,
data: [],
pointRadius: 0,
},
]
};
/* Takes in y (and makes x by itself with linspace from numeric) and produces a shortened version of the
* resulting arrays for easier load on the export tool. It will take the number of entries and divide by 100.
*/
function shorten_out(y) {
const len = y.length;
var x = numeric.linspace(0, parseFloat(document.getElementById("timerange").value),
parseInt(document.getElementById("nsteps").value));
var shortened_x = [];
var shortened_y = [];
for (var i = 0; i < len; i++) {
if (i % 100 == 0) {
shortened_x.push(x[i]);
shortened_y.push(y[i]);
}
}
return [shortened_x, shortened_y];
}
// Graphs data processed and set to global variables onto the chart for part 1.
function graph() {
var shortened_arr = shorten_out(integrator.y_collection);
var x_coords = shortened_arr[0];
var y_coords = shortened_arr[1];
if (x_coords.length != y_coords.length) {
alert("Array generation error. Please refresh page.");
}
var num_datasets = y_coords[0].length; // All the same length
// Empty everything out first
for (var i = 0; i < 6; i++) {
window.chart.data.datasets[i].data = [];
}
// Populate datasets that exist
for (var i = 0; i < num_datasets; i++) {
window.chart.data.datasets[i].data = x_coords.map((x_data, j) => ({
x: +x_data.toPrecision(4), y: +y_coords[j][i].toPrecision(5)}));
}
window.chart.update();
return [x_coords, y_coords];
}
/* Calls all the parsing functions and integrates the set of differential equations. Will also call the graph function
* to graph everything and set the output to the appropriate values.
*/
function ode() {
var ktable = parse_k_table();
var time_nsteps = parse_time_nsteps();
var num_fields = parseInt(document.getElementById("comps").value);
var y0 = ktable[1];
var k = ktable[0];
var time = time_nsteps[0];
var nsteps = time_nsteps[1];
var dt = time/nsteps;
var a_const = 1;
var select_indexes = [];
if (document.getElementById("massconc").value == "Concentration") {
a_const = math.evaluate(document.getElementById("a_const").value);
select_indexes = parse_checks("checks", num_fields);
}
var dydt = function(dydt, y, t) {
for (var i = 0; i < num_fields; i++) {
if (i == 0 && select_indexes.includes(i + 1)) {
dydt[0] = k[0] * -y[0] / (a_const);
} else if (select_indexes.includes(i + 1)) {
dydt[i] = k[i] * -y[i] + k[i - 1] * y[i - 1] / (a_const);
} else if (i == 0) {
dydt[0] = k[0] * -y[0];
} else {
dydt[i] = k[i] * -y[i] + k[i - 1] * y[i - 1];
}
}
}
integrator = new Integrator(y0, dydt, 0, dt);
integrator.steps(nsteps);
document.getElementById("output").innerHTML = process_out(integrator.y, time_nsteps[0], time_nsteps[2]);
window.chart.options.scales.xAxes[0].scaleLabel.labelString = "Time " + "(" + document.getElementById("time-units").value
+ ")";
window.chart.options.scales.yAxes[0].scaleLabel.labelString = document.getElementById("massconc").value;
full_dataset = graph();
window.chart.update();
var select_dwnld = document.getElementById("select_dwnld");
create_checks(select_dwnld, "dwnld_check", document.getElementById("button_row"),
document.getElementById("dwnld_box"), "comps");
document.getElementById("dwnld").disabled = false;
document.getElementById("filename").disabled = false;
document.getElementById("filename-label").style.opacity = 1;
document.getElementById("calcgraph").disabled = true;
}
// Gets the names of the compartments if they exist and returns them in an array to be used for headers in the exported CSV.
function get_headers(select_indexes) {
var header_arr = [];
header_arr.push("Time " + "(" + document.getElementById("time-units").value + ")");
for (var i = 0; i < select_indexes.length; i++) {
var name_id = "name" + select_indexes[i];
header_arr.push(document.getElementById(name_id).value + " (Comp " + select_indexes[i] + ")");
}
return header_arr;
}
// Processes and parses full_dataset modified by integrator in ode() to be exported to CSV.
function process_array_dwnld(select_indexes) {
var x_coords = full_dataset[0];
var y_coords = full_dataset[1];
var result = [];
result.push(get_headers(select_indexes));
for (var i = 0; i < x_coords.length; i++) {
result.push([(+x_coords[i].toPrecision(4)).toLocaleString("fullwide", {useGrouping: false})]);
}
for (var i = 0; i < y_coords.length; i++) {
for (var j = 0; j < select_indexes.length; j++) {
result[i + 1].push((+y_coords[i][select_indexes[j] - 1].toPrecision(5)).toLocaleString("fullwide",
{useGrouping: false}));
}
}
return result;
}
// Export an array to CSV format to the user's download location.
function export_to_csv(filename, rows) {
var processRow = function(row) {
var finalVal = '';
for (var j = 0; j < row.length; j++) {
var innerValue = row[j] === null ? '' : row[j].toString();
var result = innerValue.replace(/"/g, '""');
if (result.search(/("|,|\n)/g) >= 0) {
result = '"' + result + '"';
}
if (j > 0) {
finalVal += ',';
}
finalVal += result;
}
return finalVal + '\n';
};
var csvFile = '';
for (var i = 0; i < rows.length; i++) {
csvFile += processRow(rows[i]);
}
var blob = new Blob([csvFile], {type: "text/csv;charset=utf-8;"});
if (navigator.msSaveBlob) { // IE 10+
navigator.msSaveBlob(blob, filename);
} else {
var link = document.createElement("a");
if (link.download !== undefined) { // feature detection
// Browsers that support HTML5 download attribute
var url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute("download", filename);
link.style.visibility = "hidden";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}
// Download helper that is called during ode() routine.
function download() {
var select_indexes = parse_checks("dwnld_check", parseInt(document.getElementById("comps").value));
var rows = process_array_dwnld(select_indexes);
var filename = document.getElementById("filename").value ? document.getElementById("filename").value : "export";
export_to_csv(filename + ".csv", rows);
}
/* -- Part 2 k-val Calc Global Variables (More global vars because pyodide needs to access them) -- */
var in_csv_data;
var represents;
var k_table2;
var dydt2;
var num_fields2;
var a_const2;
var select_indexes2;
/* --------------------------------- Part 2 functions --------------------------------- */
// Function that attempts to load packages
function load_pkgs() {
languagePluginLoader.then( function () {
document.getElementById("labelloadpkg").innerHTML = "Loading... can take ~1 min ";
console.log(pyodide.runPython(`
import sys
import math
sys.version
`));
pyodide.loadPackage(['numpy', 'scipy']).then(() => {
pyodide.runPython('import numpy as np');
pyodide.runPython('from scipy.optimize import brute');
document.getElementById("calck").disabled = false; // Button is disabled until everything has loaded.
}).catch(err => alert("Packages failed to load in. Try Chrome or Firefox."));
});
document.getElementById("loadpkg").disabled = true; // don't need to load packages twice!
}
// Given text format of inputted CSV file processes it and returns an array.
function parse_csv(allText) {
var allTextLines = allText.split(/\r\n|\n/);
var headers = allTextLines[0].split(',');
var lines = [];
for (var i = 1; i < allTextLines.length; i++) {
var data = allTextLines[i].split(',');
if (data.length == headers.length) {
var inner_arr = [];
for (var j = 0; j < headers.length; j++) {
var point = parseFloat(data[j]);
if (!isNaN(point)) { // not adding NaN lines
inner_arr.push(point);
}
}
lines.push(inner_arr);