Fix static yrange and last bar double draw issues
							parent
							
								
									fc23b2180d
								
							
						
					
					
						commit
						c7d5ea6e15
					
				|  | @ -19,6 +19,7 @@ from ._style import ( | ||||||
|     _xaxis_at, _min_points_to_show, hcolor, |     _xaxis_at, _min_points_to_show, hcolor, | ||||||
|     CHART_MARGINS, |     CHART_MARGINS, | ||||||
|     _bars_from_right_in_follow_mode, |     _bars_from_right_in_follow_mode, | ||||||
|  |     _bars_to_left_in_follow_mode, | ||||||
| ) | ) | ||||||
| from ..data._source import Symbol | from ..data._source import Symbol | ||||||
| from .. import brokers | from .. import brokers | ||||||
|  | @ -206,6 +207,7 @@ class LinkedSplitCharts(QtGui.QWidget): | ||||||
|         xaxis: DynamicDateAxis = None, |         xaxis: DynamicDateAxis = None, | ||||||
|         ohlc: bool = False, |         ohlc: bool = False, | ||||||
|         _is_main: bool = False, |         _is_main: bool = False, | ||||||
|  |         **cpw_kwargs, | ||||||
|     ) -> 'ChartPlotWidget': |     ) -> 'ChartPlotWidget': | ||||||
|         """Add (sub)plots to chart widget by name. |         """Add (sub)plots to chart widget by name. | ||||||
| 
 | 
 | ||||||
|  | @ -226,6 +228,7 @@ class LinkedSplitCharts(QtGui.QWidget): | ||||||
|             parent=self.splitter, |             parent=self.splitter, | ||||||
|             axisItems={'bottom': xaxis, 'right': PriceAxis()}, |             axisItems={'bottom': xaxis, 'right': PriceAxis()}, | ||||||
|             viewBox=cv, |             viewBox=cv, | ||||||
|  |             **cpw_kwargs, | ||||||
|         ) |         ) | ||||||
|         # this name will be used to register the primary |         # this name will be used to register the primary | ||||||
|         # graphics curve managed by the subchart |         # graphics curve managed by the subchart | ||||||
|  | @ -283,7 +286,7 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
|         self, |         self, | ||||||
|         # the data view we generate graphics from |         # the data view we generate graphics from | ||||||
|         array: np.ndarray, |         array: np.ndarray, | ||||||
|         yrange: Optional[Tuple[float, float]] = None, |         static_yrange: Optional[Tuple[float, float]] = None, | ||||||
|         **kwargs, |         **kwargs, | ||||||
|     ): |     ): | ||||||
|         """Configure chart display settings. |         """Configure chart display settings. | ||||||
|  | @ -299,9 +302,8 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
|         self._overlays = {}  # registry of overlay curves |         self._overlays = {}  # registry of overlay curves | ||||||
|         self._labels = {}  # registry of underlying graphics |         self._labels = {}  # registry of underlying graphics | ||||||
|         self._ysticks = {}  # registry of underlying graphics |         self._ysticks = {}  # registry of underlying graphics | ||||||
|         self._yrange = yrange |  | ||||||
|         self._vb = self.plotItem.vb |         self._vb = self.plotItem.vb | ||||||
|         self._static_yrange = None |         self._static_yrange = static_yrange  # for "known y-range style" | ||||||
| 
 | 
 | ||||||
|         # show only right side axes |         # show only right side axes | ||||||
|         self.hideAxis('left') |         self.hideAxis('left') | ||||||
|  | @ -310,20 +312,21 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
|         # show background grid |         # show background grid | ||||||
|         self.showGrid(x=True, y=True, alpha=0.4) |         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 |         # use cross-hair for cursor? | ||||||
|         self.setCursor(QtCore.Qt.CrossCursor) |         # self.setCursor(QtCore.Qt.CrossCursor) | ||||||
| 
 | 
 | ||||||
|         # Assign callback for rescaling y-axis automatically |         # Assign callback for rescaling y-axis automatically | ||||||
|         # based on data contents and ``ViewBox`` state. |         # based on data contents and ``ViewBox`` state. | ||||||
|         self.sigXRangeChanged.connect(self._set_yrange) |         self.sigXRangeChanged.connect(self._set_yrange) | ||||||
| 
 | 
 | ||||||
|         vb = self._vb |  | ||||||
|         # for mouse wheel which doesn't seem to emit XRangeChanged |         # 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 |         # 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: |     def _update_contents_label(self, index: int) -> None: | ||||||
|         if index >= 0 and index < len(self._array): |         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 |         # adds all bar/candle graphics objects for each data point in | ||||||
|         # the np array buffer to be drawn on next render cycle |         # the np array buffer to be drawn on next render cycle | ||||||
|         self.addItem(graphics) |         self.addItem(graphics) | ||||||
|  | 
 | ||||||
|         # draw after to allow self.scene() to work... |         # draw after to allow self.scene() to work... | ||||||
|         graphics.draw_from_data(data) |         graphics.draw_from_data(data) | ||||||
| 
 | 
 | ||||||
