commit
ba2e1e04cd
|
@ -18,7 +18,10 @@
|
||||||
Profiling wrappers for internal libs.
|
Profiling wrappers for internal libs.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from time import perf_counter
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
# NOTE: you can pass a flag to enable this:
|
# NOTE: you can pass a flag to enable this:
|
||||||
|
@ -44,3 +47,184 @@ def timeit(fn):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
# Modified version of ``pyqtgraph.debug.Profiler`` that
|
||||||
|
# core seems hesitant to land in:
|
||||||
|
# https://github.com/pyqtgraph/pyqtgraph/pull/2281
|
||||||
|
class Profiler(object):
|
||||||
|
'''
|
||||||
|
Simple profiler allowing measurement of multiple time intervals.
|
||||||
|
|
||||||
|
By default, profilers are disabled. To enable profiling, set the
|
||||||
|
environment variable `PYQTGRAPHPROFILE` to a comma-separated list of
|
||||||
|
fully-qualified names of profiled functions.
|
||||||
|
|
||||||
|
Calling a profiler registers a message (defaulting to an increasing
|
||||||
|
counter) that contains the time elapsed since the last call. When the
|
||||||
|
profiler is about to be garbage-collected, the messages are passed to the
|
||||||
|
outer profiler if one is running, or printed to stdout otherwise.
|
||||||
|
|
||||||
|
If `delayed` is set to False, messages are immediately printed instead.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
def function(...):
|
||||||
|
profiler = Profiler()
|
||||||
|
... do stuff ...
|
||||||
|
profiler('did stuff')
|
||||||
|
... do other stuff ...
|
||||||
|
profiler('did other stuff')
|
||||||
|
# profiler is garbage-collected and flushed at function end
|
||||||
|
|
||||||
|
If this function is a method of class C, setting `PYQTGRAPHPROFILE` to
|
||||||
|
"C.function" (without the module name) will enable this profiler.
|
||||||
|
|
||||||
|
For regular functions, use the qualified name of the function, stripping
|
||||||
|
only the initial "pyqtgraph." prefix from the module.
|
||||||
|
'''
|
||||||
|
|
||||||
|
_profilers = os.environ.get("PYQTGRAPHPROFILE", None)
|
||||||
|
_profilers = _profilers.split(",") if _profilers is not None else []
|
||||||
|
|
||||||
|
_depth = 0
|
||||||
|
|
||||||
|
# NOTE: without this defined at the class level
|
||||||
|
# you won't see apprpriately "nested" sub-profiler
|
||||||
|
# instance calls.
|
||||||
|
_msgs = []
|
||||||
|
|
||||||
|
# set this flag to disable all or individual profilers at runtime
|
||||||
|
disable = False
|
||||||
|
|
||||||
|
class DisabledProfiler(object):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def mark(self, msg=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
_disabledProfiler = DisabledProfiler()
|
||||||
|
|
||||||
|
def __new__(
|
||||||
|
cls,
|
||||||
|
msg=None,
|
||||||
|
disabled='env',
|
||||||
|
delayed=True,
|
||||||
|
ms_threshold: float = 0.0,
|
||||||
|
):
|
||||||
|
"""Optionally create a new profiler based on caller's qualname.
|
||||||
|
|
||||||
|
``ms_threshold`` can be set to value in ms for which, if the
|
||||||
|
total measured time of the lifetime of this profiler is **less
|
||||||
|
than** this value, then no profiling messages will be printed.
|
||||||
|
Setting ``delayed=False`` disables this feature since messages
|
||||||
|
are emitted immediately.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
disabled is True
|
||||||
|
or (
|
||||||
|
disabled == 'env'
|
||||||
|
and len(cls._profilers) == 0
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return cls._disabledProfiler
|
||||||
|
|
||||||
|
# determine the qualified name of the caller function
|
||||||
|
caller_frame = sys._getframe(1)
|
||||||
|
try:
|
||||||
|
caller_object_type = type(caller_frame.f_locals["self"])
|
||||||
|
|
||||||
|
except KeyError: # we are in a regular function
|
||||||
|
qualifier = caller_frame.f_globals["__name__"].split(".", 1)[-1]
|
||||||
|
|
||||||
|
else: # we are in a method
|
||||||
|
qualifier = caller_object_type.__name__
|
||||||
|
func_qualname = qualifier + "." + caller_frame.f_code.co_name
|
||||||
|
|
||||||
|
if disabled == 'env' and func_qualname not in cls._profilers:
|
||||||
|
# don't do anything
|
||||||
|
return cls._disabledProfiler
|
||||||
|
|
||||||
|
# create an actual profiling object
|
||||||
|
cls._depth += 1
|
||||||
|
obj = super(Profiler, cls).__new__(cls)
|
||||||
|
obj._name = msg or func_qualname
|
||||||
|
obj._delayed = delayed
|
||||||
|
obj._markCount = 0
|
||||||
|
obj._finished = False
|
||||||
|
obj._firstTime = obj._lastTime = perf_counter()
|
||||||
|
obj._mt = ms_threshold
|
||||||
|
obj._newMsg("> Entering " + obj._name)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __call__(self, msg=None):
|
||||||
|
"""Register or print a new message with timing information.
|
||||||
|
"""
|
||||||
|
if self.disable:
|
||||||
|
return
|
||||||
|
if msg is None:
|
||||||
|
msg = str(self._markCount)
|
||||||
|
|
||||||
|
self._markCount += 1
|
||||||
|
newTime = perf_counter()
|
||||||
|
ms = (newTime - self._lastTime) * 1000
|
||||||
|
self._newMsg(" %s: %0.4f ms", msg, ms)
|
||||||
|
self._lastTime = newTime
|
||||||
|
|
||||||
|
def mark(self, msg=None):
|
||||||
|
self(msg)
|
||||||
|
|
||||||
|
def _newMsg(self, msg, *args):
|
||||||
|
msg = " " * (self._depth - 1) + msg
|
||||||
|
if self._delayed:
|
||||||
|
self._msgs.append((msg, args))
|
||||||
|
else:
|
||||||
|
print(msg % args)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.finish()
|
||||||
|
|
||||||
|
def finish(self, msg=None):
|
||||||
|
"""Add a final message; flush the message list if no parent profiler.
|
||||||
|
"""
|
||||||
|
if self._finished or self.disable:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._finished = True
|
||||||
|
if msg is not None:
|
||||||
|
self(msg)
|
||||||
|
|
||||||
|
tot_ms = (perf_counter() - self._firstTime) * 1000
|
||||||
|
self._newMsg(
|
||||||
|
"< Exiting %s, total time: %0.4f ms",
|
||||||
|
self._name,
|
||||||
|
tot_ms,
|
||||||
|
)
|
||||||
|
|
||||||
|
if tot_ms < self._mt:
|
||||||
|
# print(f'{tot_ms} < {self._mt}, clearing')
|
||||||
|
# NOTE: this list **must** be an instance var to avoid
|
||||||
|
# deleting common messages during GC I think?
|
||||||
|
self._msgs.clear()
|
||||||
|
# else:
|
||||||
|
# print(f'{tot_ms} > {self._mt}, not clearing')
|
||||||
|
|
||||||
|
# XXX: why is this needed?
|
||||||
|
# don't we **want to show** nested profiler messages?
|
||||||
|
if self._msgs: # and self._depth < 1:
|
||||||
|
|
||||||
|
# if self._msgs:
|
||||||
|
print("\n".join([m[0] % m[1] for m in self._msgs]))
|
||||||
|
|
||||||
|
# clear all entries
|
||||||
|
self._msgs.clear()
|
||||||
|
# type(self)._msgs = []
|
||||||
|
|
||||||
|
type(self)._depth -= 1
|
||||||
|
|
|
@ -56,6 +56,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
from .feed import maybe_open_feed
|
from .feed import maybe_open_feed
|
||||||
from ..log import get_logger, get_console_log
|
from ..log import get_logger, get_console_log
|
||||||
|
from .._profile import Profiler
|
||||||
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
@ -645,7 +646,7 @@ async def tsdb_history_update(
|
||||||
# * the original data feed arch blurb:
|
# * the original data feed arch blurb:
|
||||||
# - https://github.com/pikers/piker/issues/98
|
# - https://github.com/pikers/piker/issues/98
|
||||||
#
|
#
|
||||||
profiler = pg.debug.Profiler(
|
profiler = Profiler(
|
||||||
disabled=False, # not pg_profile_enabled(),
|
disabled=False, # not pg_profile_enabled(),
|
||||||
delayed=False,
|
delayed=False,
|
||||||
)
|
)
|
||||||
|
|
|
@ -44,6 +44,7 @@ from ._api import (
|
||||||
_load_builtins,
|
_load_builtins,
|
||||||
_Token,
|
_Token,
|
||||||
)
|
)
|
||||||
|
from .._profile import Profiler
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ async def fsp_compute(
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
profiler = pg.debug.Profiler(
|
profiler = Profiler(
|
||||||
delayed=False,
|
delayed=False,
|
||||||
disabled=True
|
disabled=True
|
||||||
)
|
)
|
||||||
|
@ -262,7 +263,7 @@ async def cascade(
|
||||||
destination shm array buffer.
|
destination shm array buffer.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
profiler = pg.debug.Profiler(
|
profiler = Profiler(
|
||||||
delayed=False,
|
delayed=False,
|
||||||
disabled=False
|
disabled=False
|
||||||
)
|
)
|
||||||
|
|
|
@ -111,7 +111,8 @@ class LevelMarker(QGraphicsPathItem):
|
||||||
|
|
||||||
# get polygon and scale
|
# get polygon and scale
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.scale(size, size)
|
# self.setScale(size, size)
|
||||||
|
self.setScale(size)
|
||||||
|
|
||||||
# interally generates path
|
# interally generates path
|
||||||
self._style = None
|
self._style = None
|
||||||
|
|
|
@ -78,6 +78,8 @@ async def _async_main(
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from . import _display
|
from . import _display
|
||||||
|
from ._pg_overrides import _do_overrides
|
||||||
|
_do_overrides()
|
||||||
|
|
||||||
godwidget = main_widget
|
godwidget = main_widget
|
||||||
|
|
||||||
|
|
|
@ -39,12 +39,17 @@ class Axis(pg.AxisItem):
|
||||||
'''
|
'''
|
||||||
A better axis that sizes tick contents considering font size.
|
A better axis that sizes tick contents considering font size.
|
||||||
|
|
||||||
|
Also includes tick values lru caching originally proposed in but never
|
||||||
|
accepted upstream:
|
||||||
|
https://github.com/pyqtgraph/pyqtgraph/pull/2160
|
||||||
|
|
||||||
'''
|
'''
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
linkedsplits,
|
linkedsplits,
|
||||||
typical_max_str: str = '100 000.000',
|
typical_max_str: str = '100 000.000',
|
||||||
text_color: str = 'bracket',
|
text_color: str = 'bracket',
|
||||||
|
lru_cache_tick_strings: bool = True,
|
||||||
**kwargs
|
**kwargs
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -91,6 +96,34 @@ class Axis(pg.AxisItem):
|
||||||
# size the pertinent axis dimension to a "typical value"
|
# size the pertinent axis dimension to a "typical value"
|
||||||
self.size_to_values()
|
self.size_to_values()
|
||||||
|
|
||||||
|
# NOTE: requires override ``.tickValues()`` method seen below.
|
||||||
|
if lru_cache_tick_strings:
|
||||||
|
self.tickStrings = lru_cache(
|
||||||
|
maxsize=2**20
|
||||||
|
)(self.tickStrings)
|
||||||
|
|
||||||
|
# NOTE: only overriden to cast tick values entries into tuples
|
||||||
|
# for use with the lru caching.
|
||||||
|
def tickValues(
|
||||||
|
self,
|
||||||
|
minVal: float,
|
||||||
|
maxVal: float,
|
||||||
|
size: int,
|
||||||
|
|
||||||
|
) -> list[tuple[float, tuple[str]]]:
|
||||||
|
'''
|
||||||
|
Repack tick values into tuples for lru caching.
|
||||||
|
|
||||||
|
'''
|
||||||
|
ticks = []
|
||||||
|
for scalar, values in super().tickValues(minVal, maxVal, size):
|
||||||
|
ticks.append((
|
||||||
|
scalar,
|
||||||
|
tuple(values), # this
|
||||||
|
))
|
||||||
|
|
||||||
|
return ticks
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def text_color(self) -> str:
|
def text_color(self) -> str:
|
||||||
return self._text_color
|
return self._text_color
|
||||||
|
|
|
@ -73,6 +73,8 @@ from .._profile import pg_profile_enabled, ms_slower_then
|
||||||
from ._overlay import PlotItemOverlay
|
from ._overlay import PlotItemOverlay
|
||||||
from ._flows import Flow
|
from ._flows import Flow
|
||||||
from ._search import SearchWidget
|
from ._search import SearchWidget
|
||||||
|
from . import _pg_overrides as pgo
|
||||||
|
from .._profile import Profiler
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ._display import DisplayState
|
from ._display import DisplayState
|
||||||
|
@ -831,6 +833,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
|
|
||||||
static_yrange: Optional[tuple[float, float]] = None,
|
static_yrange: Optional[tuple[float, float]] = None,
|
||||||
|
|
||||||
|
parent=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
'''
|
'''
|
||||||
|
@ -848,12 +851,15 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# source of our custom interactions
|
# source of our custom interactions
|
||||||
self.cv = cv = self.mk_vb(name)
|
self.cv = cv = self.mk_vb(name)
|
||||||
|
|
||||||
|
pi = pgo.PlotItem(viewBox=cv, **kwargs)
|
||||||
super().__init__(
|
super().__init__(
|
||||||
background=hcolor(view_color),
|
background=hcolor(view_color),
|
||||||
viewBox=cv,
|
viewBox=cv,
|
||||||
# parent=None,
|
# parent=None,
|
||||||
# plotItem=None,
|
# plotItem=None,
|
||||||
# antialias=True,
|
# antialias=True,
|
||||||
|
parent=parent,
|
||||||
|
plotItem=pi,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
# give viewbox as reference to chart
|
# give viewbox as reference to chart
|
||||||
|
@ -1144,7 +1150,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
axis_side: str = 'right',
|
axis_side: str = 'right',
|
||||||
axis_kwargs: dict = {},
|
axis_kwargs: dict = {},
|
||||||
|
|
||||||
) -> pg.PlotItem:
|
) -> pgo.PlotItem:
|
||||||
|
|
||||||
# Custom viewbox impl
|
# Custom viewbox impl
|
||||||
cv = self.mk_vb(name)
|
cv = self.mk_vb(name)
|
||||||
|
@ -1153,13 +1159,14 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
allowed_sides = {'left', 'right'}
|
allowed_sides = {'left', 'right'}
|
||||||
if axis_side not in allowed_sides:
|
if axis_side not in allowed_sides:
|
||||||
raise ValueError(f'``axis_side``` must be in {allowed_sides}')
|
raise ValueError(f'``axis_side``` must be in {allowed_sides}')
|
||||||
|
|
||||||
yaxis = PriceAxis(
|
yaxis = PriceAxis(
|
||||||
orientation=axis_side,
|
orientation=axis_side,
|
||||||
linkedsplits=self.linked,
|
linkedsplits=self.linked,
|
||||||
**axis_kwargs,
|
**axis_kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
pi = pg.PlotItem(
|
pi = pgo.PlotItem(
|
||||||
parent=self.plotItem,
|
parent=self.plotItem,
|
||||||
name=name,
|
name=name,
|
||||||
enableMenu=False,
|
enableMenu=False,
|
||||||
|
@ -1246,7 +1253,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
|
|
||||||
# TODO: this probably needs its own method?
|
# TODO: this probably needs its own method?
|
||||||
if overlay:
|
if overlay:
|
||||||
if isinstance(overlay, pg.PlotItem):
|
if isinstance(overlay, pgo.PlotItem):
|
||||||
if overlay not in self.pi_overlay.overlays:
|
if overlay not in self.pi_overlay.overlays:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f'{overlay} must be from `.plotitem_overlay()`'
|
f'{overlay} must be from `.plotitem_overlay()`'
|
||||||
|
@ -1405,7 +1412,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
If ``bars_range`` is provided use that range.
|
If ``bars_range`` is provided use that range.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
profiler = pg.debug.Profiler(
|
profiler = Profiler(
|
||||||
msg=f'`{str(self)}.maxmin(name={name})`: `{self.name}`',
|
msg=f'`{str(self)}.maxmin(name={name})`: `{self.name}`',
|
||||||
disabled=not pg_profile_enabled(),
|
disabled=not pg_profile_enabled(),
|
||||||
ms_threshold=ms_slower_then,
|
ms_threshold=ms_slower_then,
|
||||||
|
|
|
@ -44,6 +44,7 @@ from ._style import hcolor
|
||||||
# ds_m4,
|
# ds_m4,
|
||||||
# )
|
# )
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
|
from .._profile import Profiler
|
||||||
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
@ -331,7 +332,7 @@ class Curve(pg.GraphicsObject):
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
profiler = pg.debug.Profiler(
|
profiler = Profiler(
|
||||||
msg=f'Curve.paint(): `{self._name}`',
|
msg=f'Curve.paint(): `{self._name}`',
|
||||||
disabled=not pg_profile_enabled(),
|
disabled=not pg_profile_enabled(),
|
||||||
ms_threshold=ms_slower_then,
|
ms_threshold=ms_slower_then,
|
||||||
|
@ -466,7 +467,7 @@ class StepCurve(Curve):
|
||||||
def sub_paint(
|
def sub_paint(
|
||||||
self,
|
self,
|
||||||
p: QPainter,
|
p: QPainter,
|
||||||
profiler: pg.debug.Profiler,
|
profiler: Profiler,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
# p.drawLines(*tuple(filter(bool, self._last_step_lines)))
|
# p.drawLines(*tuple(filter(bool, self._last_step_lines)))
|
||||||
|
|
|
@ -66,6 +66,7 @@ from .._profile import (
|
||||||
ms_slower_then,
|
ms_slower_then,
|
||||||
)
|
)
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
|
from .._profile import Profiler
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -441,7 +442,7 @@ def graphics_update_cycle(
|
||||||
# TODO: just pass this as a direct ref to avoid so many attr accesses?
|
# TODO: just pass this as a direct ref to avoid so many attr accesses?
|
||||||
hist_chart = ds.godwidget.hist_linked.chart
|
hist_chart = ds.godwidget.hist_linked.chart
|
||||||
|
|
||||||
profiler = pg.debug.Profiler(
|
profiler = Profiler(
|
||||||
msg=f'Graphics loop cycle for: `{chart.name}`',
|
msg=f'Graphics loop cycle for: `{chart.name}`',
|
||||||
delayed=True,
|
delayed=True,
|
||||||
disabled=not pg_profile_enabled(),
|
disabled=not pg_profile_enabled(),
|
||||||
|
|
|
@ -26,7 +26,19 @@ from typing import (
|
||||||
)
|
)
|
||||||
|
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
from pyqtgraph import ViewBox, Point, QtCore, QtGui
|
from pyqtgraph import (
|
||||||
|
ViewBox,
|
||||||
|
Point,
|
||||||
|
QtCore,
|
||||||
|
QtWidgets,
|
||||||
|
)
|
||||||
|
from PyQt5.QtGui import (
|
||||||
|
QColor,
|
||||||
|
)
|
||||||
|
from PyQt5.QtWidgets import (
|
||||||
|
QLabel,
|
||||||
|
)
|
||||||
|
|
||||||
from pyqtgraph import functions as fn
|
from pyqtgraph import functions as fn
|
||||||
from PyQt5.QtCore import QPointF
|
from PyQt5.QtCore import QPointF
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -240,7 +252,7 @@ class LineEditor(Struct):
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
class SelectRect(QtGui.QGraphicsRectItem):
|
class SelectRect(QtWidgets.QGraphicsRectItem):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -249,12 +261,12 @@ class SelectRect(QtGui.QGraphicsRectItem):
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(0, 0, 1, 1)
|
super().__init__(0, 0, 1, 1)
|
||||||
|
|
||||||
# self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1)
|
# self.rbScaleBox = QGraphicsRectItem(0, 0, 1, 1)
|
||||||
self.vb = viewbox
|
self.vb = viewbox
|
||||||
self._chart: 'ChartPlotWidget' = None # noqa
|
self._chart: 'ChartPlotWidget' = None # noqa
|
||||||
|
|
||||||
# override selection box color
|
# override selection box color
|
||||||
color = QtGui.QColor(hcolor(color))
|
color = QColor(hcolor(color))
|
||||||
self.setPen(fn.mkPen(color, width=1))
|
self.setPen(fn.mkPen(color, width=1))
|
||||||
color.setAlpha(66)
|
color.setAlpha(66)
|
||||||
self.setBrush(fn.mkBrush(color))
|
self.setBrush(fn.mkBrush(color))
|
||||||
|
@ -262,7 +274,7 @@ class SelectRect(QtGui.QGraphicsRectItem):
|
||||||
self.hide()
|
self.hide()
|
||||||
self._label = None
|
self._label = None
|
||||||
|
|
||||||
label = self._label = QtGui.QLabel()
|
label = self._label = QLabel()
|
||||||
label.setTextFormat(0) # markdown
|
label.setTextFormat(0) # markdown
|
||||||
label.setFont(_font.font)
|
label.setFont(_font.font)
|
||||||
label.setMargin(0)
|
label.setMargin(0)
|
||||||
|
@ -299,8 +311,8 @@ class SelectRect(QtGui.QGraphicsRectItem):
|
||||||
# TODO: get bg color working
|
# TODO: get bg color working
|
||||||
palette.setColor(
|
palette.setColor(
|
||||||
self._label.backgroundRole(),
|
self._label.backgroundRole(),
|
||||||
# QtGui.QColor(chart.backgroundBrush()),
|
# QColor(chart.backgroundBrush()),
|
||||||
QtGui.QColor(hcolor('papas_special')),
|
QColor(hcolor('papas_special')),
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_on_resize(self, vr, r):
|
def update_on_resize(self, vr, r):
|
||||||
|
@ -348,7 +360,7 @@ class SelectRect(QtGui.QGraphicsRectItem):
|
||||||
|
|
||||||
self.setPos(r.topLeft())
|
self.setPos(r.topLeft())
|
||||||
self.resetTransform()
|
self.resetTransform()
|
||||||
self.scale(r.width(), r.height())
|
self.setRect(r)
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
y1, y2 = start_pos.y(), end_pos.y()
|
y1, y2 = start_pos.y(), end_pos.y()
|
||||||
|
|
|
@ -20,19 +20,24 @@ Trio - Qt integration
|
||||||
Run ``trio`` in guest mode on top of the Qt event loop.
|
Run ``trio`` in guest mode on top of the Qt event loop.
|
||||||
All global Qt runtime settings are mostly defined here.
|
All global Qt runtime settings are mostly defined here.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
from typing import (
|
from typing import (
|
||||||
Callable,
|
Callable,
|
||||||
Any,
|
Any,
|
||||||
Type,
|
Type,
|
||||||
|
TYPE_CHECKING,
|
||||||
)
|
)
|
||||||
import platform
|
import platform
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
# Qt specific
|
# Qt specific
|
||||||
import PyQt5 # noqa
|
import PyQt5 # noqa
|
||||||
from pyqtgraph import QtGui
|
from PyQt5.QtWidgets import (
|
||||||
|
QWidget,
|
||||||
|
QMainWindow,
|
||||||
|
QApplication,
|
||||||
|
)
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
# from PyQt5.QtGui import QLabel, QStatusBar
|
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
pyqtRemoveInputHook,
|
pyqtRemoveInputHook,
|
||||||
Qt,
|
Qt,
|
||||||
|
@ -49,6 +54,7 @@ from ..log import get_logger
|
||||||
from ._pg_overrides import _do_overrides
|
from ._pg_overrides import _do_overrides
|
||||||
from . import _style
|
from . import _style
|
||||||
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
# pyqtgraph global config
|
# pyqtgraph global config
|
||||||
|
@ -76,17 +82,17 @@ if platform.system() == "Windows":
|
||||||
def run_qtractor(
|
def run_qtractor(
|
||||||
func: Callable,
|
func: Callable,
|
||||||
args: tuple,
|
args: tuple,
|
||||||
main_widget_type: Type[QtGui.QWidget],
|
main_widget_type: Type[QWidget],
|
||||||
tractor_kwargs: dict[str, Any] = {},
|
tractor_kwargs: dict[str, Any] = {},
|
||||||
window_type: QtGui.QMainWindow = None,
|
window_type: QMainWindow = None,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
# avoids annoying message when entering debugger from qt loop
|
# avoids annoying message when entering debugger from qt loop
|
||||||
pyqtRemoveInputHook()
|
pyqtRemoveInputHook()
|
||||||
|
|
||||||
app = QtGui.QApplication.instance()
|
app = QApplication.instance()
|
||||||
if app is None:
|
if app is None:
|
||||||
app = PyQt5.QtWidgets.QApplication([])
|
app = QApplication([])
|
||||||
|
|
||||||
# TODO: we might not need this if it's desired
|
# TODO: we might not need this if it's desired
|
||||||
# to cancel the tractor machinery on Qt loop
|
# to cancel the tractor machinery on Qt loop
|
||||||
|
|
|
@ -59,6 +59,7 @@ from ._curve import (
|
||||||
FlattenedOHLC,
|
FlattenedOHLC,
|
||||||
)
|
)
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
|
from .._profile import Profiler
|
||||||
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
@ -130,7 +131,7 @@ def render_baritems(
|
||||||
int, int, np.ndarray,
|
int, int, np.ndarray,
|
||||||
int, int, np.ndarray,
|
int, int, np.ndarray,
|
||||||
],
|
],
|
||||||
profiler: pg.debug.Profiler,
|
profiler: Profiler,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -517,7 +518,7 @@ class Flow(msgspec.Struct): # , frozen=True):
|
||||||
render: bool = True,
|
render: bool = True,
|
||||||
array_key: Optional[str] = None,
|
array_key: Optional[str] = None,
|
||||||
|
|
||||||
profiler: Optional[pg.debug.Profiler] = None,
|
profiler: Optional[Profiler] = None,
|
||||||
do_append: bool = True,
|
do_append: bool = True,
|
||||||
|
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
@ -528,7 +529,7 @@ class Flow(msgspec.Struct): # , frozen=True):
|
||||||
render to graphics.
|
render to graphics.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
profiler = pg.debug.Profiler(
|
profiler = Profiler(
|
||||||
msg=f'Flow.update_graphics() for {self.name}',
|
msg=f'Flow.update_graphics() for {self.name}',
|
||||||
disabled=not pg_profile_enabled(),
|
disabled=not pg_profile_enabled(),
|
||||||
ms_threshold=4,
|
ms_threshold=4,
|
||||||
|
@ -948,7 +949,7 @@ class Renderer(msgspec.Struct):
|
||||||
|
|
||||||
new_read,
|
new_read,
|
||||||
array_key: str,
|
array_key: str,
|
||||||
profiler: pg.debug.Profiler,
|
profiler: Profiler,
|
||||||
uppx: float = 1,
|
uppx: float = 1,
|
||||||
|
|
||||||
# redraw and ds flags
|
# redraw and ds flags
|
||||||
|
|
|
@ -59,6 +59,7 @@ from ..fsp._volume import (
|
||||||
flow_rates,
|
flow_rates,
|
||||||
)
|
)
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
|
from .._profile import Profiler
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -190,7 +191,7 @@ async def open_fsp_actor_cluster(
|
||||||
|
|
||||||
from tractor._clustering import open_actor_cluster
|
from tractor._clustering import open_actor_cluster
|
||||||
|
|
||||||
# profiler = pg.debug.Profiler(
|
# profiler = Profiler(
|
||||||
# delayed=False,
|
# delayed=False,
|
||||||
# disabled=False
|
# disabled=False
|
||||||
# )
|
# )
|
||||||
|
@ -212,7 +213,7 @@ async def run_fsp_ui(
|
||||||
target: Fsp,
|
target: Fsp,
|
||||||
conf: dict[str, dict],
|
conf: dict[str, dict],
|
||||||
loglevel: str,
|
loglevel: str,
|
||||||
# profiler: pg.debug.Profiler,
|
# profiler: Profiler,
|
||||||
# _quote_throttle_rate: int = 58,
|
# _quote_throttle_rate: int = 58,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -746,6 +747,8 @@ async def open_vlm_displays(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dvlm_pi.hideAxis('left')
|
||||||
|
dvlm_pi.hideAxis('bottom')
|
||||||
# all to be overlayed curve names
|
# all to be overlayed curve names
|
||||||
fields = [
|
fields = [
|
||||||
'dolla_vlm',
|
'dolla_vlm',
|
||||||
|
@ -878,6 +881,7 @@ async def open_vlm_displays(
|
||||||
# keep both regular and dark vlm in view
|
# keep both regular and dark vlm in view
|
||||||
names=trade_rate_fields,
|
names=trade_rate_fields,
|
||||||
)
|
)
|
||||||
|
tr_pi.hideAxis('bottom')
|
||||||
|
|
||||||
chart_curves(
|
chart_curves(
|
||||||
trade_rate_fields,
|
trade_rate_fields,
|
||||||
|
@ -951,7 +955,7 @@ async def start_fsp_displays(
|
||||||
# },
|
# },
|
||||||
# },
|
# },
|
||||||
}
|
}
|
||||||
profiler = pg.debug.Profiler(
|
profiler = Profiler(
|
||||||
delayed=False,
|
delayed=False,
|
||||||
disabled=False
|
disabled=False
|
||||||
)
|
)
|
||||||
|
|
|
@ -33,6 +33,7 @@ import numpy as np
|
||||||
import trio
|
import trio
|
||||||
|
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
|
from .._profile import Profiler
|
||||||
from .._profile import pg_profile_enabled, ms_slower_then
|
from .._profile import pg_profile_enabled, ms_slower_then
|
||||||
# from ._style import _min_points_to_show
|
# from ._style import _min_points_to_show
|
||||||
from ._editors import SelectRect
|
from ._editors import SelectRect
|
||||||
|
@ -779,7 +780,7 @@ class ChartView(ViewBox):
|
||||||
'''
|
'''
|
||||||
name = self.name
|
name = self.name
|
||||||
# print(f'YRANGE ON {name}')
|
# print(f'YRANGE ON {name}')
|
||||||
profiler = pg.debug.Profiler(
|
profiler = Profiler(
|
||||||
msg=f'`ChartView._set_yrange()`: `{name}`',
|
msg=f'`ChartView._set_yrange()`: `{name}`',
|
||||||
disabled=not pg_profile_enabled(),
|
disabled=not pg_profile_enabled(),
|
||||||
ms_threshold=ms_slower_then,
|
ms_threshold=ms_slower_then,
|
||||||
|
@ -916,7 +917,7 @@ class ChartView(ViewBox):
|
||||||
autoscale_overlays: bool = True,
|
autoscale_overlays: bool = True,
|
||||||
):
|
):
|
||||||
|
|
||||||
profiler = pg.debug.Profiler(
|
profiler = Profiler(
|
||||||
msg=f'ChartView.maybe_downsample_graphics() for {self.name}',
|
msg=f'ChartView.maybe_downsample_graphics() for {self.name}',
|
||||||
disabled=not pg_profile_enabled(),
|
disabled=not pg_profile_enabled(),
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ from PyQt5.QtGui import QPainterPath
|
||||||
from .._profile import pg_profile_enabled, ms_slower_then
|
from .._profile import pg_profile_enabled, ms_slower_then
|
||||||
from ._style import hcolor
|
from ._style import hcolor
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
|
from .._profile import Profiler
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ._chart import LinkedSplits
|
from ._chart import LinkedSplits
|
||||||
|
@ -170,7 +171,7 @@ class BarItems(pg.GraphicsObject):
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
profiler = pg.debug.Profiler(
|
profiler = Profiler(
|
||||||
disabled=not pg_profile_enabled(),
|
disabled=not pg_profile_enabled(),
|
||||||
ms_threshold=ms_slower_then,
|
ms_threshold=ms_slower_then,
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,11 +15,15 @@
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Customization of ``pyqtgraph`` core routines to speed up our use mostly
|
Customization of ``pyqtgraph`` core routines and various types normally
|
||||||
based on not requiring "scentific precision" for pixel perfect view
|
for speedups.
|
||||||
transforms.
|
|
||||||
|
Generally, our does not require "scentific precision" for pixel perfect
|
||||||
|
view transforms.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,3 +50,211 @@ def _do_overrides() -> None:
|
||||||
"""
|
"""
|
||||||
# we don't care about potential fp issues inside Qt
|
# we don't care about potential fp issues inside Qt
|
||||||
pg.functions.invertQTransform = invertQTransform
|
pg.functions.invertQTransform = invertQTransform
|
||||||
|
pg.PlotItem = PlotItem
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: the below customized type contains all our changes on a method
|
||||||
|
# by method basis as per the diff:
|
||||||
|
# https://github.com/pyqtgraph/pyqtgraph/commit/8e60bc14234b6bec1369ff4192dbfb82f8682920#diff-a2b5865955d2ba703dbc4c35ff01aa761aa28d2aeaac5e68d24e338bc82fb5b1R500
|
||||||
|
|
||||||
|
class PlotItem(pg.PlotItem):
|
||||||
|
'''
|
||||||
|
Overrides for the core plot object mostly pertaining to overlayed
|
||||||
|
multi-view management as it relates to multi-axis managment.
|
||||||
|
|
||||||
|
'''
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent=None,
|
||||||
|
name=None,
|
||||||
|
labels=None,
|
||||||
|
title=None,
|
||||||
|
viewBox=None,
|
||||||
|
axisItems=None,
|
||||||
|
default_axes=['left', 'bottom'],
|
||||||
|
enableMenu=True,
|
||||||
|
**kargs
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
parent=parent,
|
||||||
|
name=name,
|
||||||
|
labels=labels,
|
||||||
|
title=title,
|
||||||
|
viewBox=viewBox,
|
||||||
|
axisItems=axisItems,
|
||||||
|
# default_axes=default_axes,
|
||||||
|
enableMenu=enableMenu,
|
||||||
|
kargs=kargs,
|
||||||
|
)
|
||||||
|
# self.setAxisItems(
|
||||||
|
# axisItems,
|
||||||
|
# default_axes=default_axes,
|
||||||
|
# )
|
||||||
|
|
||||||
|
# NOTE: this is an entirely new method not in upstream.
|
||||||
|
def removeAxis(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
unlink: bool = True,
|
||||||
|
|
||||||
|
) -> Optional[pg.AxisItem]:
|
||||||
|
"""
|
||||||
|
Remove an axis from the contained axis items
|
||||||
|
by ```name: str```.
|
||||||
|
|
||||||
|
This means the axis graphics object will be removed
|
||||||
|
from the ``.layout: QGraphicsGridLayout`` as well as unlinked
|
||||||
|
from the underlying associated ``ViewBox``.
|
||||||
|
|
||||||
|
If the ``unlink: bool`` is set to ``False`` then the axis will
|
||||||
|
stay linked to its view and will only be removed from the
|
||||||
|
layoutonly be removed from the layout.
|
||||||
|
|
||||||
|
If no axis with ``name: str`` is found then this is a noop.
|
||||||
|
|
||||||
|
Return the axis instance that was removed.
|
||||||
|
|
||||||
|
"""
|
||||||
|
entry = self.axes.pop(name, None)
|
||||||
|
|
||||||
|
if not entry:
|
||||||
|
return
|
||||||
|
|
||||||
|
axis = entry['item']
|
||||||
|
self.layout.removeItem(axis)
|
||||||
|
axis.scene().removeItem(axis)
|
||||||
|
if unlink:
|
||||||
|
axis.unlinkFromView()
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
return axis
|
||||||
|
|
||||||
|
# Why do we need to always have all axes created?
|
||||||
|
#
|
||||||
|
# I don't understand this at all.
|
||||||
|
#
|
||||||
|
# Everything seems to work if you just always apply the
|
||||||
|
# set passed to this method **EXCEPT** for some super weird reason
|
||||||
|
# the view box geometry still computes as though the space for the
|
||||||
|
# `'bottom'` axis is always there **UNLESS** you always add that
|
||||||
|
# axis but hide it?
|
||||||
|
#
|
||||||
|
# Why in tf would this be the case!?!?
|
||||||
|
def setAxisItems(
|
||||||
|
self,
|
||||||
|
# XXX: yeah yeah, i know we can't use type annots like this yet.
|
||||||
|
axisItems: Optional[dict[str, pg.AxisItem]] = None,
|
||||||
|
add_to_layout: bool = True,
|
||||||
|
default_axes: list[str] = ['left', 'bottom'],
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Override axis item setting to only
|
||||||
|
|
||||||
|
"""
|
||||||
|
axisItems = axisItems or {}
|
||||||
|
|
||||||
|
# XXX: wth is is this even saying?!?
|
||||||
|
# Array containing visible axis items
|
||||||
|
# Also containing potentially hidden axes, but they are not
|
||||||
|
# touched so it does not matter
|
||||||
|
# visibleAxes = ['left', 'bottom']
|
||||||
|
# Note that it does not matter that this adds
|
||||||
|
# some values to visibleAxes a second time
|
||||||
|
|
||||||
|
# XXX: uhhh wat^ ..?
|
||||||
|
|
||||||
|
visibleAxes = list(default_axes) + list(axisItems.keys())
|
||||||
|
|
||||||
|
# TODO: we should probably invert the loop here to not loop the
|
||||||
|
# predefined "axis name set" and instead loop the `axisItems`
|
||||||
|
# input and lookup indices from a predefined map.
|
||||||
|
for name, pos in (
|
||||||
|
('top', (1, 1)),
|
||||||
|
('bottom', (3, 1)),
|
||||||
|
('left', (2, 0)),
|
||||||
|
('right', (2, 2))
|
||||||
|
):
|
||||||
|
if (
|
||||||
|
name in self.axes and
|
||||||
|
name in axisItems
|
||||||
|
):
|
||||||
|
# we already have an axis entry for this name
|
||||||
|
# so remove the existing entry.
|
||||||
|
self.removeAxis(name)
|
||||||
|
|
||||||
|
# elif name not in axisItems:
|
||||||
|
# # this axis entry is not provided in this call
|
||||||
|
# # so remove any old/existing entry.
|
||||||
|
# self.removeAxis(name)
|
||||||
|
|
||||||
|
# Create new axis
|
||||||
|
if name in axisItems:
|
||||||
|
axis = axisItems[name]
|
||||||
|
if axis.scene() is not None:
|
||||||
|
if (
|
||||||
|
name not in self.axes
|
||||||
|
or axis != self.axes[name]["item"]
|
||||||
|
):
|
||||||
|
raise RuntimeError(
|
||||||
|
"Can't add an axis to multiple plots. Shared axes"
|
||||||
|
" can be achieved with multiple AxisItem instances"
|
||||||
|
" and set[X/Y]Link.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Set up new axis
|
||||||
|
|
||||||
|
# XXX: ok but why do we want to add axes for all entries
|
||||||
|
# if not desired by the user? The only reason I can see
|
||||||
|
# adding this is without it there's some weird
|
||||||
|
# ``ViewBox`` geometry bug.. where a gap for the
|
||||||
|
# 'bottom' axis is somehow left in?
|
||||||
|
axis = pg.AxisItem(orientation=name, parent=self)
|
||||||
|
|
||||||
|
axis.linkToView(self.vb)
|
||||||
|
|
||||||
|
# XXX: shouldn't you already know the ``pos`` from the name?
|
||||||
|
# Oh right instead of a global map that would let you
|
||||||
|
# reasily look that up it's redefined over and over and over
|
||||||
|
# again in methods..
|
||||||
|
self.axes[name] = {'item': axis, 'pos': pos}
|
||||||
|
|
||||||
|
# NOTE: in the overlay case the axis may be added to some
|
||||||
|
# other layout and should not be added here.
|
||||||
|
if add_to_layout:
|
||||||
|
self.layout.addItem(axis, *pos)
|
||||||
|
|
||||||
|
# place axis above images at z=0, items that want to draw
|
||||||
|
# over the axes should be placed at z>=1:
|
||||||
|
axis.setZValue(0.5)
|
||||||
|
axis.setFlag(
|
||||||
|
axis.GraphicsItemFlag.ItemNegativeZStacksBehindParent
|
||||||
|
)
|
||||||
|
if name in visibleAxes:
|
||||||
|
self.showAxis(name, True)
|
||||||
|
else:
|
||||||
|
# why do we need to insert all axes to ``.axes`` and
|
||||||
|
# only hide the ones the user doesn't specify? It all
|
||||||
|
# seems to work fine without doing this except for this
|
||||||
|
# weird gap for the 'bottom' axis that always shows up
|
||||||
|
# in the view box geometry??
|
||||||
|
self.hideAxis(name)
|
||||||
|
|
||||||
|
def updateGrid(
|
||||||
|
self,
|
||||||
|
*args,
|
||||||
|
):
|
||||||
|
alpha = self.ctrl.gridAlphaSlider.value()
|
||||||
|
x = alpha if self.ctrl.xGridCheck.isChecked() else False
|
||||||
|
y = alpha if self.ctrl.yGridCheck.isChecked() else False
|
||||||
|
for name, dim in (
|
||||||
|
('top', x),
|
||||||
|
('bottom', x),
|
||||||
|
('left', y),
|
||||||
|
('right', y)
|
||||||
|
):
|
||||||
|
if name in self.axes:
|
||||||
|
self.getAxis(name).setGrid(dim)
|
||||||
|
# self.getAxis('bottom').setGrid(x)
|
||||||
|
# self.getAxis('left').setGrid(y)
|
||||||
|
# self.getAxis('right').setGrid(y)
|
||||||
|
|
|
@ -28,10 +28,19 @@ from typing import (
|
||||||
)
|
)
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from pyqtgraph import QtGui
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
from PyQt5.QtWidgets import QLabel, QStatusBar
|
from PyQt5.QtWidgets import (
|
||||||
|
QWidget,
|
||||||
|
QMainWindow,
|
||||||
|
QApplication,
|
||||||
|
QLabel,
|
||||||
|
QStatusBar,
|
||||||
|
)
|
||||||
|
|
||||||
|
from PyQt5.QtGui import (
|
||||||
|
QScreen,
|
||||||
|
QCloseEvent,
|
||||||
|
)
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
from ._style import _font_small, hcolor
|
from ._style import _font_small, hcolor
|
||||||
from ._chart import GodWidget
|
from ._chart import GodWidget
|
||||||
|
@ -153,7 +162,7 @@ class MultiStatus:
|
||||||
self.bar.clearMessage()
|
self.bar.clearMessage()
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QtGui.QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
|
|
||||||
# XXX: for tiling wms this should scale
|
# XXX: for tiling wms this should scale
|
||||||
# with the alloted window size.
|
# with the alloted window size.
|
||||||
|
@ -176,12 +185,12 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
self._size: Optional[tuple[int, int]] = None
|
self._size: Optional[tuple[int, int]] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mode_label(self) -> QtGui.QLabel:
|
def mode_label(self) -> QLabel:
|
||||||
|
|
||||||
# init mode label
|
# init mode label
|
||||||
if not self._status_label:
|
if not self._status_label:
|
||||||
|
|
||||||
self._status_label = label = QtGui.QLabel()
|
self._status_label = label = QLabel()
|
||||||
label.setStyleSheet(
|
label.setStyleSheet(
|
||||||
f"""QLabel {{
|
f"""QLabel {{
|
||||||
color : {hcolor('gunmetal')};
|
color : {hcolor('gunmetal')};
|
||||||
|
@ -203,8 +212,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
|
|
||||||
def closeEvent(
|
def closeEvent(
|
||||||
self,
|
self,
|
||||||
|
event: QCloseEvent,
|
||||||
event: QtGui.QCloseEvent,
|
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
'''Cancel the root actor asap.
|
'''Cancel the root actor asap.
|
||||||
|
@ -244,8 +252,8 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
def on_focus_change(
|
def on_focus_change(
|
||||||
self,
|
self,
|
||||||
|
|
||||||
last: QtGui.QWidget,
|
last: QWidget,
|
||||||
current: QtGui.QWidget,
|
current: QWidget,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
|
@ -256,12 +264,12 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
name = getattr(current, 'mode_name', '')
|
name = getattr(current, 'mode_name', '')
|
||||||
self.set_mode_name(name)
|
self.set_mode_name(name)
|
||||||
|
|
||||||
def current_screen(self) -> QtGui.QScreen:
|
def current_screen(self) -> QScreen:
|
||||||
'''
|
'''
|
||||||
Get a frickin screen (if we can, gawd).
|
Get a frickin screen (if we can, gawd).
|
||||||
|
|
||||||
'''
|
'''
|
||||||
app = QtGui.QApplication.instance()
|
app = QApplication.instance()
|
||||||
|
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
screen = app.screenAt(self.pos())
|
screen = app.screenAt(self.pos())
|
||||||
|
@ -294,7 +302,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
'''
|
'''
|
||||||
# https://stackoverflow.com/a/18975846
|
# https://stackoverflow.com/a/18975846
|
||||||
if not size and not self._size:
|
if not size and not self._size:
|
||||||
# app = QtGui.QApplication.instance()
|
# app = QApplication.instance()
|
||||||
geo = self.current_screen().geometry()
|
geo = self.current_screen().geometry()
|
||||||
h, w = geo.height(), geo.width()
|
h, w = geo.height(), geo.width()
|
||||||
# use approx 1/3 of the area of the screen by default
|
# use approx 1/3 of the area of the screen by default
|
||||||
|
@ -331,7 +339,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
|
|
||||||
|
|
||||||
# singleton app per actor
|
# singleton app per actor
|
||||||
_qt_win: QtGui.QMainWindow = None
|
_qt_win: QMainWindow = None
|
||||||
|
|
||||||
|
|
||||||
def main_window() -> MainWindow:
|
def main_window() -> MainWindow:
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
# `pyqtgraph` peeps keep breaking, fixing, improving so might as well
|
# `pyqtgraph` peeps keep breaking, fixing, improving so might as well
|
||||||
# pin this to a dev branch that we have more control over especially
|
# pin this to a dev branch that we have more control over especially
|
||||||
# as more graphics stuff gets hashed out.
|
# as more graphics stuff gets hashed out.
|
||||||
-e git+https://github.com/pikers/pyqtgraph.git@piker_pin#egg=pyqtgraph
|
-e git+https://github.com/pikers/pyqtgraph.git@master#egg=pyqtgraph
|
||||||
|
|
||||||
# our async client for ``marketstore`` (the tsdb)
|
# our async client for ``marketstore`` (the tsdb)
|
||||||
-e git+https://github.com/pikers/anyio-marketstore.git@master#egg=anyio-marketstore
|
-e git+https://github.com/pikers/anyio-marketstore.git@master#egg=anyio-marketstore
|
||||||
|
|
Loading…
Reference in New Issue