Fix static yrange and last bar double draw issues

bar_select
Tyler Goodlet 2020-10-16 12:18:14 -04:00
parent fc23b2180d
commit c7d5ea6e15
3 changed files with 76 additions and 66 deletions

View File

@ -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):

View File

@ -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(

View File

@ -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