|  | @ -392,7 +396,7 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|         self._labels[name] = (label, update) |         self._labels[name] = (label, update) | ||||||
|         self._update_contents_label(index=-1) |         self._update_contents_label(len(data) - 1) | ||||||
|         label.show() |         label.show() | ||||||
| 
 | 
 | ||||||
|         # set xrange limits |         # set xrange limits | ||||||
|  | @ -400,7 +404,7 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
| 
 | 
 | ||||||
|         # show last 50 points on startup |         # show last 50 points on startup | ||||||
|         self.plotItem.vb.setXRange( |         self.plotItem.vb.setXRange( | ||||||
|             xlast - 50, |             xlast - _bars_to_left_in_follow_mode, | ||||||
|             xlast + _bars_from_right_in_follow_mode |             xlast + _bars_from_right_in_follow_mode | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  | @ -437,7 +441,7 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
|         # XXX: How to stack labels vertically? |         # XXX: How to stack labels vertically? | ||||||
|         label = pg.LabelItem( |         label = pg.LabelItem( | ||||||
|             justify='left', |             justify='left', | ||||||
|             size='4pt', |             size='6px', | ||||||
|         ) |         ) | ||||||
|         label.setParentItem(self._vb) |         label.setParentItem(self._vb) | ||||||
|         # label.setParentItem(self.getPlotItem()) |         # label.setParentItem(self.getPlotItem()) | ||||||
|  | @ -455,14 +459,14 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
|             label.setText(f"{name} -> {data}") |             label.setText(f"{name} -> {data}") | ||||||
| 
 | 
 | ||||||
|         self._labels[name] = (label, update) |         self._labels[name] = (label, update) | ||||||
|         self._update_contents_label(index=-1) |         self._update_contents_label(len(data) - 1) | ||||||
| 
 | 
 | ||||||
|         # set a "startup view" |         # set a "startup view" | ||||||
|         xlast = len(data) - 1 |         xlast = len(data) - 1 | ||||||
| 
 | 
 | ||||||
|         # show last 50 points on startup |         # configure "follow mode" view on startup | ||||||
|         self.plotItem.vb.setXRange( |         self.plotItem.vb.setXRange( | ||||||
|             xlast - 50, |             xlast - _bars_to_left_in_follow_mode, | ||||||
|             xlast + _bars_from_right_in_follow_mode |             xlast + _bars_from_right_in_follow_mode | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  | @ -514,11 +518,21 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
|         that data always fits nicely inside the current view of the |         that data always fits nicely inside the current view of the | ||||||
|         data set. |         data set. | ||||||
|         """ |         """ | ||||||
|  | 
 | ||||||
|  |         # yrange | ||||||
|  |         # if self._static_yrange is not None: | ||||||
|  |         #     yrange = self._static_yrange | ||||||
|  | 
 | ||||||
|  |         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() |             l, lbar, rbar, r = self.bars_range() | ||||||
| 
 | 
 | ||||||
|         # figure out x-range in view such that user can scroll "off" the data |             # figure out x-range in view such that user can scroll "off" | ||||||
|         # set up to the point where ``_min_points_to_show`` are left. |             # the data set up to the point where ``_min_points_to_show`` | ||||||
|         # if l < lbar or r > rbar: |             # are left. | ||||||
|             view_len = r - l |             view_len = r - l | ||||||
|             # TODO: logic to check if end of bars in view |             # TODO: logic to check if end of bars in view | ||||||
|             extra = view_len - _min_points_to_show |             extra = view_len - _min_points_to_show | ||||||
|  | @ -541,19 +555,10 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
|             # ) |             # ) | ||||||
|             self._set_xlimits(begin, end) |             self._set_xlimits(begin, end) | ||||||
| 
 | 
 | ||||||
|         # 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: |  | ||||||
| 
 |  | ||||||
|             # TODO: this should be some kind of numpy view api |             # TODO: this should be some kind of numpy view api | ||||||
|             bars = self._array[lbar:rbar] |             bars = self._array[lbar:rbar] | ||||||
|             if not len(bars): |             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}") |                 log.error(f"WTF bars_range = {lbar}:{rbar}") | ||||||
|                 return |                 return | ||||||
| 
 | 
 | ||||||
|  | @ -562,19 +567,18 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
|             try: |             try: | ||||||
|                 ylow = np.nanmin(bars['low']) |                 ylow = np.nanmin(bars['low']) | ||||||
|                 yhigh = np.nanmax(bars['high']) |                 yhigh = np.nanmax(bars['high']) | ||||||
|                 # std = np.std(bars['close']) |  | ||||||
|             except (IndexError, ValueError): |             except (IndexError, ValueError): | ||||||
|                 # must be non-ohlc array? |                 # must be non-ohlc array? | ||||||
|                 ylow = np.nanmin(bars) |                 ylow = np.nanmin(bars) | ||||||
|                 yhigh = np.nanmax(bars) |                 yhigh = np.nanmax(bars) | ||||||
|                 # std = np.std(bars) |  | ||||||
| 
 | 
 | ||||||
|         # view margins: stay within 10% of the "true range" |             # view margins: stay within a % of the "true range" | ||||||
|             diff = yhigh - ylow |             diff = yhigh - ylow | ||||||
|         ylow = ylow - (diff * 0.04) |             ylow = ylow - (diff * 0.08) | ||||||
|             yhigh = yhigh + (diff * 0.01) |             yhigh = yhigh + (diff * 0.01) | ||||||
| 
 | 
 | ||||||
|         # compute contents label "height" in view terms |         # compute contents label "height" in view terms | ||||||
|  |         # to avoid having data "contents" overlap with them | ||||||
|         if self._labels: |         if self._labels: | ||||||
|             label = self._labels[self.name][0] |             label = self._labels[self.name][0] | ||||||
|             rect = label.itemRect() |             rect = label.itemRect() | ||||||
|  | @ -590,13 +594,14 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
|         else: |         else: | ||||||
|             label_h = 0 |             label_h = 0 | ||||||
| 
 | 
 | ||||||
|         chart = self |         if label_h > yhigh - ylow: | ||||||
|         chart.setLimits( |             label_h = 0 | ||||||
|  | 
 | ||||||
|  |         self.setLimits( | ||||||
|             yMin=ylow, |             yMin=ylow, | ||||||
|             yMax=yhigh + label_h, |             yMax=yhigh + label_h, | ||||||
|             # minYRange=std |  | ||||||
|         ) |         ) | ||||||
|         chart.setYRange(ylow, yhigh + label_h) |         self.setYRange(ylow, yhigh + label_h) | ||||||
| 
 | 
 | ||||||
|     def enterEvent(self, ev):  # noqa |     def enterEvent(self, ev):  # noqa | ||||||
|         # pg.PlotWidget.enterEvent(self, ev) |         # pg.PlotWidget.enterEvent(self, ev) | ||||||
|  | @ -809,20 +814,23 @@ async def chart_from_fsp( | ||||||
| 
 | 
 | ||||||
|         chart = linked_charts.add_plot( |         chart = linked_charts.add_plot( | ||||||
|             name=func_name, |             name=func_name, | ||||||
| 
 |  | ||||||
|             # TODO: enforce type checking here? |  | ||||||
|             array=shm.array, |             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] |         array = shm.array[func_name] | ||||||
|         value = array[-1] |         value = array[-1] | ||||||
|         last_val_sticky = chart._ysticks[chart.name] |         last_val_sticky = chart._ysticks[chart.name] | ||||||
|         last_val_sticky.update_from_data(-1, value) |         last_val_sticky.update_from_data(-1, value) | ||||||
|         chart.update_from_array(chart.name, array) |         chart.update_from_array(chart.name, array) | ||||||
| 
 | 
 | ||||||
|         chart._set_yrange(yrange=(0, 100)) |  | ||||||
| 
 |  | ||||||
|         chart._shm = shm |         chart._shm = shm | ||||||
|  |         chart._set_yrange() | ||||||
| 
 | 
 | ||||||
|         # update chart graphics |         # update chart graphics | ||||||
|         async for value in stream: |         async for value in stream: | ||||||
|  | @ -830,7 +838,6 @@ async def chart_from_fsp( | ||||||
|             value = array[-1] |             value = array[-1] | ||||||
|             last_val_sticky.update_from_data(-1, value) |             last_val_sticky.update_from_data(-1, value) | ||||||
|             chart.update_from_array(chart.name, array) |             chart.update_from_array(chart.name, array) | ||||||
|             # chart._set_yrange() |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| async def check_for_new_bars(feed, ohlcv, linked_charts): | async def check_for_new_bars(feed, ohlcv, linked_charts): | ||||||
|  |  | ||||||
|  | @ -285,7 +285,10 @@ class BarItems(pg.GraphicsObject): | ||||||
|         index = len(lines) |         index = len(lines) | ||||||
|         self.lines[:index] = lines |         self.lines[:index] = lines | ||||||
|         self.index = index |         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 |     # @timeit | ||||||
|     def draw_lines( |     def draw_lines( | ||||||
|  |  | ||||||
|  | @ -17,10 +17,10 @@ _i3_rgba = QtGui.QColor.fromRgbF(*[0.14]*3 + [1]) | ||||||
| _xaxis_at = 'bottom' | _xaxis_at = 'bottom' | ||||||
| 
 | 
 | ||||||
| # charting config | # charting config | ||||||
|  | CHART_MARGINS = (0, 0, 2, 2) | ||||||
| _min_points_to_show = 3 | _min_points_to_show = 3 | ||||||
| _bars_from_right_in_follow_mode = 5 | _bars_from_right_in_follow_mode = 5 | ||||||
| CHART_MARGINS = (0, 0, 2, 2) | _bars_to_left_in_follow_mode = 100 | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| _tina_mode = False | _tina_mode = False | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue