Add a parent-type for graphics: `FlowGraphic`

Factor some common methods into the parent type:
- `.x_uppx()` for reading the horizontal units-per-pixel.
- `.x_last()` for reading the "closest to y-axis" last datum coordinate
  for zooming "around" during mouse interaction.
- `.px_width()` for computing the max width of any curve in view in
  pixels.

Adjust all previous derived `pg.GraphicsObject` child types to now
inherit from this new parent and in particular enable proper `.x_uppx()`
support to `BarItems`.
epoch_indexing_and_dataviz_layer
Tyler Goodlet 2022-12-26 14:46:46 -05:00
parent 6a0c36922e
commit c1988c4d8d
2 changed files with 73 additions and 62 deletions

View File

@ -51,7 +51,59 @@ _line_styles: dict[str, int] = {
} }
class Curve(pg.GraphicsObject): class FlowGraphic(pg.GraphicsObject):
'''
Base class with minimal interface for `QPainterPath` implemented,
real-time updated "data flow" graphics.
See subtypes below.
'''
# sub-type customization methods
declare_paintables: Optional[Callable] = None
sub_paint: Optional[Callable] = None
# TODO: can we remove this?
# sub_br: Optional[Callable] = None
def x_uppx(self) -> int:
px_vecs = self.pixelVectors()[0]
if px_vecs:
xs_in_px = px_vecs.x()
return round(xs_in_px)
else:
return 0
def x_last(self) -> float:
'''
Return the last most x value of the last line segment.
'''
return self._last_line.x1()
def px_width(self) -> float:
'''
Return the width of the view box containing
this graphic in pixel units.
'''
vb = self.getViewBox()
if not vb:
return 0
vr = self.viewRect()
vl, vr = int(vr.left()), int(vr.right())
return vb.mapViewToDevice(
QLineF(
vl, 0,
vr, 0,
)
).length()
class Curve(FlowGraphic):
''' '''
A faster, simpler, append friendly version of A faster, simpler, append friendly version of
``pyqtgraph.PlotCurveItem`` built for highly customizable real-time ``pyqtgraph.PlotCurveItem`` built for highly customizable real-time
@ -81,11 +133,6 @@ class Curve(pg.GraphicsObject):
''' '''
# sub-type customization methods
declare_paintables: Optional[Callable] = None
sub_paint: Optional[Callable] = None
# sub_br: Optional[Callable] = None
def __init__( def __init__(
self, self,
*args, *args,
@ -95,7 +142,6 @@ class Curve(pg.GraphicsObject):
fill_color: Optional[str] = None, fill_color: Optional[str] = None,
style: str = 'solid', style: str = 'solid',
name: Optional[str] = None, name: Optional[str] = None,
use_fpath: bool = True,
**kwargs **kwargs
@ -110,11 +156,11 @@ class Curve(pg.GraphicsObject):
# self._last_cap: int = 0 # self._last_cap: int = 0
self.path: Optional[QPainterPath] = None self.path: Optional[QPainterPath] = None
# additional path used for appends which tries to avoid # additional path that can be optionally used for appends which
# triggering an update/redraw of the presumably larger # tries to avoid triggering an update/redraw of the presumably
# historical ``.path`` above. # larger historical ``.path`` above. the flag to enable
self.use_fpath = use_fpath # this behaviour is found in `Renderer.render()`.
self.fast_path: Optional[QPainterPath] = None self.fast_path: QPainterPath | None = None
# TODO: we can probably just dispense with the parent since # TODO: we can probably just dispense with the parent since
# we're basically only using the pen setting now... # we're basically only using the pen setting now...
@ -154,58 +200,19 @@ class Curve(pg.GraphicsObject):
# endpoint (something we saw on trade rate curves) # endpoint (something we saw on trade rate curves)
self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
# XXX: see explanation for different caching modes: # XXX-NOTE-XXX: graphics caching.
# https://stackoverflow.com/a/39410081 # see explanation for different caching modes:
# seems to only be useful if we don't re-generate the entire # https://stackoverflow.com/a/39410081 seems to only be useful
# QPainterPath every time # if we don't re-generate the entire QPainterPath every time
# curve.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
# don't ever use this - it's a colossal nightmare of artefacts # don't ever use this - it's a colossal nightmare of artefacts
# and is disastrous for performance. # and is disastrous for performance.
# curve.setCacheMode(QtWidgets.QGraphicsItem.ItemCoordinateCache) # self.setCacheMode(QtWidgets.QGraphicsItem.ItemCoordinateCache)
# allow sub-type customization # allow sub-type customization
declare = self.declare_paintables declare = self.declare_paintables
if declare: if declare:
declare() declare()
# TODO: probably stick this in a new parent
# type which will contain our own version of
# what ``PlotCurveItem`` had in terms of base
# functionality? A `FlowGraphic` maybe?
def x_uppx(self) -> int:
px_vecs = self.pixelVectors()[0]
if px_vecs:
xs_in_px = px_vecs.x()
return round(xs_in_px)
else:
return 0
def x_last(self) -> float:
'''
Return the last most x value of the last line segment.
'''
return self._last_line.x2()
def px_width(self) -> float:
vb = self.getViewBox()
if not vb:
return 0
vr = self.viewRect()
l, r = int(vr.left()), int(vr.right())
start, stop = self._xrange
lbar = max(l, start)
rbar = min(r, stop)
return vb.mapViewToDevice(
QLineF(lbar, 0, rbar, 0)
).length()
# XXX: lol brutal, the internals of `CurvePoint` (inherited by # XXX: lol brutal, the internals of `CurvePoint` (inherited by
# our `LineDot`) required ``.getData()`` to work.. # our `LineDot`) required ``.getData()`` to work..
def getData(self): def getData(self):
@ -370,6 +377,9 @@ class Curve(pg.GraphicsObject):
x = src_data[index_field] x = src_data[index_field]
y = src_data[array_key] y = src_data[array_key]
x_last = x[-1]
x_2last = x[-2]
# draw the "current" step graphic segment so it # draw the "current" step graphic segment so it
# lines up with the "middle" of the current # lines up with the "middle" of the current
# (OHLC) sample. # (OHLC) sample.
@ -379,8 +389,8 @@ class Curve(pg.GraphicsObject):
# from last datum to current such that # from last datum to current such that
# the end of line touches the "beginning" # the end of line touches the "beginning"
# of the current datum step span. # of the current datum step span.
x[-2], y[-2], x_2last , y[-2],
x[-1], y[-1], x_last, y[-1],
) )
return x, y return x, y

View File

@ -36,6 +36,7 @@ from PyQt5.QtCore import (
from PyQt5.QtGui import QPainterPath from PyQt5.QtGui import QPainterPath
from ._curve import FlowGraphic
from .._profile import pg_profile_enabled, ms_slower_then from .._profile import pg_profile_enabled, ms_slower_then
from ._style import hcolor from ._style import hcolor
from ..log import get_logger from ..log import get_logger
@ -94,7 +95,7 @@ def bar_from_ohlc_row(
return [hl, o, c] return [hl, o, c]
class BarItems(pg.GraphicsObject): class BarItems(FlowGraphic):
''' '''
"Price range" bars graphics rendered from a OHLC sampled sequence. "Price range" bars graphics rendered from a OHLC sampled sequence.
@ -125,9 +126,9 @@ class BarItems(pg.GraphicsObject):
self.path = QPainterPath() self.path = QPainterPath()
self._last_bar_lines: tuple[QLineF, ...] | None = None self._last_bar_lines: tuple[QLineF, ...] | None = None
def x_uppx(self) -> int: # def x_uppx(self) -> int:
# we expect the downsample curve report this. # # we expect the downsample curve report this.
return 0 # return 0
def x_last(self) -> float: def x_last(self) -> float:
''' '''