From c7d5ea6e1552bebf8d5ab4a19328d4a6fd0b84d5 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 16 Oct 2020 12:18:14 -0400 Subject: [PATCH] Fix static yrange and last bar double draw issues --- piker/ui/_chart.py | 133 ++++++++++++++++++++++-------------------- piker/ui/_graphics.py | 5 +- piker/ui/_style.py | 4 +- 3 files changed, 76 insertions(+), 66 deletions(-) diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py index 50220174..6f06b9cc 100644 --- a/piker/ui/_chart.py +++ b/piker/ui/_chart.py @@ -19,6 +19,7 @@ from ._style import ( _xaxis_at, _min_points_to_show, hcolor, CHART_MARGINS, _bars_from_right_in_follow_mode, + _bars_to_left_in_follow_mode, ) from ..data._source import Symbol from .. import brokers @@ -206,6 +207,7 @@ class LinkedSplitCharts(QtGui.QWidget): xaxis: DynamicDateAxis = None, ohlc: bool = False, _is_main: bool = False, + **cpw_kwargs, ) -> 'ChartPlotWidget': """Add (sub)plots to chart widget by name. @@ -226,6 +228,7 @@ class LinkedSplitCharts(QtGui.QWidget): parent=self.splitter, axisItems={'bottom': xaxis, 'right': PriceAxis()}, viewBox=cv, + **cpw_kwargs, ) # this name will be used to register the primary # graphics curve managed by the subchart @@ -283,7 +286,7 @@ class ChartPlotWidget(pg.PlotWidget): self, # the data view we generate graphics from array: np.ndarray, - yrange: Optional[Tuple[float, float]] = None, + static_yrange: Optional[Tuple[float, float]] = None, **kwargs, ): """Configure chart display settings. @@ -299,9 +302,8 @@ class ChartPlotWidget(pg.PlotWidget): self._overlays = {} # registry of overlay curves self._labels = {} # registry of underlying graphics self._ysticks = {} # registry of underlying graphics - self._yrange = yrange self._vb = self.plotItem.vb - self._static_yrange = None + self._static_yrange = static_yrange # for "known y-range style" # show only right side axes self.hideAxis('left') @@ -310,20 +312,21 @@ class ChartPlotWidget(pg.PlotWidget): # show background grid self.showGrid(x=True, y=True, alpha=0.4) - self.plotItem.vb.setXRange(0, 0) + # don't need right? + # self._vb.setXRange(0, 0) - # use cross-hair for cursor - self.setCursor(QtCore.Qt.CrossCursor) + # use cross-hair for cursor? + # self.setCursor(QtCore.Qt.CrossCursor) # Assign callback for rescaling y-axis automatically # based on data contents and ``ViewBox`` state. self.sigXRangeChanged.connect(self._set_yrange) - vb = self._vb # for mouse wheel which doesn't seem to emit XRangeChanged - vb.sigRangeChangedManually.connect(self._set_yrange) + self._vb.sigRangeChangedManually.connect(self._set_yrange) + # for when the splitter(s) are resized - vb.sigResized.connect(self._set_yrange) + self._vb.sigResized.connect(self._set_yrange) def _update_contents_label(self, index: int) -> None: if index >= 0 and index < len(self._array): @@ -369,6 +372,7 @@ class ChartPlotWidget(pg.PlotWidget): # adds all bar/candle graphics objects for each data point in # the np array buffer to be drawn on next render cycle self.addItem(graphics) + # draw after to allow self.scene() to work... graphics.draw_from_data(data) @@ -392,7 +396,7 @@ class ChartPlotWidget(pg.PlotWidget): ) self._labels[name] = (label, update) - self._update_contents_label(index=-1) + self._update_contents_label(len(data) - 1) label.show() # set xrange limits @@ -400,7 +404,7 @@ class ChartPlotWidget(pg.PlotWidget): # show last 50 points on startup self.plotItem.vb.setXRange( - xlast - 50, + xlast - _bars_to_left_in_follow_mode, xlast + _bars_from_right_in_follow_mode ) @@ -437,7 +441,7 @@ class ChartPlotWidget(pg.PlotWidget): # XXX: How to stack labels vertically? label = pg.LabelItem( justify='left', - size='4pt', + size='6px', ) label.setParentItem(self._vb) # label.setParentItem(self.getPlotItem()) @@ -455,14 +459,14 @@ class ChartPlotWidget(pg.PlotWidget): label.setText(f"{name} -> {data}") self._labels[name] = (label, update) - self._update_contents_label(index=-1) + self._update_contents_label(len(data) - 1) # set a "startup view" xlast = len(data) - 1 - # show last 50 points on startup + # configure "follow mode" view on startup self.plotItem.vb.setXRange( - xlast - 50, + xlast - _bars_to_left_in_follow_mode, xlast + _bars_from_right_in_follow_mode ) @@ -514,46 +518,47 @@ class ChartPlotWidget(pg.PlotWidget): that data always fits nicely inside the current view of the data set. """ - l, lbar, rbar, r = self.bars_range() - - # figure out x-range in view such that user can scroll "off" the data - # set up to the point where ``_min_points_to_show`` are left. - # if l < lbar or r > rbar: - view_len = r - l - # TODO: logic to check if end of bars in view - extra = view_len - _min_points_to_show - begin = 0 - extra - end = len(self._array) - 1 + extra - - # XXX: test code for only rendering lines for the bars in view. - # This turns out to be very very poor perf when scaling out to - # many bars (think > 1k) on screen. - # name = self.name - # bars = self._graphics[self.name] - # bars.draw_lines( - # istart=max(lbar, l), iend=min(rbar, r), just_history=True) - - # bars_len = rbar - lbar - # log.trace( - # f"\nl: {l}, lbar: {lbar}, rbar: {rbar}, r: {r}\n" - # f"view_len: {view_len}, bars_len: {bars_len}\n" - # f"begin: {begin}, end: {end}, extra: {extra}" - # ) - self._set_xlimits(begin, end) # yrange - if self._static_yrange is not None: - yrange = self._static_yrange + # if self._static_yrange is not None: + # yrange = self._static_yrange - if yrange is not None: - ylow, yhigh = yrange - self._static_yrange = yrange - else: + if self._static_yrange is not None: + ylow, yhigh = self._static_yrange + + else: # determine max, min y values in viewable x-range + + l, lbar, rbar, r = self.bars_range() + + # figure out x-range in view such that user can scroll "off" + # the data set up to the point where ``_min_points_to_show`` + # are left. + view_len = r - l + # TODO: logic to check if end of bars in view + extra = view_len - _min_points_to_show + begin = 0 - extra + end = len(self._array) - 1 + extra + + # XXX: test code for only rendering lines for the bars in view. + # This turns out to be very very poor perf when scaling out to + # many bars (think > 1k) on screen. + # name = self.name + # bars = self._graphics[self.name] + # bars.draw_lines( + # istart=max(lbar, l), iend=min(rbar, r), just_history=True) + + # bars_len = rbar - lbar + # log.trace( + # f"\nl: {l}, lbar: {lbar}, rbar: {rbar}, r: {r}\n" + # f"view_len: {view_len}, bars_len: {bars_len}\n" + # f"begin: {begin}, end: {end}, extra: {extra}" + # ) + self._set_xlimits(begin, end) # TODO: this should be some kind of numpy view api bars = self._array[lbar:rbar] if not len(bars): - # likely no data loaded yet + # likely no data loaded yet or extreme scrolling? log.error(f"WTF bars_range = {lbar}:{rbar}") return @@ -562,19 +567,18 @@ class ChartPlotWidget(pg.PlotWidget): try: ylow = np.nanmin(bars['low']) yhigh = np.nanmax(bars['high']) - # std = np.std(bars['close']) except (IndexError, ValueError): # must be non-ohlc array? ylow = np.nanmin(bars) yhigh = np.nanmax(bars) - # std = np.std(bars) - # view margins: stay within 10% of the "true range" - diff = yhigh - ylow - ylow = ylow - (diff * 0.04) - yhigh = yhigh + (diff * 0.01) + # view margins: stay within a % of the "true range" + diff = yhigh - ylow + ylow = ylow - (diff * 0.08) + yhigh = yhigh + (diff * 0.01) # compute contents label "height" in view terms + # to avoid having data "contents" overlap with them if self._labels: label = self._labels[self.name][0] rect = label.itemRect() @@ -590,13 +594,14 @@ class ChartPlotWidget(pg.PlotWidget): else: label_h = 0 - chart = self - chart.setLimits( + if label_h > yhigh - ylow: + label_h = 0 + + self.setLimits( yMin=ylow, yMax=yhigh + label_h, - # minYRange=std ) - chart.setYRange(ylow, yhigh + label_h) + self.setYRange(ylow, yhigh + label_h) def enterEvent(self, ev): # noqa # pg.PlotWidget.enterEvent(self, ev) @@ -809,20 +814,23 @@ async def chart_from_fsp( chart = linked_charts.add_plot( name=func_name, - - # TODO: enforce type checking here? array=shm.array, + + # settings passed down to ``ChartPlotWidget`` + static_yrange=(0, 100), ) + # display contents labels asap + chart._update_contents_label(len(shm.array) - 1) + array = shm.array[func_name] value = array[-1] last_val_sticky = chart._ysticks[chart.name] last_val_sticky.update_from_data(-1, value) chart.update_from_array(chart.name, array) - chart._set_yrange(yrange=(0, 100)) - chart._shm = shm + chart._set_yrange() # update chart graphics async for value in stream: @@ -830,7 +838,6 @@ async def chart_from_fsp( value = array[-1] last_val_sticky.update_from_data(-1, value) chart.update_from_array(chart.name, array) - # chart._set_yrange() async def check_for_new_bars(feed, ohlcv, linked_charts): diff --git a/piker/ui/_graphics.py b/piker/ui/_graphics.py index 79173530..6a26d5fa 100644 --- a/piker/ui/_graphics.py +++ b/piker/ui/_graphics.py @@ -285,7 +285,10 @@ class BarItems(pg.GraphicsObject): index = len(lines) self.lines[:index] = lines self.index = index - self.draw_lines(just_history=True, iend=self.index) + + # up to last to avoid double draw of last bar + self.draw_lines(just_history=True, iend=self.index - 1) + self.draw_lines(iend=self.index) # @timeit def draw_lines( diff --git a/piker/ui/_style.py b/piker/ui/_style.py index d6f2adec..bb5b3fdf 100644 --- a/piker/ui/_style.py +++ b/piker/ui/_style.py @@ -17,10 +17,10 @@ _i3_rgba = QtGui.QColor.fromRgbF(*[0.14]*3 + [1]) _xaxis_at = 'bottom' # charting config +CHART_MARGINS = (0, 0, 2, 2) _min_points_to_show = 3 _bars_from_right_in_follow_mode = 5 -CHART_MARGINS = (0, 0, 2, 2) - +_bars_to_left_in_follow_mode = 100 _tina_mode = False