Add some new hotkey maps for chart zoom and pane hiding

account_tests
Tyler Goodlet 2023-08-02 20:41:56 -04:00
parent fff610fa8d
commit 94ebe1e87e
4 changed files with 134 additions and 29 deletions

View File

@ -406,6 +406,7 @@ class ChartnPane(QFrame):
) )
self._sidepane = sidepane self._sidepane = sidepane
@property
def sidepane(self) -> FieldsForm | SearchWidget: def sidepane(self) -> FieldsForm | SearchWidget:
return self._sidepane return self._sidepane
@ -495,7 +496,7 @@ class LinkedSplits(QWidget):
Set the proportion of space allocated for linked subcharts. Set the proportion of space allocated for linked subcharts.
''' '''
ln = len(self.subplots) or 1 ln: int = len(self.subplots) or 1
# proportion allocated to consumer subcharts # proportion allocated to consumer subcharts
if not prop: if not prop:
@ -925,6 +926,7 @@ class ChartPlotWidget(pg.PlotWidget):
self.useOpenGL(use_open_gl) self.useOpenGL(use_open_gl)
self.name = name self.name = name
self.data_key = data_key or name self.data_key = data_key or name
self.qframe: ChartnPane | None = None
# scene-local placeholder for book graphics # scene-local placeholder for book graphics
# sizing to avoid overlap with data contents # sizing to avoid overlap with data contents

View File

@ -21,7 +21,6 @@ Text entry "forms" widgets (mostly for configuration and UI user input).
from __future__ import annotations from __future__ import annotations
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from functools import partial from functools import partial
from math import floor
from typing import ( from typing import (
Any, Any,
Callable, Callable,

View File

@ -283,6 +283,7 @@ async def run_fsp_ui(
name, name,
array_key=array_key, array_key=array_key,
) )
assert chart.qframe
chart.linked.focus() chart.linked.focus()

View File

@ -32,8 +32,18 @@ from typing import (
import pyqtgraph as pg import pyqtgraph as pg
# from pyqtgraph.GraphicsScene import mouseEvents # from pyqtgraph.GraphicsScene import mouseEvents
from PyQt5.QtWidgets import QGraphicsSceneMouseEvent as gs_mouse from PyQt5.QtWidgets import QGraphicsSceneMouseEvent as gs_mouse
from PyQt5.QtCore import Qt, QEvent from PyQt5.QtGui import (
from pyqtgraph import ViewBox, Point, QtCore QWheelEvent,
)
from PyQt5.QtCore import (
Qt,
QEvent,
)
from pyqtgraph import (
ViewBox,
Point,
QtCore,
)
from pyqtgraph import functions as fn from pyqtgraph import functions as fn
import numpy as np import numpy as np
import trio import trio
@ -50,8 +60,16 @@ from ._editors import SelectRect
from . import _event from . import _event
if TYPE_CHECKING: if TYPE_CHECKING:
from ._chart import ChartPlotWidget # from ._search import (
# SearchWidget,
# )
from ._chart import (
ChartnPane,
ChartPlotWidget,
GodWidget,
)
from ._dataviz import Viz from ._dataviz import Viz
from .order_mode import OrderMode
log = get_logger(__name__) log = get_logger(__name__)
@ -83,7 +101,8 @@ async def handle_viewmode_kb_inputs(
) -> None: ) -> None:
order_mode = view.order_mode order_mode: OrderMode = view.order_mode
godw: GodWidget = order_mode.godw # noqa
# track edge triggered keys # track edge triggered keys
# (https://en.wikipedia.org/wiki/Interrupt#Triggering_methods) # (https://en.wikipedia.org/wiki/Interrupt#Triggering_methods)
@ -147,14 +166,14 @@ async def handle_viewmode_kb_inputs(
if mods == Qt.ControlModifier: if mods == Qt.ControlModifier:
ctrl = True ctrl = True
# UI REPL-shell # UI REPL-shell, with ctrl-p (for "pause")
if ( if (
ctrl and key in { ctrl
Qt.Key_U, and key in {
Qt.Key_P,
} }
): ):
import tractor import tractor
god = order_mode.godw # noqa
feed = order_mode.feed # noqa feed = order_mode.feed # noqa
chart = order_mode.chart # noqa chart = order_mode.chart # noqa
viz = chart.main_viz # noqa viz = chart.main_viz # noqa
@ -167,9 +186,10 @@ async def handle_viewmode_kb_inputs(
# SEARCH MODE # # SEARCH MODE #
# ctlr-<space>/<l> for "lookup", "search" -> open search tree # ctlr-<space>/<l> for "lookup", "search" -> open search tree
if ( if (
ctrl and key in { ctrl
and key in {
Qt.Key_L, Qt.Key_L,
Qt.Key_Space, # Qt.Key_Space,
} }
): ):
godw = view._chart.linked.godwidget godw = view._chart.linked.godwidget
@ -177,19 +197,53 @@ async def handle_viewmode_kb_inputs(
godw.search.focus() godw.search.focus()
# esc and ctrl-c # esc and ctrl-c
if key == Qt.Key_Escape or (ctrl and key == Qt.Key_C): if (
key == Qt.Key_Escape
or (
ctrl
and key == Qt.Key_C
)
):
# ctrl-c as cancel # ctrl-c as cancel
# https://forum.qt.io/topic/532/how-to-catch-ctrl-c-on-a-widget/9 # https://forum.qt.io/topic/532/how-to-catch-ctrl-c-on-a-widget/9
view.select_box.clear() view.select_box.clear()
view.linked.focus() view.linked.focus()
# cancel order or clear graphics # cancel order or clear graphics
if key == Qt.Key_C or key == Qt.Key_Delete: if (
key == Qt.Key_C
or key == Qt.Key_Delete
):
order_mode.cancel_orders_under_cursor() order_mode.cancel_orders_under_cursor()
# View modes # View modes
if key == Qt.Key_R: if (
ctrl
and (
key == Qt.Key_Equal
or key == Qt.Key_I
)
):
view.wheelEvent(
ev=None,
axis=None,
delta=view.def_delta,
)
elif (
ctrl
and (
key == Qt.Key_Minus
or key == Qt.Key_O
)
):
view.wheelEvent(
ev=None,
axis=None,
delta=-view.def_delta,
)
elif key == Qt.Key_R:
# NOTE: seems that if we don't yield a Qt render # NOTE: seems that if we don't yield a Qt render
# cycle then the m4 downsampled curves will show here # cycle then the m4 downsampled curves will show here
@ -235,15 +289,47 @@ async def handle_viewmode_kb_inputs(
# Toggle position config pane # Toggle position config pane
if ( if (
ctrl and key in { ctrl
Qt.Key_P, and key in {
Qt.Key_Space,
} }
): ):
pp_pane = order_mode.current_pp.pane # searchw: SearchWidget = godw.search
if pp_pane.isHidden(): # pp_pane = order_mode.current_pp.pane
pp_pane.show() qframes: list[ChartnPane] = []
for linked in (
godw.rt_linked,
godw.hist_linked,
):
for chartw in (
[linked.chart]
+
list(linked.subplots.values())
):
qframes.append(
chartw.qframe
)
# NOTE: place priority on FIRST hiding all
# panes before showing them.
# TODO: make this more "fancy"?
# - maybe look at majority of hidden states and then
# flip based on that?
# - move these loops into the chart APIs?
# - store the UX-state for a given feed/symbol and
# apply when opening a new one (eg. if panes were
# hidden then also hide them on newly loaded mkt
# feeds).
if not any(
qf.sidepane.isHidden() for qf in qframes
):
for qf in qframes:
qf.sidepane.hide()
else: else:
pp_pane.hide() for qf in qframes:
qf.sidepane.show()
# ORDER MODE # ORDER MODE
# ---------- # ----------
@ -378,6 +464,8 @@ class ChartView(ViewBox):
''' '''
mode_name: str = 'view' mode_name: str = 'view'
def_delta: float = 616 * 6
def_scale_factor: float = 1.016 ** (def_delta * -1 / 20)
def __init__( def __init__(
self, self,
@ -502,8 +590,9 @@ class ChartView(ViewBox):
def wheelEvent( def wheelEvent(
self, self,
ev, ev: QWheelEvent | None = None,
axis=None, axis: int | None = None,
delta: float | None = None,
): ):
''' '''
Override "center-point" location for scrolling. Override "center-point" location for scrolling.
@ -514,6 +603,12 @@ class ChartView(ViewBox):
TODO: PR a method into ``pyqtgraph`` to make this configurable TODO: PR a method into ``pyqtgraph`` to make this configurable
''' '''
# NOTE: certain operations are only avail when this handler is
# actually called on events.
if ev is None:
assert delta
assert axis is None
linked = self.linked linked = self.linked
if ( if (
not linked not linked
@ -524,7 +619,7 @@ class ChartView(ViewBox):
mask = [False, False] mask = [False, False]
mask[axis] = self.state['mouseEnabled'][axis] mask[axis] = self.state['mouseEnabled'][axis]
else: else:
mask = self.state['mouseEnabled'][:] mask: list[bool] = self.state['mouseEnabled'][:]
chart = self.linked.chart chart = self.linked.chart
@ -545,8 +640,15 @@ class ChartView(ViewBox):
# return # return
# actual scaling factor # actual scaling factor
s = 1.016 ** (ev.delta() * -1 / 20) # self.state['wheelScaleFactor']) delta: float = ev.delta() if ev else delta
s = [(None if m is False else s) for m in mask] scale_factor: float = 1.016 ** (delta * -1 / 20)
# NOTE: if elem is False -> None meaning "do not scale that
# axis".
scales: list[float | bool] = [
(None if m is False else scale_factor)
for m in mask
]
if ( if (
# zoom happened on axis # zoom happened on axis
@ -569,7 +671,7 @@ class ChartView(ViewBox):
).map(ev.pos()) ).map(ev.pos())
) )
# scale_y = 1.3 ** (center.y() * -1 / 20) # scale_y = 1.3 ** (center.y() * -1 / 20)
self.scaleBy(s, center) self.scaleBy(scales, center)
# zoom in view-box area # zoom in view-box area
else: else:
@ -584,7 +686,7 @@ class ChartView(ViewBox):
# NOTE: scroll "around" the right most datum-element in view # NOTE: scroll "around" the right most datum-element in view
# gives the feeling of staying "pinned" in place. # gives the feeling of staying "pinned" in place.
self.scaleBy(s, focal) self.scaleBy(scales, focal)
# XXX: the order of the next 2 lines i'm pretty sure # XXX: the order of the next 2 lines i'm pretty sure
# matters, we want the resize to trigger before the graphics # matters, we want the resize to trigger before the graphics
@ -604,7 +706,8 @@ class ChartView(ViewBox):
self.interact_graphics_cycle() self.interact_graphics_cycle()
self.interact_graphics_cycle() self.interact_graphics_cycle()
ev.accept() if ev:
ev.accept()
def mouseDragEvent( def mouseDragEvent(
self, self,