Add an experimental "fast appendable curve" graphic

chart_trader
Tyler Goodlet 2020-12-28 12:47:09 -05:00
parent 93e76fa12c
commit 588213a230
1 changed files with 138 additions and 6 deletions

View File

@ -339,6 +339,133 @@ class LinkedSplitCharts(QtGui.QWidget):
return cpw return cpw
class FastAppendCurve(pg.PlotCurveItem):
def __init__(self, *args, **kwargs):
# TODO: we can probably just dispense with the parent since
# we're basically only using the pen setting now...
super().__init__(*args, **kwargs)
self._last_line: QtCore.QLineF = None
self._xrange: Tuple[int, int] = self.dataBounds(ax=0)
# TODO: one question still remaining is if this makes trasform
# interactions slower (such as zooming) and if so maybe if/when
# we implement a "history" mode for the view we disable this in
# that mode?
self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache)
def update_from_array(
self,
x,
y,
) -> QtGui.QPainterPath:
profiler = pg.debug.Profiler(disabled=True)
flip_cache = False
# print(f"xrange: {self._xrange}")
istart, istop = self._xrange
prepend_length = istart - x[0]
append_length = x[-1] - istop
if self.path is None or prepend_length:
self.path = pg.functions.arrayToQPath(
x[:-1],
y[:-1],
connect='all'
)
profiler('generate fresh path')
# TODO: get this working - right now it's giving heck on vwap...
# if prepend_length:
# breakpoint()
# prepend_path = pg.functions.arrayToQPath(
# x[0:prepend_length],
# y[0:prepend_length],
# connect='all'
# )
# # swap prepend path in "front"
# old_path = self.path
# self.path = prepend_path
# # self.path.moveTo(new_x[0], new_y[0])
# self.path.connectPath(old_path)
if append_length:
# print(f"append_length: {append_length}")
new_x = x[-append_length - 2:-1]
new_y = y[-append_length - 2:-1]
# print((new_x, new_y))
append_path = pg.functions.arrayToQPath(
new_x,
new_y,
connect='all'
)
# print(f"append_path br: {append_path.boundingRect()}")
# self.path.moveTo(new_x[0], new_y[0])
# self.path.connectPath(append_path)
self.path.connectPath(append_path)
# XXX: pretty annoying but, without this there's little
# artefacts on the append updates to the curve...
self.setCacheMode(QtGui.QGraphicsItem.NoCache)
self.prepareGeometryChange()
flip_cache = True
# print(f"update br: {self.path.boundingRect()}")
# XXX: lol brutal, the internals of `CurvePoint` (inherited by
# our `LineDot`) required ``.getData()`` to work..
self.xData = x
self.yData = y
self._xrange = x[0], x[-1]
self._last_line = QtCore.QLineF(x[-2], y[-2], x[-1], y[-1])
# trigger redraw of path
# do update before reverting to cache mode
self.prepareGeometryChange()
self.update()
if flip_cache:
self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache)
def boundingRect(self):
hb = self.path.controlPointRect()
hb_size = hb.size()
# print(f'hb_size: {hb_size}')
w = hb_size.width() + 1
h = hb_size.height() + 1
br = QtCore.QRectF(
# top left
QtCore.QPointF(hb.topLeft()),
# total size
QtCore.QSizeF(w, h)
)
# print(f'bounding rect: {br}')
return br
def paint(self, p, opt, widget):
profiler = pg.debug.Profiler(disabled=True)
# p.setRenderHint(p.Antialiasing, True)
p.setPen(self.opts['pen'])
p.drawLine(self._last_line)
profiler('.drawLine()')
p.drawPath(self.path)
profiler('.drawPath()')
class ChartPlotWidget(pg.PlotWidget): class ChartPlotWidget(pg.PlotWidget):
"""``GraphicsView`` subtype containing a single ``PlotItem``. """``GraphicsView`` subtype containing a single ``PlotItem``.
@ -550,8 +677,9 @@ class ChartPlotWidget(pg.PlotWidget):
} }
pdi_kwargs.update(_pdi_defaults) pdi_kwargs.update(_pdi_defaults)
# curve = pg.PlotDataItem(
# curve = pg.PlotCurveItem( # curve = pg.PlotCurveItem(
curve = pg.PlotDataItem( curve = FastAppendCurve(
y=data[name], y=data[name],
x=data['index'], x=data['index'],
# antialias=True, # antialias=True,
@ -663,6 +791,7 @@ class ChartPlotWidget(pg.PlotWidget):
"""Update the named internal graphics from ``array``. """Update the named internal graphics from ``array``.
""" """
if name not in self._overlays: if name not in self._overlays:
self._ohlc = array self._ohlc = array
else: else:
@ -670,11 +799,13 @@ class ChartPlotWidget(pg.PlotWidget):
curve = self._graphics[name] curve = self._graphics[name]
# TODO: we should instead implement a diff based if len(array):
# "only update with new items" on the pg.PlotCurveItem # TODO: we should instead implement a diff based
# one place to dig around this might be the `QBackingStore` # "only update with new items" on the pg.PlotCurveItem
# https://doc.qt.io/qt-5/qbackingstore.html # one place to dig around this might be the `QBackingStore`
curve.setData(y=array[name], x=array['index'], **kwargs) # https://doc.qt.io/qt-5/qbackingstore.html
# curve.setData(y=array[name], x=array['index'], **kwargs)
curve.update_from_array(x=array['index'], y=array[name], **kwargs)
return curve return curve
@ -1061,6 +1192,7 @@ async def chart_from_quotes(
mn_in_view = min(price, mn_in_view) mn_in_view = min(price, mn_in_view)
if mx_in_view > last_mx or mn_in_view < last_mn: if mx_in_view > last_mx or mn_in_view < last_mn:
print('scaling')
chart._set_yrange(yrange=(mn_in_view, mx_in_view)) chart._set_yrange(yrange=(mn_in_view, mx_in_view))
last_mx, last_mn = mx_in_view, mn_in_view last_mx, last_mn = mx_in_view, mn_in_view