"""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._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_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()