Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/petab_gui/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,46 @@ def _apply(self, src, dst):
self.model.dataChanged.emit(
self.model_index, self.model_index, [Qt.DisplayRole]
)


class RenameValueCommand(QUndoCommand):
"""Command to rename values in specified columns."""

def __init__(
self, model, old_id: str, new_id: str, column_names: str | list[str]
):
super().__init__(f"Rename value {old_id} → {new_id}")
self.model = model
self.old_id = old_id
self.new_id = new_id
self.column_names = (
column_names if isinstance(column_names, list) else [column_names]
)
self.changes = {} # {(row_idx, col_name): (old_val, new_val)}

df = self.model._data_frame
for col_name in self.column_names:
mask = df[col_name].eq(self.old_id)
for row_idx in df.index[mask]:
self.changes[(row_idx, col_name)] = (self.old_id, self.new_id)

def redo(self):
self._apply_changes(use_new=True)

def undo(self):
self._apply_changes(use_new=False)

def _apply_changes(self, use_new: bool):
df = self.model._data_frame
for (row_idx, col_name), (old_val, new_val) in self.changes.items():
df.at[row_idx, col_name] = new_val if use_new else old_val

if self.changes:
rows = [df.index.get_loc(row) for (row, _) in self.changes]
cols = [df.columns.get_loc(col) + 1 for (_, col) in self.changes]
top_left = self.model.index(min(rows), min(cols))
bottom_right = self.model.index(max(rows), max(cols))
self.model.dataChanged.emit(
top_left, bottom_right, [Qt.DisplayRole, Qt.EditRole]
)
self.model.something_changed.emit(True)
4 changes: 3 additions & 1 deletion src/petab_gui/controllers/default_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ def get_default(
default_value = column_config.get(DEFAULT_VALUE, "")

if strategy == USE_DEFAULT:
if np.issubdtype(self.model.dtypes[column_name], np.floating):
if column_name != self.model.index.name and np.issubdtype(
self.model.dtypes[column_name], np.floating
):
return float(default_value)
return default_value
if strategy == NO_DEFAULT:
Expand Down
18 changes: 16 additions & 2 deletions src/petab_gui/controllers/mother_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@ def __init__(self, view, model: PEtabModel):
self.actions = self.setup_actions()
self.view.setup_toolbar(self.actions)

self.plotter = None
self.init_plotter()
self.setup_connections()
self.setup_task_bar()
self.setup_context_menu()
self.plotter = None
self.init_plotter()

@property
def window_title(self):
Expand Down Expand Up @@ -194,6 +194,13 @@ def setup_connections(self):
column_names="observableId",
)
)
self.observable_controller.observable_2be_renamed.connect(
partial(
self.visualization_controller.rename_value,
column_names="yValues",
)
)
# Maybe TODO: add renaming dataset id?
# Rename Condition
self.condition_controller.condition_2be_renamed.connect(
partial(
Expand All @@ -204,6 +211,13 @@ def setup_connections(self):
],
)
)
# Plotting Disable Temporarily
for controller in self.controllers:
if controller == self.sbml_controller:
continue
controller.model.plotting_needs_break.connect(
self.plotter.disable_plotting
)
# Add new condition or observable
self.model.measurement.relevant_id_changed.connect(
lambda x, y, z: self.observable_controller.maybe_add_observable(
Expand Down
59 changes: 20 additions & 39 deletions src/petab_gui/controllers/table_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
)

from ..C import COLUMN, INDEX
from ..commands import RenameValueCommand
from ..models.pandas_table_model import (
PandasTableFilterProxy,
PandasTableModel,
Expand Down Expand Up @@ -588,6 +589,25 @@ def save_table(self, file_name):
f"Failed to save table: {str(e)}",
)

def rename_value(
self, old_id: str, new_id: str, column_names: str | list[str]
):
"""Rename the values in the dataframe.

Triggered by changes in the original observable_df or condition_df id.

Parameters
----------
old_id:
The old id, which was changed.
new_id:
The new id.
column_names:
The column or list of columns in which the id should be changed.
"""
command = RenameValueCommand(self.model, old_id, new_id, column_names)
self.undo_stack.push(command)


class MeasurementController(TableController):
"""Controller of the Measurement table."""
Expand All @@ -608,45 +628,6 @@ def check_petab_lint(
observable_df=observable_df,
)

def rename_value(
self, old_id: str, new_id: str, column_names: str | list[str]
):
"""Rename the values in the measurement_df.

Triggered by changes in the original observable_df or condition_df id.

Parameters
----------
old_id:
The old id, which was changed.
new_id:
The new id.
"""
if not isinstance(column_names, list):
column_names = [column_names]

for col_name in column_names:
# Find occurrences
mask = self.model._data_frame[col_name].eq(old_id)
if not mask.any():
continue

self.model._data_frame.loc[mask, col_name] = new_id
first_row, last_row = (
mask.idxmax(),
mask[::-1].idxmax(),
)
top_left = self.model.index(first_row, 1)
bottom_right = self.model.index(
last_row, self.model.columnCount() - 1
)
self.model.dataChanged.emit(
top_left, bottom_right, [Qt.DisplayRole, Qt.EditRole]
)

# Emit change signal
self.model.something_changed.emit(True)

def copy_noise_parameters(
self, observable_id: str, condition_id: str | None = None
) -> str:
Expand Down
4 changes: 4 additions & 0 deletions src/petab_gui/models/pandas_table_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class PandasTableModel(QAbstractTableModel):
cell_needs_validation = Signal(int, int) # row, column
something_changed = Signal(bool)
inserted_row = Signal(QModelIndex)
plotting_needs_break = Signal(bool)

def __init__(
self,
Expand Down Expand Up @@ -310,6 +311,7 @@ def setData(

if is_invalid(value) or value == "":
value = None
self.plotting_needs_break.emit(True) # Temp disable plotting
multi_row_change = False
if check_multi:
# check whether multiple rows but only one column is selected
Expand All @@ -318,13 +320,15 @@ def setData(
self.undo_stack.beginMacro("Set data")
success = self._set_data_single(index, value)
self.undo_stack.endMacro()
self.plotting_needs_break.emit(False)
return success
# multiple rows but only one column is selected
all_set = []
self.undo_stack.beginMacro("Set data")
for index in selected:
all_set.append(self._set_data_single(index, value))
self.undo_stack.endMacro()
self.plotting_needs_break.emit(False)
return all(all_set)

def _set_data_single(self, index, value):
Expand Down
5 changes: 3 additions & 2 deletions src/petab_gui/settings_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def save_current_settings(self):

def default_col_config(self):
"""Return default config for new columns."""
return {"strategy": NO_DEFAULT}
return settings_manager.get_table_defaults(self.table_name)


class SettingsDialog(QDialog):
Expand Down Expand Up @@ -332,7 +332,8 @@ def init_general_page(self):
# Header
header = QLabel("<b>Profile</b>")
desc = QLabel(
"These information can be automatically used when saving a COMBINE archive."
"These information can be automatically used when saving "
"a COMBINE archive."
)
desc.setWordWrap(True)

Expand Down
9 changes: 9 additions & 0 deletions src/petab_gui/views/simple_plot_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def __init__(self, parent=None):
self.update_timer.setSingleShot(True)
self.update_timer.timeout.connect(self.plot_it)
self.observable_to_subplot = {}
self.no_plotting_rn = False

def initialize(
self, meas_proxy, sim_proxy, cond_proxy, vis_proxy, petab_model
Expand Down Expand Up @@ -104,6 +105,8 @@ def initialize(
self.plot_it()

def plot_it(self):
if self.no_plotting_rn:
return
if not self.meas_proxy or not self.cond_proxy:
return
if not self.isVisible():
Expand Down Expand Up @@ -336,6 +339,12 @@ def plot_residuals(self):
# fig_fit.tight_layout()
create_plot_tab(fig_fit, self, "Goodness of Fit")

def disable_plotting(self, disable: bool):
"""Set self.no_plotting_rn to enable/disable plotting."""
self.no_plotting_rn = disable
if not self.no_plotting_rn:
self._debounced_plot()


class MeasurementHighlighter:
def __init__(self):
Expand Down