From 6385380f3a14b3fa338e56064fdf8a6003501703 Mon Sep 17 00:00:00 2001 From: Akshar Sarvesh Date: Tue, 30 Jul 2024 16:55:54 -0700 Subject: [PATCH 01/11] FIX: Axes changes independent of Formulas --- pydm/widgets/axis_table_model.py | 2 ++ pydm/widgets/baseplot.py | 16 ++++++++++++-- pydm/widgets/multi_axis_plot.py | 36 ++++++++++++++++++++++++-------- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/pydm/widgets/axis_table_model.py b/pydm/widgets/axis_table_model.py index e1cbf4974..c95d69378 100644 --- a/pydm/widgets/axis_table_model.py +++ b/pydm/widgets/axis_table_model.py @@ -69,6 +69,8 @@ def get_data(self, column_name, axis): return axis.auto_range elif column_name == "Log Mode": return axis.log_mode + elif column_name == "Hidden": + return not axis.isVisible() def setData(self, index, value, role=Qt.EditRole): if not index.isValid(): diff --git a/pydm/widgets/baseplot.py b/pydm/widgets/baseplot.py index 6fcca979e..4c5b22da8 100644 --- a/pydm/widgets/baseplot.py +++ b/pydm/widgets/baseplot.py @@ -473,7 +473,7 @@ def __init__( **kws, ) -> None: super(BasePlotAxisItem, self).__init__(orientation, **kws) - + self._curves: List[BasePlotCurveItem] = [] self._name = name self._orientation = orientation self._label = label @@ -638,6 +638,18 @@ def log_mode(self, log_mode: bool) -> None: self.setLogMode(x=False, y=log_mode) self.log_mode_updated.emit(self.name, log_mode) + def setHidden(self, shouldHide: bool): + if shouldHide: + self.hide() + for curve in self._curves: + curve.hide + + else: + self.show() + for curve in self._curves: + curve.show() + # Potentially can be fixed to not show curves previously marked hidden + def to_dict(self) -> OrderedDict: """ Returns an OrderedDict representation with values for all properties @@ -892,7 +904,7 @@ def removeCurve(self, plot_item: BasePlotCurveItem) -> None: The cureve to be removed from this plot """ if plot_item.y_axis_name in self.plotItem.axes: - self.plotItem.unlinkDataFromAxis(plot_item.y_axis_name) + self.plotItem.unlinkDataFromAxis(plot_item) self.removeItem(plot_item) self._curves.remove(plot_item) diff --git a/pydm/widgets/multi_axis_plot.py b/pydm/widgets/multi_axis_plot.py index 4f812634c..23ddfc5c0 100644 --- a/pydm/widgets/multi_axis_plot.py +++ b/pydm/widgets/multi_axis_plot.py @@ -30,7 +30,6 @@ def __init__(self, parent=None, axisItems=None, **kargs): viewBox.menu = MultiAxisViewBoxMenu(viewBox) super(MultiAxisPlot, self).__init__(viewBox=viewBox, axisItems=axisItems, **kargs) - self.curvesPerAxis = Counter() # A simple mapping of AxisName to a count of curves that using that axis self.axesOriginalRanges = {} # Dict from axis name to floats (x, y) representing original range of the axis # A set containing view boxes which are stacked underneath the top level view. These views will be needed @@ -220,22 +219,24 @@ def linkDataToAxis(self, plotDataItem: PlotDataItem, axisName: str) -> None: # pyqtgraph expects data items on plots to be added to both the list of curves and items to function properly self.curves.append(plotDataItem) self.items.append(plotDataItem) - self.curvesPerAxis[axisName] += 1 + axisToLink._curves.append(plotDataItem) + axisToLink.show() def removeAxis(self, axisName): if axisName not in self.axes: return - self.curvesPerAxis[axisName] = 0 - oldAxis = self.axes[axisName]["item"] self.layout.removeItem(oldAxis) if oldAxis.scene() is not None: oldAxis.scene().removeItem(oldAxis) oldAxis.unlinkFromView() + stackedView = oldAxis.linkedView() + if stackedView and stackedView is not self.vb: + self.stackedViews.remove(stackedView) del self.axes[axisName] - def unlinkDataFromAxis(self, axisName): + def unlinkDataFromAxis(self, curve: PlotDataItem): """ Lets the plot know that this axis is now associated with one less curve. If there are no longer any curves linked with this axis, then removes it from the scene and cleans it up. @@ -244,10 +245,27 @@ def unlinkDataFromAxis(self, axisName): axisName: str The name of the axis that a curve is being removed from """ - - self.curvesPerAxis[axisName] -= 1 - if self.curvesPerAxis[axisName] == 0: - self.removeAxis(axisName) + if hasattr(curve, "y_axis_name"): + self.axes[curve.y_axis_name]["item"]._curves.remove(curve) + self.autoVisible(curve.y_axis_name) + + def autoVisible(self, axisName): + + # Do we have any visible curves? + axis = self.axes[axisName]["item"] + for curve in axis._curves: + if curve.isVisible(): + axis.show() + return + + # We don't have any visible curves, but are we the only curve being shown? + for otherAxis in self.axes.keys(): + otherItem = self.axes[otherAxis]["item"] + if otherItem is not axis and otherItem.isVisible: + axis.hide() + return + # No other axis is visible. + axis.show() def setXRange(self, minX, maxX, padding=0, update=True): """ From 8fbfca44496721d15aeb2809361125d3f007163e Mon Sep 17 00:00:00 2001 From: Akshar Sarvesh Date: Tue, 30 Jul 2024 17:02:53 -0700 Subject: [PATCH 02/11] FIX: Bug fix on replacing a pv with another pv not updating automatically --- pydm/widgets/archiver_time_plot.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pydm/widgets/archiver_time_plot.py b/pydm/widgets/archiver_time_plot.py index 4518a24c7..747073388 100644 --- a/pydm/widgets/archiver_time_plot.py +++ b/pydm/widgets/archiver_time_plot.py @@ -389,10 +389,11 @@ def requestDataFromArchiver(self, min_x: Optional[float] = None, max_x: Optional processing_command = "" if curve.use_archive_data: if max_x is None: - if curve.points_accumulated > 0: - max_x = curve.data_buffer[0][curve.getBufferSize() - curve.points_accumulated] - else: - max_x = self._starting_timestamp + max_x = curve.min_x() + # if curve.points_accumulated > 0: + # max_x = curve.data_buffer[0][curve.getBufferSize() - curve.points_accumulated] + # else: + # max_x = self._starting_timestamp requested_seconds = max_x - min_x if requested_seconds <= 5: continue # Avoids noisy requests when first rendering the plot From 706a7075d9a0aff4130e9342e347e879aafc2bcf Mon Sep 17 00:00:00 2001 From: Akshar Sarvesh Date: Tue, 30 Jul 2024 17:38:54 -0700 Subject: [PATCH 03/11] FIX: Segmentation Faulting accessing old view boxes --- pydm/widgets/multi_axis_plot.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/pydm/widgets/multi_axis_plot.py b/pydm/widgets/multi_axis_plot.py index 23ddfc5c0..67eabe6ed 100644 --- a/pydm/widgets/multi_axis_plot.py +++ b/pydm/widgets/multi_axis_plot.py @@ -221,6 +221,8 @@ def linkDataToAxis(self, plotDataItem: PlotDataItem, axisName: str) -> None: self.items.append(plotDataItem) axisToLink._curves.append(plotDataItem) axisToLink.show() + for otherAxisName in self.axes.keys(): + self.autoVisible(otherAxisName) def removeAxis(self, axisName): if axisName not in self.axes: @@ -230,8 +232,8 @@ def removeAxis(self, axisName): self.layout.removeItem(oldAxis) if oldAxis.scene() is not None: oldAxis.scene().removeItem(oldAxis) - oldAxis.unlinkFromView() stackedView = oldAxis.linkedView() + oldAxis.unlinkFromView() if stackedView and stackedView is not self.vb: self.stackedViews.remove(stackedView) del self.axes[axisName] @@ -250,22 +252,23 @@ def unlinkDataFromAxis(self, curve: PlotDataItem): self.autoVisible(curve.y_axis_name) def autoVisible(self, axisName): - # Do we have any visible curves? axis = self.axes[axisName]["item"] - for curve in axis._curves: - if curve.isVisible(): - axis.show() - return - - # We don't have any visible curves, but are we the only curve being shown? - for otherAxis in self.axes.keys(): - otherItem = self.axes[otherAxis]["item"] - if otherItem is not axis and otherItem.isVisible: - axis.hide() - return - # No other axis is visible. - axis.show() + if hasattr(axis,"_curves"): + for curve in axis._curves: + if curve.isVisible(): + axis.show() + return + + # We don't have any visible curves, but are we the only curve being shown? + for otherAxis in self.axes.keys(): + otherItem = self.axes[otherAxis]["item"] + if otherItem is not axis and otherAxis not in ["bottom", "top"] and otherItem.isVisible(): + + axis.hide() + return + # No other axis is visible. + axis.show() def setXRange(self, minX, maxX, padding=0, update=True): """ From f28bd9c54df8eddfc1d8ca6a7136fab8ba9fb2af Mon Sep 17 00:00:00 2001 From: Akshar Sarvesh Date: Wed, 31 Jul 2024 11:18:10 -0700 Subject: [PATCH 04/11] STY: Precommit Issues --- pydm/tests/widgets/test_baseplot.py | 18 +++++++++--------- pydm/widgets/multi_axis_plot.py | 7 +++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/pydm/tests/widgets/test_baseplot.py b/pydm/tests/widgets/test_baseplot.py index 81cf98dce..cc0538e5d 100644 --- a/pydm/tests/widgets/test_baseplot.py +++ b/pydm/tests/widgets/test_baseplot.py @@ -132,9 +132,9 @@ def test_baseplot_multiple_y_axes(qtbot): assert base_plot.plotItem.axes["Test Axis 3"]["item"].orientation == "left" # Verify the curves got assigned to the correct axes - assert base_plot.plotItem.curvesPerAxis["Test Axis 1"] == 2 - assert base_plot.plotItem.curvesPerAxis["Test Axis 2"] == 1 - assert base_plot.plotItem.curvesPerAxis["Test Axis 3"] == 1 + assert len(base_plot.plotItem.axes["Test Axis 1"]["item"]._curves) == 2 + assert len(base_plot.plotItem.axes["Test Axis 2"]["item"]._curves) == 1 + assert len(base_plot.plotItem.axes["Test Axis 3"]["item"]._curves) == 1 def test_baseplot_no_added_y_axes(qtbot): @@ -224,10 +224,10 @@ def test_timeplot_add_multiple_axes(qtbot): # Verify the curves got assigned to the correct axes assert len(time_plot.curves) == 5 - assert time_plot.plotItem.curvesPerAxis["Axis 1"] == 2 - assert time_plot.plotItem.curvesPerAxis["Axis 2"] == 1 - assert time_plot.plotItem.curvesPerAxis["Axis 3"] == 1 - assert time_plot.plotItem.curvesPerAxis["Axis 4"] == 1 + assert len(time_plot.plotItem.axes["Axis 1"]["item"]._curves) == 2 + assert len(time_plot.plotItem.axes["Axis 2"]["item"]._curves) == 1 + assert len(time_plot.plotItem.axes["Axis 3"]["item"]._curves) == 1 + assert len(time_plot.plotItem.axes["Axis 4"]["item"]._curves) == 1 def test_multiaxis_plot_no_designer_flow(qtbot): @@ -249,8 +249,8 @@ def test_multiaxis_plot_no_designer_flow(qtbot): assert "Axis 2" in plot.plotItem.axes assert "right" in plot.plotItem.axes assert "top" in plot.plotItem.axes - assert plot.plotItem.curvesPerAxis["Axis 1"] == 2 - assert plot.plotItem.curvesPerAxis["Axis 2"] == 1 + assert len(plot.plotItem.axes["Axis 1"]["item"]._curves) == 2 + assert len(plot.plotItem.axes["Axis 2"]["item"]._curves) == 1 # Now check the case where no new y-axis name is specified for any of the new channels. plot = PyDMTimePlot() diff --git a/pydm/widgets/multi_axis_plot.py b/pydm/widgets/multi_axis_plot.py index 67eabe6ed..08bd56928 100644 --- a/pydm/widgets/multi_axis_plot.py +++ b/pydm/widgets/multi_axis_plot.py @@ -1,5 +1,4 @@ import weakref -from collections import Counter from pyqtgraph import AxisItem, PlotDataItem, PlotItem, ViewBox from typing import List, Optional from qtpy.QtCore import Qt @@ -219,7 +218,8 @@ def linkDataToAxis(self, plotDataItem: PlotDataItem, axisName: str) -> None: # pyqtgraph expects data items on plots to be added to both the list of curves and items to function properly self.curves.append(plotDataItem) self.items.append(plotDataItem) - axisToLink._curves.append(plotDataItem) + if hasattr(axisToLink, "_curves"): + axisToLink._curves.append(plotDataItem) axisToLink.show() for otherAxisName in self.axes.keys(): self.autoVisible(otherAxisName) @@ -254,7 +254,7 @@ def unlinkDataFromAxis(self, curve: PlotDataItem): def autoVisible(self, axisName): # Do we have any visible curves? axis = self.axes[axisName]["item"] - if hasattr(axis,"_curves"): + if hasattr(axis, "_curves"): for curve in axis._curves: if curve.isVisible(): axis.show() @@ -264,7 +264,6 @@ def autoVisible(self, axisName): for otherAxis in self.axes.keys(): otherItem = self.axes[otherAxis]["item"] if otherItem is not axis and otherAxis not in ["bottom", "top"] and otherItem.isVisible(): - axis.hide() return # No other axis is visible. From 2839cdb9076eab9a6b9bdc2b659afe455772a349 Mon Sep 17 00:00:00 2001 From: Akshar Sarvesh Date: Wed, 31 Jul 2024 13:51:54 -0700 Subject: [PATCH 05/11] STY: Removed commented out old code --- pydm/widgets/archiver_time_plot.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pydm/widgets/archiver_time_plot.py b/pydm/widgets/archiver_time_plot.py index 747073388..16dedfd66 100644 --- a/pydm/widgets/archiver_time_plot.py +++ b/pydm/widgets/archiver_time_plot.py @@ -390,10 +390,6 @@ def requestDataFromArchiver(self, min_x: Optional[float] = None, max_x: Optional if curve.use_archive_data: if max_x is None: max_x = curve.min_x() - # if curve.points_accumulated > 0: - # max_x = curve.data_buffer[0][curve.getBufferSize() - curve.points_accumulated] - # else: - # max_x = self._starting_timestamp requested_seconds = max_x - min_x if requested_seconds <= 5: continue # Avoids noisy requests when first rendering the plot From 72b6ac2abc330c30e1bb9e78bad0d888143beebe Mon Sep 17 00:00:00 2001 From: Akshar Sarvesh Date: Wed, 31 Jul 2024 17:04:26 -0700 Subject: [PATCH 06/11] FIX: Bug fix with handling axis name changes, updates the internal attribute for all of the curves --- pydm/widgets/multi_axis_plot.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pydm/widgets/multi_axis_plot.py b/pydm/widgets/multi_axis_plot.py index 08bd56928..76221a937 100644 --- a/pydm/widgets/multi_axis_plot.py +++ b/pydm/widgets/multi_axis_plot.py @@ -111,7 +111,11 @@ def addAxis( def change_axis_name(self, old_name: str, new_name: str): """Change the name of the axis by changing the item's key in the axes dictionary.""" + axis = self.axes[old_name]["item"] self.axes[new_name] = self.axes[old_name] + if hasattr(axis, "_curves"): + for curve in axis._curves: + curve.y_axis_name = new_name del self.axes[old_name] def addStackedView(self, view): From 7a9cba06c6f74a918a4c0d43a15b36267f5276d3 Mon Sep 17 00:00:00 2001 From: Akshar Sarvesh Date: Thu, 1 Aug 2024 11:19:43 -0700 Subject: [PATCH 07/11] FIX: Moved hiding/showing curves attached to axes to pydm side so other BasePlots can have this functionality --- pydm/widgets/baseplot.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pydm/widgets/baseplot.py b/pydm/widgets/baseplot.py index 4c5b22da8..2fee839f0 100644 --- a/pydm/widgets/baseplot.py +++ b/pydm/widgets/baseplot.py @@ -640,15 +640,13 @@ def log_mode(self, log_mode: bool) -> None: def setHidden(self, shouldHide: bool): if shouldHide: - self.hide() for curve in self._curves: - curve.hide - + curve.hide() + self.hide() else: - self.show() for curve in self._curves: curve.show() - # Potentially can be fixed to not show curves previously marked hidden + self.show() def to_dict(self) -> OrderedDict: """ From 7576f3f226417893f252bb8a331be2dec50a34c8 Mon Sep 17 00:00:00 2001 From: Akshar Sarvesh Date: Mon, 26 Aug 2024 22:46:11 -0700 Subject: [PATCH 08/11] Merge branch 'master' into axes --- pydm/data_plugins/epics_plugins/p4p_plugin_component.py | 2 +- pydm/widgets/axis_table_model.py | 3 +++ pydm/widgets/multi_axis_plot.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pydm/data_plugins/epics_plugins/p4p_plugin_component.py b/pydm/data_plugins/epics_plugins/p4p_plugin_component.py index 43bb9138b..00b48ef95 100644 --- a/pydm/data_plugins/epics_plugins/p4p_plugin_component.py +++ b/pydm/data_plugins/epics_plugins/p4p_plugin_component.py @@ -262,7 +262,7 @@ def send_new_value(self, value: Value) -> None: if self.nttable_data_location: msg = f"Invalid channel... {self.nttable_data_location}" for subfield in self.nttable_data_location: - if isinstance(new_value, collections.Container) and not isinstance(new_value, str): + if isinstance(new_value, collections.abc.Container) and not isinstance(new_value, str): if isinstance(subfield, str): try: new_value = new_value[subfield] diff --git a/pydm/widgets/axis_table_model.py b/pydm/widgets/axis_table_model.py index c95d69378..9e21fb443 100644 --- a/pydm/widgets/axis_table_model.py +++ b/pydm/widgets/axis_table_model.py @@ -99,6 +99,9 @@ def set_data(self, column_name, axis, value): axis.orientation = "left" # The PyQtGraph default is the left axis else: axis.orientation = str(value) + self.plot.plotItem.rebuildLayout() + if axis.isVisible(): + axis.show() elif column_name == "Y-Axis Label": axis.label_text = str(value) elif column_name == "Min Y Range": diff --git a/pydm/widgets/multi_axis_plot.py b/pydm/widgets/multi_axis_plot.py index 76221a937..7d777ba5e 100644 --- a/pydm/widgets/multi_axis_plot.py +++ b/pydm/widgets/multi_axis_plot.py @@ -251,7 +251,7 @@ def unlinkDataFromAxis(self, curve: PlotDataItem): axisName: str The name of the axis that a curve is being removed from """ - if hasattr(curve, "y_axis_name"): + if hasattr(curve, "y_axis_name") and curve.y_axis_name in self.axes: self.axes[curve.y_axis_name]["item"]._curves.remove(curve) self.autoVisible(curve.y_axis_name) From c4dd8650ec89207bad4a676c516168f96a05601c Mon Sep 17 00:00:00 2001 From: Akshar Sarvesh Date: Tue, 27 Aug 2024 11:41:46 -0700 Subject: [PATCH 09/11] FIX: Don't try to remove a curve from an axis that doesn't think that it exists --- pydm/widgets/multi_axis_plot.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pydm/widgets/multi_axis_plot.py b/pydm/widgets/multi_axis_plot.py index 7d777ba5e..550b81d45 100644 --- a/pydm/widgets/multi_axis_plot.py +++ b/pydm/widgets/multi_axis_plot.py @@ -251,7 +251,11 @@ def unlinkDataFromAxis(self, curve: PlotDataItem): axisName: str The name of the axis that a curve is being removed from """ - if hasattr(curve, "y_axis_name") and curve.y_axis_name in self.axes: + if ( + hasattr(curve, "y_axis_name") + and curve.y_axis_name in self.axes + and curve in self.axes[curve.y_axis_name]["item"]._curves + ): self.axes[curve.y_axis_name]["item"]._curves.remove(curve) self.autoVisible(curve.y_axis_name) From 73e8912427f989ab246cd3a2ef9fd6ad5335f245 Mon Sep 17 00:00:00 2001 From: Akshar Sarvesh Date: Fri, 30 Aug 2024 16:09:52 -0700 Subject: [PATCH 10/11] STY: PR Suggestions --- pydm/widgets/baseplot.py | 1 + pydm/widgets/multi_axis_plot.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/pydm/widgets/baseplot.py b/pydm/widgets/baseplot.py index e07770b52..962e5b5e6 100644 --- a/pydm/widgets/baseplot.py +++ b/pydm/widgets/baseplot.py @@ -641,6 +641,7 @@ def log_mode(self, log_mode: bool) -> None: self.log_mode_updated.emit(self.name, log_mode) def setHidden(self, shouldHide: bool): + """Set an axis to hide/show and do the same for all of its connected curves""" if shouldHide: for curve in self._curves: curve.hide() diff --git a/pydm/widgets/multi_axis_plot.py b/pydm/widgets/multi_axis_plot.py index 550b81d45..4ff5ec668 100644 --- a/pydm/widgets/multi_axis_plot.py +++ b/pydm/widgets/multi_axis_plot.py @@ -260,6 +260,14 @@ def unlinkDataFromAxis(self, curve: PlotDataItem): self.autoVisible(curve.y_axis_name) def autoVisible(self, axisName): + """Handle automatically hiding or showing an axis based on whether it has + visible curves attached and/or if it's the last visible axis + (don't automatically hide the last axis, even if all of it's curves are hidden) + + Parameters + ------------- + axisName: str + The name of the axis we are going to try to hide if possible, or show if not""" # Do we have any visible curves? axis = self.axes[axisName]["item"] if hasattr(axis, "_curves"): From bf44b1719eeda138a7eae6e72e15e8a4df4b683f Mon Sep 17 00:00:00 2001 From: Akshar Sarvesh Date: Fri, 30 Aug 2024 16:30:36 -0700 Subject: [PATCH 11/11] FIX: Shifted Axis "hidden" attribute getter to Archive Viewer Side --- pydm/widgets/axis_table_model.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pydm/widgets/axis_table_model.py b/pydm/widgets/axis_table_model.py index 9e21fb443..9358d553c 100644 --- a/pydm/widgets/axis_table_model.py +++ b/pydm/widgets/axis_table_model.py @@ -69,8 +69,6 @@ def get_data(self, column_name, axis): return axis.auto_range elif column_name == "Log Mode": return axis.log_mode - elif column_name == "Hidden": - return not axis.isVisible() def setData(self, index, value, role=Qt.EditRole): if not index.isValid():