dzs commited on
Commit
5384735
·
1 Parent(s): 350f14a

restructured layout code so easier to edit

Browse files
Files changed (1) hide show
  1. main_layout.py +697 -572
main_layout.py CHANGED
@@ -1,21 +1,130 @@
1
  from dash import html, dcc
2
  import dash_bootstrap_components as dbc
 
 
 
3
  from data.functions import *
4
  from figure import build_figure
5
 
6
- def build_main_layout() -> html.Div:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  n = len(soluteData)
8
- df0 = pd.DataFrame({
9
- "CASRN": soluteData["CASRN"].to_numpy(),
10
- "Condition 1": np.zeros(n),
11
- "Cond1_err_plus": np.zeros(n),
12
- "Cond1_err_minus": np.zeros(n),
13
- "Condition 2": np.zeros(n),
14
- "Cond2_err_plus": np.zeros(n),
15
- "Cond2_err_minus": np.zeros(n),
16
- })
 
 
 
 
17
  placeholder_fig = build_figure(df0)
18
- placeholder_fig.update_yaxes(range=[-2, 0])
 
 
 
 
19
  placeholder_fig.add_annotation(
20
  text="Click 'Calculate' to run model",
21
  xref="paper",
@@ -23,590 +132,606 @@ def build_main_layout() -> html.Div:
23
  x=0.5,
24
  y=0.5,
25
  showarrow=False,
26
- font=dict(size=22, color="gray"),
 
 
 
27
  align="center",
28
  )
 
 
 
 
 
 
 
 
 
 
29
  return html.Div(
30
- style={
31
- "fontFamily": "Arial, Helvetica, sans-serif",
32
- "padding": "15px",
33
- },
34
- children=[
35
- html.H1("Extraction condition comparison tool",
36
- style={
37
- "textAlign": "center",
38
- "marginBottom": "10px",
39
- "fontSize": "36px",
40
- },
41
- ),
42
- html.Div(
43
- style={
44
- "display": "flex",
45
- "gap": "40px",
46
- },
47
- children=[
48
- # LEFT: Form / Controls
49
- html.Div(
50
- style={
51
- "flex": "0 0 40%",
52
- "maxWidth": "500px",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  },
54
- children=[
55
- # Polymer component section
56
- html.H3(
57
- "Polymer component",
58
- style={
59
- "textDecoration": "underline",
60
- "fontSize": "18px",
61
- "marginBottom": "10px",
62
- },
63
- ),
64
- html.Div(
65
- style={
66
- "display": "grid",
67
- "gridTemplateColumns": "1fr 1fr",
68
- "columnGap": "20px",
69
- "rowGap": "10px",
70
- "marginBottom": "10px",
71
- },
72
- children=[
73
- # Left column
74
- html.Div(
75
- children=[
76
- html.Div("Matrix"),
77
- dcc.Dropdown(
78
- id="matrix",
79
- options=[{"label": x, "value": x} for x in polymers],
80
- value=polymers[25], # default selection (optional)
81
- clearable=False,
82
- style={"width": "100%"}
83
- ),
84
- html.Div("Tg (°C)", style={"marginTop": "10px"}),
85
- dcc.Input(
86
- id="tg",
87
- type="number",
88
- placeholder="0.0",
89
- value="0.0",
90
- style={"width": "100%"},
91
- required=True,
92
- ),
93
- html.Div(
94
- "Crystallinity (%)",
95
- style={"marginTop": "10px"},
96
- ),
97
- dcc.Input(
98
- id="crystal",
99
- type="number",
100
- placeholder="0.0",
101
- value="0.0",
102
- style={"width": "100%"},
103
- required=True,
104
- ),
105
- ]
106
- ),
107
- # Right column
108
- html.Div(
109
- children=[
110
- html.Div("Density (g/cm³)"),
111
- dcc.Input(
112
- id="density",
113
- type="number",
114
- placeholder="1.0",
115
- value="1.0",
116
- style={"width": "100%"},
117
- required=True,
118
- ),
119
- html.Div(
120
- "Volume (cm³)",
121
- style={"marginTop": "10px"},
122
- ),
123
- dcc.Input(
124
- id="polymer_volume",
125
- type="number",
126
- placeholder="1.0",
127
- value="1.0",
128
- style={"width": "100%"},
129
- required=True,
130
- ),
131
- html.Div(
132
- "Surface area (cm²)",
133
- style={"marginTop": "10px"},
134
- ),
135
- dcc.Input(
136
- id="surface_area",
137
- type="number",
138
- placeholder="30.0",
139
- value="30.0",
140
- style={"width": "100%"},
141
- required=True,
142
- ),
143
- ]
144
  ),
145
- ],
146
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
- # Solutes row
149
- html.H3(
150
- "Solutes",
151
- style={
152
- "textDecoration": "underline",
153
- "fontSize": "18px",
154
- "marginBottom": "10px",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  },
156
- ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
- dcc.RadioItems(
159
- id="solute_source",
160
- options=[
161
- {"label": "Default boundary chemicals", "value": "default"},
162
- {"label": "User-defined chemicals", "value": "upload"},
163
- ],
164
- value="default",
165
- inline=True,
166
- labelStyle={"marginRight": "15px"},
167
- ),
168
 
169
- html.Div(
170
- id="solute_upload_container",
171
- children=[
172
- dcc.Upload(
173
- id="solute_upload",
174
- children=html.Div([
175
- "Drag and drop or ",
176
- html.A("select an Excel file"),
177
- ]),
178
- accept=".xlsx,.xls",
179
- multiple=False,
180
- style={
181
- "width": "100%",
182
- "height": "55px",
183
- "lineHeight": "55px",
184
- "borderWidth": "1px",
185
- "borderStyle": "dashed",
186
- "borderRadius": "4px",
187
- "textAlign": "center",
188
- "marginTop": "10px",
189
- },
190
- ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
 
192
- html.Div(
193
- id="solute_upload_status",
194
- style={
195
- "marginTop": "5px",
196
- "fontSize": "13px",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  },
198
- ),
199
- html.Div(
200
- id="solute_calculate_warning",
201
- style={
202
- "marginTop": "6px",
203
- "fontSize": "13px",
204
- "color": "firebrick",
205
- "fontWeight": "bold",
206
  },
207
- ),
208
- ],
209
- style={"display": "none"},
210
- ),
 
 
 
 
 
 
 
211
 
212
- dcc.Store(id="uploaded_solute_data"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
 
214
- # Conditions row
215
- html.Div(
216
- style={
217
- "display": "grid",
218
- "gridTemplateColumns": "1fr 1fr",
219
- "columnGap": "30px",
220
- },
221
- children=[
222
- # Condition 1
223
- html.Div(
224
- children=[
225
- html.H3(
226
- "Condition #1",
227
- style={
228
- "textDecoration": "underline",
229
- "fontSize": "18px",
230
- "marginBottom": "10px",
231
- },
232
- ),
233
- html.Div(
234
- [
235
- html.Div(
236
- dcc.RadioItems(
237
- id="condition1_mode",
238
- options=[
239
- {
240
- "label": "In vitro",
241
- "value": "in_vitro",
242
- },
243
- {
244
- "label": "In vivo (conservative)",
245
- "value": "in_vivo_conservative",
246
- },
247
- {
248
- "label": "In vivo (tissue)",
249
- "value": "in_vivo_tissue",
250
- },
251
- ],
252
- value="in_vitro",
253
- labelStyle={
254
- "display": "inline-block"
255
- },
256
- ),
257
- style={"marginBottom": "10px"},
258
- ),
259
- ],
260
- ),
261
- html.Div("Solvent"),
262
- dcc.Dropdown(
263
- id="c1_solvent",
264
- options=[{"label": x, "value": x} for x in solvents],
265
- value=solvents[9], # default selection (optional)
266
- clearable=False,
267
- placeholder="",
268
- disabled=True,
269
- style={"width": "100%"}
270
- ),
271
- html.Div(
272
- "Volume (cm³)",
273
- style={"marginTop": "10px"},
274
- ),
275
- dcc.Input(
276
- id="c1_volume",
277
- type="number",
278
- placeholder="10.0",
279
- value="10.0",
280
- style={"width": "100%"},
281
- disabled=True,
282
- required=True,
283
- ),
284
- html.Div(
285
- "Swelling ratio (m/m)",
286
- style={"marginTop": "10px"},
287
- ),
288
- dcc.Input(
289
- id="c1_swelling",
290
- type="number",
291
- placeholder="1.0",
292
- value="1.0",
293
- style={"width": "100%"},
294
- disabled=True,
295
- required=True,
296
- ),
297
- html.Div(
298
- "Extraction T (°C)",
299
- style={"marginTop": "10px"},
300
- ),
301
- dcc.Input(
302
- id="c1_temp",
303
- type="number",
304
- placeholder="50.0",
305
- value="50.0",
306
- style={"width": "100%"},
307
- disabled=True,
308
- required=True,
309
- ),
310
- html.Div(
311
- "Extraction time (h)",
312
- style={"marginTop": "10px"},
313
- ),
314
- dcc.Input(
315
- id="c1_time",
316
- type="number",
317
- placeholder="24.0",
318
- value="24.0",
319
- style={"width": "100%"},
320
- #disabled=True,
321
- required=True,
322
- ),
323
- html.Div(
324
- "# of iterations",
325
- style={"marginTop": "10px"},
326
- ),
327
- dcc.Input(
328
- id="c1_iterations",
329
- type="number",
330
- placeholder="1",
331
- value="1",
332
- style={"width": "100%"},
333
- disabled=True,
334
- required=True,
335
- ),
336
- ]
337
- ),
338
- # Condition 2
339
- html.Div(
340
- children=[
341
- html.H3(
342
- "Condition #2",
343
- style={
344
- "textDecoration": "underline",
345
- "fontSize": "18px",
346
- "marginBottom": "10px",
347
- },
348
- ),
349
- html.Div(
350
- [
351
- html.Div(
352
- dcc.RadioItems(
353
- id="condition2_mode",
354
- options=[
355
- {
356
- "label": "In vitro",
357
- "value": "in_vitro",
358
- },
359
- {
360
- "label": "In vivo (conservative)",
361
- "value": "in_vivo_conservative",
362
- },
363
- {
364
- "label": "In vivo (tissue)",
365
- "value": "in_vivo_tissue",
366
- },
367
- ],
368
- value="in_vivo_conservative",
369
- labelStyle={
370
- "display": "inline-block"
371
- },
372
- ),
373
- style={"marginBottom": "10px"},
374
- ),
375
- ],
376
- ),
377
- html.Div("Solvent"),
378
- dcc.Dropdown(
379
- id="c2_solvent",
380
- options=[{"label": x, "value": x} for x in solvents],
381
- value=solvents[2], # default selection (optional)
382
- clearable=False,
383
- placeholder="",
384
- disabled=True,
385
- style={"width": "100%"}
386
- ),
387
- html.Div(
388
- "Volume (cm³)",
389
- style={"marginTop": "10px"},
390
- ),
391
- dcc.Input(
392
- id="c2_volume",
393
- type="number",
394
- placeholder="10.0",
395
- value="10.0",
396
- style={"width": "100%"},
397
- disabled=True,
398
- required=True,
399
- ),
400
- html.Div(
401
- "Swelling ratio (m/m)",
402
- style={"marginTop": "10px"},
403
- ),
404
- dcc.Input(
405
- id="c2_swelling",
406
- type="number",
407
- placeholder="1.0",
408
- value="1.0",
409
- style={"width": "100%"},
410
- disabled=True,
411
- required=True,
412
- ),
413
- html.Div(
414
- "Extraction T (°C)",
415
- style={"marginTop": "10px"},
416
- ),
417
- dcc.Input(
418
- id="c2_temp",
419
- type="number",
420
- placeholder="50.0",
421
- value="50.0",
422
- style={"width": "100%"},
423
- disabled=True,
424
- required=True,
425
- ),
426
- html.Div(
427
- "Extraction time (h)",
428
- style={"marginTop": "10px"},
429
- ),
430
- dcc.Input(
431
- id="c2_time",
432
- type="number",
433
- placeholder="24.0",
434
- value="24.0",
435
- style={"width": "100%"},
436
- #disabled=True,
437
- required=True,
438
- ),
439
- html.Div(
440
- "# of iterations",
441
- style={"marginTop": "10px"},
442
- ),
443
- dcc.Input(
444
- id="c2_iterations",
445
- type="number",
446
- placeholder="1",
447
- value="1",
448
- style={"width": "100%"},
449
- disabled=True,
450
- required=True,
451
- ),
452
- ]
453
- ),
454
- ],
455
- ),
456
 
457
- # Calculate button
458
- # Monte Carlo
459
- html.Div(
460
- style={
461
- "marginTop": "10px",
462
- },
463
- children=[
464
- html.H3(
465
- "Monte Carlo",
466
- style={
467
- "textDecoration": "underline",
468
- "fontSize": "18px",
469
- "marginBottom": "15px",
470
- },
471
- ),
472
 
473
- # ---- Samples + radio buttons on same row ----
474
- html.Div(
475
- style={
476
- "display": "grid",
477
- "gridTemplateColumns": "1fr 1fr",
478
- "columnGap": "20px",
479
- "alignItems": "end",
480
- },
481
- children=[
482
- # Samples
483
- html.Div(
484
- children=[
485
- html.Div("Samples"),
486
- dcc.Input(
487
- id="samples",
488
- type="number",
489
- placeholder="1000",
490
- value="1000",
491
- required=True,
492
- style={"width": "100%"},
493
- ),
494
- ],
495
- ),
496
-
497
- # Radio buttons
498
- html.Div(
499
- children=[
500
- html.Div("Comparison quantity"),
501
- dcc.RadioItems(
502
- id="mc_option",
503
- options=[
504
- {"label": "absolute", "value": "absolute"},
505
- {"label": "ratio", "value": "ratio"},
506
- ],
507
- value="absolute",
508
- inline=True,
509
- labelStyle={
510
- "marginRight": "15px",
511
- },
512
- ),
513
- ],
514
- ),
515
- ],
516
- ),
517
 
518
- # ---- Calculate centered below ----
519
- html.Div(
520
- style={
521
- "textAlign": "center",
522
- "marginTop": "20px",
523
- },
524
- children=[
525
- html.Button(
526
- "Calculate",
527
- id="calculate",
528
- n_clicks=0,
529
- style={
530
- "padding": "8px 25px",
531
- "border": "1px solid #333",
532
- "borderRadius": "3px",
533
- "backgroundColor": "white",
534
- "cursor": "pointer",
535
- },
536
- )
537
- ],
538
- ),
539
- ],
540
- )
 
 
 
 
 
 
 
 
 
 
 
 
541
  ],
 
 
 
542
  ),
543
 
544
- # RIGHT: Bar Chart
545
- html.Div(
 
 
 
 
546
  style={
547
- "flex": "1",
548
- "display": "flex",
549
- "flexDirection": "column", # <-- key change
550
- "justifyContent": "center",
551
  },
552
- children=[
553
- dcc.Loading(
554
- id="mm0_loading",
555
- type="default", # still works with custom_spinner
556
- # This box defines the exact area of the graph and overlay
557
- parent_style={
558
- "position": "relative",
559
- "width": "800px", # match figure layout.width
560
- "height": "600px", # match figure layout.height
561
- },
562
- # This is the semi-transparent veil over that 600x800 box
563
- overlay_style={
564
- "backgroundColor": "rgba(255, 255, 255, 0.5)",
565
- "display": "flex",
566
- "justifyContent": "center",
567
- "alignItems": "center",
568
- },
569
- custom_spinner=html.Div(
570
- [
571
- html.H2("Processing"),
572
- dbc.Spinner(color="danger"),
573
- ],
574
- style={"textAlign": "center"},
575
- ),
576
- children=dcc.Graph(
577
- id="mm0_chart",
578
- figure=placeholder_fig,
579
- config={"displayModeBar": False},
580
- # Graph fills the 600x800 parent
581
- style={
582
- "width": "800px",
583
- "height": "600px",
584
- },
585
- ),
586
- ),
587
- html.Div(
588
- html.P(
589
- [
590
- "This tool provides an interface to physics-based models that can assist in selecting extraction conditions. ",
591
- "It enables comparison of predicted fractional release across in vitro and in vivo scenarios. ",
592
- "For additional information, please refer to the ",
593
- dcc.Link("quick start guide", href="/instructions"),
594
- # ", and for reference and disclaimer information, please see the ",
595
- # dcc.Link("RST information page", href="/rst"),
596
- ".",
597
- ],
598
- style={
599
- "maxWidth": "800px",
600
- "margin": "1.25rem auto 2rem auto",
601
- "lineHeight": "1.4",
602
- "color": "#555",
603
- }
604
- ),
605
- style={"width": "800px"},
606
  ),
 
 
607
  ],
 
 
 
 
 
 
608
  ),
609
- ],
610
- ),
611
- ],
612
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from dash import html, dcc
2
  import dash_bootstrap_components as dbc
3
+ import pandas as pd
4
+ import numpy as np
5
+
6
  from data.functions import *
7
  from figure import build_figure
8
 
9
+
10
+ # ============================================================
11
+ # Shared styles
12
+ # ============================================================
13
+
14
+ PAGE_STYLE = {
15
+ "fontFamily": "Arial, Helvetica, sans-serif",
16
+ "padding": "15px",
17
+ }
18
+
19
+ TITLE_STYLE = {
20
+ "textAlign": "center",
21
+ "marginBottom": "10px",
22
+ "fontSize": "36px",
23
+ }
24
+
25
+ SECTION_HEADER_STYLE = {
26
+ "textDecoration": "underline",
27
+ "fontSize": "18px",
28
+ "marginBottom": "10px",
29
+ }
30
+
31
+ MONTE_CARLO_HEADER_STYLE = {
32
+ **SECTION_HEADER_STYLE,
33
+ "marginBottom": "15px",
34
+ }
35
+
36
+ FULL_WIDTH_STYLE = {
37
+ "width": "100%",
38
+ }
39
+
40
+ FIELD_LABEL_STYLE = {
41
+ "marginTop": "10px",
42
+ }
43
+
44
+ TWO_COLUMN_GRID_STYLE = {
45
+ "display": "grid",
46
+ "gridTemplateColumns": "1fr 1fr",
47
+ "columnGap": "20px",
48
+ }
49
+
50
+ CONDITIONS_GRID_STYLE = {
51
+ "display": "grid",
52
+ "gridTemplateColumns": "1fr 1fr",
53
+ "columnGap": "30px",
54
+ }
55
+
56
+
57
+ # ============================================================
58
+ # Small reusable layout helpers
59
+ # ============================================================
60
+
61
+ def section_header(text):
62
+ """Standard underlined section heading."""
63
+ return html.H3(
64
+ text,
65
+ style=SECTION_HEADER_STYLE,
66
+ )
67
+
68
+
69
+ def field_label(text, first=False):
70
+ """Standard input label."""
71
+ if first:
72
+ return html.Div(text)
73
+
74
+ return html.Div(
75
+ text,
76
+ style=FIELD_LABEL_STYLE,
77
+ )
78
+
79
+
80
+ def numeric_input(
81
+ component_id,
82
+ value,
83
+ placeholder=None,
84
+ disabled=False,
85
+ ):
86
+ """Standard full-width numeric input."""
87
+
88
+ if placeholder is None:
89
+ placeholder = str(value)
90
+
91
+ return dcc.Input(
92
+ id=component_id,
93
+ type="number",
94
+ placeholder=placeholder,
95
+ value=value,
96
+ style=FULL_WIDTH_STYLE,
97
+ disabled=disabled,
98
+ required=True,
99
+ )
100
+
101
+
102
+ # ============================================================
103
+ # Placeholder figure
104
+ # ============================================================
105
+
106
+ def build_placeholder_figure():
107
+
108
  n = len(soluteData)
109
+
110
+ df0 = pd.DataFrame(
111
+ {
112
+ "CASRN": soluteData["CASRN"].to_numpy(),
113
+ "Condition 1": np.zeros(n),
114
+ "Cond1_err_plus": np.zeros(n),
115
+ "Cond1_err_minus": np.zeros(n),
116
+ "Condition 2": np.zeros(n),
117
+ "Cond2_err_plus": np.zeros(n),
118
+ "Cond2_err_minus": np.zeros(n),
119
+ }
120
+ )
121
+
122
  placeholder_fig = build_figure(df0)
123
+
124
+ placeholder_fig.update_yaxes(
125
+ range=[-2, 0]
126
+ )
127
+
128
  placeholder_fig.add_annotation(
129
  text="Click 'Calculate' to run model",
130
  xref="paper",
 
132
  x=0.5,
133
  y=0.5,
134
  showarrow=False,
135
+ font=dict(
136
+ size=22,
137
+ color="gray",
138
+ ),
139
  align="center",
140
  )
141
+
142
+ return placeholder_fig
143
+
144
+
145
+ # ============================================================
146
+ # Polymer section
147
+ # ============================================================
148
+
149
+ def build_polymer_section():
150
+
151
  return html.Div(
152
+ children=[
153
+ section_header("Polymer component"),
154
+
155
+ html.Div(
156
+ style={
157
+ **TWO_COLUMN_GRID_STYLE,
158
+ "rowGap": "10px",
159
+ "marginBottom": "10px",
160
+ },
161
+ children=[
162
+
163
+ # ------------------------------------------------
164
+ # Left column
165
+ # ------------------------------------------------
166
+ html.Div(
167
+ children=[
168
+ field_label("Matrix", first=True),
169
+
170
+ dcc.Dropdown(
171
+ id="matrix",
172
+ options=[
173
+ {"label": x, "value": x}
174
+ for x in polymers
175
+ ],
176
+ value=polymers[25],
177
+ clearable=False,
178
+ style=FULL_WIDTH_STYLE,
179
+ ),
180
+
181
+ field_label("Tg (°C)"),
182
+
183
+ numeric_input(
184
+ component_id="tg",
185
+ value="0.0",
186
+ placeholder="0.0",
187
+ ),
188
+
189
+ field_label("Crystallinity (%)"),
190
+
191
+ numeric_input(
192
+ component_id="crystal",
193
+ value="0.0",
194
+ placeholder="0.0",
195
+ ),
196
+ ]
197
+ ),
198
+
199
+ # ------------------------------------------------
200
+ # Right column
201
+ # ------------------------------------------------
202
+ html.Div(
203
+ children=[
204
+ field_label(
205
+ "Density (g/cm³)",
206
+ first=True,
207
+ ),
208
+
209
+ numeric_input(
210
+ component_id="density",
211
+ value="1.0",
212
+ placeholder="1.0",
213
+ ),
214
+
215
+ field_label("Volume (cm³)"),
216
+
217
+ numeric_input(
218
+ component_id="polymer_volume",
219
+ value="1.0",
220
+ placeholder="1.0",
221
+ ),
222
+
223
+ field_label("Surface area (cm²)"),
224
+
225
+ numeric_input(
226
+ component_id="surface_area",
227
+ value="30.0",
228
+ placeholder="30.0",
229
+ ),
230
+ ]
231
+ ),
232
+ ],
233
+ ),
234
+ ]
235
+ )
236
+
237
+
238
+ # ============================================================
239
+ # Solute section
240
+ # ============================================================
241
+
242
+ def build_solute_section():
243
+
244
+ return html.Div(
245
+ children=[
246
+ section_header("Solutes"),
247
+
248
+ dcc.RadioItems(
249
+ id="solute_source",
250
+ options=[
251
+ {
252
+ "label": "Default boundary chemicals",
253
+ "value": "default",
254
  },
255
+ {
256
+ "label": "User-defined chemicals",
257
+ "value": "upload",
258
+ },
259
+ ],
260
+ value="default",
261
+ inline=True,
262
+ labelStyle={
263
+ "marginRight": "15px",
264
+ },
265
+ ),
266
+
267
+ # ----------------------------------------------------
268
+ # Upload controls
269
+ # Hidden unless "User-defined chemicals" is selected
270
+ # ----------------------------------------------------
271
+ html.Div(
272
+ id="solute_upload_container",
273
+ children=[
274
+ dcc.Upload(
275
+ id="solute_upload",
276
+ children=html.Div(
277
+ [
278
+ "Drag and drop or ",
279
+ html.A(
280
+ "select an Excel file"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  ),
282
+ ]
283
  ),
284
+ accept=".xlsx,.xls",
285
+ multiple=False,
286
+ style={
287
+ "width": "100%",
288
+ "height": "55px",
289
+ "lineHeight": "55px",
290
+ "borderWidth": "1px",
291
+ "borderStyle": "dashed",
292
+ "borderRadius": "4px",
293
+ "textAlign": "center",
294
+ "marginTop": "10px",
295
+ },
296
+ ),
297
 
298
+ # File-read / validation message
299
+ html.Div(
300
+ id="solute_upload_status",
301
+ style={
302
+ "marginTop": "5px",
303
+ "fontSize": "13px",
304
+ },
305
+ ),
306
+
307
+ # Calculate-button warning
308
+ html.Div(
309
+ id="solute_calculate_warning",
310
+ style={
311
+ "marginTop": "6px",
312
+ "fontSize": "13px",
313
+ "color": "firebrick",
314
+ "fontWeight": "bold",
315
+ },
316
+ ),
317
+ ],
318
+ style={
319
+ "display": "none",
320
+ },
321
+ ),
322
+
323
+ # Stores validated uploaded solute records
324
+ dcc.Store(
325
+ id="uploaded_solute_data"
326
+ ),
327
+ ],
328
+ )
329
+
330
+
331
+ # ============================================================
332
+ # Condition controls
333
+ # ============================================================
334
+
335
+ def build_condition_column(
336
+ number,
337
+ default_mode,
338
+ default_solvent,
339
+ ):
340
+ """
341
+ Build either Condition #1 or Condition #2.
342
+
343
+ IDs are generated automatically:
344
+ c1_solvent, c1_volume, ...
345
+ c2_solvent, c2_volume, ...
346
+ """
347
+
348
+ prefix = f"c{number}"
349
+
350
+ return html.Div(
351
+ children=[
352
+ html.H3(
353
+ f"Condition #{number}",
354
+ style=SECTION_HEADER_STYLE,
355
+ ),
356
+
357
+ # ----------------------------------------------------
358
+ # Condition mode
359
+ # ----------------------------------------------------
360
+ html.Div(
361
+ style={
362
+ "marginBottom": "10px",
363
+ },
364
+ children=[
365
+ dcc.RadioItems(
366
+ id=f"condition{number}_mode",
367
+ options=[
368
+ {
369
+ "label": "In vitro",
370
+ "value": "in_vitro",
371
  },
372
+ {
373
+ "label": "In vivo (conservative)",
374
+ "value": "in_vivo_conservative",
375
+ },
376
+ {
377
+ "label": "In vivo (tissue)",
378
+ "value": "in_vivo_tissue",
379
+ },
380
+ ],
381
+ value=default_mode,
382
+ labelStyle={
383
+ "display": "inline-block",
384
+ },
385
+ ),
386
+ ],
387
+ ),
388
 
389
+ # ----------------------------------------------------
390
+ # Solvent
391
+ # ----------------------------------------------------
392
+ field_label(
393
+ "Solvent",
394
+ first=True,
395
+ ),
 
 
 
396
 
397
+ dcc.Dropdown(
398
+ id=f"{prefix}_solvent",
399
+ options=[
400
+ {"label": x, "value": x}
401
+ for x in solvents
402
+ ],
403
+ value=default_solvent,
404
+ clearable=False,
405
+ placeholder="",
406
+ disabled=True,
407
+ style=FULL_WIDTH_STYLE,
408
+ ),
409
+
410
+ # ----------------------------------------------------
411
+ # Volume
412
+ # ----------------------------------------------------
413
+ field_label("Volume (cm³)"),
414
+
415
+ numeric_input(
416
+ component_id=f"{prefix}_volume",
417
+ value="10.0",
418
+ placeholder="10.0",
419
+ disabled=True,
420
+ ),
421
+
422
+ # ----------------------------------------------------
423
+ # Swelling ratio
424
+ # ----------------------------------------------------
425
+ field_label("Swelling ratio (m/m)"),
426
+
427
+ numeric_input(
428
+ component_id=f"{prefix}_swelling",
429
+ value="1.0",
430
+ placeholder="1.0",
431
+ disabled=True,
432
+ ),
433
+
434
+ # ----------------------------------------------------
435
+ # Extraction temperature
436
+ # ----------------------------------------------------
437
+ field_label("Extraction T (°C)"),
438
+
439
+ numeric_input(
440
+ component_id=f"{prefix}_temp",
441
+ value="50.0",
442
+ placeholder="50.0",
443
+ disabled=True,
444
+ ),
445
+
446
+ # ----------------------------------------------------
447
+ # Extraction time
448
+ # ----------------------------------------------------
449
+ field_label("Extraction time (h)"),
450
+
451
+ numeric_input(
452
+ component_id=f"{prefix}_time",
453
+ value="24.0",
454
+ placeholder="24.0",
455
+ disabled=False,
456
+ ),
457
+
458
+ # ----------------------------------------------------
459
+ # Number of iterations
460
+ # ----------------------------------------------------
461
+ field_label("# of iterations"),
462
+
463
+ numeric_input(
464
+ component_id=f"{prefix}_iterations",
465
+ value="1",
466
+ placeholder="1",
467
+ disabled=True,
468
+ ),
469
+ ]
470
+ )
471
+
472
+
473
+ def build_conditions_section():
474
+
475
+ return html.Div(
476
+ style=CONDITIONS_GRID_STYLE,
477
+ children=[
478
+ build_condition_column(
479
+ number=1,
480
+ default_mode="in_vitro",
481
+ default_solvent=solvents[9],
482
+ ),
483
+
484
+ build_condition_column(
485
+ number=2,
486
+ default_mode="in_vivo_conservative",
487
+ default_solvent=solvents[2],
488
+ ),
489
+ ],
490
+ )
491
+
492
+
493
+ # ============================================================
494
+ # Monte Carlo section
495
+ # ============================================================
496
+
497
+ def build_monte_carlo_section():
498
 
499
+ return html.Div(
500
+ style={
501
+ "marginTop": "10px",
502
+ },
503
+ children=[
504
+ html.H3(
505
+ "Monte Carlo",
506
+ style=MONTE_CARLO_HEADER_STYLE,
507
+ ),
508
+
509
+ # ----------------------------------------------------
510
+ # Samples + comparison mode
511
+ # ----------------------------------------------------
512
+ html.Div(
513
+ style={
514
+ **TWO_COLUMN_GRID_STYLE,
515
+ "alignItems": "end",
516
+ },
517
+ children=[
518
+
519
+ # Samples
520
+ html.Div(
521
+ children=[
522
+ html.Div("Samples"),
523
+
524
+ numeric_input(
525
+ component_id="samples",
526
+ value="1000",
527
+ placeholder="1000",
528
+ ),
529
+ ]
530
+ ),
531
+
532
+ # Comparison quantity
533
+ html.Div(
534
+ children=[
535
+ html.Div(
536
+ "Comparison quantity"
537
+ ),
538
+
539
+ dcc.RadioItems(
540
+ id="mc_option",
541
+ options=[
542
+ {
543
+ "label": "absolute",
544
+ "value": "absolute",
545
  },
546
+ {
547
+ "label": "ratio",
548
+ "value": "ratio",
 
 
 
 
 
549
  },
550
+ ],
551
+ value="absolute",
552
+ inline=True,
553
+ labelStyle={
554
+ "marginRight": "15px",
555
+ },
556
+ ),
557
+ ]
558
+ ),
559
+ ],
560
+ ),
561
 
562
+ # ----------------------------------------------------
563
+ # Calculate button
564
+ # ----------------------------------------------------
565
+ html.Div(
566
+ style={
567
+ "textAlign": "center",
568
+ "marginTop": "20px",
569
+ },
570
+ children=[
571
+ html.Button(
572
+ "Calculate",
573
+ id="calculate",
574
+ n_clicks=0,
575
+ style={
576
+ "padding": "8px 25px",
577
+ "border": "1px solid #333",
578
+ "borderRadius": "3px",
579
+ "backgroundColor": "white",
580
+ "cursor": "pointer",
581
+ },
582
+ )
583
+ ],
584
+ ),
585
+ ],
586
+ )
587
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
 
589
+ # ============================================================
590
+ # Results / graph panel
591
+ # ============================================================
 
 
 
 
 
 
 
 
 
 
 
 
592
 
593
+ def build_results_panel(placeholder_fig):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
 
595
+ return html.Div(
596
+ style={
597
+ "flex": "1",
598
+ "display": "flex",
599
+ "flexDirection": "column",
600
+ "justifyContent": "center",
601
+ },
602
+ children=[
603
+
604
+ # ----------------------------------------------------
605
+ # Plot + processing overlay
606
+ # ----------------------------------------------------
607
+ dcc.Loading(
608
+ id="mm0_loading",
609
+ type="default",
610
+
611
+ parent_style={
612
+ "position": "relative",
613
+ "width": "800px",
614
+ "height": "600px",
615
+ },
616
+
617
+ overlay_style={
618
+ "backgroundColor": "rgba(255, 255, 255, 0.5)",
619
+ "display": "flex",
620
+ "justifyContent": "center",
621
+ "alignItems": "center",
622
+ },
623
+
624
+ custom_spinner=html.Div(
625
+ [
626
+ html.H2("Processing"),
627
+ dbc.Spinner(
628
+ color="danger"
629
+ ),
630
  ],
631
+ style={
632
+ "textAlign": "center",
633
+ },
634
  ),
635
 
636
+ children=dcc.Graph(
637
+ id="mm0_chart",
638
+ figure=placeholder_fig,
639
+ config={
640
+ "displayModeBar": False,
641
+ },
642
  style={
643
+ "width": "800px",
644
+ "height": "600px",
 
 
645
  },
646
+ ),
647
+ ),
648
+
649
+ # ----------------------------------------------------
650
+ # Footer / quick-start link
651
+ # ----------------------------------------------------
652
+ html.Div(
653
+ html.P(
654
+ [
655
+ "This tool provides an interface to "
656
+ "physics-based models that can assist "
657
+ "in selecting extraction conditions. ",
658
+
659
+ "It enables comparison of predicted "
660
+ "fractional release across in vitro "
661
+ "and in vivo scenarios. ",
662
+
663
+ "For additional information, please "
664
+ "refer to the ",
665
+
666
+ dcc.Link(
667
+ "quick start guide",
668
+ href="/instructions",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
669
  ),
670
+
671
+ ".",
672
  ],
673
+ style={
674
+ "maxWidth": "800px",
675
+ "margin": "1.25rem auto 2rem auto",
676
+ "lineHeight": "1.4",
677
+ "color": "#555",
678
+ },
679
  ),
680
+ style={
681
+ "width": "800px",
682
+ },
683
+ ),
684
+ ],
685
+ )
686
+
687
+
688
+ # ============================================================
689
+ # Left-side control panel
690
+ # ============================================================
691
+
692
+ def build_controls_panel():
693
+
694
+ return html.Div(
695
+ style={
696
+ "flex": "0 0 40%",
697
+ "maxWidth": "500px",
698
+ },
699
+ children=[
700
+ build_polymer_section(),
701
+ build_solute_section(),
702
+ build_conditions_section(),
703
+ build_monte_carlo_section(),
704
+ ],
705
+ )
706
+
707
+
708
+ # ============================================================
709
+ # Main layout
710
+ # ============================================================
711
+
712
+ def build_main_layout() -> html.Div:
713
+
714
+ placeholder_fig = build_placeholder_figure()
715
+
716
+ return html.Div(
717
+ style=PAGE_STYLE,
718
+ children=[
719
+ html.H1(
720
+ "Extraction condition comparison tool",
721
+ style=TITLE_STYLE,
722
+ ),
723
+
724
+ html.Div(
725
+ style={
726
+ "display": "flex",
727
+ "gap": "40px",
728
+ },
729
+ children=[
730
+ build_controls_panel(),
731
+ build_results_panel(
732
+ placeholder_fig
733
+ ),
734
+ ],
735
+ ),
736
+ ],
737
+ )