Add some new hotkey maps for chart zoom and pane hiding
parent
fff610fa8d
commit
94ebe1e87e
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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,6 +706,7 @@ class ChartView(ViewBox):
|
||||||
self.interact_graphics_cycle()
|
self.interact_graphics_cycle()
|
||||||
self.interact_graphics_cycle()
|
self.interact_graphics_cycle()
|
||||||
|
|
||||||
|
if ev:
|
||||||
ev.accept()
|
ev.accept()
|
||||||
|
|
||||||
def mouseDragEvent(
|
def mouseDragEvent(
|
||||||
|
|
Loading…
Reference in New Issue