Justify / main_layout.py
3v324v23's picture
more tidying
3532bf6
Raw History Blame Contribute Delete
16.6 kB
from dash import html, dcc
import pandas as pd
import numpy as np
from data.functions import *
from figure import build_figure
import plotly.graph_objects as go
# ============================================================
# Small reusable layout helpers
# ============================================================
FULL_WIDTH_STYLE = {"width": "100%"}
FIELD_LABEL_STYLE = {"marginTop": "4px"}
def section_header(text):
"""Standard section heading."""
return html.H3(text, className="section-header")
def field_label(text, first=False):
"""Standard input label."""
if first:
return html.Div(text)
return html.Div(text, style=FIELD_LABEL_STYLE)
def numeric_input(
component_id,
value,
placeholder=None,
disabled=False,
):
"""Standard full-width numeric input."""
if placeholder is None:
placeholder = str(value)
return dcc.Input(
id=component_id,
type="number",
placeholder=placeholder,
value=value,
style=FULL_WIDTH_STYLE,
disabled=disabled,
required=True,
)
# ============================================================
# Placeholder figure
# ============================================================
def build_placeholder_figure():
n = len(soluteData)
df0 = pd.DataFrame(
{
"CASRN": soluteData["CASRN"].to_numpy(),
"Condition 1": np.zeros(n),
"Cond1_err_plus": np.zeros(n),
"Cond1_err_minus": np.zeros(n),
"Condition 2": np.zeros(n),
"Cond2_err_plus": np.zeros(n),
"Cond2_err_minus": np.zeros(n),
}
)
placeholder_fig = build_figure(df0)
# Keep the CAS axis layout, but hide the placeholder bar traces.
placeholder_fig.update_traces(
opacity=0,
hoverinfo="skip",
showlegend=False,
)
placeholder_fig.update_yaxes(
range=[-2, 0]
)
placeholder_fig.add_annotation(
text="Click 'Calculate' to run model",
xref="paper",
yref="paper",
x=0.5,
y=0.5,
showarrow=False,
font=dict(
size=22,
color="gray",
),
align="center",
)
return placeholder_fig
# ============================================================
# Polymer section
# ============================================================
def build_polymer_section():
return html.Div(
className="polymer-section",
children=[
section_header("Polymer component"),
html.Div(
className="fields-grid",
children=[
# Left column
html.Div(
children=[
field_label("Matrix", first=True),
dcc.Dropdown(
id="matrix",
options=[
{"label": x, "value": x}
for x in polymers
],
value=polymers[25],
clearable=False,
style=FULL_WIDTH_STYLE,
),
field_label("Tg (°C)"),
numeric_input(
component_id="tg",
value="0.0",
placeholder="0.0",
),
field_label("Crystallinity (%)"),
numeric_input(
component_id="crystal",
value="0.0",
placeholder="0.0",
),
]
),
# Right column
html.Div(
children=[
field_label("Density (g/cm³)", first=True),
numeric_input(
component_id="density",
value="1.0",
placeholder="1.0",
),
field_label("Volume (cm³)"),
numeric_input(
component_id="polymer_volume",
value="1.0",
placeholder="1.0",
),
field_label("Surface area (cm²)"),
numeric_input(
component_id="surface_area",
value="30.0",
placeholder="30.0",
),
]
),
],
),
]
)
# ============================================================
# Solute section
# ============================================================
def build_solute_section():
return html.Div(
children=[
section_header("Solutes"),
dcc.RadioItems(
id="solute_source",
options=[
{
"label": "Default boundary chemicals",
"value": "default",
},
{
"label": "User-defined chemicals",
"value": "upload",
},
],
value="default",
inline=True,
labelStyle={"marginRight": "15px"},
),
# Hidden unless "User-defined chemicals" is selected.
html.Div(
id="solute_upload_container",
children=[
dcc.Upload(
id="solute_upload",
children=html.Div(
[
"Drag and drop or ",
html.A("select an Excel file"),
]
),
accept=".xlsx,.xls",
multiple=False,
style={
"width": "100%",
"height": "55px",
"lineHeight": "55px",
"borderWidth": "1px",
"borderStyle": "dashed",
"borderRadius": "4px",
"textAlign": "center",
"marginTop": "10px",
},
),
html.Div(
id="solute_upload_status",
style={
"marginTop": "5px",
"fontSize": "13px",
},
),
html.Div(
id="solute_calculate_warning",
style={
"marginTop": "6px",
"fontSize": "13px",
"color": "firebrick",
"fontWeight": "bold",
},
),
],
style={"display": "none"},
),
dcc.Store(id="uploaded_solute_data"),
]
)
# ============================================================
# Condition controls
# ============================================================
def build_condition_column(
number,
default_mode,
default_solvent,
):
"""
Build either Condition #1 or Condition #2.
IDs are generated automatically:
c1_solvent, c1_volume, ...
c2_solvent, c2_volume, ...
"""
prefix = f"c{number}"
return html.Div(
children=[
html.H3(
f"Condition #{number}",
className="section-header",
),
html.Div(
style={"marginBottom": "10px"},
children=[
dcc.RadioItems(
id=f"condition{number}_mode",
options=[
{
"label": "In vitro",
"value": "in_vitro",
},
{
"label": "In vivo (conservative)",
"value": "in_vivo_conservative",
},
{
"label": "In vivo (representative tissue)",
"value": "in_vivo_tissue",
},
],
value=default_mode,
inline=False,
labelStyle={
"display": "block",
"marginBottom": "2px",
},
),
],
),
field_label("Solvent", first=True),
dcc.Dropdown(
id=f"{prefix}_solvent",
options=[
{"label": x, "value": x}
for x in solvents
],
value=default_solvent,
clearable=False,
placeholder="",
disabled=True,
style=FULL_WIDTH_STYLE,
),
field_label("Volume (cm³)"),
numeric_input(
component_id=f"{prefix}_volume",
value="10.0",
placeholder="10.0",
disabled=True,
),
field_label("Swelling ratio (m/m)"),
numeric_input(
component_id=f"{prefix}_swelling",
value="1.0",
placeholder="1.0",
disabled=True,
),
field_label("Extraction T (°C)"),
numeric_input(
component_id=f"{prefix}_temp",
value="50.0",
placeholder="50.0",
disabled=True,
),
field_label("Extraction time (h)"),
numeric_input(
component_id=f"{prefix}_time",
value="24.0",
placeholder="24.0",
disabled=False,
),
field_label("# of iterations"),
numeric_input(
component_id=f"{prefix}_iterations",
value="1",
placeholder="1",
disabled=True,
),
]
)
def build_conditions_section():
return html.Div(
className="conditions-grid",
children=[
build_condition_column(
number=1,
default_mode="in_vitro",
default_solvent=solvents[9],
),
build_condition_column(
number=2,
default_mode="in_vivo_conservative",
default_solvent=solvents[2],
),
],
)
# ============================================================
# Monte Carlo section
# ============================================================
def build_monte_carlo_section():
return html.Div(
children=[
section_header("Monte Carlo"),
html.Div(
className="fields-grid",
children=[
html.Div(
children=[
html.Div("Samples"),
numeric_input(
component_id="samples",
value="1000",
placeholder="1000",
),
]
),
html.Div(
children=[
html.Div("Comparison quantity"),
dcc.RadioItems(
id="mc_option",
options=[
{
"label": "absolute",
"value": "absolute",
},
{
"label": "ratio",
"value": "ratio",
},
],
value="absolute",
inline=True,
labelStyle={"marginRight": "15px"},
),
]
),
],
),
html.Div(
className="calculate-row",
children=[
html.Button(
"Calculate",
id="calculate",
n_clicks=0,
)
],
),
]
)
# ============================================================
# Results / graph panel
# ============================================================
def build_results_panel(placeholder_fig):
return html.Div(
className="results-panel",
style={
"flex": "1",
"minWidth": 0,
"boxSizing": "border-box",
"border": "1px solid #D5DADF",
"borderRadius": "4px",
"backgroundColor": "#FFFFFF",
"padding": "16px",
"paddingTop": "12px",
},
children=[
section_header("Results"),
dcc.Loading(
id="mm0_loading",
type="default",
color="#D60036",
parent_style={
"position": "relative",
"width": "100%",
"minWidth": 0,
},
children=dcc.Graph(
id="mm0_chart",
figure=placeholder_fig,
responsive=True,
config={"displayModeBar": False},
style={
"width": "100%",
"height": "600px",
},
),
),
html.P(
[
"This tool provides an interface to "
"physics-based models that can assist "
"in selecting extraction conditions. ",
"It enables comparison of predicted "
"fractional release across in vitro "
"and in vivo scenarios. ",
"For additional information, please "
"refer to the ",
dcc.Link("quick start guide", href="/instructions"),
".",
],
style={
"margin": "1.25rem 0 0",
"lineHeight": "1.4",
"color": "#555",
},
),
],
)
# ============================================================
# Left-side control panel
# ============================================================
def build_controls_panel():
return html.Section(
className="controls-panel",
**{"aria-label": "Model inputs"},
children=[
build_polymer_section(),
build_solute_section(),
build_conditions_section(),
build_monte_carlo_section(),
],
)
# ============================================================
# Main layout
# ============================================================
def build_main_layout() -> html.Div:
placeholder_fig = build_placeholder_figure()
return html.Div(
className="model-page",
children=[
html.H1(
"Extraction condition comparison tool",
className="visually-hidden",
),
html.Div(
className="model-grid",
children=[
build_controls_panel(),
build_results_panel(placeholder_fig),
],
),
],
)