Skip to content

Commit

Permalink
Add a downsampled line-curve support to BarItems
Browse files Browse the repository at this point in the history
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
  • Loading branch information
goodboy committed Apr 16, 2022
1 parent a6c103a commit da5d2ef
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 8 deletions.
16 changes: 16 additions & 0 deletions piker/ui/_interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from ._style import _min_points_to_show
from ._editors import SelectRect
from . import _event
from ._ohlc import BarItems


log = get_logger(__name__)
Expand Down Expand Up @@ -429,6 +430,12 @@ def maxmin(self) -> Callable:
def maxmin(self, callback: Callable) -> None:
self._maxmin = callback


def maybe_downsample_graphics(self):
for graphic in self._chart._graphics.values():
if isinstance(graphic, BarItems):
graphic.maybe_paint_line()

def wheelEvent(
self,
ev,
Expand Down Expand Up @@ -775,6 +782,15 @@ def enable_auto_yrange(
'''
vb.sigXRangeChanged.connect(vb._set_yrange)

# TODO: a smarter way to avoid calling this needlessly?
# 2 things i can think of:
# - register downsample-able graphics specially and only
# iterate those.
# - only register this when certain downsampleable graphics are
# "added to scene".
vb.sigXRangeChanged.connect(vb.maybe_downsample_graphics)

# mouse wheel doesn't emit XRangeChanged
vb.sigRangeChangedManually.connect(vb._set_yrange)
vb.sigResized.connect(vb._set_yrange) # splitter(s) resizing
Expand Down
110 changes: 102 additions & 8 deletions piker/ui/_ohlc.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@

from .._profile import pg_profile_enabled
from ._style import hcolor
from ..log import get_logger


log = get_logger(__name__)


def _mk_lines_array(
Expand Down Expand Up @@ -170,8 +174,10 @@ def gen_qpath(


class BarItems(pg.GraphicsObject):
"""Price range bars graphics rendered from a OHLC sequence.
"""
'''
"Price range" bars graphics rendered from a OHLC sampled sequence.
'''
sigPlotChanged = QtCore.pyqtSignal(object)

# 0.5 is no overlap between arms, 1.0 is full overlap
Expand All @@ -183,11 +189,15 @@ def __init__(
plotitem: 'pg.PlotItem', # noqa
pen_color: str = 'bracket',
last_bar_color: str = 'bracket',

name: Optional[str] = None,

) -> None:
super().__init__()

super().__init__()
# XXX: for the mega-lulz increasing width here increases draw
# latency... so probably don't do it until we figure that out.
self._color = pen_color
self.bars_pen = pg.mkPen(hcolor(pen_color), width=1)
self.last_bar_pen = pg.mkPen(hcolor(last_bar_color), width=2)

Expand Down Expand Up @@ -219,15 +229,20 @@ def __init__(
self.start_index: int = 0
self.stop_index: int = 0

self._in_ds: bool = False

def draw_from_data(
self,
data: np.ndarray,
start: int = 0,

) -> QtGui.QPainterPath:
"""Draw OHLC datum graphics from a ``np.ndarray``.
'''
Draw OHLC datum graphics from a ``np.ndarray``.
This routine is usually only called to draw the initial history.
"""
'''
hist, last = data[:-1], data[-1]

self.path = gen_qpath(hist, start, self.w)
Expand All @@ -249,14 +264,28 @@ def draw_from_data(
# https://doc.qt.io/qt-5/qgraphicsitem.html#update
self.update()

from ._curve import FastAppendCurve
self._ds_line = FastAppendCurve(
y=data['close'],
x=data['index'],
name='ohlc_ds_line',
color=self._color,
# use_polyline=True, # pretty sure this is slower?
)
self.update_from_array(data)
self._pi.addItem(self._ds_line)
self._ds_line.hide()

return self.path

def update_from_array(
self,
array: np.ndarray,
just_history=False,

) -> None:
"""Update the last datum's bar graphic from input data array.
'''
Update the last datum's bar graphic from input data array.
This routine should be interface compatible with
``pg.PlotCurveItem.setData()``. Normally this method in
Expand All @@ -266,7 +295,16 @@ def update_from_array(
does) so this "should" be simpler and faster.
This routine should be made (transitively) as fast as possible.
"""
'''
# XXX: always do this?
if self._in_ds:
self._ds_line.update_from_array(
x=array['index'],
y=array['close'],
)
return

# index = self.start_index
istart, istop = self._xrange

Expand Down Expand Up @@ -400,14 +438,59 @@ def boundingRect(self):

)

def maybe_paint_line(
self,
x_gt: float = 2.,

) -> bool:
'''
Call this when you want to stop drawing individual
bars and instead use a ``FastAppendCurve`` intepolation
line (normally when the width of a bar (aka 1.0 in the x)
is less then a pixel width on the device).
'''
# this is the ``float`` value of the "number of x units" (in
# view coords) that a pixel spans.
xs_in_px = self.pixelVectors()[0].x()
if (
not self._in_ds
and xs_in_px >= x_gt
):
# TODO: a `.ui()` log level?
log.info(f'downsampling to line graphic')
self._in_ds = True
self.hide()
self._pi.addItem(self._ds_line)
self._ds_line.show()
return True

elif (
self._in_ds
and xs_in_px < x_gt
):
log.info(f'showing bars graphic')
self._in_ds = False
self.show()
self._ds_line.hide()
self._pi.removeItem(self._ds_line)
return False

def paint(
self,
p: QtGui.QPainter,
opt: QtWidgets.QStyleOptionGraphicsItem,
w: QtWidgets.QWidget

) -> None:

profiler = pg.debug.Profiler(disabled=not pg_profile_enabled())
if self._in_ds:
return

profiler = pg.debug.Profiler(
disabled=not pg_profile_enabled(),
delayed=False,
)

# p.setCompositionMode(0)

Expand All @@ -424,3 +507,14 @@ def paint(
p.setPen(self.bars_pen)
p.drawPath(self.path)
profiler('draw history path')
profiler.finish()

# NOTE: for testing paint frequency as throttled by display loop.
# now = time.time()
# global _last_draw
# print(f'DRAW RATE {1/(now - _last_draw)}')
# _last_draw = now


# import time
# _last_draw: float = time.time()

0 comments on commit da5d2ef

Please sign in to comment.