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
# 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
def reset_cache(self) -> None:
self.setCacheMode(QtWidgets.QGraphicsItem.NoCache)
try:
log.debug(f'{self._name} -> CACHE DISABLE')
none = QGraphicsItem.NoCache
log.debug(
f'{self._name} -> CACHE DISABLE: {none}'
)
self.setCacheMode(none)
yield
finally:
log.debug(f'{self._name} -> CACHE ENABLE')
self.setCacheMode(self.cache_mode)
mode = self.cache_mode
log.debug(f'{self._name} -> CACHE ENABLE {mode}')
self.setCacheMode(mode)
class Curve(FlowGraphic):
'''
A faster, simpler, append friendly version of
``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
"graphics object" in the sense that the internal lower level
@ -385,7 +395,6 @@ class Curve(FlowGraphic):
) -> None:
# default line draw last call
# with self.reset_cache():
x = src_data[index_field]
y = src_data[array_key]
@ -413,12 +422,20 @@ class Curve(FlowGraphic):
# element such that the current datum in view can be shown
# (via it's max / min) even when highly zoomed out.
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
# as well as mouse over artefacts when the vlm chart series
# is "shorter" then some overlay..
# cache_mode: int = QGraphicsItem.NoCache
The main implementation different is that ``.draw_last_datum()``
expects an underlying OHLC array for the ``src_data`` input.
'''
def draw_last_datum(
self,
path: QPainterPath,
@ -443,12 +460,19 @@ class FlattenedOHLC(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(
self,
) -> None:

View File

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

View File

@ -19,7 +19,10 @@ Chart view box primitives
'''
from __future__ import annotations
from contextlib import asynccontextmanager
from contextlib import (
asynccontextmanager,
ExitStack,
)
import time
from typing import (
Callable,
@ -405,7 +408,8 @@ class ChartView(ViewBox):
self.order_mode: bool = False
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
# allocated since they're 1to1 with views..
@ -420,10 +424,20 @@ class ChartView(ViewBox):
to any interested task waiters.
'''
if self._ic is None:
if self._in_interact is None:
chart = self.chart
try:
self.chart.pause_all_feeds()
self._ic = trio.Event()
chart.pause_all_feeds()
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:
pass
@ -437,10 +451,11 @@ class ChartView(ViewBox):
to any waiters.
'''
if self._ic:
if self._in_interact:
try:
self._ic.set()
self._ic = None
self._in_interact.set()
self._in_interact = None
self._interact_stack.close()
self.chart.resume_all_feeds()
except RuntimeError:
pass
@ -667,9 +682,6 @@ class ChartView(ViewBox):
self.start_ic()
except RuntimeError:
pass
# if self._ic is None:
# self.chart.pause_all_feeds()
# self._ic = trio.Event()
if axis == 1:
self.chart._static_yrange = 'axis'
@ -693,8 +705,8 @@ class ChartView(ViewBox):
if ev.isFinish():
self.signal_ic()
# self._ic.set()
# self._ic = None
# self._in_interact.set()
# self._in_interact = None
# self.chart.resume_all_feeds()
# # XXX: WHY

View File

@ -28,7 +28,6 @@ from PyQt5.QtCore import (
QLineF,
QRectF,
)
from PyQt5.QtWidgets import QGraphicsItem
from PyQt5.QtGui import QPainterPath
from ._curve import FlowGraphic
@ -91,10 +90,6 @@ class BarItems(FlowGraphic):
"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__(
self,
*args,