bindmc 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bindmc/main.py +67 -0
- bindmc/webgui/__init__.py +0 -0
- bindmc/webgui/app.py +54 -0
- bindmc/webgui/classes/BindingConstant.py +23 -0
- bindmc/webgui/classes/ChemicalShiftParam.py +40 -0
- bindmc/webgui/classes/Component.py +111 -0
- bindmc/webgui/classes/ExptData.py +485 -0
- bindmc/webgui/classes/ExptDataType.py +92 -0
- bindmc/webgui/classes/FitResult.py +173 -0
- bindmc/webgui/classes/MCMCSim.py +232 -0
- bindmc/webgui/classes/Model.py +86 -0
- bindmc/webgui/classes/RawData.py +36 -0
- bindmc/webgui/classes/Simulation.py +104 -0
- bindmc/webgui/classes/UIBindings.py +19 -0
- bindmc/webgui/classes/__init__.py +28 -0
- bindmc/webgui/components/__init__.py +29 -0
- bindmc/webgui/components/base.py +24 -0
- bindmc/webgui/components/bayes.py +689 -0
- bindmc/webgui/components/bayes_priors.py +351 -0
- bindmc/webgui/components/binding_model.py +330 -0
- bindmc/webgui/components/body.py +276 -0
- bindmc/webgui/components/data_gen.py +419 -0
- bindmc/webgui/components/data_import.py +450 -0
- bindmc/webgui/components/data_model.py +609 -0
- bindmc/webgui/components/fitting.py +886 -0
- bindmc/webgui/components/graph.py +649 -0
- bindmc/webgui/components/header.py +124 -0
- bindmc/webgui/components/simulation.py +385 -0
- bindmc/webgui/export/__init__.py +0 -0
- bindmc/webgui/export/notebook_exporter.py +727 -0
- bindmc/webgui/state/__init__.py +1 -0
- bindmc/webgui/state/statemanager.py +2043 -0
- bindmc/webgui/utils.py +322 -0
- bindmc-0.1.0.dist-info/METADATA +22 -0
- bindmc-0.1.0.dist-info/RECORD +37 -0
- bindmc-0.1.0.dist-info/WHEEL +5 -0
- bindmc-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
from .base import BaseComponent
|
|
2
|
+
from nicegui import ui
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from ..classes import Component
|
|
6
|
+
from .graph import Graph
|
|
7
|
+
|
|
8
|
+
from nicegui.events import UploadEventArguments
|
|
9
|
+
from collections import Counter
|
|
10
|
+
import io
|
|
11
|
+
|
|
12
|
+
class DataGenerationPanel(BaseComponent):
|
|
13
|
+
def _create_comp_table(self, df: pd.DataFrame) -> None:
|
|
14
|
+
self.compTable = (
|
|
15
|
+
ui.table.from_pandas(
|
|
16
|
+
df,
|
|
17
|
+
column_defaults={
|
|
18
|
+
"align": "right",
|
|
19
|
+
":format": """value => {
|
|
20
|
+
if (value == null) return ''
|
|
21
|
+
const v = Number(value)
|
|
22
|
+
|
|
23
|
+
if (Math.abs(v) < 1e-16) return '0'
|
|
24
|
+
|
|
25
|
+
return (Math.abs(v) >= 1e4 || Math.abs(v) < 1e-3)
|
|
26
|
+
? v.toExponential(3)
|
|
27
|
+
: v.toFixed(3)
|
|
28
|
+
}""",
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
.classes("w-full")
|
|
32
|
+
.props('dense hide-bottom :pagination="{rowsPerPage: 0}"')
|
|
33
|
+
.mark("gen-data-table")
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def setup_nicegui(self):
|
|
37
|
+
self.container = ui.column().classes("w-full")
|
|
38
|
+
|
|
39
|
+
with self.container:
|
|
40
|
+
# Responsive layout:
|
|
41
|
+
# - narrow screens: stack cards top-to-bottom
|
|
42
|
+
# - wide screens: show cards side-by-side (generation left, table/graph right)
|
|
43
|
+
with ui.row().classes("w-full gap-4 items-start flex-col lg:flex-row"):
|
|
44
|
+
with ui.card().classes("w-full lg:flex-1 min-w-0"):
|
|
45
|
+
self.gen_settings = ui.element()
|
|
46
|
+
with self.gen_settings:
|
|
47
|
+
ui.label("Data Generation Panel").classes("text-lg font-bold mb-4")
|
|
48
|
+
ui.label("This panel contains data generation tools and options.")
|
|
49
|
+
with ui.row():
|
|
50
|
+
self.nCompInp = (
|
|
51
|
+
ui.input("Number of components")
|
|
52
|
+
.bind_value(self.sm, "nComp")
|
|
53
|
+
.mark("num-components")
|
|
54
|
+
.set_enabled(False)
|
|
55
|
+
) # .on_value_change(self.simUpdateNumComponents)
|
|
56
|
+
# self.nCompEditable = ui.checkbox("",value=False).props('checked-icon=edit unchecked-icon=edit_off').classes('q-pa-md')
|
|
57
|
+
# self.nCompInp.bind_enabled_from(self.nCompEditable,'value')
|
|
58
|
+
# change this to be a nice pen icon that is crossed out
|
|
59
|
+
self.nStepInp = (
|
|
60
|
+
ui.number("Number of steps", precision=0, min=1)
|
|
61
|
+
.mark("num-steps")
|
|
62
|
+
.bind_value(self.sm, "nStep")
|
|
63
|
+
)
|
|
64
|
+
ui.button("Upload component concentrations", on_click=self.upload_comp_concs)
|
|
65
|
+
# Wrap component blocks left-to-right, then top-to-bottom
|
|
66
|
+
self.comp_gens = ui.row().classes("w-full flex-wrap items-start gap-4")
|
|
67
|
+
|
|
68
|
+
self.set_up_component_gen_rows(self.sm.nComp)
|
|
69
|
+
with ui.row().classes("mt-4 gap-4"):
|
|
70
|
+
ui.button(
|
|
71
|
+
"Generate Component Concentrations", on_click=self.calc_comp_concs
|
|
72
|
+
)
|
|
73
|
+
ui.button("Reset", on_click=self.resetcomp_concs)
|
|
74
|
+
|
|
75
|
+
with ui.card().classes("w-full lg:flex-1 min-w-0"):
|
|
76
|
+
self.table_and_graph = ui.element().classes("w-full")
|
|
77
|
+
with self.table_and_graph:
|
|
78
|
+
with (
|
|
79
|
+
ui.tabs()
|
|
80
|
+
.classes("w-full")
|
|
81
|
+
.props('align="justify"')
|
|
82
|
+
.on("update:model-value", self._table_graph_tab_changed)
|
|
83
|
+
) as tabs:
|
|
84
|
+
self.table_tab = ui.tab("Table")
|
|
85
|
+
self.graph_tab = ui.tab("Graph")
|
|
86
|
+
with ui.tab_panels(tabs, value=self.table_tab).classes("w-full"):
|
|
87
|
+
with ui.tab_panel(self.table_tab):
|
|
88
|
+
self.tableBlock = ui.element().classes("w-full")
|
|
89
|
+
with self.tableBlock:
|
|
90
|
+
ui.label("Generated data:")
|
|
91
|
+
with (
|
|
92
|
+
ui.element("div")
|
|
93
|
+
.classes("w-full max-h-[30rem] overflow-y-scroll")
|
|
94
|
+
.style("scrollbar-gutter: stable;")
|
|
95
|
+
):
|
|
96
|
+
self._create_comp_table(self.sm.comp_concs)
|
|
97
|
+
|
|
98
|
+
with ui.tab_panel(self.graph_tab):
|
|
99
|
+
ui.label("Generated data:")
|
|
100
|
+
self.preview_graph = Graph(self.sm, mode="data_preview")
|
|
101
|
+
if len(self.sm.comp_concs) > 0:
|
|
102
|
+
self.preview_graph.add_graph_lines(self.sm.comp_concs, run_name="Gen", run_id=str(self.sm.active_model_id)) # Add graph lines for the generated data
|
|
103
|
+
self.preview_graph.update_graph()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _table_graph_tab_changed(self, e) -> None:
|
|
107
|
+
# Plotly elements often compute width incorrectly when created inside a hidden tab.
|
|
108
|
+
# Forcing an update when the Graph tab becomes active makes it fill the card width.
|
|
109
|
+
if getattr(e, "args", None) != "Graph":
|
|
110
|
+
return
|
|
111
|
+
if not hasattr(self, "preview_graph"):
|
|
112
|
+
return
|
|
113
|
+
try:
|
|
114
|
+
self.preview_graph.graph.update()
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
try:
|
|
118
|
+
ui.run_javascript('window.dispatchEvent(new Event("resize"));')
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def upload_comp_concs(self, e):
|
|
126
|
+
|
|
127
|
+
with ui.dialog() as dialog, ui.card():
|
|
128
|
+
ui.label("Upload concentrations (csv)").classes("w-full text-center text-2xl font-bold")
|
|
129
|
+
template_text = ','.join([comp.name for comp in self.sm.components])
|
|
130
|
+
template_text += '\nmM'+',mM'*(len(self.sm.components)-1) # Header row with units
|
|
131
|
+
template_text += '\n' + '0.001' + ',0'*(len(self.sm.components)-1) # Add a newline after the header
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
ui.button(
|
|
135
|
+
"Download template CSV",
|
|
136
|
+
on_click=lambda: ui.download.content(template_text, 'component_concentrations_template.csv')
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
ui.label("""
|
|
140
|
+
1. Download the template CSV file and open it in Excel or your preferred spreadsheet software.
|
|
141
|
+
2. The first row contains the component names. Do not change this row.
|
|
142
|
+
3. The second row contains the units for each component (M, mM, µM/uM, nM). Ensure these are correct, and change them if needed.
|
|
143
|
+
4. Starting from the third row, enter the concentration values for each component. You can change the existing data.
|
|
144
|
+
5. Save as a CSV, then upload your file by pressing the (+) button below.""").style('white-space: pre-wrap')
|
|
145
|
+
upload_box = ui.upload(label="Choose file", auto_upload=True).props(
|
|
146
|
+
'accept=".csv"'
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
ui.button("Cancel", on_click=lambda: dialog.submit("cancel"))
|
|
150
|
+
|
|
151
|
+
def on_upload_complete(e: UploadEventArguments) -> None:
|
|
152
|
+
dialog.submit(e) # Store result for later
|
|
153
|
+
|
|
154
|
+
upload_box.on_upload(on_upload_complete)
|
|
155
|
+
|
|
156
|
+
result: UploadEventArguments|str= await dialog
|
|
157
|
+
|
|
158
|
+
if isinstance(result, str) and result == "cancel":
|
|
159
|
+
ui.notify("Project loading cancelled", type="info")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
elif isinstance(result, UploadEventArguments):
|
|
163
|
+
filename = result.file.name
|
|
164
|
+
|
|
165
|
+
if filename.endswith(".csv"):
|
|
166
|
+
file_content = await result.file.text("utf-8")
|
|
167
|
+
else:
|
|
168
|
+
ui.notify("Unsupported file format. Please upload a .csv file.",type='negative')
|
|
169
|
+
return
|
|
170
|
+
concs = pd.read_csv(io.StringIO(file_content),header=0,index_col=False,skiprows=[1])
|
|
171
|
+
units = file_content.splitlines()[1].split(',')
|
|
172
|
+
|
|
173
|
+
# check names are all valid
|
|
174
|
+
expected_names = [comp.name for comp in self.sm.components]
|
|
175
|
+
if Counter(concs.columns) != Counter(expected_names):
|
|
176
|
+
ui.notify(f"Component names in uploaded file do not match expected names: {', '.join(expected_names)}",type='negative')
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
# check units are all valid
|
|
180
|
+
valid_units = ['M','mM','uM','µM','nM']
|
|
181
|
+
for unit in units:
|
|
182
|
+
if unit not in valid_units:
|
|
183
|
+
ui.notify(f"Invalid unit '{unit}' found in uploaded file. Valid units are: {', '.join(valid_units)}",type='negative')
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
# do unit conversion to M
|
|
187
|
+
for i, comp in enumerate(concs.columns):
|
|
188
|
+
unit = units[i]
|
|
189
|
+
if unit == 'M':
|
|
190
|
+
factor = 1
|
|
191
|
+
elif unit == 'mM':
|
|
192
|
+
factor = 1e-3
|
|
193
|
+
elif unit in ['uM','µM']:
|
|
194
|
+
factor = 1e-6
|
|
195
|
+
elif unit == 'nM':
|
|
196
|
+
factor = 1e-9
|
|
197
|
+
else:
|
|
198
|
+
factor = 1 # should not happen due to earlier check
|
|
199
|
+
concs[comp] = concs[comp] * factor
|
|
200
|
+
|
|
201
|
+
self.sm.comp_concs = concs
|
|
202
|
+
self.sm.active_model.component_concs = concs
|
|
203
|
+
self.update_comp_table()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def calc_comp_concs(self, e):
|
|
209
|
+
"""Calculate component concentrations based on user input."""
|
|
210
|
+
|
|
211
|
+
# remove components that are no longer in the list
|
|
212
|
+
compNames = [comp.name for comp in self.sm.components]
|
|
213
|
+
self.sm.components = [
|
|
214
|
+
comp for comp in self.sm.components if comp.name in compNames
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
df = pd.DataFrame({})
|
|
218
|
+
try:
|
|
219
|
+
for comp in self.sm.components:
|
|
220
|
+
if comp.constant is True:
|
|
221
|
+
df[comp.name] = [comp.start_conc] * int(self.sm.nStep)
|
|
222
|
+
|
|
223
|
+
else:
|
|
224
|
+
if comp.spacing == "lin":
|
|
225
|
+
df[comp.name] = np.linspace(
|
|
226
|
+
comp.start_conc if comp.start_conc is not None else 0,
|
|
227
|
+
comp.end_conc if comp.end_conc is not None else 0,
|
|
228
|
+
int(self.sm.nStep),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
elif comp.spacing == "log":
|
|
232
|
+
pass
|
|
233
|
+
except Exception as e:
|
|
234
|
+
if isinstance(e, TypeError):
|
|
235
|
+
ui.notify(
|
|
236
|
+
"Error in component concentration calculation. Most likely you have not provided start/end concentrations. "
|
|
237
|
+
+ str(e),
|
|
238
|
+
type="negative",
|
|
239
|
+
)
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
self.sm.comp_concs = df
|
|
243
|
+
self.sm.active_model.component_concs = df # Update the active model's component concentrations # TODO make this a getter setter deal
|
|
244
|
+
# self.preview_graph.add_graph_lines(df, run_name="Gen", run_id=str(self.sm.active_model_id)) # Add graph lines for the generated data
|
|
245
|
+
|
|
246
|
+
# self.preview_graph.update_graph()
|
|
247
|
+
self.update_comp_table()
|
|
248
|
+
|
|
249
|
+
self.sm.notify_listeners("comp_concs_updated")
|
|
250
|
+
# self.compTable.clear()
|
|
251
|
+
# self.compTable = ui.table.from_pandas(df) # make it pretty - rounding, units, spacing reduction.
|
|
252
|
+
|
|
253
|
+
def update_comp_table(self):
|
|
254
|
+
self.tableBlock.clear()
|
|
255
|
+
with self.tableBlock:
|
|
256
|
+
ui.label("Generated data:")
|
|
257
|
+
with (
|
|
258
|
+
ui.element("div")
|
|
259
|
+
.classes("w-full max-h-[30rem] overflow-y-scroll")
|
|
260
|
+
.style("scrollbar-gutter: stable;")
|
|
261
|
+
):
|
|
262
|
+
self._create_comp_table(self.sm.comp_concs)
|
|
263
|
+
|
|
264
|
+
self.preview_graph.clear_graph() # Clear existing graph data
|
|
265
|
+
self.preview_graph.add_graph_lines(self.sm.comp_concs, run_name="Gen", run_id=str(self.sm.active_model_id)) # Add graph lines for the generated data
|
|
266
|
+
self.preview_graph.update_graph()
|
|
267
|
+
|
|
268
|
+
self.sm.notify_listeners("comp_concs_updated")
|
|
269
|
+
|
|
270
|
+
def resetcomp_concs(self, e):
|
|
271
|
+
self.comp_concs = pd.DataFrame({})
|
|
272
|
+
self.set_up_component_gen_rows(
|
|
273
|
+
self.sm.nComp
|
|
274
|
+
) # Reset component generation rows based on the number of components
|
|
275
|
+
|
|
276
|
+
# save states e.g. initial, after simrun, etc?
|
|
277
|
+
# history/undo options?
|
|
278
|
+
|
|
279
|
+
def setup_bindings(self):
|
|
280
|
+
self.sm.add_listener("model_changed", self.regen_comps)
|
|
281
|
+
|
|
282
|
+
def regen_comps(self, e=None):
|
|
283
|
+
self.set_up_component_gen_rows(
|
|
284
|
+
len(self.sm.components)
|
|
285
|
+
) # Update component generation rows based on the model's components
|
|
286
|
+
self.tableBlock.clear()
|
|
287
|
+
with self.tableBlock:
|
|
288
|
+
ui.label("Generated data:")
|
|
289
|
+
with (
|
|
290
|
+
ui.element("div")
|
|
291
|
+
.classes("w-full max-h-[30rem] overflow-y-scroll")
|
|
292
|
+
.style("scrollbar-gutter: stable;")
|
|
293
|
+
):
|
|
294
|
+
self._create_comp_table(self.sm.comp_concs)
|
|
295
|
+
|
|
296
|
+
def set_up_component_gen_rows(self, n: int):
|
|
297
|
+
"""Set up the component generation rows based on the number of components."""
|
|
298
|
+
|
|
299
|
+
self.comp_gens.clear()
|
|
300
|
+
|
|
301
|
+
for ii in range(n):
|
|
302
|
+
with self.comp_gens:
|
|
303
|
+
if ii < len(self.sm.components):
|
|
304
|
+
# If the component already exists, use its data
|
|
305
|
+
self.addCompBox(
|
|
306
|
+
ii,
|
|
307
|
+
name=self.sm.components[ii].name,
|
|
308
|
+
constant=self.sm.components[ii].constant,
|
|
309
|
+
start=self.sm.components[ii].start_conc_nice,
|
|
310
|
+
startunit=self.sm.components[ii].start_units,
|
|
311
|
+
end=self.sm.components[ii].end_conc_nice,
|
|
312
|
+
endunit=self.sm.components[ii].end_units,
|
|
313
|
+
)
|
|
314
|
+
else:
|
|
315
|
+
# If the component does not exist, create a new one with default values
|
|
316
|
+
# This allows for adding new components without losing existing ones
|
|
317
|
+
self.sm.components.append(
|
|
318
|
+
Component(name=f"Component {ii+1}")
|
|
319
|
+
) # Add a new component to the list
|
|
320
|
+
self.addCompBox(ii)
|
|
321
|
+
|
|
322
|
+
if len(self.sm.components) > n:
|
|
323
|
+
# If there are more components than requested, remove the excess
|
|
324
|
+
self.sm.components = self.sm.components[:n]
|
|
325
|
+
|
|
326
|
+
def addCompBox(
|
|
327
|
+
self,
|
|
328
|
+
n,
|
|
329
|
+
name="",
|
|
330
|
+
constant=False,
|
|
331
|
+
start=None,
|
|
332
|
+
startunit="mM",
|
|
333
|
+
end=None,
|
|
334
|
+
endunit="mM",
|
|
335
|
+
new=False,
|
|
336
|
+
):
|
|
337
|
+
|
|
338
|
+
c = ui.card().classes("w-full sm:w-56 md:w-64 flex-none")
|
|
339
|
+
with c:
|
|
340
|
+
ui.label(f"Component {n+1}:").classes("w-full text-center text-lg font-bold")
|
|
341
|
+
ui.input("Name").mark(f"comp-name-{n+1}").bind_value(
|
|
342
|
+
self.sm.components[n], "name"
|
|
343
|
+
)
|
|
344
|
+
ui.checkbox(
|
|
345
|
+
"Constant conc?", value=constant, on_change=self.changeConstantConc
|
|
346
|
+
).mark(f"constant-conc-{n+1}-checkbox").bind_value(
|
|
347
|
+
self.sm.components[n], "constant"
|
|
348
|
+
)
|
|
349
|
+
with ui.row().classes("start-conc-row"):
|
|
350
|
+
ui.number("Start:", step=0.001, value=start).classes("w-24").mark(
|
|
351
|
+
f"start-conc-{n+1}-val"
|
|
352
|
+
).bind_value(self.sm.components[n], "start_conc_nice")
|
|
353
|
+
ui.select(["M", "mM", "µM", "nM"], value=startunit).classes(
|
|
354
|
+
"w-20"
|
|
355
|
+
).mark(f"start-conc-{n+1}-unit").bind_value(
|
|
356
|
+
self.sm.components[n], "start_units"
|
|
357
|
+
)
|
|
358
|
+
with ui.row().classes("end-conc-row"):
|
|
359
|
+
ui.number("End:", step=0.001, value=end).classes("w-24").mark(
|
|
360
|
+
f"end-conc-{n+1}-val"
|
|
361
|
+
).bind_value(self.sm.components[n], "end_conc_nice")
|
|
362
|
+
ui.select(["M", "mM", "µM", "nM"], value=endunit).classes("w-20").mark(
|
|
363
|
+
f"end-conc-{n+1}-unit"
|
|
364
|
+
).bind_value(self.sm.components[n], "end_units")
|
|
365
|
+
|
|
366
|
+
return c
|
|
367
|
+
|
|
368
|
+
def getEventSiblings(self, e):
|
|
369
|
+
"""Get all siblings of the event sender."""
|
|
370
|
+
compBlock = next(e.sender.ancestors())
|
|
371
|
+
return compBlock.default_slot.children
|
|
372
|
+
|
|
373
|
+
def changeConstantConc(self, e):
|
|
374
|
+
"""Handle changes to the constant concentration checkbox."""
|
|
375
|
+
|
|
376
|
+
# Store references to UI elements when creating them instead of trying to find them later
|
|
377
|
+
# For now, just print the checkbox state as a placeholder
|
|
378
|
+
els = self.getEventSiblings(e)
|
|
379
|
+
|
|
380
|
+
if e.value:
|
|
381
|
+
for el in els:
|
|
382
|
+
|
|
383
|
+
if isinstance(el, ui.row):
|
|
384
|
+
# these are start/end concentration rows
|
|
385
|
+
inStartRow = True
|
|
386
|
+
for child in el.default_slot.children:
|
|
387
|
+
if isinstance(child, ui.number) and child.label == "Start:":
|
|
388
|
+
inStartRow = True
|
|
389
|
+
child.label = "Conc:"
|
|
390
|
+
elif isinstance(child, ui.number) and child.label == "End:":
|
|
391
|
+
inStartRow = False
|
|
392
|
+
child.value = None
|
|
393
|
+
child.enabled = False
|
|
394
|
+
elif isinstance(child, ui.select) and not inStartRow:
|
|
395
|
+
child.value = "mM"
|
|
396
|
+
child.enabled = False
|
|
397
|
+
|
|
398
|
+
else:
|
|
399
|
+
self.resetCompConcBlock(els)
|
|
400
|
+
|
|
401
|
+
def resetCompConcBlock(self, els):
|
|
402
|
+
"""Reset the concentration block for a component."""
|
|
403
|
+
"""Takes a list of elements, which should comprise a component block."""
|
|
404
|
+
for el in els:
|
|
405
|
+
|
|
406
|
+
if isinstance(el, ui.row):
|
|
407
|
+
# these are start/end concentration rows
|
|
408
|
+
inStartRow = True
|
|
409
|
+
for child in el.default_slot.children:
|
|
410
|
+
if isinstance(child, ui.number) and child.label == "Conc:":
|
|
411
|
+
inStartRow = True
|
|
412
|
+
child.label = "Start:"
|
|
413
|
+
elif isinstance(child, ui.number) and child.label == "End:":
|
|
414
|
+
inStartRow = False
|
|
415
|
+
child.value = None
|
|
416
|
+
child.enabled = True
|
|
417
|
+
elif isinstance(child, ui.select) and not inStartRow:
|
|
418
|
+
child.value = "mM"
|
|
419
|
+
child.enabled = True
|