Skip to content

Commit 6ce9d2e

Browse files
Merge pull request #1172 from zdomke/dev_plot_fetch_data
ENH: ArchiveTimePlot Fetch Data on X-Axis Change
2 parents 74eb933 + 86ecd92 commit 6ce9d2e

File tree

1 file changed

+95
-36
lines changed

1 file changed

+95
-36
lines changed

pydm/widgets/archiver_time_plot.py

Lines changed: 95 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -817,11 +817,17 @@ class PyDMArchiverTimePlot(PyDMTimePlot):
817817
The parent of this widget.
818818
init_y_channels : list
819819
A list of scalar channels to plot vs time.
820-
background: str
820+
background : str
821821
The background color for the plot. Accepts any arguments that
822822
pyqtgraph.mkColor will accept.
823-
optimized_data_bins: int
823+
optimized_data_bins : int
824824
The number of bins of data returned from the archiver when using optimized requests
825+
request_cooldown : int
826+
The time, in milliseconds, between requests to the archiver appliance
827+
cache_data : bool
828+
Whether curves should retain archive data or fetch new data when the x-axis changes
829+
show_all : bool
830+
Shifts the x-axis range to show all data, or stay where the user set the x-axis to
825831
"""
826832

827833
def __init__(
@@ -830,6 +836,9 @@ def __init__(
830836
init_y_channels: List[str] = [],
831837
background: str = "default",
832838
optimized_data_bins: int = 2000,
839+
request_cooldown: int = 1000,
840+
cache_data: bool = True,
841+
show_all: bool = True,
833842
):
834843
super(PyDMArchiverTimePlot, self).__init__(
835844
parent=parent,
@@ -838,56 +847,104 @@ def __init__(
838847
background=background,
839848
bottom_axis=DateAxisItem("bottom"),
840849
)
850+
self._cache_data = None
851+
841852
self.optimized_data_bins = optimized_data_bins
853+
self.request_cooldown = request_cooldown
854+
self.cache_data = cache_data
855+
self._show_all = show_all # Show all plotted data after archiver fetch
856+
842857
self._starting_timestamp = time.time() # The timestamp at which the plot was first rendered
843858
self._min_x = self._starting_timestamp - DEFAULT_TIME_SPAN
844859
self._prev_x = self._min_x # Holds the minimum x-value of the previous update of the plot
845860
self._archive_request_queued = False
846861
self.setTimeSpan(DEFAULT_TIME_SPAN)
847862

848-
def updateXAxis(self, update_immediately: bool = False) -> None:
863+
@property
864+
def cache_data(self):
865+
"""Returns if the curves of the plot are caching archive data or
866+
fetching new archive data on every change to the x-axis"""
867+
return self._cache_data
868+
869+
@cache_data.setter
870+
def cache_data(self, enable: bool):
871+
"""If true, the curves on the plot will keep thier most recently fetched archive data. New
872+
data will only be fetched when users navigate to an "unseen" section of the plot.
873+
When false, the curves will fetch new archive data on every change to the x-axis.
874+
"""
875+
if self._cache_data == enable:
876+
return
877+
if enable:
878+
try:
879+
self.plotItem.sigXRangeChanged.disconnect(self.updateXAxis)
880+
self.plotItem.sigXRangeChangedManually.disconnect(self.updateXAxis)
881+
except TypeError:
882+
pass
883+
else:
884+
self.plotItem.sigXRangeChanged.connect(self.updateXAxis)
885+
self.plotItem.sigXRangeChangedManually.connect(self.updateXAxis)
886+
self._cache_data = enable
887+
888+
def updateXAxis(self) -> None:
849889
"""Manages the requests to archiver appliance. When the user pans or zooms the x axis to the left,
850890
a request will be made for backfill data"""
851-
if len(self._curves) == 0 or self.auto_scroll_timer.isActive():
891+
if not self._curves:
852892
return
853893

854-
min_x = self.plotItem.getAxis("bottom").range[0] # Gets the leftmost timestamp displayed on the x-axis
855-
max_x = self.plotItem.getAxis("bottom").range[1]
856-
max_point = max([curve.max_x() for curve in self._curves])
857-
if min_x == 0: # This is zero when the plot first renders
858-
self._max_x = time.time()
859-
self._min_x = self._max_x - DEFAULT_TIME_SPAN
860-
self._starting_timestamp = self._max_x
861-
if self.getTimeSpan() != MIN_TIME_SPAN:
862-
# Initialize x-axis based on the time span as well as trigger a call to the archiver below
863-
self._min_x = self._min_x - self.getTimeSpan()
894+
min_x, max_x = self.plotItem.getAxis("bottom").range # Get current visible x-axis range
895+
if min_x == 0: # Initial render case
896+
self._initialize_x_axis()
897+
elif not self._cache_data:
898+
self._handle_caching_off(min_x, max_x)
899+
elif not self.plotItem.isAnyXAutoRange():
900+
self._handle_manual_scrolling_or_zoom(min_x, max_x)
901+
902+
self._prev_x = min_x
903+
904+
def _initialize_x_axis(self) -> None:
905+
"""Initializes the x-axis for the first render."""
906+
self._max_x = time.time()
907+
self._min_x = self._max_x - DEFAULT_TIME_SPAN
908+
self._starting_timestamp = self._max_x
909+
910+
if self.getTimeSpan() != MIN_TIME_SPAN:
911+
self._min_x -= self.getTimeSpan()
912+
self._archive_request_queued = True
913+
self.requestDataFromArchiver()
914+
915+
blocked = self.plotItem.blockSignals(True)
916+
self.plotItem.setXRange(self._min_x, self._max_x, padding=0.0, update=False)
917+
self.plotItem.blockSignals(blocked)
918+
919+
def _handle_caching_off(self, min_x: float, max_x: float) -> None:
920+
"""Handles the situation when there is no cached data and the user has changed the x-axis range."""
921+
if min_x != self._min_x or max_x != self._max_x:
922+
self._min_x = min_x
923+
self._max_x = max_x
924+
self.setTimeSpan(max_x - min_x)
925+
if not self._archive_request_queued:
864926
self._archive_request_queued = True
865-
self.requestDataFromArchiver()
866-
self.plotItem.setXRange(
867-
time.time() - DEFAULT_TIME_SPAN, time.time(), padding=0.0, update=update_immediately
868-
)
869-
elif min_x < self._min_x and not self.plotItem.isAnyXAutoRange():
870-
# This means the user has manually scrolled to the left, so request archived data
927+
QTimer.singleShot(self.request_cooldown, self.requestDataFromArchiver)
928+
929+
def _handle_manual_scrolling_or_zoom(self, min_x: float, max_x: float) -> None:
930+
"""Handles scenarios of manual scrolling or zooming when autorange is disabled."""
931+
max_point = max(curve.max_x() for curve in self._curves)
932+
933+
if min_x < self._min_x:
934+
# User scrolled to the left, request archived data
871935
self._min_x = min_x
872936
self.setTimeSpan(max_point - min_x)
873937
if not self._archive_request_queued:
874-
# Letting the user pan or scroll the plot is convenient, but can generate a lot of events in under
875-
# a second that would trigger a request for data. By using a timer, we avoid this burst of events
876-
# and consolidate what would be many requests to archiver into just one.
877938
self._archive_request_queued = True
878-
QTimer.singleShot(1000, self.requestDataFromArchiver)
879-
# Here we only update the x-axis if the user hasn't asked for autorange and they haven't zoomed in (as
880-
# detected by the max range showing on the plot being less than the data available)
881-
elif not self.plotItem.isAnyXAutoRange() and max_x >= max_point - 10:
882-
if min_x > (self._prev_x + 15) or min_x < (self._prev_x - 15):
883-
# The plus/minus 15 just makes sure we don't do this on every update tick of the graph
939+
QTimer.singleShot(self.request_cooldown, self.requestDataFromArchiver)
940+
elif max_x >= max_point - 10:
941+
# Check if we should update the x-axis
942+
if abs(min_x - self._prev_x) > 15:
884943
self.setTimeSpan(max_point - min_x)
885944
else:
886-
# Keep the plot moving with a rolling window based on the current timestamp
887-
self.plotItem.setXRange(
888-
max_point - self.getTimeSpan(), max_point, padding=0.0, update=update_immediately
889-
)
890-
self._prev_x = min_x
945+
blocked = self.plotItem.blockSignals(True)
946+
self.plotItem.setXRange(max_point - self.getTimeSpan(), max_point, padding=0.0, update=False)
947+
self.plotItem.blockSignals(blocked)
891948

892949
def requestDataFromArchiver(self, min_x: Optional[float] = None, max_x: Optional[float] = None) -> None:
893950
"""
@@ -911,8 +968,10 @@ def requestDataFromArchiver(self, min_x: Optional[float] = None, max_x: Optional
911968
if curve.use_archive_data:
912969
if max_x is None:
913970
max_x = curve.min_x()
971+
if not self._cache_data:
972+
max_x = min(max_x, self._max_x)
914973
requested_seconds = max_x - min_x
915-
if requested_seconds <= 5:
974+
if requested_seconds <= MIN_TIME_SPAN:
916975
continue # Avoids noisy requests when first rendering the plot
917976
# Max amount of raw data to return before using optimized data
918977
max_data_request = int(0.80 * self.getArchiveBufferSize())
@@ -963,7 +1022,7 @@ def createCurveItem(self, *args, **kwargs) -> ArchivePlotCurveItem:
9631022
def archive_data_received(self):
9641023
"""Take any action needed when this plot receives new data from archiver appliance"""
9651024
self._archive_request_queued = False
966-
if self.auto_scroll_timer.isActive():
1025+
if self.auto_scroll_timer.isActive() or not self._show_all:
9671026
return
9681027

9691028
max_x = max([curve.max_x() for curve in self._curves])

0 commit comments

Comments
 (0)