Factor color and cache mode settings into `FlowGraphics`

Curve-path colouring and cache mode settings are used (and can thus be
factored out of) all child types; this moves them into the parent type's
`.__init__()` and adjusts all sub-types match:

- the bulk was moved out of the `Curve.__init__()` including all
  previous commentary around cache settings.
- adjust `BarItems` to use a `NoCache` mode and instead use the
  `last_step_pen: pg.Pen` and `._pen` inside it's `.pain()` instead of
  defining functionally duplicate vars.
- adjust all (transitive) calls to `BarItems` to use the new kwargs
  names.
multichartz
Tyler Goodlet 2023-01-23 15:22:42 -05:00
parent 426ae9e2ca
commit 28c0f80e6d
4 changed files with 98 additions and 104 deletions

View File

@ -1152,8 +1152,6 @@ class ChartPlotWidget(pg.PlotWidget):
if is_ohlc: if is_ohlc:
graphics = BarItems( graphics = BarItems(
linked=self.linked,
plotitem=pi,
color=color, color=color,
name=name, name=name,
**graphics_kwargs, **graphics_kwargs,

View File

@ -60,11 +60,82 @@ class FlowGraphic(pg.GraphicsObject):
''' '''
# sub-type customization methods # sub-type customization methods
declare_paintables: Optional[Callable] = None declare_paintables: Callable | None = None
sub_paint: Optional[Callable] = None sub_paint: Callable | None = None
# TODO: can we remove this? # XXX-NOTE-XXX: graphics caching B)
# sub_br: Optional[Callable] = None # see explanation for different caching modes:
# https://stackoverflow.com/a/39410081
cache_mode: int = QGraphicsItem.DeviceCoordinateCache
# XXX: WARNING item caching seems to only be useful
# if we don't re-generate the entire QPainterPath every time
# don't ever use this - it's a colossal nightmare of artefacts
# and is disastrous for performance.
# QGraphicsItem.ItemCoordinateCache
# TODO: still questions todo with coord-cacheing that we should
# probably talk to a core dev about:
# - 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?
def __init__(
self,
*args,
name: str | None = None,
# line styling
color: str = 'bracket',
last_step_color: str = 'original',
fill_color: Optional[str] = None,
style: str = 'solid',
**kwargs
) -> None:
self._name = name
# primary graphics item used for history
self.path: QPainterPath = QPainterPath()
# additional path that can be optionally used for appends which
# tries to avoid triggering an update/redraw of the presumably
# larger historical ``.path`` above. the flag to enable
# this behaviour is found in `Renderer.render()`.
self.fast_path: QPainterPath | None = None
# TODO: evaluating the path capacity stuff and see
# if it really makes much diff pre-allocating it.
# self._last_cap: int = 0
# cap = path.capacity()
# if cap != self._last_cap:
# print(f'NEW CAPACITY: {self._last_cap} -> {cap}')
# self._last_cap = cap
# all history of curve is drawn in single px thickness
self._color: str = color
pen = pg.mkPen(hcolor(color), width=1)
pen.setStyle(_line_styles[style])
if 'dash' in style:
pen.setDashPattern([8, 3])
self._pen = pen
self._brush = pg.functions.mkBrush(
hcolor(fill_color or color)
)
# last segment is drawn in 2px thickness for emphasis
self.last_step_pen = pg.mkPen(
hcolor(last_step_color),
width=2,
)
self._last_line: QLineF = QLineF()
super().__init__(*args, **kwargs)
# apply cache mode
self.setCacheMode(self.cache_mode)
def x_uppx(self) -> int: def x_uppx(self) -> int:
@ -112,82 +183,32 @@ class Curve(FlowGraphic):
updates don't trigger a full path redraw. updates don't trigger a full path redraw.
''' '''
cache_mode: int = QGraphicsItem.DeviceCoordinateCache # TODO: can we remove this?
# sub_br: Optional[Callable] = None
def __init__( def __init__(
self, self,
*args, *args,
step_mode: bool = False, # color: str = 'default_lightest',
color: str = 'default_lightest', # fill_color: Optional[str] = None,
fill_color: Optional[str] = None, # style: str = 'solid',
style: str = 'solid',
name: Optional[str] = None,
**kwargs **kwargs
) -> None: ) -> None:
self._name = name
# brutaaalll, see comments within.. # brutaaalll, see comments within..
self.yData = None self.yData = None
self.xData = None self.xData = None
# self._last_cap: int = 0
self.path: Optional[QPainterPath] = None
# additional path that can be optionally used for appends which
# tries to avoid triggering an update/redraw of the presumably
# larger historical ``.path`` above. the flag to enable
# this behaviour is found in `Renderer.render()`.
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...
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# all history of curve is drawn in single px thickness
pen = pg.mkPen(hcolor(color))
pen.setStyle(_line_styles[style])
if 'dash' in style:
pen.setDashPattern([8, 3])
self._pen = pen
# last segment is drawn in 2px thickness for emphasis
# self.last_step_pen = pg.mkPen(hcolor(color), width=2)
self.last_step_pen = pg.mkPen(pen, width=2)
self._last_line: QLineF = QLineF() self._last_line: QLineF = QLineF()
# flat-top style histogram-like discrete curve
# self._step_mode: bool = step_mode
# self._fill = True # self._fill = True
self._brush = pg.functions.mkBrush(hcolor(fill_color or color))
# NOTE: this setting seems to mostly prevent redraws on mouse
# interaction which is a huge boon for avg interaction latency.
# 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?
# don't enable caching by default for the case where the
# only thing drawn is the "last" line segment which can
# have a weird artifact where it won't be fully drawn to its
# endpoint (something we saw on trade rate curves)
self.setCacheMode(self.cache_mode)
# XXX-NOTE-XXX: graphics caching.
# see explanation for different caching modes:
# https://stackoverflow.com/a/39410081 seems to only be useful
# if we don't re-generate the entire QPainterPath every time
# don't ever use this - it's a colossal nightmare of artefacts
# and is disastrous for performance.
# self.setCacheMode(QtWidgets.QGraphicsItem.ItemCoordinateCache)
# allow sub-type customization # allow sub-type customization
declare = self.declare_paintables declare = self.declare_paintables
@ -318,14 +339,10 @@ class Curve(FlowGraphic):
p.setPen(self.last_step_pen) p.setPen(self.last_step_pen)
p.drawLine(self._last_line) p.drawLine(self._last_line)
profiler('.drawLine()') profiler('last datum `.drawLine()`')
p.setPen(self._pen)
p.setPen(self._pen)
path = self.path path = self.path
# cap = path.capacity()
# if cap != self._last_cap:
# print(f'NEW CAPACITY: {self._last_cap} -> {cap}')
# self._last_cap = cap
if path: if path:
p.drawPath(path) p.drawPath(path)

View File

@ -447,7 +447,8 @@ async def graphics_update_loop(
# and quote_rate >= _quote_throttle_rate * 2 # and quote_rate >= _quote_throttle_rate * 2
and quote_rate >= display_rate and quote_rate >= display_rate
): ):
log.warning(f'High quote rate {symbol.key}: {quote_rate}') pass
# log.warning(f'High quote rate {symbol.key}: {quote_rate}')
last_quote_s = time.time() last_quote_s = time.time()
@ -493,9 +494,9 @@ def graphics_update_cycle(
profiler = Profiler( profiler = Profiler(
msg=f'Graphics loop cycle for: `{ds.fqsn}`', msg=f'Graphics loop cycle for: `{ds.fqsn}`',
delayed=True,
disabled=not pg_profile_enabled(), disabled=not pg_profile_enabled(),
ms_threshold=ms_slower_then, ms_threshold=ms_slower_then,
delayed=True,
# ms_threshold=4, # ms_threshold=4,
) )
@ -1319,7 +1320,7 @@ async def display_symbol_data(
is_ohlc=True, is_ohlc=True,
color=bg_chart_color, color=bg_chart_color,
last_bar_color=bg_last_bar_color, last_step_color=bg_last_bar_color,
) )
# ensure the last datum graphic is generated # ensure the last datum graphic is generated
@ -1356,7 +1357,7 @@ async def display_symbol_data(
is_ohlc=True, is_ohlc=True,
color=bg_chart_color, color=bg_chart_color,
last_bar_color=bg_last_bar_color, last_step_color=bg_last_bar_color,
) )
rt_pi.vb.maxmin = partial( rt_pi.vb.maxmin = partial(
rt_chart.maxmin, rt_chart.maxmin,

View File

@ -18,13 +18,8 @@ Super fast OHLC sampling graphics types.
""" """
from __future__ import annotations from __future__ import annotations
from typing import (
Optional,
TYPE_CHECKING,
)
import numpy as np import numpy as np
import pyqtgraph as pg
from PyQt5 import ( from PyQt5 import (
QtGui, QtGui,
QtWidgets, QtWidgets,
@ -33,18 +28,14 @@ from PyQt5.QtCore import (
QLineF, QLineF,
QRectF, QRectF,
) )
from PyQt5.QtWidgets import QGraphicsItem
from PyQt5.QtGui import QPainterPath from PyQt5.QtGui import QPainterPath
from ._curve import FlowGraphic 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 ..log import get_logger from ..log import get_logger
from .._profile import Profiler from .._profile import Profiler
if TYPE_CHECKING:
from ._chart import LinkedSplits
log = get_logger(__name__) log = get_logger(__name__)
@ -100,30 +91,18 @@ class BarItems(FlowGraphic):
"Price range" bars graphics rendered from a OHLC sampled sequence. "Price range" bars graphics rendered from a OHLC sampled sequence.
''' '''
def __init__(
self,
linked: LinkedSplits,
plotitem: 'pg.PlotItem', # noqa
color: str = 'bracket',
last_bar_color: str = 'original',
name: Optional[str] = None,
) -> None:
super().__init__()
self.linked = linked
# XXX: for the mega-lulz increasing width here increases draw
# latency... so probably don't do it until we figure that out.
self._color = color
self.bars_pen = pg.mkPen(hcolor(color), width=1)
self.last_bar_pen = pg.mkPen(hcolor(last_bar_color), width=2)
self._name = name
# XXX: causes this weird jitter bug when click-drag panning # XXX: causes this weird jitter bug when click-drag panning
# where the path curve will awkwardly flicker back and forth? # where the path curve will awkwardly flicker back and forth?
self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) cache_mode: int = QGraphicsItem.NoCache
self.path = QPainterPath() def __init__(
self,
*args,
**kwargs,
) -> None:
super().__init__(*args, **kwargs)
self._last_bar_lines: tuple[QLineF, ...] | None = None self._last_bar_lines: tuple[QLineF, ...] | None = None
def x_last(self) -> None | float: def x_last(self) -> None | float:
@ -218,12 +197,12 @@ class BarItems(FlowGraphic):
# as is necesarry for what's in "view". Not sure if this will # as is necesarry for what's in "view". Not sure if this will
# lead to any perf gains other then when zoomed in to less bars # lead to any perf gains other then when zoomed in to less bars
# in view. # in view.
p.setPen(self.last_bar_pen) p.setPen(self.last_step_pen)
if self._last_bar_lines: if self._last_bar_lines:
p.drawLines(*tuple(filter(bool, self._last_bar_lines))) p.drawLines(*tuple(filter(bool, self._last_bar_lines)))
profiler('draw last bar') profiler('draw last bar')
p.setPen(self.bars_pen) p.setPen(self._pen)
p.drawPath(self.path) p.drawPath(self.path)
profiler(f'draw history path: {self.path.capacity()}') profiler(f'draw history path: {self.path.capacity()}')
@ -299,5 +278,4 @@ class BarItems(FlowGraphic):
# date / from some previous sample. It's weird though # date / from some previous sample. It's weird though
# because i've seen it do this to bars i - 3 back? # because i've seen it do this to bars i - 3 back?
# return ohlc['time'], ohlc['close']
return ohlc[index_field], ohlc['close'] return ohlc[index_field], ohlc['close']