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,450 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import math
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from nicegui import ui
|
|
6
|
+
from nicegui.events import UploadEventArguments, ClickEventArguments
|
|
7
|
+
|
|
8
|
+
from .base import BaseComponent
|
|
9
|
+
from .graph import Graph
|
|
10
|
+
from ..classes import ExptData, RawData, ExptDataType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DataImportPanel(BaseComponent):
|
|
14
|
+
def __init__(self, sm):
|
|
15
|
+
super().__init__(sm)
|
|
16
|
+
self.dep_indep_dropdowns = []
|
|
17
|
+
self.dep_indep_labels = []
|
|
18
|
+
self.ignore_checkboxes = []
|
|
19
|
+
|
|
20
|
+
self.dtype_labels = []
|
|
21
|
+
self.dtype_dropdowns = []
|
|
22
|
+
|
|
23
|
+
def setup_nicegui(self):
|
|
24
|
+
self.container = ui.column().classes("w-full")
|
|
25
|
+
|
|
26
|
+
with self.container:
|
|
27
|
+
ui.label("Data Import panel").classes("text-lg font-bold mb-4")
|
|
28
|
+
# Responsive layout:
|
|
29
|
+
# - narrow screens: stack cards top-to-bottom
|
|
30
|
+
# - wide screens: show cards side-by-side (controls left, table/graph right)
|
|
31
|
+
with ui.row().classes("w-full gap-4 items-start flex-col lg:flex-row"):
|
|
32
|
+
with ui.card().classes("w-full lg:flex-1 min-w-0"):
|
|
33
|
+
active_expt = self.sm.active_expt_data_or_none
|
|
34
|
+
if active_expt is not None:
|
|
35
|
+
self.expt_data_dropdown_button = (
|
|
36
|
+
ui.dropdown_button("Choose dataset", auto_close=True)
|
|
37
|
+
.bind_text(active_expt, "name")
|
|
38
|
+
.classes("mb-5")
|
|
39
|
+
)
|
|
40
|
+
else:
|
|
41
|
+
self.expt_data_dropdown_button = (
|
|
42
|
+
ui.dropdown_button("No active model", auto_close=True)
|
|
43
|
+
.classes("mb-5")
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if len(self.sm.raw_datas) > 0:
|
|
47
|
+
self.generate_data_dropdown()
|
|
48
|
+
else:
|
|
49
|
+
self.expt_data_dropdown_button.visible = False
|
|
50
|
+
|
|
51
|
+
ui.label("Upload Data File (CSV or Excel)")
|
|
52
|
+
ui.button("Upload File", on_click=self.load_exptdata)
|
|
53
|
+
|
|
54
|
+
self.expt_data_col_block = ui.element()
|
|
55
|
+
with self.expt_data_col_block:
|
|
56
|
+
ui.label("Column Metadata:")
|
|
57
|
+
|
|
58
|
+
self.make_model_button = ui.button(
|
|
59
|
+
"Prepare data model", on_click=self.prepare_data_model
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
with ui.card().classes("w-full lg:flex-1 min-w-0"):
|
|
63
|
+
ui.label("Data")
|
|
64
|
+
self.table_and_graph = ui.element().classes("w-full")
|
|
65
|
+
with self.table_and_graph:
|
|
66
|
+
with (
|
|
67
|
+
ui.tabs()
|
|
68
|
+
.classes("w-full")
|
|
69
|
+
.props('align="justify"')
|
|
70
|
+
.on("update:model-value", self._table_graph_tab_changed)
|
|
71
|
+
) as tabs:
|
|
72
|
+
self.table_tab = ui.tab("Table")
|
|
73
|
+
self.graph_tab = ui.tab("Graph")
|
|
74
|
+
|
|
75
|
+
with ui.tab_panels(tabs, value=self.table_tab).classes("w-full"):
|
|
76
|
+
with ui.tab_panel(self.table_tab):
|
|
77
|
+
self.expt_data_table_block = ui.element().classes("w-full")
|
|
78
|
+
|
|
79
|
+
with ui.tab_panel(self.graph_tab):
|
|
80
|
+
ui.label("Experimental data preview")
|
|
81
|
+
self.preview_graph = Graph(self.sm, mode="expt_preview")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _table_graph_tab_changed(self, e) -> None:
|
|
85
|
+
if getattr(e, "args", None) != "Graph":
|
|
86
|
+
return
|
|
87
|
+
if not hasattr(self, "preview_graph"):
|
|
88
|
+
return
|
|
89
|
+
try:
|
|
90
|
+
self.preview_graph.graph.update()
|
|
91
|
+
except Exception:
|
|
92
|
+
pass
|
|
93
|
+
try:
|
|
94
|
+
ui.run_javascript('window.dispatchEvent(new Event("resize"));')
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def setup_bindings(self):
|
|
100
|
+
super().setup_bindings()
|
|
101
|
+
self.sm.add_listener("data_imported", self._load_data_to_table)
|
|
102
|
+
self.sm.add_listener("data_imported", self.generate_data_dropdown)
|
|
103
|
+
self.sm.add_listener("active_context_changed", self._load_data_to_table)
|
|
104
|
+
self.sm.add_listener("active_context_changed", self.generate_data_dropdown)
|
|
105
|
+
self.sm.add_listener("expt_data_columns_changed", self._load_data_to_table) # Update when column selection changes
|
|
106
|
+
|
|
107
|
+
def prepare_data_model(self, e: ClickEventArguments):
|
|
108
|
+
active_raw = self.sm.active_raw_data_or_none
|
|
109
|
+
active_expt = self.sm.active_expt_data_or_none
|
|
110
|
+
if self.sm.active_raw_data_id is not None and active_raw is not None:
|
|
111
|
+
if active_expt is not None and active_expt.raw_data_id == active_raw.id:
|
|
112
|
+
# Auto-deselect unassigned columns
|
|
113
|
+
for col in active_expt.data.columns:
|
|
114
|
+
col_details = active_expt.col_details.get(col, {})
|
|
115
|
+
is_component = col.startswith('[') and col.endswith(']')
|
|
116
|
+
has_assignment = (col_details.get('depindep') in ['dep', 'indep']) and (col_details.get('dtype') is not None)
|
|
117
|
+
|
|
118
|
+
if not is_component and not has_assignment:
|
|
119
|
+
# Remove from selected columns instead of marking as ignored
|
|
120
|
+
if col in active_expt.selected_columns:
|
|
121
|
+
active_expt.selected_columns.remove(col)
|
|
122
|
+
|
|
123
|
+
self._load_expt_data_col_details() # Refresh UI to show changes
|
|
124
|
+
self._load_data_to_table() # Refresh table and graph to show selected data
|
|
125
|
+
ui.notify("Data model prepared. Unassigned columns have been deselected.", type="positive")
|
|
126
|
+
else:
|
|
127
|
+
rd = active_raw
|
|
128
|
+
new_expt_data = ExptData(name=rd.filename,init_raw_data=rd, init_model=self.sm.active_model)
|
|
129
|
+
self.sm.add_expt_data(new_expt_data)
|
|
130
|
+
else:
|
|
131
|
+
ui.notify("No raw data selected to prepare data model from.", type="negative")
|
|
132
|
+
|
|
133
|
+
async def load_exptdata(self):
|
|
134
|
+
try:
|
|
135
|
+
with ui.dialog() as dialog, ui.card():
|
|
136
|
+
ui.label("Load experimental data file")
|
|
137
|
+
upload_box = ui.upload(label="Choose file", auto_upload=True).props(
|
|
138
|
+
'accept=".csv, .xlsx, .xls"'
|
|
139
|
+
)
|
|
140
|
+
ui.button("Cancel", on_click=lambda: dialog.submit("cancel"))
|
|
141
|
+
|
|
142
|
+
def on_upload_complete(e: UploadEventArguments):
|
|
143
|
+
dialog.submit(e) # Store result for later
|
|
144
|
+
|
|
145
|
+
upload_box.on_upload(on_upload_complete)
|
|
146
|
+
|
|
147
|
+
result = await dialog
|
|
148
|
+
if isinstance(result, str) and result == "cancel":
|
|
149
|
+
ui.notify("Experimental data loading cancelled", type="info")
|
|
150
|
+
return
|
|
151
|
+
elif isinstance(result, UploadEventArguments):
|
|
152
|
+
data = pd.DataFrame() # Initialize an empty DataFrame
|
|
153
|
+
if result.file.name.endswith(".xlsx") or result.file.name.endswith(".xls"):
|
|
154
|
+
# If the file is an Excel file, read it into a DataFrame
|
|
155
|
+
file_content = await result.file.read()
|
|
156
|
+
data = pd.read_excel(io.BytesIO(file_content))
|
|
157
|
+
|
|
158
|
+
elif result.file.name.endswith(".csv"):
|
|
159
|
+
# If the file is a CSV file, read it into a DataFrame
|
|
160
|
+
file_content = await result.file.text(encoding='utf-8')
|
|
161
|
+
data = pd.read_csv(io.StringIO(file_content))
|
|
162
|
+
|
|
163
|
+
name = result.file.name
|
|
164
|
+
# if result.name is already in the list of expt_datas, append a number to the name
|
|
165
|
+
if any(ed.name == name for ed in self.sm.expt_datas.values()):
|
|
166
|
+
count = 1
|
|
167
|
+
while any(ed.name == f"{name}_({count})" for ed in self.sm.expt_datas.values()):
|
|
168
|
+
count += 1
|
|
169
|
+
name = f"{name}_({count})"
|
|
170
|
+
|
|
171
|
+
rd = RawData(filename=name, data=data)
|
|
172
|
+
self.sm.add_raw_data(rd) # Add raw data to the state manager
|
|
173
|
+
self.sm.add_expt_data(ExptData(name=name,init_raw_data=rd,
|
|
174
|
+
init_model=self.sm.active_model) ) # Load CSV data into a DataFrame
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# self.sm.expt_data.col_details = {c: {'depindep': None} for i,c in enumerate(self.sm.expt_data.data.columns)}
|
|
178
|
+
ui.notify("Experimental data loaded successfully", type="info")
|
|
179
|
+
self.sm.notify_listeners("data_imported")
|
|
180
|
+
else:
|
|
181
|
+
ui.notify("No file uploaded or unsupported file type", type="negative")
|
|
182
|
+
return
|
|
183
|
+
except Exception as e:
|
|
184
|
+
print(f"Error loading data: {str(e)}")
|
|
185
|
+
ui.notify("Failed to load data", type="negative")
|
|
186
|
+
|
|
187
|
+
def _load_data_to_table(self, e=None):
|
|
188
|
+
"""Load the experimental data into the table."""
|
|
189
|
+
self.expt_data_table_block.clear()
|
|
190
|
+
if self.sm.active_expt_data_id is not None and self.sm.active_expt_data.data is not None and not self.sm.active_expt_data.data.empty:
|
|
191
|
+
|
|
192
|
+
with self.expt_data_table_block:
|
|
193
|
+
if not self.sm.active_expt_data.data.empty:
|
|
194
|
+
ui.label("Experimental Data:")
|
|
195
|
+
with (
|
|
196
|
+
ui.element("div")
|
|
197
|
+
.classes("w-full max-h-[30rem] overflow-y-scroll")
|
|
198
|
+
.style("scrollbar-gutter: stable;")
|
|
199
|
+
):
|
|
200
|
+
# Show selected data in the table if column selection is active
|
|
201
|
+
data_to_display = self.sm.active_expt_data.selected_data
|
|
202
|
+
if data_to_display.empty:
|
|
203
|
+
data_to_display = self.sm.active_expt_data.data # Fallback to all data if no selection
|
|
204
|
+
|
|
205
|
+
self.expt_dataTable = (
|
|
206
|
+
ui.table.from_pandas(data_to_display)
|
|
207
|
+
.classes("w-full")
|
|
208
|
+
.props('dense hide-bottom :pagination="{rowsPerPage: 0}"')
|
|
209
|
+
.mark("expt-data-table")
|
|
210
|
+
)
|
|
211
|
+
else:
|
|
212
|
+
ui.label("No experimental data loaded.")
|
|
213
|
+
self._load_expt_data_col_details()
|
|
214
|
+
self.preview_graph.clear_graph()
|
|
215
|
+
# Show only selected data in the graph preview
|
|
216
|
+
selected_data_to_show = self.sm.active_expt_data.selected_data
|
|
217
|
+
if not selected_data_to_show.empty:
|
|
218
|
+
self.preview_graph.add_graph_lines(
|
|
219
|
+
selected_data_to_show,
|
|
220
|
+
run_name="Expt (selected)",
|
|
221
|
+
run_id=str(self.sm.active_expt_data_id),
|
|
222
|
+
scatter="markers",
|
|
223
|
+
)
|
|
224
|
+
self.preview_graph.update_graph()
|
|
225
|
+
else: # i.e. there is no data, we have deleted the last data
|
|
226
|
+
self.expt_data_col_block.clear()
|
|
227
|
+
self.preview_graph.clear_graph()
|
|
228
|
+
self.preview_graph.update_graph()
|
|
229
|
+
|
|
230
|
+
def _load_expt_data_col_details(self):
|
|
231
|
+
self.expt_data_col_block.clear()
|
|
232
|
+
# Clear UI element lists to prevent stale references
|
|
233
|
+
self.dep_indep_dropdowns.clear()
|
|
234
|
+
self.dep_indep_labels.clear()
|
|
235
|
+
self.ignore_checkboxes.clear()
|
|
236
|
+
self.dtype_labels.clear()
|
|
237
|
+
self.dtype_dropdowns.clear()
|
|
238
|
+
|
|
239
|
+
with self.expt_data_col_block:
|
|
240
|
+
if not self.sm.active_expt_data.data.empty:
|
|
241
|
+
ui.label("Experimental Data Columns:")
|
|
242
|
+
# populate column details dict
|
|
243
|
+
# {col: {'depindep': None, ...}}
|
|
244
|
+
|
|
245
|
+
# Ensure selected_columns is initialized once before creating UI
|
|
246
|
+
if not self.sm.active_expt_data.selected_columns:
|
|
247
|
+
self.sm.active_expt_data.selected_columns = self.sm.active_expt_data.data.columns.tolist()
|
|
248
|
+
|
|
249
|
+
for col in self.sm.active_expt_data.data.columns: # Show all original columns
|
|
250
|
+
|
|
251
|
+
with ui.card() as card:
|
|
252
|
+
with ui.row().classes('items-center gap-2'):
|
|
253
|
+
# Column name label
|
|
254
|
+
label = ui.label(col).classes("text-sm font-semibold")
|
|
255
|
+
self.dep_indep_labels.append(label)
|
|
256
|
+
|
|
257
|
+
# "Include this column" checkbox
|
|
258
|
+
is_selected = col in self.sm.active_expt_data.selected_columns
|
|
259
|
+
include_cb = ui.checkbox("Include this column", value=is_selected)
|
|
260
|
+
self.ignore_checkboxes.append(include_cb) # Reuse existing list for simplicity
|
|
261
|
+
|
|
262
|
+
# Dep/Indep radio buttons
|
|
263
|
+
with ui.row().classes('items-center gap-2'):
|
|
264
|
+
dep_indep_radio = ui.radio({
|
|
265
|
+
'indep': 'Independent variable',
|
|
266
|
+
'dep': 'Dependent variable'
|
|
267
|
+
}).props('inline').bind_value(
|
|
268
|
+
self.sm.active_expt_data.col_details[col], 'depindep'
|
|
269
|
+
)
|
|
270
|
+
self.dep_indep_dropdowns.append(dep_indep_radio)
|
|
271
|
+
|
|
272
|
+
# Data type dropdown
|
|
273
|
+
with ui.row().classes('items-center gap-2'):
|
|
274
|
+
dtype_label = ui.label("Data type:").classes("text-sm").props('inline')
|
|
275
|
+
self.dtype_labels.append(dtype_label)
|
|
276
|
+
|
|
277
|
+
opts = {k: v.name for k,v in self.sm._expt_dtypes.items()}
|
|
278
|
+
dtype_select = ui.select(opts,label="Data type").props('inline').bind_value(
|
|
279
|
+
self.sm.active_expt_data.col_details[col], 'dtype'
|
|
280
|
+
)
|
|
281
|
+
dtype_select.classes('w-40')
|
|
282
|
+
self.dtype_dropdowns.append(dtype_select)
|
|
283
|
+
|
|
284
|
+
# Set up column selection functionality
|
|
285
|
+
def setup_selection_behavior(card, dep_radio, dtype_sel, dtype_lbl, include_checkbox, col_name):
|
|
286
|
+
def update_selection_state(notify_listeners=True):
|
|
287
|
+
is_selected = include_checkbox.value
|
|
288
|
+
if is_selected:
|
|
289
|
+
# Add to selected columns if not already there
|
|
290
|
+
if col_name not in self.sm.active_expt_data.selected_columns:
|
|
291
|
+
self.sm.active_expt_data.selected_columns.append(col_name)
|
|
292
|
+
# Enable controls
|
|
293
|
+
card.classes(remove="opacity-50")
|
|
294
|
+
dep_radio.set_enabled(True)
|
|
295
|
+
dtype_sel.set_enabled(True)
|
|
296
|
+
else:
|
|
297
|
+
# Remove from selected columns
|
|
298
|
+
if col_name in self.sm.active_expt_data.selected_columns:
|
|
299
|
+
self.sm.active_expt_data.selected_columns.remove(col_name)
|
|
300
|
+
# Grey out and clear other controls
|
|
301
|
+
card.classes("opacity-50")
|
|
302
|
+
dep_radio.set_enabled(False)
|
|
303
|
+
dtype_sel.set_enabled(False)
|
|
304
|
+
# Clear assignments when deselected
|
|
305
|
+
self.sm.active_expt_data.col_details[col_name]['depindep'] = None
|
|
306
|
+
self.sm.active_expt_data.col_details[col_name]['dtype'] = None
|
|
307
|
+
|
|
308
|
+
# Only notify listeners if not during initial setup
|
|
309
|
+
if notify_listeners:
|
|
310
|
+
self.sm.notify_listeners("expt_data_columns_changed")
|
|
311
|
+
|
|
312
|
+
# Update state when checkbox changes (with notification)
|
|
313
|
+
include_checkbox.on_value_change(lambda: update_selection_state(True))
|
|
314
|
+
# Set initial state without triggering events
|
|
315
|
+
update_selection_state(False)
|
|
316
|
+
|
|
317
|
+
setup_selection_behavior(card, dep_indep_radio, dtype_select, dtype_label, include_cb, col)
|
|
318
|
+
|
|
319
|
+
with ui.row():
|
|
320
|
+
ui.button("Add new data type", on_click=self.add_new_expt_data_type).props(
|
|
321
|
+
"unelevated color=primary"
|
|
322
|
+
).classes("q-mx-xs")
|
|
323
|
+
else:
|
|
324
|
+
ui.label("No columns available in experimental data.")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
async def add_new_expt_data_type(self):
|
|
328
|
+
"""Add a new experimental data type."""
|
|
329
|
+
with ui.dialog() as dialog, ui.card():
|
|
330
|
+
ui.label("Add New Experimental Data Type")
|
|
331
|
+
name_input = ui.input("Name", placeholder="Enter data type name").props("clearable")
|
|
332
|
+
with ui.row().classes('items-center gap-2'):
|
|
333
|
+
ui.label("Measurement Method")
|
|
334
|
+
meas_input = ui.select(label="Measurement Method",
|
|
335
|
+
options={'grav_vol': "Grav/volumetric", 'nmr_integ': "NMR integration",
|
|
336
|
+
'nmr_ppm': "NMR chemical shift", 'uv_abs': "UV-vis",
|
|
337
|
+
'fluorescence': "Fluorescence"}).props(
|
|
338
|
+
"clearable").on_value_change(lambda e: method_changed(e.value)).classes('w-40')
|
|
339
|
+
|
|
340
|
+
with ui.row().classes('items-center gap-2'):
|
|
341
|
+
ui.label("Units")
|
|
342
|
+
units_input = ui.select(label="Units", options=["ppm", "M", "mM", "uM", "nM", "absorbance", "intensity"]).props("clearable").classes('w-15')
|
|
343
|
+
|
|
344
|
+
variance_input = ui.number("Variance", placeholder="Enter variance",min=1e-20).props("clearable").tooltip("Give the variance in the data, using the same units as the measurements. Default: 0.005")
|
|
345
|
+
|
|
346
|
+
def method_changed(value):
|
|
347
|
+
if value == "nmr_ppm":
|
|
348
|
+
units_input.value = "ppm"
|
|
349
|
+
elif value in ["grav_vol", "nmr_integ"]:
|
|
350
|
+
units_input.value = "M"
|
|
351
|
+
elif value == "uv_abs":
|
|
352
|
+
units_input.value = "absorbance"
|
|
353
|
+
elif value == "fluorescence":
|
|
354
|
+
units_input.value = "intensity"
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def on_submit():
|
|
358
|
+
if name_input.value and meas_input.value and units_input.value:
|
|
359
|
+
lnsigma_centre = float(math.log(variance_input.value)) if variance_input.value else -5
|
|
360
|
+
lnsigma = (lnsigma_centre - 3, lnsigma_centre, lnsigma_centre + 3)
|
|
361
|
+
new_dtype = ExptDataType(
|
|
362
|
+
name=name_input.value,
|
|
363
|
+
init_meas=meas_input.value,
|
|
364
|
+
units=units_input.value,
|
|
365
|
+
lnsigma= lnsigma[1],
|
|
366
|
+
lnsigma_min=lnsigma[0],
|
|
367
|
+
lnsigma_max=lnsigma[2]
|
|
368
|
+
)
|
|
369
|
+
try:
|
|
370
|
+
self.sm.add_expt_data_type(new_dtype)
|
|
371
|
+
except ValueError as e:
|
|
372
|
+
ui.notify(str(e), type="negative")
|
|
373
|
+
dialog.submit(True)
|
|
374
|
+
else:
|
|
375
|
+
ui.notify("Please fill in all fields.", type="negative")
|
|
376
|
+
|
|
377
|
+
ui.button("Submit", on_click=on_submit).props("unelevated color=primary")
|
|
378
|
+
ui.button("Cancel", on_click=lambda: dialog.close()).props(
|
|
379
|
+
"unelevated color=secondary"
|
|
380
|
+
)
|
|
381
|
+
result = await dialog
|
|
382
|
+
if result:
|
|
383
|
+
ui.notify(f"New experimental data type '{name_input.value}' added.", type="positive")
|
|
384
|
+
self._load_expt_data_col_details()
|
|
385
|
+
|
|
386
|
+
def generate_data_dropdown(self, e=None):
|
|
387
|
+
"""Generate the dropdown for selecting experimental data."""
|
|
388
|
+
|
|
389
|
+
self.expt_data_dropdown_button.clear()
|
|
390
|
+
if len(self.sm.expt_datas) >0 and len(self.sm.raw_datas) >0:
|
|
391
|
+
self.expt_data_dropdown_button.visible = True
|
|
392
|
+
with self.expt_data_dropdown_button:
|
|
393
|
+
self.expt_data_dropdown_rows = []
|
|
394
|
+
for m in self.sm.raw_datas.values():
|
|
395
|
+
with ui.row().classes("p-5 items-center") as x:
|
|
396
|
+
self.expt_data_dropdown_rows.append(x)
|
|
397
|
+
with ui.item(on_click=lambda m=m: self.load_raw_data(m)):
|
|
398
|
+
ui.item_label(m.filename)
|
|
399
|
+
ui.icon("delete").on(
|
|
400
|
+
"click", lambda m=m: self.delete_raw_data(m)
|
|
401
|
+
).classes("cursor-pointer text-red-600")
|
|
402
|
+
#ui.item("Add a new model...", on_click= self.add_new_expt_data)
|
|
403
|
+
else:
|
|
404
|
+
self.expt_data_dropdown_button.visible = False
|
|
405
|
+
active_raw = self.sm.active_raw_data_or_none
|
|
406
|
+
if active_raw is not None and hasattr(active_raw, 'filename'):
|
|
407
|
+
self.expt_data_dropdown_button.bind_text_from(active_raw, "filename")
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def load_raw_data(self, raw_data):
|
|
412
|
+
self.sm.active_raw_data_id = raw_data.id
|
|
413
|
+
active_expt = self.sm.active_expt_data_or_none
|
|
414
|
+
if active_expt is None or active_expt.raw_data_id != raw_data.id:
|
|
415
|
+
self.sm.active_expt_data_id = None # reset active expt data if raw data changes
|
|
416
|
+
# activate the newest expt data that uses this raw data
|
|
417
|
+
for ed in reversed(list(self.sm.expt_datas.values())):
|
|
418
|
+
if ed.raw_data_id == raw_data.id:
|
|
419
|
+
self.sm.active_expt_data_id = ed.id
|
|
420
|
+
break
|
|
421
|
+
|
|
422
|
+
active_fit = self.sm.active_fit_or_none
|
|
423
|
+
if active_fit is None or active_fit.expt_data_id != self.sm.active_expt_data_id:
|
|
424
|
+
self.sm.active_fit_id = None # reset active fit if expt data changes
|
|
425
|
+
# activate the newest fit that uses this expt data
|
|
426
|
+
for f in reversed(list(self.sm.fits.values())):
|
|
427
|
+
if f.expt_data_id == self.sm.active_expt_data_id:
|
|
428
|
+
self.sm.active_fit_id = f.id
|
|
429
|
+
break
|
|
430
|
+
|
|
431
|
+
self.sm.reconcile_active_context(reason="load_raw_data_selection", emit_events=True)
|
|
432
|
+
self.sm.notify_listeners("data_imported")
|
|
433
|
+
|
|
434
|
+
def delete_raw_data(self, raw_data):
|
|
435
|
+
self.sm.delete_raw_data(raw_data)
|
|
436
|
+
# objs_to_delete = []
|
|
437
|
+
# for f in self.sm.expt_datas.values():
|
|
438
|
+
# if f.raw_data_id == raw_data.id:
|
|
439
|
+
# objs_to_delete.append(f)
|
|
440
|
+
# for ff in self.sm.fits.values():
|
|
441
|
+
# if ff.expt_data_id == f.id:
|
|
442
|
+
# objs_to_delete.append(ff)
|
|
443
|
+
# for obj in objs_to_delete:
|
|
444
|
+
# self.sm.delete_object(obj)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def add_new_raw_data(self):
|
|
450
|
+
pass
|