Disable coordinate caching during interaction

This finally seems to mitigate all the "smearing" and "jitter" artifacts
when using Qt's "coordinate cache" graphics-mode:

- whenever we're in a mouse interaction (as per calls to
  `ChartView.start/signal_ic()`) we simply disable the caching mode (set
  `.NoCache` until the interaction is complete.
- only do this (for now) during a pan since it doesn't seem to be an
  issue when zooming?
- ensure disabling all `Viz.graphics` and `.ds_graphics` to be agnostic
  to any case where there's both a zoom and a pan simultaneously (not
  that it's easy to do manually XD) as well as solving the problem
  whenever an OHLC series is in traced-and-downsampled mode (during low
  zoom).

Impl deatz:
- rename `ChartView._ic` -> `._in_interact: trio.Event`
- add `.ChartView._interact_stack: ExitStack` which  we use to open.
  and close the `FlowGraphics.reset_cache()` mngrs from mouse handlers.
- drop all the commented per-subtype overrides for `.cache_mode: int`.
- write up much better doc strings for `FlattenedOHLC` and `StepCurve`
  including some very basic ASCII-art diagrams.
log_linearized_curve_overlays
Tyler Goodlet 2023-02-28 18:03:41 -05:00
parent 9b960594aa
commit f7dfe57090
4 changed files with 66 additions and 35 deletions

View File

@ -163,22 +163,32 @@ class FlowGraphic(pg.GraphicsObject):
return None return None
# XXX: due to a variety of weird jitter bugs and "smearing"
# artifacts when click-drag panning and viewing history time series,
# we offer this ctx-mngr interface to allow temporarily disabling
# Qt's graphics caching mode; this is now currently used from
# ``ChartView.start/signal_ic()`` methods which also disable the
# rt-display loop when the user is moving around a view.
@cm @cm
def reset_cache(self) -> None: def reset_cache(self) -> None:
self.setCacheMode(QtWidgets.QGraphicsItem.NoCache)
try: try:
log.debug(f'{self._name} -> CACHE DISABLE') none = QGraphicsItem.NoCache
log.debug(
f'{self._name} -> CACHE DISABLE: {none}'
)
self.setCacheMode(none)
yield yield
finally: finally:
log.debug(f'{self._name} -> CACHE ENABLE') mode = self.cache_mode
self.setCacheMode(self.cache_mode) log.debug(f'{self._name} -> CACHE ENABLE {mode}')
self.setCacheMode(mode)
class Curve(FlowGraphic): 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
updates. updates; a graphics object to render a simple "line" plot.
This type is a much stripped down version of a ``pyqtgraph`` style This type is a much stripped down version of a ``pyqtgraph`` style
"graphics object" in the sense that the internal lower level "graphics object" in the sense that the internal lower level
@ -385,7 +395,6 @@ class Curve(FlowGraphic):
) -> None: ) -> None:
# default line draw last call # default line draw last call
# with self.reset_cache():
x = src_data[index_field] x = src_data[index_field]
y = src_data[array_key] y = src_data[array_key]
@ -413,12 +422,20 @@ class Curve(FlowGraphic):
# element such that the current datum in view can be shown # element such that the current datum in view can be shown
# (via it's max / min) even when highly zoomed out. # (via it's max / min) even when highly zoomed out.
class FlattenedOHLC(Curve): class FlattenedOHLC(Curve):
'''
More or less the exact same as a standard line ``Curve`` above
but meant to handle a traced-and-downsampled OHLC time series.
_
_| | _
|_ | |_ | |
_| => |_| |
| |
|_ |_
# avoids strange dragging/smearing artifacts when panning The main implementation different is that ``.draw_last_datum()``
# as well as mouse over artefacts when the vlm chart series expects an underlying OHLC array for the ``src_data`` input.
# is "shorter" then some overlay..
# cache_mode: int = QGraphicsItem.NoCache
'''
def draw_last_datum( def draw_last_datum(
self, self,
path: QPainterPath, path: QPainterPath,
@ -443,12 +460,19 @@ class FlattenedOHLC(Curve):
class StepCurve(Curve): class StepCurve(Curve):
'''
A familiar rectangle-with-y-height-per-datum type curve:
# avoids strange dragging/smearing artifacts when panning ||
# as well as mouse over artefacts when the vlm chart series || ||
# is "shorter" then some overlay.. || || ||||
# cache_mode: int = QGraphicsItem.NoCache _||_||_||_||||_ where each datum's y-value is drawn as
a nearly full rectangle, each "level" spans some x-step size.
This is most often used for vlm and option OI style curves and/or
the very popular "bar chart".
'''
def declare_paintables( def declare_paintables(
self, self,
) -> None: ) -> None:

View File

@ -473,7 +473,7 @@ async def graphics_update_loop(
fast_chart.pause_all_feeds() fast_chart.pause_all_feeds()
continue continue
ic = fast_chart.view._ic ic = fast_chart.view._in_interact
if ic: if ic:
fast_chart.pause_all_feeds() fast_chart.pause_all_feeds()
print(f'{fqsn} PAUSING DURING INTERACTION') print(f'{fqsn} PAUSING DURING INTERACTION')
@ -756,8 +756,8 @@ def graphics_update_cycle(
mx = max(mx, lmx) mx = max(mx, lmx)
if ( if (
main_vb._ic is None main_vb._in_interact is None
or not main_vb._ic.is_set() or not main_vb._in_interact.is_set()
): ):
# print(f'SETTING Y-mnmx -> {main_viz.name}: {(mn, mx)}') # print(f'SETTING Y-mnmx -> {main_viz.name}: {(mn, mx)}')
this_vb.interact_graphics_cycle( this_vb.interact_graphics_cycle(

View File

@ -19,7 +19,10 @@ Chart view box primitives
''' '''
from __future__ import annotations from __future__ import annotations
from contextlib import asynccontextmanager from contextlib import (
asynccontextmanager,
ExitStack,
)
import time import time
from typing import ( from typing import (
Callable, Callable,
@ -405,7 +408,8 @@ class ChartView(ViewBox):
self.order_mode: bool = False self.order_mode: bool = False
self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setFocusPolicy(QtCore.Qt.StrongFocus)
self._ic = None self._in_interact: trio.Event | None = None
self._interact_stack: ExitStack = ExitStack()
# TODO: probably just assign this whenever a new `PlotItem` is # TODO: probably just assign this whenever a new `PlotItem` is
# allocated since they're 1to1 with views.. # allocated since they're 1to1 with views..
@ -420,10 +424,20 @@ class ChartView(ViewBox):
to any interested task waiters. to any interested task waiters.
''' '''
if self._ic is None: if self._in_interact is None:
chart = self.chart
try: try:
self.chart.pause_all_feeds() chart.pause_all_feeds()
self._ic = trio.Event() self._in_interact = trio.Event()
for viz in chart.iter_vizs():
self._interact_stack.enter_context(
viz.graphics.reset_cache(),
)
dsg = viz.ds_graphics
if dsg:
self._interact_stack.enter_context(
dsg.reset_cache(),
)
except RuntimeError: except RuntimeError:
pass pass
@ -437,10 +451,11 @@ class ChartView(ViewBox):
to any waiters. to any waiters.
''' '''
if self._ic: if self._in_interact:
try: try:
self._ic.set() self._in_interact.set()
self._ic = None self._in_interact = None
self._interact_stack.close()
self.chart.resume_all_feeds() self.chart.resume_all_feeds()
except RuntimeError: except RuntimeError:
pass pass
@ -667,9 +682,6 @@ class ChartView(ViewBox):
self.start_ic() self.start_ic()
except RuntimeError: except RuntimeError:
pass pass
# if self._ic is None:
# self.chart.pause_all_feeds()
# self._ic = trio.Event()
if axis == 1: if axis == 1:
self.chart._static_yrange = 'axis' self.chart._static_yrange = 'axis'
@ -693,8 +705,8 @@ class ChartView(ViewBox):
if ev.isFinish(): if ev.isFinish():
self.signal_ic() self.signal_ic()
# self._ic.set() # self._in_interact.set()
# self._ic = None # self._in_interact = None
# self.chart.resume_all_feeds() # self.chart.resume_all_feeds()
# # XXX: WHY # # XXX: WHY

View File

@ -28,7 +28,6 @@ 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
@ -91,10 +90,6 @@ class BarItems(FlowGraphic):
"Price range" bars graphics rendered from a OHLC sampled sequence. "Price range" bars graphics rendered from a OHLC sampled sequence.
''' '''
# XXX: causes this weird jitter bug when click-drag panning
# where the path curve will awkwardly flicker back and forth?
# cache_mode: int = QGraphicsItem.NoCache
def __init__( def __init__(
self, self,
*args, *args,