diff --git a/trace/widgets/control_panel.py b/trace/widgets/control_panel.py index 45e9742..e94e9ca 100644 --- a/trace/widgets/control_panel.py +++ b/trace/widgets/control_panel.py @@ -235,7 +235,7 @@ def add_curves(self, pvs: list[str]) -> None: List of PV names to add as curves """ for pv in pvs: - self.add_curve(pv) + self.add_curve(pv, duplicate=False) def add_empty_axis(self, name: str = "") -> "AxisItem": logger.debug("Adding new empty axis to the plot") @@ -325,7 +325,14 @@ def curve_item_dict(self): return plot_curves @QtCore.Slot() - def add_curve(self, pv: str = None) -> "CurveItem": + def add_curve(self, pv: str = None, duplicate: bool = True) -> "CurveItem": + """ + Add curve to the plot. New curves are added to the last axis by default, then sorted by unit. + + Parameters: + pv (str): EPICS PV or formula string + duplicate (bool): If True, duplicate PVs will be added on a new axis. If False, skip duplicates. + """ if pv is None and self.sender(): pv = self.sender().text() @@ -336,7 +343,51 @@ def add_curve(self, pv: str = None) -> "CurveItem": if pv.startswith("f://"): return last_axis.add_formula_curve(pv) else: - return last_axis.add_curve(pv) + # Check for duplicate PVs. If pv already exists, either add to new axis or skip + # depending on 'duplicate' flag + curves = self.curve_item_dict + if pv in [curves[curve]["name"] for curve in curves]: + if duplicate: + logger.debug(f"Adding duplicate curve to new axis for PV '{pv}'.") + return self.add_curve_to_new_axis(pv) + else: + logger.debug(f"Skipping duplicate curve for PV '{pv}'.") + return None + + curve = last_axis.add_curve(pv) + self.move_to_axis_from_unit(curve) # Move to axis based on unit + + return curve + + def add_curve_to_new_axis(self, pv: str) -> "CurveItem": + """ + Add curve to a new, empty axis. Used to avoid adding duplicate curves to the same axis. + + Parameters + ---------- + pv (str): EPICS PV name + """ + if not pv: + return None + axis_item = self.add_empty_axis() + return axis_item.add_curve(pv) + + def move_to_axis_from_unit(self, curve: "CurveItem") -> None: + """ + Detect curve units and automatically move curve to an axis matching its unit + + Parameters + ---------- + curve : CurveItem + """ + if not isinstance(curve, CurveItem): + return + if not curve.is_formula_curve(): + unit = curve.source.units + if unit: + self.move_curve_to_axis(curve, unit) + else: + curve.source.unitSignal.connect(lambda unit: self.move_curve_to_axis(curve, unit)) def clear_all(self) -> None: """Clear all axes and curves from the plot and control panel.""" @@ -606,6 +657,7 @@ def add_curve(self, pv: str, channel_args: dict = None) -> "CurveItem": CurveItem The created CurveItem widget. """ + palette = self.control_panel.curve_palette color = ColorButton.index_color(len(self.plot._curves), palette=palette) args = { @@ -622,6 +674,20 @@ def add_curve(self, pv: str, channel_args: dict = None) -> "CurveItem": return self.make_curve_widget(plot_curve_item) + def get_curves(self) -> list["CurveItem"]: + """Returns a list of CurveItems on this axis""" + curves = [] + for i in range(self.layout().count() - 1, -1, -1): + item = self.layout().itemAt(i).widget() + if isinstance(item, CurveItem): + curves.append(item) + + return curves + + def get_curve_names(self) -> list[str]: + """Returns a list of curve names on this axis""" + return [curve.source.name() for curve in self.get_curves()] + def add_formula_curve(self, formula: str) -> "CurveItem": """Create a new FormulaCurveItem for the given formula and add it to this AxisItem. Also creates a CurveItem widget for it. @@ -950,8 +1016,6 @@ def __init__(self, axis_item: AxisItem, source: ArchivePlotCurveItem | FormulaCu self.variable_name = self.control_panel.key_gen.send(self.source) self.control_panel.curve_dict[self.variable_name] = self.source - if not self.is_formula_curve(): - self.source.unitSignal.connect(lambda unit: self.control_panel.move_curve_to_axis(self, unit)) self.setup_layout()