Skip to content

Commit 0890f38

Browse files
Update Visualization Table on Changes (#197)
* Temporarily shut off plotting during data changes. Closes #192 * Rename yValues when observable is renamed * Only check datatype for columns, not index. Fixes #180 * Make values redoable * Aligning default values
1 parent 2e4d2b5 commit 0890f38

File tree

7 files changed

+98
-44
lines changed

7 files changed

+98
-44
lines changed

src/petab_gui/commands.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,3 +318,46 @@ def _apply(self, src, dst):
318318
self.model.dataChanged.emit(
319319
self.model_index, self.model_index, [Qt.DisplayRole]
320320
)
321+
322+
323+
class RenameValueCommand(QUndoCommand):
324+
"""Command to rename values in specified columns."""
325+
326+
def __init__(
327+
self, model, old_id: str, new_id: str, column_names: str | list[str]
328+
):
329+
super().__init__(f"Rename value {old_id}{new_id}")
330+
self.model = model
331+
self.old_id = old_id
332+
self.new_id = new_id
333+
self.column_names = (
334+
column_names if isinstance(column_names, list) else [column_names]
335+
)
336+
self.changes = {} # {(row_idx, col_name): (old_val, new_val)}
337+
338+
df = self.model._data_frame
339+
for col_name in self.column_names:
340+
mask = df[col_name].eq(self.old_id)
341+
for row_idx in df.index[mask]:
342+
self.changes[(row_idx, col_name)] = (self.old_id, self.new_id)
343+
344+
def redo(self):
345+
self._apply_changes(use_new=True)
346+
347+
def undo(self):
348+
self._apply_changes(use_new=False)
349+
350+
def _apply_changes(self, use_new: bool):
351+
df = self.model._data_frame
352+
for (row_idx, col_name), (old_val, new_val) in self.changes.items():
353+
df.at[row_idx, col_name] = new_val if use_new else old_val
354+
355+
if self.changes:
356+
rows = [df.index.get_loc(row) for (row, _) in self.changes]
357+
cols = [df.columns.get_loc(col) + 1 for (_, col) in self.changes]
358+
top_left = self.model.index(min(rows), min(cols))
359+
bottom_right = self.model.index(max(rows), max(cols))
360+
self.model.dataChanged.emit(
361+
top_left, bottom_right, [Qt.DisplayRole, Qt.EditRole]
362+
)
363+
self.model.something_changed.emit(True)

src/petab_gui/controllers/default_handler.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ def get_default(
6666
default_value = column_config.get(DEFAULT_VALUE, "")
6767

6868
if strategy == USE_DEFAULT:
69-
if np.issubdtype(self.model.dtypes[column_name], np.floating):
69+
if column_name != self.model.index.name and np.issubdtype(
70+
self.model.dtypes[column_name], np.floating
71+
):
7072
return float(default_value)
7173
return default_value
7274
if strategy == NO_DEFAULT:

src/petab_gui/controllers/mother_controller.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,11 @@ def __init__(self, view, model: PEtabModel):
155155
self.actions = self.setup_actions()
156156
self.view.setup_toolbar(self.actions)
157157

158+
self.plotter = None
159+
self.init_plotter()
158160
self.setup_connections()
159161
self.setup_task_bar()
160162
self.setup_context_menu()
161-
self.plotter = None
162-
self.init_plotter()
163163

164164
@property
165165
def window_title(self):
@@ -194,6 +194,13 @@ def setup_connections(self):
194194
column_names="observableId",
195195
)
196196
)
197+
self.observable_controller.observable_2be_renamed.connect(
198+
partial(
199+
self.visualization_controller.rename_value,
200+
column_names="yValues",
201+
)
202+
)
203+
# Maybe TODO: add renaming dataset id?
197204
# Rename Condition
198205
self.condition_controller.condition_2be_renamed.connect(
199206
partial(
@@ -204,6 +211,13 @@ def setup_connections(self):
204211
],
205212
)
206213
)
214+
# Plotting Disable Temporarily
215+
for controller in self.controllers:
216+
if controller == self.sbml_controller:
217+
continue
218+
controller.model.plotting_needs_break.connect(
219+
self.plotter.disable_plotting
220+
)
207221
# Add new condition or observable
208222
self.model.measurement.relevant_id_changed.connect(
209223
lambda x, y, z: self.observable_controller.maybe_add_observable(

src/petab_gui/controllers/table_controllers.py

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
)
1919

2020
from ..C import COLUMN, INDEX
21+
from ..commands import RenameValueCommand
2122
from ..models.pandas_table_model import (
2223
PandasTableFilterProxy,
2324
PandasTableModel,
@@ -588,6 +589,25 @@ def save_table(self, file_name):
588589
f"Failed to save table: {str(e)}",
589590
)
590591

592+
def rename_value(
593+
self, old_id: str, new_id: str, column_names: str | list[str]
594+
):
595+
"""Rename the values in the dataframe.
596+
597+
Triggered by changes in the original observable_df or condition_df id.
598+
599+
Parameters
600+
----------
601+
old_id:
602+
The old id, which was changed.
603+
new_id:
604+
The new id.
605+
column_names:
606+
The column or list of columns in which the id should be changed.
607+
"""
608+
command = RenameValueCommand(self.model, old_id, new_id, column_names)
609+
self.undo_stack.push(command)
610+
591611

592612
class MeasurementController(TableController):
593613
"""Controller of the Measurement table."""
@@ -608,45 +628,6 @@ def check_petab_lint(
608628
observable_df=observable_df,
609629
)
610630

611-
def rename_value(
612-
self, old_id: str, new_id: str, column_names: str | list[str]
613-
):
614-
"""Rename the values in the measurement_df.
615-
616-
Triggered by changes in the original observable_df or condition_df id.
617-
618-
Parameters
619-
----------
620-
old_id:
621-
The old id, which was changed.
622-
new_id:
623-
The new id.
624-
"""
625-
if not isinstance(column_names, list):
626-
column_names = [column_names]
627-
628-
for col_name in column_names:
629-
# Find occurrences
630-
mask = self.model._data_frame[col_name].eq(old_id)
631-
if not mask.any():
632-
continue
633-
634-
self.model._data_frame.loc[mask, col_name] = new_id
635-
first_row, last_row = (
636-
mask.idxmax(),
637-
mask[::-1].idxmax(),
638-
)
639-
top_left = self.model.index(first_row, 1)
640-
bottom_right = self.model.index(
641-
last_row, self.model.columnCount() - 1
642-
)
643-
self.model.dataChanged.emit(
644-
top_left, bottom_right, [Qt.DisplayRole, Qt.EditRole]
645-
)
646-
647-
# Emit change signal
648-
self.model.something_changed.emit(True)
649-
650631
def copy_noise_parameters(
651632
self, observable_id: str, condition_id: str | None = None
652633
) -> str:

src/petab_gui/models/pandas_table_model.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class PandasTableModel(QAbstractTableModel):
4545
cell_needs_validation = Signal(int, int) # row, column
4646
something_changed = Signal(bool)
4747
inserted_row = Signal(QModelIndex)
48+
plotting_needs_break = Signal(bool)
4849

4950
def __init__(
5051
self,
@@ -310,6 +311,7 @@ def setData(
310311

311312
if is_invalid(value) or value == "":
312313
value = None
314+
self.plotting_needs_break.emit(True) # Temp disable plotting
313315
multi_row_change = False
314316
if check_multi:
315317
# check whether multiple rows but only one column is selected
@@ -318,13 +320,15 @@ def setData(
318320
self.undo_stack.beginMacro("Set data")
319321
success = self._set_data_single(index, value)
320322
self.undo_stack.endMacro()
323+
self.plotting_needs_break.emit(False)
321324
return success
322325
# multiple rows but only one column is selected
323326
all_set = []
324327
self.undo_stack.beginMacro("Set data")
325328
for index in selected:
326329
all_set.append(self._set_data_single(index, value))
327330
self.undo_stack.endMacro()
331+
self.plotting_needs_break.emit(False)
328332
return all(all_set)
329333

330334
def _set_data_single(self, index, value):

src/petab_gui/settings_manager.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ def save_current_settings(self):
286286

287287
def default_col_config(self):
288288
"""Return default config for new columns."""
289-
return {"strategy": NO_DEFAULT}
289+
return settings_manager.get_table_defaults(self.table_name)
290290

291291

292292
class SettingsDialog(QDialog):
@@ -332,7 +332,8 @@ def init_general_page(self):
332332
# Header
333333
header = QLabel("<b>Profile</b>")
334334
desc = QLabel(
335-
"These information can be automatically used when saving a COMBINE archive."
335+
"These information can be automatically used when saving "
336+
"a COMBINE archive."
336337
)
337338
desc.setWordWrap(True)
338339

src/petab_gui/views/simple_plot_view.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def __init__(self, parent=None):
7575
self.update_timer.setSingleShot(True)
7676
self.update_timer.timeout.connect(self.plot_it)
7777
self.observable_to_subplot = {}
78+
self.no_plotting_rn = False
7879

7980
def initialize(
8081
self, meas_proxy, sim_proxy, cond_proxy, vis_proxy, petab_model
@@ -104,6 +105,8 @@ def initialize(
104105
self.plot_it()
105106

106107
def plot_it(self):
108+
if self.no_plotting_rn:
109+
return
107110
if not self.meas_proxy or not self.cond_proxy:
108111
return
109112
if not self.isVisible():
@@ -336,6 +339,12 @@ def plot_residuals(self):
336339
# fig_fit.tight_layout()
337340
create_plot_tab(fig_fit, self, "Goodness of Fit")
338341

342+
def disable_plotting(self, disable: bool):
343+
"""Set self.no_plotting_rn to enable/disable plotting."""
344+
self.no_plotting_rn = disable
345+
if not self.no_plotting_rn:
346+
self._debounced_plot()
347+
339348

340349
class MeasurementHighlighter:
341350
def __init__(self):

0 commit comments

Comments
 (0)