Source code for wolfhece.assets.curve.editor

"""Graphical editor for curve plot asset configuration."""

from __future__ import annotations

import wx
import wx.grid as wxgrid
import wx.propgrid as pg

from ...CpGrid import CpGrid
from ...PyParams import Type_Param, Wolf_Param, new_json
from .controller import CurveZonesController
from .zones_asset import CurveZonesAsset


[docs] GROUP_GEOM = "Curve - Geometry"
[docs] GROUP_NORM = "Curve - Normalization"
[docs] GROUP_STYLE = "Curve - Style"
[docs] GROUP_AXES_GRID = "Curve - Axes/Grid"
[docs] GROUP_CURVE_TEMPLATE = "Curve $n$"
[docs] LINE_STYLE_LABEL_TO_CODE = { "Solid": 0, "Dashed": 1, "Dotted": 2, "Dash-dot": 3, }
[docs] LINE_STYLE_CODE_TO_NAME = { 0: "solid", 1: "dashed", 2: "dotted", 3: "dashdot", }
[docs] LINE_STYLE_NAME_TO_CODE = {v: k for k, v in LINE_STYLE_CODE_TO_NAME.items()}
[docs] TICK_POS_LABEL_TO_CODE = { "Inside": 0, "Outside": 1, "Centered": 2, }
[docs] TICK_POS_CODE_TO_NAME = { 0: "inside", 1: "outside", 2: "centered", }
[docs] TICK_POS_NAME_TO_CODE = {v: k for k, v in TICK_POS_CODE_TO_NAME.items()}
[docs] class CurveZonesEditor(wx.Frame): """Editor for curve points with Wolf_Param for options.""" def __init__(self, parent, controller: CurveZonesController): super().__init__(parent, title=f"Curve Editor - {controller.id}", size=(980, 680))
[docs] self.controller = controller
[docs] self._updating_ui = False
panel = wx.Panel(self) main = wx.BoxSizer(wx.VERTICAL) splitter = wx.SplitterWindow(panel, style=wx.SP_LIVE_UPDATE | wx.SP_3D) left_panel = wx.Panel(splitter) right_panel = wx.Panel(splitter) left = wx.BoxSizer(wx.VERTICAL) right = wx.BoxSizer(wx.VERTICAL)
[docs] self._wp = Wolf_Param(right_panel, to_read=False, withbuttons=False, init_GUI=False, force_even_if_same_default=True)
self._wp.ensure_prop(wxparent=right_panel, show_in_active_if_default=True, height=260) self._init_wolf_param_schema() if self._wp.prop is not None: self._wp.prop.SetMinSize((380, -1))
[docs] self._grid = CpGrid(left_panel, wx.ID_ANY, wx.WANTS_CHARS)
self._grid.CreateGrid(0, 3) self._grid.SetColLabelValue(0, "Curve") self._grid.SetColLabelValue(1, "X") self._grid.SetColLabelValue(2, "Y") self._grid.EnableEditing(True) tools = wx.BoxSizer(wx.HORIZONTAL)
[docs] self._pick_position = wx.Button(right_panel, label="Pick canvas origin on map")
[docs] self._transform_map = wx.Button(right_panel, label="Transform on map")
[docs] self._live = wx.CheckBox(right_panel, label="Live update")
self._live.SetValue(True) tools.Add(self._pick_position, 0, wx.RIGHT, 12) tools.Add(self._transform_map, 0, wx.RIGHT, 12) btns = wx.BoxSizer(wx.HORIZONTAL)
[docs] self._add = wx.Button(left_panel, label="Row+")
[docs] self._del = wx.Button(left_panel, label="Row-")
[docs] self._apply = wx.Button(left_panel, label="Apply")
[docs] self._save_json = wx.Button(left_panel, label="Save JSON")
[docs] self._load_json = wx.Button(left_panel, label="Load JSON")
for b in (self._add, self._del, self._apply, self._save_json, self._load_json): btns.Add(b, 0, wx.RIGHT, 6) left.Add(self._grid, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 8) left.Add(btns, 0, wx.ALL, 8) if self._wp.prop is not None: right.Add(self._wp.prop, 1, wx.EXPAND | wx.ALL, 8) right.Add(self._live, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 8) right.Add(tools, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 8) left_panel.SetSizer(left) right_panel.SetSizer(right) right_panel.SetMinSize((380, -1)) splitter.SplitVertically(left_panel, right_panel, sashPosition=600) splitter.SetSashGravity(1.0) splitter.SetMinimumPaneSize(300) main.Add(splitter, 1, wx.EXPAND) panel.SetSizer(main) self._bind_events() self.refresh_from_controller()
[docs] def _init_wolf_param_schema(self) -> None: wp = self._wp wp.add_param(GROUP_GEOM, "Canvas X", 0.0, Type_Param.Float, "Canvas origin X", whichdict="All") wp.add_param(GROUP_GEOM, "Canvas Y", 0.0, Type_Param.Float, "Canvas origin Y", whichdict="All") wp.add_param(GROUP_GEOM, "Canvas W", 100.0, Type_Param.Float, "Canvas width", whichdict="All") wp.add_param(GROUP_GEOM, "Canvas H", 100.0, Type_Param.Float, "Canvas height", whichdict="All") wp.add_param(GROUP_GEOM, "Area fx", 0.1, Type_Param.Float, "Area fraction origin x", whichdict="All") wp.add_param(GROUP_GEOM, "Area fy", 0.1, Type_Param.Float, "Area fraction origin y", whichdict="All") wp.add_param(GROUP_GEOM, "Area fw", 0.8, Type_Param.Float, "Area fraction width", whichdict="All") wp.add_param(GROUP_GEOM, "Area fh", 0.3, Type_Param.Float, "Area fraction height", whichdict="All") wp.add_param(GROUP_NORM, "X min", 0.0, Type_Param.Float, "Normalization min bound for X", whichdict="All") wp.add_param(GROUP_NORM, "X max", 1.0, Type_Param.Float, "Normalization max bound for X", whichdict="All") wp.add_param(GROUP_NORM, "Y min", 0.0, Type_Param.Float, "Normalization min bound for Y", whichdict="All") wp.add_param(GROUP_NORM, "Y max", 1.0, Type_Param.Float, "Normalization max bound for Y", whichdict="All") wp.add_param(GROUP_NORM, "Clamp projected", True, Type_Param.Logical, "Clamp projected points to [0,1]", whichdict="All") wp.add_param(GROUP_NORM, "Sort by X", True, Type_Param.Logical, "Sort points by normalized X", whichdict="All") wp.add_param(GROUP_STYLE, "Nb curves", 1, Type_Param.Integer, "Number of curve style groups", whichdict="All") wp.add_param(GROUP_STYLE, "Show area frame", True, Type_Param.Logical, "Display plot frame", whichdict="All") wp.add_param(GROUP_STYLE, "Legend", False, Type_Param.Logical, "Display curve labels", whichdict="All") wp.add_param(GROUP_AXES_GRID, "Show axes", False, Type_Param.Logical, "Display x=0/y=0 axes", whichdict="All") wp.add_param(GROUP_AXES_GRID, "Show grid", False, Type_Param.Logical, "Display plot grid", whichdict="All") wp.add_param(GROUP_AXES_GRID, "Grid dX", 1.0, Type_Param.Float, "Grid spacing on X values", whichdict="All") wp.add_param(GROUP_AXES_GRID, "Grid dY", 1.0, Type_Param.Float, "Grid spacing on Y values", whichdict="All") wp.add_param(GROUP_AXES_GRID, "Show X ticks", False, Type_Param.Logical, "Display X axis ticks", whichdict="All") wp.add_param(GROUP_AXES_GRID, "Show Y ticks", False, Type_Param.Logical, "Display Y axis ticks", whichdict="All") wp.add_param(GROUP_AXES_GRID, "Show X tick labels", False, Type_Param.Logical, "Display numeric labels for X ticks", whichdict="All") wp.add_param(GROUP_AXES_GRID, "Show Y tick labels", False, Type_Param.Logical, "Display numeric labels for Y ticks", whichdict="All") wp.add_param( GROUP_AXES_GRID, "X tick format", "", Type_Param.String, "Format for X tick labels: strftime pattern (e.g. '%Y-%m-%d') or Python format spec (e.g. '.2f'). Leave empty for auto.", whichdict="All", ) wp.add_param( GROUP_AXES_GRID, "Y tick format", "", Type_Param.String, "Format for Y tick labels: strftime pattern (e.g. '%H:%M') or Python format spec (e.g. '.2f'). Leave empty for auto.", whichdict="All", ) wp.add_param( GROUP_AXES_GRID, "X tick position", 0, Type_Param.Integer, "Position of X ticks", whichdict="All", jsonstr=new_json(TICK_POS_LABEL_TO_CODE, "Tick position"), ) wp.add_param( GROUP_AXES_GRID, "Y tick position", 0, Type_Param.Integer, "Position of Y ticks", whichdict="All", jsonstr=new_json(TICK_POS_LABEL_TO_CODE, "Tick position"), ) self._wp.add_IncGroup(GROUP_CURVE_TEMPLATE, 1, 999, GROUP_STYLE, "Nb curves") self._wp.add_param(GROUP_CURVE_TEMPLATE, "Color", (0, 0, 0, 255), Type_Param.Color, "Curve color", whichdict="IncGroup") self._wp.add_param(GROUP_CURVE_TEMPLATE, "Alpha", 255, Type_Param.Integer, "Curve alpha in [0,255]", whichdict="IncGroup") self._wp.add_param(GROUP_CURVE_TEMPLATE, "Width", 1.5, Type_Param.Float, "Curve line width", whichdict="IncGroup") self._wp.add_param( GROUP_CURVE_TEMPLATE, "Line style", 0, Type_Param.Integer, "Line style", whichdict="IncGroup", jsonstr=new_json(LINE_STYLE_LABEL_TO_CODE, "Per-curve line style"), )
@staticmethod
[docs] def _curve_group_name(idx: int) -> str: return f"Curve {idx}"
[docs] def _curve_ids_from_grid(self) -> list[int]: ids: set[int] = set() for r in range(self._grid.GetNumberRows()): raw = self._grid.GetCellValue(r, 0).strip() or "1" try: ids.add(int(float(raw))) except Exception: continue return sorted(ids)
[docs] def _sync_incremental_curve_groups(self, count: int) -> None: count = max(1, int(count)) try: self._wp.apply_changes_to_memory(verbosity=False) except Exception: pass self._wp[(GROUP_STYLE, "Nb curves")] = count self._wp.update_incremental_groups_params(update_groups=True, update_params=False)
[docs] def _styles_from_controller(self, count: int) -> tuple[list[tuple[int, int, int, int]], list[dict]]: colors: list[tuple[int, int, int, int]] = [] for i in range(count): if self.controller.colors and i < len(self.controller.colors): c = self.controller.colors[i] if len(c) == 3: colors.append((int(c[0]), int(c[1]), int(c[2]), 255)) else: colors.append((int(c[0]), int(c[1]), int(c[2]), int(c[3]))) else: colors.append(tuple(CurveZonesAsset.DEFAULT_COLORS[i % len(CurveZonesAsset.DEFAULT_COLORS)])) styles: list[dict] = [] for i in range(count): if self.controller.curve_styles and i < len(self.controller.curve_styles): styles.append(dict(self.controller.curve_styles[i])) else: styles.append({"line_width": self.controller.line_width, "line_style": "solid"}) return colors, styles
[docs] def _set_incremental_curve_values(self, colors: list[tuple[int, int, int, int]], styles: list[dict]) -> None: count = len(colors) self._sync_incremental_curve_groups(count) for i in range(count): grp = self._curve_group_name(i + 1) rgba = colors[i] sty = styles[i] self._wp[(grp, "Color")] = (int(rgba[0]), int(rgba[1]), int(rgba[2]), int(rgba[3])) self._wp[(grp, "Alpha")] = int(max(0, min(255, rgba[3]))) self._wp[(grp, "Width")] = float(max(0.1, sty.get("line_width", self.controller.line_width))) style_name = str(sty.get("line_style", "solid")).strip().lower() self._wp[(grp, "Line style")] = int(LINE_STYLE_NAME_TO_CODE.get(style_name, 0))
[docs] def _read_incremental_curve_values(self, count: int) -> tuple[list[tuple[int, int, int, int]], list[dict]]: colors: list[tuple[int, int, int, int]] = [] styles: list[dict] = [] for i in range(count): grp = self._curve_group_name(i + 1) rgb = tuple(int(v) for v in self._wp[(grp, "Color")]) alpha = int(self._wp[(grp, "Alpha")]) width = float(self._wp[(grp, "Width")]) style_code = int(self._wp[(grp, "Line style")]) colors.append(( max(0, min(255, rgb[0])), max(0, min(255, rgb[1])), max(0, min(255, rgb[2])), max(0, min(255, alpha)), )) styles.append({ "line_width": max(0.1, width), "line_style": LINE_STYLE_CODE_TO_NAME.get(style_code, "solid"), }) return colors, styles
[docs] def _read_wp(self) -> dict: self._wp.apply_changes_to_memory(verbosity=False) return { "canvas_origin": ( float(self._wp[(GROUP_GEOM, "Canvas X")]), float(self._wp[(GROUP_GEOM, "Canvas Y")]), ), "canvas_size": ( float(self._wp[(GROUP_GEOM, "Canvas W")]), float(self._wp[(GROUP_GEOM, "Canvas H")]), ), "area_fraction": ( float(self._wp[(GROUP_GEOM, "Area fx")]), float(self._wp[(GROUP_GEOM, "Area fy")]), float(self._wp[(GROUP_GEOM, "Area fw")]), float(self._wp[(GROUP_GEOM, "Area fh")]), ), "x_bounds": ( float(self._wp[(GROUP_NORM, "X min")]), float(self._wp[(GROUP_NORM, "X max")]), ), "y_bounds": ( float(self._wp[(GROUP_NORM, "Y min")]), float(self._wp[(GROUP_NORM, "Y max")]), ), "clamp_projected_points": bool(self._wp[(GROUP_NORM, "Clamp projected")]), "sort_by_x": bool(self._wp[(GROUP_NORM, "Sort by X")]), "show_area_frame": bool(self._wp[(GROUP_STYLE, "Show area frame")]), "legend_visible": bool(self._wp[(GROUP_STYLE, "Legend")]), "show_axes": bool(self._wp[(GROUP_AXES_GRID, "Show axes")]), "show_grid": bool(self._wp[(GROUP_AXES_GRID, "Show grid")]), "grid_dx": float(self._wp[(GROUP_AXES_GRID, "Grid dX")]), "grid_dy": float(self._wp[(GROUP_AXES_GRID, "Grid dY")]), "show_x_ticks": bool(self._wp[(GROUP_AXES_GRID, "Show X ticks")]), "show_y_ticks": bool(self._wp[(GROUP_AXES_GRID, "Show Y ticks")]), "show_x_tick_labels": bool(self._wp[(GROUP_AXES_GRID, "Show X tick labels")]), "show_y_tick_labels": bool(self._wp[(GROUP_AXES_GRID, "Show Y tick labels")]), "x_tick_format": str(self._wp[(GROUP_AXES_GRID, "X tick format")]), "y_tick_format": str(self._wp[(GROUP_AXES_GRID, "Y tick format")]), "x_tick_position": TICK_POS_CODE_TO_NAME.get(int(self._wp[(GROUP_AXES_GRID, "X tick position")]), "inside"), "y_tick_position": TICK_POS_CODE_TO_NAME.get(int(self._wp[(GROUP_AXES_GRID, "Y tick position")]), "inside"), }
[docs] def _bind_events(self) -> None: self._pick_position.Bind(wx.EVT_BUTTON, self.on_pick_position) self._transform_map.Bind(wx.EVT_BUTTON, self.on_transform_map) self._add.Bind(wx.EVT_BUTTON, self.on_add_row) self._del.Bind(wx.EVT_BUTTON, self.on_del_row) self._apply.Bind(wx.EVT_BUTTON, self.on_apply) self._save_json.Bind(wx.EVT_BUTTON, self.on_save_json) self._load_json.Bind(wx.EVT_BUTTON, self.on_load_json) self._grid.Bind(wxgrid.EVT_GRID_CELL_CHANGED, self.on_grid_changed) self._grid.Bind(wxgrid.EVT_GRID_EDITOR_HIDDEN, self.on_grid_editor_hidden) if self._wp.prop is not None: self._wp.prop.Bind(pg.EVT_PG_CHANGED, self.on_prop_changed)
[docs] def _collect_curves(self) -> tuple[list[list[tuple[float, float]]], list[str]]: rows = self._grid.GetNumberRows() groups: dict[int, list[tuple[float, float]]] = {} for r in range(rows): curve_id = int(float(self._grid.GetCellValue(r, 0).strip() or "1")) x = float(self._grid.GetCellValue(r, 1).strip()) y = float(self._grid.GetCellValue(r, 2).strip()) groups.setdefault(curve_id, []).append((x, y)) curves: list[list[tuple[float, float]]] = [] labels: list[str] = [] for cid in sorted(groups.keys()): pts = groups[cid] if len(pts) < 2: raise ValueError(f"Curve {cid} must contain at least 2 points") curves.append(pts) labels.append(f"Curve {cid}") if not curves: raise ValueError("At least one curve with 2 points is required") return curves, labels
[docs] def _apply_controller(self) -> None: try: if self._grid.IsCellEditControlShown(): self._grid.SaveEditControlValue() self._grid.HideCellEditControl() except Exception: pass cfg = self._read_wp() curves, labels = self._collect_curves() self._sync_incremental_curve_groups(len(curves)) colors, styles = self._read_incremental_curve_values(len(curves)) self.controller.update_curves(curves=curves, labels=labels, colors=colors, curve_styles=styles, rebuild=False) self.controller.update_geometry( x_bounds=cfg["x_bounds"], y_bounds=cfg["y_bounds"], canvas_origin=cfg["canvas_origin"], canvas_size=cfg["canvas_size"], area_fraction=cfg["area_fraction"], rebuild=False, ) self.controller.update_style( clamp_projected_points=cfg["clamp_projected_points"], sort_by_x=cfg["sort_by_x"], show_area_frame=cfg["show_area_frame"], show_axes=cfg["show_axes"], show_grid=cfg["show_grid"], grid_dx=cfg["grid_dx"], grid_dy=cfg["grid_dy"], show_x_ticks=cfg["show_x_ticks"], show_y_ticks=cfg["show_y_ticks"], show_x_tick_labels=cfg["show_x_tick_labels"], show_y_tick_labels=cfg["show_y_tick_labels"], x_tick_format=cfg["x_tick_format"], y_tick_format=cfg["y_tick_format"], x_tick_position=cfg["x_tick_position"], y_tick_position=cfg["y_tick_position"], legend_visible=cfg["legend_visible"], rebuild=False, ) self.controller.rebuild(ToCheck=True) self.refresh_from_controller()
[docs] def refresh_from_controller(self) -> None: self._updating_ui = True try: c = self.controller self._wp[(GROUP_GEOM, "Canvas X")] = c.canvas_x self._wp[(GROUP_GEOM, "Canvas Y")] = c.canvas_y self._wp[(GROUP_GEOM, "Canvas W")] = c.canvas_width self._wp[(GROUP_GEOM, "Canvas H")] = c.canvas_height self._wp[(GROUP_GEOM, "Area fx")] = c.area_fraction[0] self._wp[(GROUP_GEOM, "Area fy")] = c.area_fraction[1] self._wp[(GROUP_GEOM, "Area fw")] = c.area_fraction[2] self._wp[(GROUP_GEOM, "Area fh")] = c.area_fraction[3] x_bounds = c.x_bounds if c.x_bounds is not None else (0.0, 1.0) y_bounds = c.y_bounds if c.y_bounds is not None else (0.0, 1.0) self._wp[(GROUP_NORM, "X min")] = x_bounds[0] self._wp[(GROUP_NORM, "X max")] = x_bounds[1] self._wp[(GROUP_NORM, "Y min")] = y_bounds[0] self._wp[(GROUP_NORM, "Y max")] = y_bounds[1] self._wp[(GROUP_NORM, "Clamp projected")] = c.clamp_projected_points self._wp[(GROUP_NORM, "Sort by X")] = c.sort_by_x self._wp[(GROUP_STYLE, "Show area frame")] = c.show_area_frame self._wp[(GROUP_STYLE, "Legend")] = c.legend_visible self._wp[(GROUP_AXES_GRID, "Show axes")] = c.show_axes self._wp[(GROUP_AXES_GRID, "Show grid")] = c.show_grid self._wp[(GROUP_AXES_GRID, "Grid dX")] = c.grid_dx self._wp[(GROUP_AXES_GRID, "Grid dY")] = c.grid_dy self._wp[(GROUP_AXES_GRID, "Show X ticks")] = c.show_x_ticks self._wp[(GROUP_AXES_GRID, "Show Y ticks")] = c.show_y_ticks self._wp[(GROUP_AXES_GRID, "Show X tick labels")] = c.show_x_tick_labels self._wp[(GROUP_AXES_GRID, "Show Y tick labels")] = c.show_y_tick_labels self._wp[(GROUP_AXES_GRID, "X tick format")] = c.x_tick_format self._wp[(GROUP_AXES_GRID, "Y tick format")] = c.y_tick_format self._wp[(GROUP_AXES_GRID, "X tick position")] = TICK_POS_NAME_TO_CODE.get(c.x_tick_position, 0) self._wp[(GROUP_AXES_GRID, "Y tick position")] = TICK_POS_NAME_TO_CODE.get(c.y_tick_position, 0) colors, styles = self._styles_from_controller(len(c.curves)) self._set_incremental_curve_values(colors, styles) self._wp.Populate(sorted_groups=False) data_rows: list[tuple[int, float, float]] = [] for i, curve in enumerate(c.curves): curve_id = i + 1 for x, y in curve: data_rows.append((curve_id, x, y)) cur_rows = self._grid.GetNumberRows() target_rows = len(data_rows) if cur_rows < target_rows: self._grid.AppendRows(target_rows - cur_rows) elif cur_rows > target_rows: self._grid.DeleteRows(0, cur_rows - target_rows) for r, (cid, x, y) in enumerate(data_rows): self._grid.SetCellValue(r, 0, str(cid)) self._grid.SetCellValue(r, 1, str(x)) self._grid.SetCellValue(r, 2, str(y)) finally: self._updating_ui = False
[docs] def _maybe_live(self) -> None: if self._updating_ui: return if self._live.GetValue(): try: self._apply_controller() except Exception: pass
[docs] def _sync_groups_from_grid(self) -> None: if self._updating_ui: return count = max(1, len(self._curve_ids_from_grid())) self._sync_incremental_curve_groups(count) self._wp.Populate(sorted_groups=False)
[docs] def on_pick_position(self, event): mv = self.controller.mapviewer if mv is None: wx.MessageBox("Curve plot is not attached to a mapviewer", "Curve Editor", wx.OK | wx.ICON_WARNING) event.Skip() return mv._curve_pick_controller = self.controller mv._curve_pick_editor = self mv.start_action("pick curve canvas origin", "Pick curve canvas origin on map") wx.MessageBox("Right-click on map to set curve canvas origin", "Curve Editor", wx.OK | wx.ICON_INFORMATION) event.Skip()
[docs] def on_transform_map(self, event): """Start interactive move/resize mode with map handles.""" mv = self.controller.mapviewer if mv is None: wx.MessageBox("Curve plot is not attached to a mapviewer", "Curve Editor", wx.OK | wx.ICON_WARNING) event.Skip() return mv._asset_transform_controller = self.controller mv._asset_transform_editor = self mv.start_action('transform asset bounds', 'Transform asset bounds on map') wx.MessageBox( "Left-click a handle (corners/sides/center), then drag to move/resize the asset.\n" "Shift: keep ratio (corners) | Ctrl: resize from center | Alt: enable snap.", "Curve Editor", wx.OK | wx.ICON_INFORMATION, ) event.Skip()
[docs] def on_prop_changed(self, event): self._maybe_live() event.Skip()
[docs] def on_grid_changed(self, event): self._sync_groups_from_grid() self._maybe_live() event.Skip()
[docs] def on_grid_editor_hidden(self, event): """Keep focus on grid after Enter validation to avoid focus jump.""" wx.CallAfter(self._grid.SetFocus) event.Skip()
[docs] def on_add_row(self, event): self._grid.AppendRows(1) row = self._grid.GetNumberRows() - 1 self._grid.SetCellValue(row, 0, "1") self._grid.SetCellValue(row, 1, "0.0") self._grid.SetCellValue(row, 2, "0.0") self._sync_groups_from_grid() self._maybe_live() event.Skip()
[docs] def on_del_row(self, event): row = self._grid.GetGridCursorRow() if row < 0: row = self._grid.GetNumberRows() - 1 if row >= 0 and self._grid.GetNumberRows() > 0: self._grid.DeleteRows(row, 1) self._sync_groups_from_grid() self._maybe_live() event.Skip()
[docs] def on_apply(self, event): try: self._apply_controller() except Exception as exc: wx.MessageBox(str(exc), "Curve Editor", wx.OK | wx.ICON_ERROR) event.Skip()
[docs] def on_save_json(self, event): with wx.FileDialog( self, "Save curve chart JSON", wildcard="JSON files (*.json)|*.json", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, ) as dlg: if dlg.ShowModal() == wx.ID_OK: self.controller.save_json(dlg.GetPath()) event.Skip()
[docs] def on_load_json(self, event): with wx.FileDialog( self, "Load curve chart JSON", wildcard="JSON files (*.json)|*.json", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST, ) as dlg: if dlg.ShowModal() == wx.ID_OK: loaded = CurveZonesController.load_json(dlg.GetPath()) c = self.controller c.update_curves( curves=loaded.curves, labels=loaded.labels, colors=loaded.colors, curve_styles=loaded.curve_styles, rebuild=False, ) c.update_geometry( x_bounds=loaded.x_bounds, y_bounds=loaded.y_bounds, canvas_origin=(loaded.canvas_x, loaded.canvas_y), canvas_size=(loaded.canvas_width, loaded.canvas_height), area_fraction=loaded.area_fraction, rebuild=False, ) c.update_style( clamp_projected_points=loaded.clamp_projected_points, sort_by_x=loaded.sort_by_x, line_width=loaded.line_width, line_alpha=loaded.line_alpha, show_area_frame=loaded.show_area_frame, frame_color=loaded.frame_color, show_axes=loaded.show_axes, axes_color=loaded.axes_color, axes_line_width=loaded.axes_line_width, show_grid=loaded.show_grid, grid_color=loaded.grid_color, grid_line_width=loaded.grid_line_width, grid_dx=loaded.grid_dx, grid_dy=loaded.grid_dy, show_x_ticks=loaded.show_x_ticks, show_y_ticks=loaded.show_y_ticks, show_x_tick_labels=loaded.show_x_tick_labels, show_y_tick_labels=loaded.show_y_tick_labels, x_tick_format=loaded.x_tick_format, y_tick_format=loaded.y_tick_format, x_tick_position=loaded.x_tick_position, y_tick_position=loaded.y_tick_position, legend_visible=loaded.legend_visible, legend_text_color=loaded.legend_text_color, rebuild=True, ) self.refresh_from_controller() event.Skip()