Merge pull request #448 from pikers/axis_sticky_api
Axis sticky api, `PlotItem` is the new "chart"kraken_deposits_fixes
commit
11ba706797
|
@ -118,17 +118,10 @@ async def _async_main(
|
||||||
# godwidget.hbox.addWidget(search)
|
# godwidget.hbox.addWidget(search)
|
||||||
godwidget.search = search
|
godwidget.search = search
|
||||||
|
|
||||||
symbols: list[str] = []
|
|
||||||
|
|
||||||
for sym in syms:
|
|
||||||
symbol, _, provider = sym.rpartition('.')
|
|
||||||
symbols.append(symbol)
|
|
||||||
|
|
||||||
# this internally starts a ``display_symbol_data()`` task above
|
# this internally starts a ``display_symbol_data()`` task above
|
||||||
order_mode_ready = await godwidget.load_symbols(
|
order_mode_ready = await godwidget.load_symbols(
|
||||||
provider,
|
fqsns=syms,
|
||||||
symbols,
|
loglevel=loglevel,
|
||||||
loglevel
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# spin up a search engine for the local cached symbol set
|
# spin up a search engine for the local cached symbol set
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
Chart axes graphics and behavior.
|
Chart axes graphics and behavior.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Optional, Callable
|
from typing import Optional, Callable
|
||||||
from math import floor
|
from math import floor
|
||||||
|
@ -27,6 +28,7 @@ import pyqtgraph as pg
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from PyQt5.QtCore import QPointF
|
from PyQt5.QtCore import QPointF
|
||||||
|
|
||||||
|
from . import _pg_overrides as pgo
|
||||||
from ..data._source import float_digits
|
from ..data._source import float_digits
|
||||||
from ._label import Label
|
from ._label import Label
|
||||||
from ._style import DpiAwareFont, hcolor, _font
|
from ._style import DpiAwareFont, hcolor, _font
|
||||||
|
@ -46,7 +48,7 @@ class Axis(pg.AxisItem):
|
||||||
'''
|
'''
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
linkedsplits,
|
plotitem: pgo.PlotItem,
|
||||||
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,
|
lru_cache_tick_strings: bool = True,
|
||||||
|
@ -61,27 +63,32 @@ class Axis(pg.AxisItem):
|
||||||
# XXX: pretty sure this makes things slower
|
# XXX: pretty sure this makes things slower
|
||||||
# self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
|
# self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
|
||||||
|
|
||||||
self.linkedsplits = linkedsplits
|
self.pi = plotitem
|
||||||
self._dpi_font = _font
|
self._dpi_font = _font
|
||||||
|
|
||||||
self.setTickFont(_font.font)
|
self.setTickFont(_font.font)
|
||||||
font_size = self._dpi_font.font.pixelSize()
|
font_size = self._dpi_font.font.pixelSize()
|
||||||
|
|
||||||
|
style_conf = {
|
||||||
|
'textFillLimits': [(0, 0.5)],
|
||||||
|
'tickFont': self._dpi_font.font,
|
||||||
|
|
||||||
|
}
|
||||||
|
text_offset = None
|
||||||
if self.orientation in ('bottom',):
|
if self.orientation in ('bottom',):
|
||||||
text_offset = floor(0.25 * font_size)
|
text_offset = floor(0.25 * font_size)
|
||||||
|
|
||||||
elif self.orientation in ('left', 'right'):
|
elif self.orientation in ('left', 'right'):
|
||||||
text_offset = floor(font_size / 2)
|
text_offset = floor(font_size / 2)
|
||||||
|
|
||||||
self.setStyle(**{
|
if text_offset:
|
||||||
'textFillLimits': [(0, 0.5)],
|
style_conf.update({
|
||||||
'tickFont': self._dpi_font.font,
|
# offset of text *away from* axis line in px
|
||||||
|
# use approx. half the font pixel size (height)
|
||||||
# offset of text *away from* axis line in px
|
'tickTextOffset': text_offset,
|
||||||
# use approx. half the font pixel size (height)
|
})
|
||||||
'tickTextOffset': text_offset,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
self.setStyle(**style_conf)
|
||||||
self.setTickFont(_font.font)
|
self.setTickFont(_font.font)
|
||||||
|
|
||||||
# NOTE: this is for surrounding "border"
|
# NOTE: this is for surrounding "border"
|
||||||
|
@ -102,6 +109,9 @@ class Axis(pg.AxisItem):
|
||||||
maxsize=2**20
|
maxsize=2**20
|
||||||
)(self.tickStrings)
|
)(self.tickStrings)
|
||||||
|
|
||||||
|
# axis "sticky" labels
|
||||||
|
self._stickies: dict[str, YAxisLabel] = {}
|
||||||
|
|
||||||
# NOTE: only overriden to cast tick values entries into tuples
|
# NOTE: only overriden to cast tick values entries into tuples
|
||||||
# for use with the lru caching.
|
# for use with the lru caching.
|
||||||
def tickValues(
|
def tickValues(
|
||||||
|
@ -139,6 +149,40 @@ class Axis(pg.AxisItem):
|
||||||
def txt_offsets(self) -> tuple[int, int]:
|
def txt_offsets(self) -> tuple[int, int]:
|
||||||
return tuple(self.style['tickTextOffset'])
|
return tuple(self.style['tickTextOffset'])
|
||||||
|
|
||||||
|
def add_sticky(
|
||||||
|
self,
|
||||||
|
pi: pgo.PlotItem,
|
||||||
|
name: None | str = None,
|
||||||
|
digits: None | int = 2,
|
||||||
|
# axis_name: str = 'right',
|
||||||
|
bg_color='bracket',
|
||||||
|
|
||||||
|
) -> YAxisLabel:
|
||||||
|
|
||||||
|
# if the sticky is for our symbol
|
||||||
|
# use the tick size precision for display
|
||||||
|
name = name or pi.name
|
||||||
|
digits = digits or 2
|
||||||
|
|
||||||
|
# TODO: ``._ysticks`` should really be an attr on each
|
||||||
|
# ``PlotItem`` no instead of the (containing because of
|
||||||
|
# overlays) widget?
|
||||||
|
|
||||||
|
# add y-axis "last" value label
|
||||||
|
sticky = self._stickies[name] = YAxisLabel(
|
||||||
|
pi=pi,
|
||||||
|
parent=self,
|
||||||
|
# TODO: pass this from symbol data
|
||||||
|
digits=digits,
|
||||||
|
opacity=1,
|
||||||
|
bg_color=bg_color,
|
||||||
|
)
|
||||||
|
|
||||||
|
pi.sigRangeChanged.connect(sticky.update_on_resize)
|
||||||
|
# pi.addItem(sticky)
|
||||||
|
# pi.addItem(last)
|
||||||
|
return sticky
|
||||||
|
|
||||||
|
|
||||||
class PriceAxis(Axis):
|
class PriceAxis(Axis):
|
||||||
|
|
||||||
|
@ -255,7 +299,9 @@ class DynamicDateAxis(Axis):
|
||||||
|
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
|
|
||||||
chart = self.linkedsplits.chart
|
# XX: ARGGGGG AG:LKSKDJF:LKJSDFD
|
||||||
|
chart = self.pi.chart_widget
|
||||||
|
|
||||||
flow = chart._flows[chart.name]
|
flow = chart._flows[chart.name]
|
||||||
shm = flow.shm
|
shm = flow.shm
|
||||||
bars = shm.array
|
bars = shm.array
|
||||||
|
@ -522,7 +568,7 @@ class XAxisLabel(AxisLabel):
|
||||||
|
|
||||||
|
|
||||||
class YAxisLabel(AxisLabel):
|
class YAxisLabel(AxisLabel):
|
||||||
_y_margin = 4
|
_y_margin: int = 4
|
||||||
|
|
||||||
text_flags = (
|
text_flags = (
|
||||||
QtCore.Qt.AlignLeft
|
QtCore.Qt.AlignLeft
|
||||||
|
@ -533,19 +579,19 @@ class YAxisLabel(AxisLabel):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
chart,
|
pi: pgo.PlotItem,
|
||||||
*args,
|
*args,
|
||||||
**kwargs
|
**kwargs
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self._chart = chart
|
self._pi = pi
|
||||||
|
pi.sigRangeChanged.connect(self.update_on_resize)
|
||||||
chart.sigRangeChanged.connect(self.update_on_resize)
|
|
||||||
|
|
||||||
self._last_datum = (None, None)
|
self._last_datum = (None, None)
|
||||||
|
|
||||||
|
self.x_offset = 0
|
||||||
# pull text offset from axis from parent axis
|
# pull text offset from axis from parent axis
|
||||||
if getattr(self._parent, 'txt_offsets', False):
|
if getattr(self._parent, 'txt_offsets', False):
|
||||||
self.x_offset, y_offset = self._parent.txt_offsets()
|
self.x_offset, y_offset = self._parent.txt_offsets()
|
||||||
|
@ -564,7 +610,8 @@ class YAxisLabel(AxisLabel):
|
||||||
value: float, # data for text
|
value: float, # data for text
|
||||||
|
|
||||||
# on odd dimension and/or adds nice black line
|
# on odd dimension and/or adds nice black line
|
||||||
x_offset: Optional[int] = None
|
x_offset: int = 0,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
# this is read inside ``.paint()``
|
# this is read inside ``.paint()``
|
||||||
|
@ -610,7 +657,7 @@ class YAxisLabel(AxisLabel):
|
||||||
self._last_datum = (index, value)
|
self._last_datum = (index, value)
|
||||||
|
|
||||||
self.update_label(
|
self.update_label(
|
||||||
self._chart.mapFromView(QPointF(index, value)),
|
self._pi.mapFromView(QPointF(index, value)),
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,6 @@ import trio
|
||||||
from ._axes import (
|
from ._axes import (
|
||||||
DynamicDateAxis,
|
DynamicDateAxis,
|
||||||
PriceAxis,
|
PriceAxis,
|
||||||
YAxisLabel,
|
|
||||||
)
|
)
|
||||||
from ._cursor import (
|
from ._cursor import (
|
||||||
Cursor,
|
Cursor,
|
||||||
|
@ -168,18 +167,18 @@ class GodWidget(QWidget):
|
||||||
# self.strategy_box = StrategyBoxWidget(self)
|
# self.strategy_box = StrategyBoxWidget(self)
|
||||||
# self.toolbar_layout.addWidget(self.strategy_box)
|
# self.toolbar_layout.addWidget(self.strategy_box)
|
||||||
|
|
||||||
def set_chart_symbol(
|
def set_chart_symbols(
|
||||||
self,
|
self,
|
||||||
symbol_key: str, # of form <fqsn>.<providername>
|
group_key: tuple[str], # of form <fqsn>.<providername>
|
||||||
all_linked: tuple[LinkedSplits, LinkedSplits], # type: ignore
|
all_linked: tuple[LinkedSplits, LinkedSplits], # type: ignore
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
# re-sort org cache symbol list in LIFO order
|
# re-sort org cache symbol list in LIFO order
|
||||||
cache = self._chart_cache
|
cache = self._chart_cache
|
||||||
cache.pop(symbol_key, None)
|
cache.pop(group_key, None)
|
||||||
cache[symbol_key] = all_linked
|
cache[group_key] = all_linked
|
||||||
|
|
||||||
def get_chart_symbol(
|
def get_chart_symbols(
|
||||||
self,
|
self,
|
||||||
symbol_key: str,
|
symbol_key: str,
|
||||||
|
|
||||||
|
@ -188,8 +187,7 @@ class GodWidget(QWidget):
|
||||||
|
|
||||||
async def load_symbols(
|
async def load_symbols(
|
||||||
self,
|
self,
|
||||||
providername: str,
|
fqsns: list[str],
|
||||||
symbol_keys: list[str],
|
|
||||||
loglevel: str,
|
loglevel: str,
|
||||||
reset: bool = False,
|
reset: bool = False,
|
||||||
|
|
||||||
|
@ -200,20 +198,11 @@ class GodWidget(QWidget):
|
||||||
Expects a ``numpy`` structured array containing all the ohlcv fields.
|
Expects a ``numpy`` structured array containing all the ohlcv fields.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
fqsns: list[str] = []
|
|
||||||
|
|
||||||
# our symbol key style is always lower case
|
|
||||||
for key in list(map(str.lower, symbol_keys)):
|
|
||||||
|
|
||||||
# fully qualified symbol name (SNS i guess is what we're making?)
|
|
||||||
fqsn = '.'.join([key, providername])
|
|
||||||
fqsns.append(fqsn)
|
|
||||||
|
|
||||||
# NOTE: for now we use the first symbol in the set as the "key"
|
# NOTE: for now we use the first symbol in the set as the "key"
|
||||||
# for the overlay of feeds on the chart.
|
# for the overlay of feeds on the chart.
|
||||||
group_key = fqsns[0]
|
group_key: tuple[str] = tuple(fqsns)
|
||||||
|
|
||||||
all_linked = self.get_chart_symbol(group_key)
|
all_linked = self.get_chart_symbols(group_key)
|
||||||
order_mode_started = trio.Event()
|
order_mode_started = trio.Event()
|
||||||
|
|
||||||
if not self.vbox.isEmpty():
|
if not self.vbox.isEmpty():
|
||||||
|
@ -245,7 +234,6 @@ class GodWidget(QWidget):
|
||||||
self._root_n.start_soon(
|
self._root_n.start_soon(
|
||||||
display_symbol_data,
|
display_symbol_data,
|
||||||
self,
|
self,
|
||||||
providername,
|
|
||||||
fqsns,
|
fqsns,
|
||||||
loglevel,
|
loglevel,
|
||||||
order_mode_started,
|
order_mode_started,
|
||||||
|
@ -253,8 +241,8 @@ class GodWidget(QWidget):
|
||||||
|
|
||||||
# self.vbox.addWidget(hist_charts)
|
# self.vbox.addWidget(hist_charts)
|
||||||
self.vbox.addWidget(rt_charts)
|
self.vbox.addWidget(rt_charts)
|
||||||
self.set_chart_symbol(
|
self.set_chart_symbols(
|
||||||
fqsn,
|
group_key,
|
||||||
(hist_charts, rt_charts),
|
(hist_charts, rt_charts),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -495,7 +483,10 @@ class LinkedSplits(QWidget):
|
||||||
from . import _display
|
from . import _display
|
||||||
ds = self.display_state
|
ds = self.display_state
|
||||||
if ds:
|
if ds:
|
||||||
return _display.graphics_update_cycle(ds, **kwargs)
|
return _display.graphics_update_cycle(
|
||||||
|
ds,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def symbol(self) -> Symbol:
|
def symbol(self) -> Symbol:
|
||||||
|
@ -548,7 +539,7 @@ class LinkedSplits(QWidget):
|
||||||
shm: ShmArray,
|
shm: ShmArray,
|
||||||
sidepane: FieldsForm,
|
sidepane: FieldsForm,
|
||||||
|
|
||||||
style: str = 'bar',
|
style: str = 'ohlc_bar',
|
||||||
|
|
||||||
) -> ChartPlotWidget:
|
) -> ChartPlotWidget:
|
||||||
'''
|
'''
|
||||||
|
@ -568,12 +559,10 @@ class LinkedSplits(QWidget):
|
||||||
# be no distinction since we will have multiple symbols per
|
# be no distinction since we will have multiple symbols per
|
||||||
# view as part of "aggregate feeds".
|
# view as part of "aggregate feeds".
|
||||||
self.chart = self.add_plot(
|
self.chart = self.add_plot(
|
||||||
|
name=symbol.fqsn,
|
||||||
name=symbol.key,
|
|
||||||
shm=shm,
|
shm=shm,
|
||||||
style=style,
|
style=style,
|
||||||
_is_main=True,
|
_is_main=True,
|
||||||
|
|
||||||
sidepane=sidepane,
|
sidepane=sidepane,
|
||||||
)
|
)
|
||||||
# add crosshair graphic
|
# add crosshair graphic
|
||||||
|
@ -615,12 +604,13 @@ class LinkedSplits(QWidget):
|
||||||
# TODO: we gotta possibly assign this back
|
# TODO: we gotta possibly assign this back
|
||||||
# to the last subplot on removal of some last subplot
|
# to the last subplot on removal of some last subplot
|
||||||
xaxis = DynamicDateAxis(
|
xaxis = DynamicDateAxis(
|
||||||
|
None,
|
||||||
orientation='bottom',
|
orientation='bottom',
|
||||||
linkedsplits=self
|
linkedsplits=self
|
||||||
)
|
)
|
||||||
axes = {
|
axes = {
|
||||||
'right': PriceAxis(linkedsplits=self, orientation='right'),
|
'right': PriceAxis(None, orientation='right'),
|
||||||
'left': PriceAxis(linkedsplits=self, orientation='left'),
|
'left': PriceAxis(None, orientation='left'),
|
||||||
'bottom': xaxis,
|
'bottom': xaxis,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -645,6 +635,11 @@ class LinkedSplits(QWidget):
|
||||||
axisItems=axes,
|
axisItems=axes,
|
||||||
**cpw_kwargs,
|
**cpw_kwargs,
|
||||||
)
|
)
|
||||||
|
# TODO: wow i can't believe how confusing garbage all this axes
|
||||||
|
# stuff iss..
|
||||||
|
for axis in axes.values():
|
||||||
|
axis.pi = cpw.plotItem
|
||||||
|
|
||||||
cpw.hideAxis('left')
|
cpw.hideAxis('left')
|
||||||
cpw.hideAxis('bottom')
|
cpw.hideAxis('bottom')
|
||||||
|
|
||||||
|
@ -707,7 +702,7 @@ class LinkedSplits(QWidget):
|
||||||
anchor_at = ('top', 'left')
|
anchor_at = ('top', 'left')
|
||||||
|
|
||||||
# draw curve graphics
|
# draw curve graphics
|
||||||
if style == 'bar':
|
if style == 'ohlc_bar':
|
||||||
|
|
||||||
graphics, data_key = cpw.draw_ohlc(
|
graphics, data_key = cpw.draw_ohlc(
|
||||||
name,
|
name,
|
||||||
|
@ -744,30 +739,33 @@ class LinkedSplits(QWidget):
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Chart style {style} is currently unsupported")
|
raise ValueError(f"Chart style {style} is currently unsupported")
|
||||||
|
|
||||||
if not _is_main:
|
if _is_main:
|
||||||
|
assert style == 'ohlc_bar', 'main chart must be OHLC'
|
||||||
|
else:
|
||||||
# track by name
|
# track by name
|
||||||
self.subplots[name] = cpw
|
self.subplots[name] = cpw
|
||||||
if qframe is not None:
|
if qframe is not None:
|
||||||
self.splitter.addWidget(qframe)
|
self.splitter.addWidget(qframe)
|
||||||
|
|
||||||
else:
|
|
||||||
assert style == 'bar', 'main chart must be OHLC'
|
|
||||||
|
|
||||||
# add to cross-hair's known plots
|
# add to cross-hair's known plots
|
||||||
# NOTE: add **AFTER** creating the underlying ``PlotItem``s
|
# NOTE: add **AFTER** creating the underlying ``PlotItem``s
|
||||||
# since we require that global (linked charts wide) axes have
|
# since we require that global (linked charts wide) axes have
|
||||||
# been created!
|
# been created!
|
||||||
self.cursor.add_plot(cpw)
|
if self.cursor:
|
||||||
|
if (
|
||||||
|
_is_main
|
||||||
|
or style != 'ohlc_bar'
|
||||||
|
):
|
||||||
|
self.cursor.add_plot(cpw)
|
||||||
|
if style != 'ohlc_bar':
|
||||||
|
self.cursor.add_curve_cursor(cpw, graphics)
|
||||||
|
|
||||||
if self.cursor and style != 'bar':
|
if add_label:
|
||||||
self.cursor.add_curve_cursor(cpw, graphics)
|
self.cursor.contents_labels.add_label(
|
||||||
|
cpw,
|
||||||
if add_label:
|
data_key,
|
||||||
self.cursor.contents_labels.add_label(
|
anchor_at=anchor_at,
|
||||||
cpw,
|
)
|
||||||
data_key,
|
|
||||||
anchor_at=anchor_at,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.resize_sidepanes()
|
self.resize_sidepanes()
|
||||||
return cpw
|
return cpw
|
||||||
|
@ -860,7 +858,12 @@ 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)
|
pi = pgo.PlotItem(
|
||||||
|
viewBox=cv,
|
||||||
|
name=name,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
pi.chart_widget = self
|
||||||
super().__init__(
|
super().__init__(
|
||||||
background=hcolor(view_color),
|
background=hcolor(view_color),
|
||||||
viewBox=cv,
|
viewBox=cv,
|
||||||
|
@ -913,18 +916,20 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
self._on_screen: bool = False
|
self._on_screen: bool = False
|
||||||
|
|
||||||
def resume_all_feeds(self):
|
def resume_all_feeds(self):
|
||||||
try:
|
...
|
||||||
for feed in self._feeds.values():
|
# try:
|
||||||
for flume in feed.flumes.values():
|
# for feed in self._feeds.values():
|
||||||
self.linked.godwidget._root_n.start_soon(feed.resume)
|
# for flume in feed.flumes.values():
|
||||||
except RuntimeError:
|
# self.linked.godwidget._root_n.start_soon(flume.resume)
|
||||||
# TODO: cancel the qtractor runtime here?
|
# except RuntimeError:
|
||||||
raise
|
# # TODO: cancel the qtractor runtime here?
|
||||||
|
# raise
|
||||||
|
|
||||||
def pause_all_feeds(self):
|
def pause_all_feeds(self):
|
||||||
for feed in self._feeds.values():
|
...
|
||||||
for flume in feed.flumes.values():
|
# for feed in self._feeds.values():
|
||||||
self.linked.godwidget._root_n.start_soon(feed.pause)
|
# for flume in feed.flumes.values():
|
||||||
|
# self.linked.godwidget._root_n.start_soon(flume.pause)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def view(self) -> ChartView:
|
def view(self) -> ChartView:
|
||||||
|
@ -1116,43 +1121,6 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
padding=0,
|
padding=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
def draw_ohlc(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
shm: ShmArray,
|
|
||||||
|
|
||||||
array_key: Optional[str] = None,
|
|
||||||
|
|
||||||
) -> (pg.GraphicsObject, str):
|
|
||||||
'''
|
|
||||||
Draw OHLC datums to chart.
|
|
||||||
|
|
||||||
'''
|
|
||||||
graphics = BarItems(
|
|
||||||
self.linked,
|
|
||||||
self.plotItem,
|
|
||||||
pen_color=self.pen_color,
|
|
||||||
name=name,
|
|
||||||
)
|
|
||||||
|
|
||||||
# adds all bar/candle graphics objects for each data point in
|
|
||||||
# the np array buffer to be drawn on next render cycle
|
|
||||||
self.plotItem.addItem(graphics)
|
|
||||||
|
|
||||||
data_key = array_key or name
|
|
||||||
|
|
||||||
self._flows[data_key] = Flow(
|
|
||||||
name=name,
|
|
||||||
plot=self.plotItem,
|
|
||||||
_shm=shm,
|
|
||||||
is_ohlc=True,
|
|
||||||
graphics=graphics,
|
|
||||||
)
|
|
||||||
|
|
||||||
self._add_sticky(name, bg_color='davies')
|
|
||||||
|
|
||||||
return graphics, data_key
|
|
||||||
|
|
||||||
def overlay_plotitem(
|
def overlay_plotitem(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
|
@ -1172,8 +1140,8 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
raise ValueError(f'``axis_side``` must be in {allowed_sides}')
|
raise ValueError(f'``axis_side``` must be in {allowed_sides}')
|
||||||
|
|
||||||
yaxis = PriceAxis(
|
yaxis = PriceAxis(
|
||||||
|
plotitem=None,
|
||||||
orientation=axis_side,
|
orientation=axis_side,
|
||||||
linkedsplits=self.linked,
|
|
||||||
**axis_kwargs,
|
**axis_kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1188,6 +1156,9 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
},
|
},
|
||||||
default_axes=[],
|
default_axes=[],
|
||||||
)
|
)
|
||||||
|
# pi.vb.background.setOpacity(0)
|
||||||
|
yaxis.pi = pi
|
||||||
|
pi.chart_widget = self
|
||||||
pi.hideButtons()
|
pi.hideButtons()
|
||||||
|
|
||||||
# compose this new plot's graphics with the current chart's
|
# compose this new plot's graphics with the current chart's
|
||||||
|
@ -1231,43 +1202,56 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
add_label: bool = True,
|
add_label: bool = True,
|
||||||
pi: Optional[pg.PlotItem] = None,
|
pi: Optional[pg.PlotItem] = None,
|
||||||
step_mode: bool = False,
|
step_mode: bool = False,
|
||||||
|
is_ohlc: bool = False,
|
||||||
|
add_sticky: None | str = 'right',
|
||||||
|
|
||||||
**pdi_kwargs,
|
**graphics_kwargs,
|
||||||
|
|
||||||
) -> (pg.PlotDataItem, str):
|
) -> tuple[
|
||||||
|
pg.GraphicsObject,
|
||||||
|
str,
|
||||||
|
]:
|
||||||
'''
|
'''
|
||||||
Draw a "curve" (line plot graphics) for the provided data in
|
Draw a "curve" (line plot graphics) for the provided data in
|
||||||
the input shm array ``shm``.
|
the input shm array ``shm``.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
color = color or self.pen_color or 'default_light'
|
color = color or self.pen_color or 'default_light'
|
||||||
pdi_kwargs.update({
|
|
||||||
'color': color
|
|
||||||
})
|
|
||||||
|
|
||||||
data_key = array_key or name
|
data_key = array_key or name
|
||||||
|
|
||||||
curve_type = {
|
|
||||||
None: Curve,
|
|
||||||
'step': StepCurve,
|
|
||||||
# TODO:
|
|
||||||
# 'bars': BarsItems
|
|
||||||
}['step' if step_mode else None]
|
|
||||||
|
|
||||||
curve = curve_type(
|
|
||||||
name=name,
|
|
||||||
**pdi_kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
pi = pi or self.plotItem
|
pi = pi or self.plotItem
|
||||||
|
|
||||||
|
if is_ohlc:
|
||||||
|
graphics = BarItems(
|
||||||
|
linked=self.linked,
|
||||||
|
plotitem=pi,
|
||||||
|
# pen_color=self.pen_color,
|
||||||
|
color=color,
|
||||||
|
name=name,
|
||||||
|
**graphics_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
curve_type = {
|
||||||
|
None: Curve,
|
||||||
|
'step': StepCurve,
|
||||||
|
# TODO:
|
||||||
|
# 'bars': BarsItems
|
||||||
|
}['step' if step_mode else None]
|
||||||
|
|
||||||
|
graphics = curve_type(
|
||||||
|
name=name,
|
||||||
|
color=color,
|
||||||
|
**graphics_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
self._flows[data_key] = Flow(
|
self._flows[data_key] = Flow(
|
||||||
name=name,
|
name=name,
|
||||||
plot=pi,
|
plot=pi,
|
||||||
_shm=shm,
|
_shm=shm,
|
||||||
is_ohlc=False,
|
is_ohlc=is_ohlc,
|
||||||
# register curve graphics with this flow
|
# register curve graphics with this flow
|
||||||
graphics=curve,
|
graphics=graphics,
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: this probably needs its own method?
|
# TODO: this probably needs its own method?
|
||||||
|
@ -1278,12 +1262,41 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
f'{overlay} must be from `.plotitem_overlay()`'
|
f'{overlay} must be from `.plotitem_overlay()`'
|
||||||
)
|
)
|
||||||
pi = overlay
|
pi = overlay
|
||||||
else:
|
|
||||||
# anchor_at = ('top', 'left')
|
|
||||||
|
|
||||||
# TODO: something instead of stickies for overlays
|
if add_sticky:
|
||||||
# (we need something that avoids clutter on x-axis).
|
axis = pi.getAxis(add_sticky)
|
||||||
self._add_sticky(name, bg_color=color)
|
if pi.name not in axis._stickies:
|
||||||
|
|
||||||
|
if pi is not self.plotItem:
|
||||||
|
overlay = self.pi_overlay
|
||||||
|
assert pi in overlay.overlays
|
||||||
|
overlay_axis = overlay.get_axis(
|
||||||
|
pi,
|
||||||
|
add_sticky,
|
||||||
|
)
|
||||||
|
assert overlay_axis is axis
|
||||||
|
|
||||||
|
# TODO: UGH! just make this not here! we should
|
||||||
|
# be making the sticky from code which has access
|
||||||
|
# to the ``Symbol`` instance..
|
||||||
|
|
||||||
|
# if the sticky is for our symbol
|
||||||
|
# use the tick size precision for display
|
||||||
|
name = name or pi.name
|
||||||
|
sym = self.linked.symbol
|
||||||
|
digits = None
|
||||||
|
if name == sym.key:
|
||||||
|
digits = sym.tick_size_digits
|
||||||
|
|
||||||
|
# anchor_at = ('top', 'left')
|
||||||
|
|
||||||
|
# TODO: something instead of stickies for overlays
|
||||||
|
# (we need something that avoids clutter on x-axis).
|
||||||
|
axis.add_sticky(
|
||||||
|
pi=pi,
|
||||||
|
bg_color=color,
|
||||||
|
digits=digits,
|
||||||
|
)
|
||||||
|
|
||||||
# NOTE: this is more or less the RENDER call that tells Qt to
|
# NOTE: this is more or less the RENDER call that tells Qt to
|
||||||
# start showing the generated graphics-curves. This is kind of
|
# start showing the generated graphics-curves. This is kind of
|
||||||
|
@ -1294,38 +1307,30 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# the next render cycle; just note a lot of the real-time
|
# the next render cycle; just note a lot of the real-time
|
||||||
# updates are implicit and require a bit of digging to
|
# updates are implicit and require a bit of digging to
|
||||||
# understand.
|
# understand.
|
||||||
pi.addItem(curve)
|
pi.addItem(graphics)
|
||||||
|
|
||||||
return curve, data_key
|
return graphics, data_key
|
||||||
|
|
||||||
# TODO: make this a ctx mngr
|
def draw_ohlc(
|
||||||
def _add_sticky(
|
|
||||||
self,
|
self,
|
||||||
|
|
||||||
name: str,
|
name: str,
|
||||||
bg_color='bracket',
|
shm: ShmArray,
|
||||||
|
|
||||||
) -> YAxisLabel:
|
array_key: Optional[str] = None,
|
||||||
|
**draw_curve_kwargs,
|
||||||
|
|
||||||
# if the sticky is for our symbol
|
) -> (pg.GraphicsObject, str):
|
||||||
# use the tick size precision for display
|
'''
|
||||||
sym = self.linked.symbol
|
Draw OHLC datums to chart.
|
||||||
if name == sym.key:
|
|
||||||
digits = sym.tick_size_digits
|
|
||||||
else:
|
|
||||||
digits = 2
|
|
||||||
|
|
||||||
# add y-axis "last" value label
|
'''
|
||||||
last = self._ysticks[name] = YAxisLabel(
|
return self.draw_curve(
|
||||||
chart=self,
|
name=name,
|
||||||
# parent=self.getAxis('right'),
|
shm=shm,
|
||||||
parent=self.pi_overlay.get_axis(self.plotItem, 'right'),
|
array_key=array_key,
|
||||||
# TODO: pass this from symbol data
|
is_ohlc=True,
|
||||||
digits=digits,
|
**draw_curve_kwargs,
|
||||||
opacity=1,
|
|
||||||
bg_color=bg_color,
|
|
||||||
)
|
)
|
||||||
return last
|
|
||||||
|
|
||||||
def update_graphics_from_flow(
|
def update_graphics_from_flow(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -418,7 +418,7 @@ class Cursor(pg.GraphicsObject):
|
||||||
hl.hide()
|
hl.hide()
|
||||||
|
|
||||||
yl = YAxisLabel(
|
yl = YAxisLabel(
|
||||||
chart=plot,
|
pi=plot.plotItem,
|
||||||
# parent=plot.getAxis('right'),
|
# parent=plot.getAxis('right'),
|
||||||
parent=plot.pi_overlay.get_axis(plot.plotItem, 'right'),
|
parent=plot.pi_overlay.get_axis(plot.plotItem, 'right'),
|
||||||
digits=digits or self.digits,
|
digits=digits or self.digits,
|
||||||
|
|
|
@ -260,12 +260,14 @@ async def graphics_update_loop(
|
||||||
hist_ohlcv = flume.hist_shm
|
hist_ohlcv = flume.hist_shm
|
||||||
|
|
||||||
# update last price sticky
|
# update last price sticky
|
||||||
last_price_sticky = fast_chart._ysticks[fast_chart.name]
|
last_price_sticky = fast_chart.plotItem.getAxis(
|
||||||
|
'right')._stickies.get(fast_chart.name)
|
||||||
last_price_sticky.update_from_data(
|
last_price_sticky.update_from_data(
|
||||||
*ohlcv.array[-1][['index', 'close']]
|
*ohlcv.array[-1][['index', 'close']]
|
||||||
)
|
)
|
||||||
|
|
||||||
hist_last_price_sticky = hist_chart._ysticks[hist_chart.name]
|
hist_last_price_sticky = hist_chart.plotItem.getAxis(
|
||||||
|
'right')._stickies.get(hist_chart.name)
|
||||||
hist_last_price_sticky.update_from_data(
|
hist_last_price_sticky.update_from_data(
|
||||||
*hist_ohlcv.array[-1][['index', 'close']]
|
*hist_ohlcv.array[-1][['index', 'close']]
|
||||||
)
|
)
|
||||||
|
@ -289,7 +291,7 @@ async def graphics_update_loop(
|
||||||
symbol = fast_chart.linked.symbol
|
symbol = fast_chart.linked.symbol
|
||||||
|
|
||||||
l1 = L1Labels(
|
l1 = L1Labels(
|
||||||
fast_chart,
|
fast_chart.plotItem,
|
||||||
# determine precision/decimal lengths
|
# determine precision/decimal lengths
|
||||||
digits=symbol.tick_size_digits,
|
digits=symbol.tick_size_digits,
|
||||||
size_digits=symbol.lot_size_digits,
|
size_digits=symbol.lot_size_digits,
|
||||||
|
@ -333,7 +335,8 @@ async def graphics_update_loop(
|
||||||
})
|
})
|
||||||
|
|
||||||
if vlm_chart:
|
if vlm_chart:
|
||||||
vlm_sticky = vlm_chart._ysticks['volume']
|
vlm_sticky = vlm_chart.plotItem.getAxis(
|
||||||
|
'right')._stickies.get('volume')
|
||||||
ds.vlm_chart = vlm_chart
|
ds.vlm_chart = vlm_chart
|
||||||
ds.vlm_sticky = vlm_sticky
|
ds.vlm_sticky = vlm_sticky
|
||||||
|
|
||||||
|
@ -947,7 +950,6 @@ async def link_views_with_region(
|
||||||
|
|
||||||
async def display_symbol_data(
|
async def display_symbol_data(
|
||||||
godwidget: GodWidget,
|
godwidget: GodWidget,
|
||||||
provider: str,
|
|
||||||
fqsns: list[str],
|
fqsns: list[str],
|
||||||
loglevel: str,
|
loglevel: str,
|
||||||
order_mode_started: trio.Event,
|
order_mode_started: trio.Event,
|
||||||
|
@ -999,6 +1001,7 @@ async def display_symbol_data(
|
||||||
|
|
||||||
symbol = flume.symbol
|
symbol = flume.symbol
|
||||||
fqsn = symbol.fqsn
|
fqsn = symbol.fqsn
|
||||||
|
brokername = symbol.brokers[0]
|
||||||
|
|
||||||
step_size_s = 1
|
step_size_s = 1
|
||||||
tf_key = tf_in_1s[step_size_s]
|
tf_key = tf_in_1s[step_size_s]
|
||||||
|
@ -1082,7 +1085,7 @@ async def display_symbol_data(
|
||||||
|
|
||||||
# if available load volume related built-in display(s)
|
# if available load volume related built-in display(s)
|
||||||
if (
|
if (
|
||||||
not symbol.broker_info[provider].get('no_vlm', False)
|
not symbol.broker_info[brokername].get('no_vlm', False)
|
||||||
and has_vlm(ohlcv)
|
and has_vlm(ohlcv)
|
||||||
):
|
):
|
||||||
vlm_chart = await ln.start(
|
vlm_chart = await ln.start(
|
||||||
|
|
|
@ -110,7 +110,8 @@ def update_fsp_chart(
|
||||||
# sub-charts reference it under different 'named charts'.
|
# sub-charts reference it under different 'named charts'.
|
||||||
|
|
||||||
# read from last calculated value and update any label
|
# read from last calculated value and update any label
|
||||||
last_val_sticky = chart._ysticks.get(graphics_name)
|
last_val_sticky = chart.plotItem.getAxis(
|
||||||
|
'right')._stickies.get(chart.name)
|
||||||
if last_val_sticky:
|
if last_val_sticky:
|
||||||
last = last_row[array_key]
|
last = last_row[array_key]
|
||||||
last_val_sticky.update_from_data(-1, last)
|
last_val_sticky.update_from_data(-1, last)
|
||||||
|
@ -685,7 +686,8 @@ async def open_vlm_displays(
|
||||||
assert chart.name != linked.chart.name
|
assert chart.name != linked.chart.name
|
||||||
|
|
||||||
# sticky only on sub-charts atm
|
# sticky only on sub-charts atm
|
||||||
last_val_sticky = chart._ysticks[chart.name]
|
last_val_sticky = chart.plotItem.getAxis(
|
||||||
|
'right')._stickies.get(chart.name)
|
||||||
|
|
||||||
# read from last calculated value
|
# read from last calculated value
|
||||||
value = shm.array['volume'][-1]
|
value = shm.array['volume'][-1]
|
||||||
|
|
|
@ -26,6 +26,7 @@ from PyQt5.QtCore import QPointF
|
||||||
|
|
||||||
from ._axes import YAxisLabel
|
from ._axes import YAxisLabel
|
||||||
from ._style import hcolor
|
from ._style import hcolor
|
||||||
|
from ._pg_overrides import PlotItem
|
||||||
|
|
||||||
|
|
||||||
class LevelLabel(YAxisLabel):
|
class LevelLabel(YAxisLabel):
|
||||||
|
@ -132,7 +133,7 @@ class LevelLabel(YAxisLabel):
|
||||||
level = self.fields['level']
|
level = self.fields['level']
|
||||||
|
|
||||||
# map "level" to local coords
|
# map "level" to local coords
|
||||||
abs_xy = self._chart.mapFromView(QPointF(0, level))
|
abs_xy = self._pi.mapFromView(QPointF(0, level))
|
||||||
|
|
||||||
self.update_label(
|
self.update_label(
|
||||||
abs_xy,
|
abs_xy,
|
||||||
|
@ -149,7 +150,7 @@ class LevelLabel(YAxisLabel):
|
||||||
h, w = self.set_label_str(fields)
|
h, w = self.set_label_str(fields)
|
||||||
|
|
||||||
if self._adjust_to_l1:
|
if self._adjust_to_l1:
|
||||||
self._x_offset = self._chart._max_l1_line_len
|
self._x_offset = self._pi.chart_widget._max_l1_line_len
|
||||||
|
|
||||||
self.setPos(QPointF(
|
self.setPos(QPointF(
|
||||||
self._h_shift * (w + self._x_offset),
|
self._h_shift * (w + self._x_offset),
|
||||||
|
@ -236,10 +237,10 @@ class L1Label(LevelLabel):
|
||||||
# Set a global "max L1 label length" so we can
|
# Set a global "max L1 label length" so we can
|
||||||
# look it up on order lines and adjust their
|
# look it up on order lines and adjust their
|
||||||
# labels not to overlap with it.
|
# labels not to overlap with it.
|
||||||
chart = self._chart
|
chart = self._pi.chart_widget
|
||||||
chart._max_l1_line_len: float = max(
|
chart._max_l1_line_len: float = max(
|
||||||
chart._max_l1_line_len,
|
chart._max_l1_line_len,
|
||||||
w
|
w,
|
||||||
)
|
)
|
||||||
|
|
||||||
return h, w
|
return h, w
|
||||||
|
@ -251,17 +252,17 @@ class L1Labels:
|
||||||
"""
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
chart: 'ChartPlotWidget', # noqa
|
plotitem: PlotItem,
|
||||||
digits: int = 2,
|
digits: int = 2,
|
||||||
size_digits: int = 3,
|
size_digits: int = 3,
|
||||||
font_size: str = 'small',
|
font_size: str = 'small',
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
self.chart = chart
|
chart = self.chart = plotitem.chart_widget
|
||||||
|
|
||||||
raxis = chart.getAxis('right')
|
raxis = plotitem.getAxis('right')
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'chart': chart,
|
'chart': plotitem,
|
||||||
'parent': raxis,
|
'parent': raxis,
|
||||||
|
|
||||||
'opacity': 1,
|
'opacity': 1,
|
||||||
|
|
|
@ -98,7 +98,7 @@ class BarItems(pg.GraphicsObject):
|
||||||
self,
|
self,
|
||||||
linked: LinkedSplits,
|
linked: LinkedSplits,
|
||||||
plotitem: 'pg.PlotItem', # noqa
|
plotitem: 'pg.PlotItem', # noqa
|
||||||
pen_color: str = 'bracket',
|
color: str = 'bracket',
|
||||||
last_bar_color: str = 'bracket',
|
last_bar_color: str = 'bracket',
|
||||||
|
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
|
@ -108,8 +108,8 @@ class BarItems(pg.GraphicsObject):
|
||||||
self.linked = linked
|
self.linked = linked
|
||||||
# XXX: for the mega-lulz increasing width here increases draw
|
# XXX: for the mega-lulz increasing width here increases draw
|
||||||
# latency... so probably don't do it until we figure that out.
|
# latency... so probably don't do it until we figure that out.
|
||||||
self._color = pen_color
|
self._color = color
|
||||||
self.bars_pen = pg.mkPen(hcolor(pen_color), width=1)
|
self.bars_pen = pg.mkPen(hcolor(color), width=1)
|
||||||
self.last_bar_pen = pg.mkPen(hcolor(last_bar_color), width=2)
|
self.last_bar_pen = pg.mkPen(hcolor(last_bar_color), width=2)
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ from typing import Optional
|
||||||
|
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
|
|
||||||
|
from ._axes import Axis
|
||||||
|
|
||||||
|
|
||||||
def invertQTransform(tr):
|
def invertQTransform(tr):
|
||||||
"""Return a QTransform that is the inverse of *tr*.
|
"""Return a QTransform that is the inverse of *tr*.
|
||||||
|
@ -62,6 +64,20 @@ class PlotItem(pg.PlotItem):
|
||||||
Overrides for the core plot object mostly pertaining to overlayed
|
Overrides for the core plot object mostly pertaining to overlayed
|
||||||
multi-view management as it relates to multi-axis managment.
|
multi-view management as it relates to multi-axis managment.
|
||||||
|
|
||||||
|
This object is the combination of a ``ViewBox`` and multiple
|
||||||
|
``AxisItem``s and so far we've added additional functionality and
|
||||||
|
APIs for:
|
||||||
|
- removal of axes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
From ``pyqtgraph`` super type docs:
|
||||||
|
- Manage placement of ViewBox, AxisItems, and LabelItems
|
||||||
|
- Create and manage a list of PlotDataItems displayed inside the
|
||||||
|
ViewBox
|
||||||
|
- Implement a context menu with commonly used display and analysis
|
||||||
|
options
|
||||||
|
|
||||||
'''
|
'''
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -86,6 +102,8 @@ class PlotItem(pg.PlotItem):
|
||||||
enableMenu=enableMenu,
|
enableMenu=enableMenu,
|
||||||
kargs=kargs,
|
kargs=kargs,
|
||||||
)
|
)
|
||||||
|
self.name = name
|
||||||
|
self.chart_widget = None
|
||||||
# self.setAxisItems(
|
# self.setAxisItems(
|
||||||
# axisItems,
|
# axisItems,
|
||||||
# default_axes=default_axes,
|
# default_axes=default_axes,
|
||||||
|
@ -209,7 +227,12 @@ class PlotItem(pg.PlotItem):
|
||||||
# adding this is without it there's some weird
|
# adding this is without it there's some weird
|
||||||
# ``ViewBox`` geometry bug.. where a gap for the
|
# ``ViewBox`` geometry bug.. where a gap for the
|
||||||
# 'bottom' axis is somehow left in?
|
# 'bottom' axis is somehow left in?
|
||||||
axis = pg.AxisItem(orientation=name, parent=self)
|
# axis = pg.AxisItem(orientation=name, parent=self)
|
||||||
|
axis = Axis(
|
||||||
|
self,
|
||||||
|
orientation=name,
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
|
|
||||||
axis.linkToView(self.vb)
|
axis.linkToView(self.vb)
|
||||||
|
|
||||||
|
|
|
@ -416,12 +416,26 @@ class CompleterView(QTreeView):
|
||||||
section: str,
|
section: str,
|
||||||
values: Sequence[str],
|
values: Sequence[str],
|
||||||
clear_all: bool = False,
|
clear_all: bool = False,
|
||||||
|
reverse: bool = False,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
Set result-rows for depth = 1 tree section ``section``.
|
Set result-rows for depth = 1 tree section ``section``.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
if (
|
||||||
|
values
|
||||||
|
and not isinstance(values[0], str)
|
||||||
|
):
|
||||||
|
flattened: list[str] = []
|
||||||
|
for val in values:
|
||||||
|
flattened.extend(val)
|
||||||
|
|
||||||
|
values = flattened
|
||||||
|
|
||||||
|
if reverse:
|
||||||
|
values = reversed(values)
|
||||||
|
|
||||||
model = self.model()
|
model = self.model()
|
||||||
if clear_all:
|
if clear_all:
|
||||||
# XXX: rewrite the model from scratch if caller requests it
|
# XXX: rewrite the model from scratch if caller requests it
|
||||||
|
@ -598,22 +612,34 @@ class SearchWidget(QtWidgets.QWidget):
|
||||||
self.show()
|
self.show()
|
||||||
self.bar.focus()
|
self.bar.focus()
|
||||||
|
|
||||||
def show_only_cache_entries(self) -> None:
|
def show_cache_entries(
|
||||||
|
self,
|
||||||
|
only: bool = False,
|
||||||
|
) -> None:
|
||||||
'''
|
'''
|
||||||
Clear the search results view and show only cached (aka recently
|
Clear the search results view and show only cached (aka recently
|
||||||
loaded with active data) feeds in the results section.
|
loaded with active data) feeds in the results section.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
godw = self.godwidget
|
godw = self.godwidget
|
||||||
|
|
||||||
|
# first entry in the cache is the current symbol(s)
|
||||||
|
fqsns = []
|
||||||
|
|
||||||
|
for multi_fqsns in list(godw._chart_cache):
|
||||||
|
fqsns.extend(list(multi_fqsns))
|
||||||
|
|
||||||
self.view.set_section_entries(
|
self.view.set_section_entries(
|
||||||
'cache',
|
'cache',
|
||||||
list(reversed(godw._chart_cache)),
|
list(fqsns),
|
||||||
# remove all other completion results except for cache
|
# remove all other completion results except for cache
|
||||||
clear_all=True,
|
clear_all=only,
|
||||||
|
reverse=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_current_item(self) -> Optional[tuple[str, str]]:
|
def get_current_item(self) -> Optional[tuple[str, str]]:
|
||||||
'''Return the current completer tree selection as
|
'''
|
||||||
|
Return the current completer tree selection as
|
||||||
a tuple ``(parent: str, child: str)`` if valid, else ``None``.
|
a tuple ``(parent: str, child: str)`` if valid, else ``None``.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
@ -663,12 +689,13 @@ class SearchWidget(QtWidgets.QWidget):
|
||||||
provider, symbol = value
|
provider, symbol = value
|
||||||
godw = self.godwidget
|
godw = self.godwidget
|
||||||
|
|
||||||
log.info(f'Requesting symbol: {symbol}.{provider}')
|
fqsn = f'{symbol}.{provider}'
|
||||||
|
log.info(f'Requesting symbol: {fqsn}')
|
||||||
|
|
||||||
|
# assert provider in symbol
|
||||||
await godw.load_symbols(
|
await godw.load_symbols(
|
||||||
provider,
|
fqsns=[fqsn],
|
||||||
[symbol],
|
loglevel='info',
|
||||||
'info',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# fully qualified symbol name (SNS i guess is what we're
|
# fully qualified symbol name (SNS i guess is what we're
|
||||||
|
@ -682,13 +709,13 @@ class SearchWidget(QtWidgets.QWidget):
|
||||||
# Re-order the symbol cache on the chart to display in
|
# Re-order the symbol cache on the chart to display in
|
||||||
# LIFO order. this is normally only done internally by
|
# LIFO order. this is normally only done internally by
|
||||||
# the chart on new symbols being loaded into memory
|
# the chart on new symbols being loaded into memory
|
||||||
godw.set_chart_symbol(
|
godw.set_chart_symbols(
|
||||||
fqsn, (
|
(fqsn,), (
|
||||||
godw.hist_linked,
|
godw.hist_linked,
|
||||||
godw.rt_linked,
|
godw.rt_linked,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.show_only_cache_entries()
|
self.show_cache_entries(only=True)
|
||||||
|
|
||||||
self.bar.focus()
|
self.bar.focus()
|
||||||
return fqsn
|
return fqsn
|
||||||
|
@ -757,9 +784,10 @@ async def pack_matches(
|
||||||
with trio.CancelScope() as cs:
|
with trio.CancelScope() as cs:
|
||||||
task_status.started(cs)
|
task_status.started(cs)
|
||||||
# ensure ^ status is updated
|
# ensure ^ status is updated
|
||||||
results = await search(pattern)
|
results = list(await search(pattern))
|
||||||
|
|
||||||
if provider != 'cache': # XXX: don't cache the cache results xD
|
# XXX: don't cache the cache results xD
|
||||||
|
if provider != 'cache':
|
||||||
matches[(provider, pattern)] = results
|
matches[(provider, pattern)] = results
|
||||||
|
|
||||||
# print(f'results from {provider}: {results}')
|
# print(f'results from {provider}: {results}')
|
||||||
|
@ -806,7 +834,7 @@ async def fill_results(
|
||||||
has_results: defaultdict[str, set[str]] = defaultdict(set)
|
has_results: defaultdict[str, set[str]] = defaultdict(set)
|
||||||
|
|
||||||
# show cached feed list at startup
|
# show cached feed list at startup
|
||||||
search.show_only_cache_entries()
|
search.show_cache_entries()
|
||||||
search.on_resize()
|
search.on_resize()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
@ -860,8 +888,9 @@ async def fill_results(
|
||||||
# it hasn't already been searched with the current
|
# it hasn't already been searched with the current
|
||||||
# input pattern (in which case just look up the old
|
# input pattern (in which case just look up the old
|
||||||
# results).
|
# results).
|
||||||
if (period >= pause) and (
|
if (
|
||||||
provider not in already_has_results
|
period >= pause
|
||||||
|
and provider not in already_has_results
|
||||||
):
|
):
|
||||||
|
|
||||||
# TODO: it may make more sense TO NOT search the
|
# TODO: it may make more sense TO NOT search the
|
||||||
|
@ -869,7 +898,9 @@ async def fill_results(
|
||||||
# cpu-bound.
|
# cpu-bound.
|
||||||
if provider != 'cache':
|
if provider != 'cache':
|
||||||
view.clear_section(
|
view.clear_section(
|
||||||
provider, status_field='-> searchin..')
|
provider,
|
||||||
|
status_field='-> searchin..',
|
||||||
|
)
|
||||||
|
|
||||||
await n.start(
|
await n.start(
|
||||||
pack_matches,
|
pack_matches,
|
||||||
|
@ -890,11 +921,20 @@ async def fill_results(
|
||||||
# re-searching it's ``dict`` since it's easier
|
# re-searching it's ``dict`` since it's easier
|
||||||
# but it also causes it to be slower then cached
|
# but it also causes it to be slower then cached
|
||||||
# results from other providers on occasion.
|
# results from other providers on occasion.
|
||||||
if results and provider != 'cache':
|
if (
|
||||||
view.set_section_entries(
|
results
|
||||||
section=provider,
|
):
|
||||||
values=results,
|
if provider != 'cache':
|
||||||
)
|
view.set_section_entries(
|
||||||
|
section=provider,
|
||||||
|
values=results,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# if provider == 'cache':
|
||||||
|
# for the cache just show what we got
|
||||||
|
# that matches
|
||||||
|
search.show_cache_entries()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
view.clear_section(provider)
|
view.clear_section(provider)
|
||||||
|
|
||||||
|
@ -937,7 +977,7 @@ async def handle_keyboard_input(
|
||||||
)
|
)
|
||||||
|
|
||||||
bar.focus()
|
bar.focus()
|
||||||
search.show_only_cache_entries()
|
search.show_cache_entries()
|
||||||
await trio.sleep(0)
|
await trio.sleep(0)
|
||||||
|
|
||||||
async for kbmsg in recv_chan:
|
async for kbmsg in recv_chan:
|
||||||
|
@ -949,20 +989,21 @@ async def handle_keyboard_input(
|
||||||
if mods == Qt.ControlModifier:
|
if mods == Qt.ControlModifier:
|
||||||
ctl = True
|
ctl = True
|
||||||
|
|
||||||
if key in (Qt.Key_Enter, Qt.Key_Return):
|
if key in (
|
||||||
|
Qt.Key_Enter,
|
||||||
|
Qt.Key_Return
|
||||||
|
):
|
||||||
_search_enabled = False
|
_search_enabled = False
|
||||||
await search.chart_current_item(clear_to_cache=True)
|
await search.chart_current_item(clear_to_cache=True)
|
||||||
search.show_only_cache_entries()
|
search.show_cache_entries(only=True)
|
||||||
view.show_matches()
|
view.show_matches()
|
||||||
search.focus()
|
search.focus()
|
||||||
|
|
||||||
elif not ctl and not bar.text():
|
elif not ctl and not bar.text():
|
||||||
# if nothing in search text show the cache
|
|
||||||
view.set_section_entries(
|
# TODO: really should factor this somewhere..bc
|
||||||
'cache',
|
# we're doin it in another spot as well..
|
||||||
list(reversed(godwidget._chart_cache)),
|
search.show_cache_entries(only=True)
|
||||||
clear_all=True,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# cancel and close
|
# cancel and close
|
||||||
|
@ -1025,6 +1066,8 @@ async def handle_keyboard_input(
|
||||||
if parent_item and parent_item.text() == 'cache':
|
if parent_item and parent_item.text() == 'cache':
|
||||||
await search.chart_current_item(clear_to_cache=False)
|
await search.chart_current_item(clear_to_cache=False)
|
||||||
|
|
||||||
|
# ACTUAL SEARCH BLOCK #
|
||||||
|
# where we fuzzy complete and fill out sections.
|
||||||
elif not ctl:
|
elif not ctl:
|
||||||
# relay to completer task
|
# relay to completer task
|
||||||
_search_enabled = True
|
_search_enabled = True
|
||||||
|
@ -1035,13 +1078,21 @@ async def handle_keyboard_input(
|
||||||
async def search_simple_dict(
|
async def search_simple_dict(
|
||||||
text: str,
|
text: str,
|
||||||
source: dict,
|
source: dict,
|
||||||
|
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
|
||||||
|
tokens = []
|
||||||
|
for key in source:
|
||||||
|
if not isinstance(key, str):
|
||||||
|
tokens.extend(key)
|
||||||
|
else:
|
||||||
|
tokens.append(key)
|
||||||
|
|
||||||
# search routine can be specified as a function such
|
# search routine can be specified as a function such
|
||||||
# as in the case of the current app's local symbol cache
|
# as in the case of the current app's local symbol cache
|
||||||
matches = fuzzy.extractBests(
|
matches = fuzzy.extractBests(
|
||||||
text,
|
text,
|
||||||
source.keys(),
|
tokens,
|
||||||
score_cutoff=90,
|
score_cutoff=90,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue