Merge pull request #448 from pikers/axis_sticky_api

Axis sticky api, `PlotItem` is the new "chart"
kraken_deposits_fixes
goodboy 2023-02-05 15:32:22 -05:00 committed by GitHub
commit 11ba706797
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 347 additions and 222 deletions

View File

@ -118,17 +118,10 @@ async def _async_main(
# godwidget.hbox.addWidget(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
order_mode_ready = await godwidget.load_symbols(
provider,
symbols,
loglevel
fqsns=syms,
loglevel=loglevel,
)
# spin up a search engine for the local cached symbol set

View File

@ -18,6 +18,7 @@
Chart axes graphics and behavior.
"""
from __future__ import annotations
from functools import lru_cache
from typing import Optional, Callable
from math import floor
@ -27,6 +28,7 @@ import pyqtgraph as pg
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QPointF
from . import _pg_overrides as pgo
from ..data._source import float_digits
from ._label import Label
from ._style import DpiAwareFont, hcolor, _font
@ -46,7 +48,7 @@ class Axis(pg.AxisItem):
'''
def __init__(
self,
linkedsplits,
plotitem: pgo.PlotItem,
typical_max_str: str = '100 000.000',
text_color: str = 'bracket',
lru_cache_tick_strings: bool = True,
@ -61,27 +63,32 @@ class Axis(pg.AxisItem):
# XXX: pretty sure this makes things slower
# self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
self.linkedsplits = linkedsplits
self.pi = plotitem
self._dpi_font = _font
self.setTickFont(_font.font)
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',):
text_offset = floor(0.25 * font_size)
elif self.orientation in ('left', 'right'):
text_offset = floor(font_size / 2)
self.setStyle(**{
'textFillLimits': [(0, 0.5)],
'tickFont': self._dpi_font.font,
if text_offset:
style_conf.update({
# offset of text *away from* axis line in px
# use approx. half the font pixel size (height)
'tickTextOffset': text_offset,
})
self.setStyle(**style_conf)
self.setTickFont(_font.font)
# NOTE: this is for surrounding "border"
@ -102,6 +109,9 @@ class Axis(pg.AxisItem):
maxsize=2**20
)(self.tickStrings)
# axis "sticky" labels
self._stickies: dict[str, YAxisLabel] = {}
# NOTE: only overriden to cast tick values entries into tuples
# for use with the lru caching.
def tickValues(
@ -139,6 +149,40 @@ class Axis(pg.AxisItem):
def txt_offsets(self) -> tuple[int, int]:
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):
@ -255,7 +299,9 @@ class DynamicDateAxis(Axis):
) -> list[str]:
chart = self.linkedsplits.chart
# XX: ARGGGGG AG:LKSKDJF:LKJSDFD
chart = self.pi.chart_widget
flow = chart._flows[chart.name]
shm = flow.shm
bars = shm.array
@ -522,7 +568,7 @@ class XAxisLabel(AxisLabel):
class YAxisLabel(AxisLabel):
_y_margin = 4
_y_margin: int = 4
text_flags = (
QtCore.Qt.AlignLeft
@ -533,19 +579,19 @@ class YAxisLabel(AxisLabel):
def __init__(
self,
chart,
pi: pgo.PlotItem,
*args,
**kwargs
) -> None:
super().__init__(*args, **kwargs)
self._chart = chart
chart.sigRangeChanged.connect(self.update_on_resize)
self._pi = pi
pi.sigRangeChanged.connect(self.update_on_resize)
self._last_datum = (None, None)
self.x_offset = 0
# pull text offset from axis from parent axis
if getattr(self._parent, 'txt_offsets', False):
self.x_offset, y_offset = self._parent.txt_offsets()
@ -564,7 +610,8 @@ class YAxisLabel(AxisLabel):
value: float, # data for text
# on odd dimension and/or adds nice black line
x_offset: Optional[int] = None
x_offset: int = 0,
) -> None:
# this is read inside ``.paint()``
@ -610,7 +657,7 @@ class YAxisLabel(AxisLabel):
self._last_datum = (index, value)
self.update_label(
self._chart.mapFromView(QPointF(index, value)),
self._pi.mapFromView(QPointF(index, value)),
value
)

View File

@ -45,7 +45,6 @@ import trio
from ._axes import (
DynamicDateAxis,
PriceAxis,
YAxisLabel,
)
from ._cursor import (
Cursor,
@ -168,18 +167,18 @@ class GodWidget(QWidget):
# self.strategy_box = StrategyBoxWidget(self)
# self.toolbar_layout.addWidget(self.strategy_box)
def set_chart_symbol(
def set_chart_symbols(
self,
symbol_key: str, # of form <fqsn>.<providername>
group_key: tuple[str], # of form <fqsn>.<providername>
all_linked: tuple[LinkedSplits, LinkedSplits], # type: ignore
) -> None:
# re-sort org cache symbol list in LIFO order
cache = self._chart_cache
cache.pop(symbol_key, None)
cache[symbol_key] = all_linked
cache.pop(group_key, None)
cache[group_key] = all_linked
def get_chart_symbol(
def get_chart_symbols(
self,
symbol_key: str,
@ -188,8 +187,7 @@ class GodWidget(QWidget):
async def load_symbols(
self,
providername: str,
symbol_keys: list[str],
fqsns: list[str],
loglevel: str,
reset: bool = False,
@ -200,20 +198,11 @@ class GodWidget(QWidget):
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"
# 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()
if not self.vbox.isEmpty():
@ -245,7 +234,6 @@ class GodWidget(QWidget):
self._root_n.start_soon(
display_symbol_data,
self,
providername,
fqsns,
loglevel,
order_mode_started,
@ -253,8 +241,8 @@ class GodWidget(QWidget):
# self.vbox.addWidget(hist_charts)
self.vbox.addWidget(rt_charts)
self.set_chart_symbol(
fqsn,
self.set_chart_symbols(
group_key,
(hist_charts, rt_charts),
)
@ -495,7 +483,10 @@ class LinkedSplits(QWidget):
from . import _display
ds = self.display_state
if ds:
return _display.graphics_update_cycle(ds, **kwargs)
return _display.graphics_update_cycle(
ds,
**kwargs,
)
@property
def symbol(self) -> Symbol:
@ -548,7 +539,7 @@ class LinkedSplits(QWidget):
shm: ShmArray,
sidepane: FieldsForm,
style: str = 'bar',
style: str = 'ohlc_bar',
) -> ChartPlotWidget:
'''
@ -568,12 +559,10 @@ class LinkedSplits(QWidget):
# be no distinction since we will have multiple symbols per
# view as part of "aggregate feeds".
self.chart = self.add_plot(
name=symbol.key,
name=symbol.fqsn,
shm=shm,
style=style,
_is_main=True,
sidepane=sidepane,
)
# add crosshair graphic
@ -615,12 +604,13 @@ class LinkedSplits(QWidget):
# TODO: we gotta possibly assign this back
# to the last subplot on removal of some last subplot
xaxis = DynamicDateAxis(
None,
orientation='bottom',
linkedsplits=self
)
axes = {
'right': PriceAxis(linkedsplits=self, orientation='right'),
'left': PriceAxis(linkedsplits=self, orientation='left'),
'right': PriceAxis(None, orientation='right'),
'left': PriceAxis(None, orientation='left'),
'bottom': xaxis,
}
@ -645,6 +635,11 @@ class LinkedSplits(QWidget):
axisItems=axes,
**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('bottom')
@ -707,7 +702,7 @@ class LinkedSplits(QWidget):
anchor_at = ('top', 'left')
# draw curve graphics
if style == 'bar':
if style == 'ohlc_bar':
graphics, data_key = cpw.draw_ohlc(
name,
@ -744,22 +739,25 @@ class LinkedSplits(QWidget):
else:
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
self.subplots[name] = cpw
if qframe is not None:
self.splitter.addWidget(qframe)
else:
assert style == 'bar', 'main chart must be OHLC'
# add to cross-hair's known plots
# NOTE: add **AFTER** creating the underlying ``PlotItem``s
# since we require that global (linked charts wide) axes have
# been created!
if self.cursor:
if (
_is_main
or style != 'ohlc_bar'
):
self.cursor.add_plot(cpw)
if self.cursor and style != 'bar':
if style != 'ohlc_bar':
self.cursor.add_curve_cursor(cpw, graphics)
if add_label:
@ -860,7 +858,12 @@ class ChartPlotWidget(pg.PlotWidget):
# source of our custom interactions
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__(
background=hcolor(view_color),
viewBox=cv,
@ -913,18 +916,20 @@ class ChartPlotWidget(pg.PlotWidget):
self._on_screen: bool = False
def resume_all_feeds(self):
try:
for feed in self._feeds.values():
for flume in feed.flumes.values():
self.linked.godwidget._root_n.start_soon(feed.resume)
except RuntimeError:
# TODO: cancel the qtractor runtime here?
raise
...
# try:
# for feed in self._feeds.values():
# for flume in feed.flumes.values():
# self.linked.godwidget._root_n.start_soon(flume.resume)
# except RuntimeError:
# # TODO: cancel the qtractor runtime here?
# raise
def pause_all_feeds(self):
for feed in self._feeds.values():
for flume in feed.flumes.values():
self.linked.godwidget._root_n.start_soon(feed.pause)
...
# for feed in self._feeds.values():
# for flume in feed.flumes.values():
# self.linked.godwidget._root_n.start_soon(flume.pause)
@property
def view(self) -> ChartView:
@ -1116,43 +1121,6 @@ class ChartPlotWidget(pg.PlotWidget):
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(
self,
name: str,
@ -1172,8 +1140,8 @@ class ChartPlotWidget(pg.PlotWidget):
raise ValueError(f'``axis_side``` must be in {allowed_sides}')
yaxis = PriceAxis(
plotitem=None,
orientation=axis_side,
linkedsplits=self.linked,
**axis_kwargs,
)
@ -1188,6 +1156,9 @@ class ChartPlotWidget(pg.PlotWidget):
},
default_axes=[],
)
# pi.vb.background.setOpacity(0)
yaxis.pi = pi
pi.chart_widget = self
pi.hideButtons()
# compose this new plot's graphics with the current chart's
@ -1231,22 +1202,36 @@ class ChartPlotWidget(pg.PlotWidget):
add_label: bool = True,
pi: Optional[pg.PlotItem] = None,
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
the input shm array ``shm``.
'''
color = color or self.pen_color or 'default_light'
pdi_kwargs.update({
'color': color
})
data_key = array_key or name
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,
@ -1254,20 +1239,19 @@ class ChartPlotWidget(pg.PlotWidget):
# 'bars': BarsItems
}['step' if step_mode else None]
curve = curve_type(
graphics = curve_type(
name=name,
**pdi_kwargs,
color=color,
**graphics_kwargs,
)
pi = pi or self.plotItem
self._flows[data_key] = Flow(
name=name,
plot=pi,
_shm=shm,
is_ohlc=False,
is_ohlc=is_ohlc,
# register curve graphics with this flow
graphics=curve,
graphics=graphics,
)
# TODO: this probably needs its own method?
@ -1278,12 +1262,41 @@ class ChartPlotWidget(pg.PlotWidget):
f'{overlay} must be from `.plotitem_overlay()`'
)
pi = overlay
else:
if add_sticky:
axis = pi.getAxis(add_sticky)
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).
self._add_sticky(name, bg_color=color)
axis.add_sticky(
pi=pi,
bg_color=color,
digits=digits,
)
# NOTE: this is more or less the RENDER call that tells Qt to
# 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
# updates are implicit and require a bit of digging to
# understand.
pi.addItem(curve)
pi.addItem(graphics)
return curve, data_key
return graphics, data_key
# TODO: make this a ctx mngr
def _add_sticky(
def draw_ohlc(
self,
name: str,
bg_color='bracket',
shm: ShmArray,
) -> YAxisLabel:
array_key: Optional[str] = None,
**draw_curve_kwargs,
# if the sticky is for our symbol
# use the tick size precision for display
sym = self.linked.symbol
if name == sym.key:
digits = sym.tick_size_digits
else:
digits = 2
) -> (pg.GraphicsObject, str):
'''
Draw OHLC datums to chart.
# add y-axis "last" value label
last = self._ysticks[name] = YAxisLabel(
chart=self,
# parent=self.getAxis('right'),
parent=self.pi_overlay.get_axis(self.plotItem, 'right'),
# TODO: pass this from symbol data
digits=digits,
opacity=1,
bg_color=bg_color,
'''
return self.draw_curve(
name=name,
shm=shm,
array_key=array_key,
is_ohlc=True,
**draw_curve_kwargs,
)
return last
def update_graphics_from_flow(
self,

View File

@ -418,7 +418,7 @@ class Cursor(pg.GraphicsObject):
hl.hide()
yl = YAxisLabel(
chart=plot,
pi=plot.plotItem,
# parent=plot.getAxis('right'),
parent=plot.pi_overlay.get_axis(plot.plotItem, 'right'),
digits=digits or self.digits,

View File

@ -260,12 +260,14 @@ async def graphics_update_loop(
hist_ohlcv = flume.hist_shm
# 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(
*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_ohlcv.array[-1][['index', 'close']]
)
@ -289,7 +291,7 @@ async def graphics_update_loop(
symbol = fast_chart.linked.symbol
l1 = L1Labels(
fast_chart,
fast_chart.plotItem,
# determine precision/decimal lengths
digits=symbol.tick_size_digits,
size_digits=symbol.lot_size_digits,
@ -333,7 +335,8 @@ async def graphics_update_loop(
})
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_sticky = vlm_sticky
@ -947,7 +950,6 @@ async def link_views_with_region(
async def display_symbol_data(
godwidget: GodWidget,
provider: str,
fqsns: list[str],
loglevel: str,
order_mode_started: trio.Event,
@ -999,6 +1001,7 @@ async def display_symbol_data(
symbol = flume.symbol
fqsn = symbol.fqsn
brokername = symbol.brokers[0]
step_size_s = 1
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 (
not symbol.broker_info[provider].get('no_vlm', False)
not symbol.broker_info[brokername].get('no_vlm', False)
and has_vlm(ohlcv)
):
vlm_chart = await ln.start(

View File

@ -110,7 +110,8 @@ def update_fsp_chart(
# sub-charts reference it under different 'named charts'.
# 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:
last = last_row[array_key]
last_val_sticky.update_from_data(-1, last)
@ -685,7 +686,8 @@ async def open_vlm_displays(
assert chart.name != linked.chart.name
# 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
value = shm.array['volume'][-1]

View File

@ -26,6 +26,7 @@ from PyQt5.QtCore import QPointF
from ._axes import YAxisLabel
from ._style import hcolor
from ._pg_overrides import PlotItem
class LevelLabel(YAxisLabel):
@ -132,7 +133,7 @@ class LevelLabel(YAxisLabel):
level = self.fields['level']
# map "level" to local coords
abs_xy = self._chart.mapFromView(QPointF(0, level))
abs_xy = self._pi.mapFromView(QPointF(0, level))
self.update_label(
abs_xy,
@ -149,7 +150,7 @@ class LevelLabel(YAxisLabel):
h, w = self.set_label_str(fields)
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._h_shift * (w + self._x_offset),
@ -236,10 +237,10 @@ class L1Label(LevelLabel):
# Set a global "max L1 label length" so we can
# look it up on order lines and adjust their
# 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,
w
w,
)
return h, w
@ -251,17 +252,17 @@ class L1Labels:
"""
def __init__(
self,
chart: 'ChartPlotWidget', # noqa
plotitem: PlotItem,
digits: int = 2,
size_digits: int = 3,
font_size: str = 'small',
) -> None:
self.chart = chart
chart = self.chart = plotitem.chart_widget
raxis = chart.getAxis('right')
raxis = plotitem.getAxis('right')
kwargs = {
'chart': chart,
'chart': plotitem,
'parent': raxis,
'opacity': 1,

View File

@ -98,7 +98,7 @@ class BarItems(pg.GraphicsObject):
self,
linked: LinkedSplits,
plotitem: 'pg.PlotItem', # noqa
pen_color: str = 'bracket',
color: str = 'bracket',
last_bar_color: str = 'bracket',
name: Optional[str] = None,
@ -108,8 +108,8 @@ class BarItems(pg.GraphicsObject):
self.linked = linked
# XXX: for the mega-lulz increasing width here increases draw
# latency... so probably don't do it until we figure that out.
self._color = pen_color
self.bars_pen = pg.mkPen(hcolor(pen_color), width=1)
self._color = color
self.bars_pen = pg.mkPen(hcolor(color), width=1)
self.last_bar_pen = pg.mkPen(hcolor(last_bar_color), width=2)
self._name = name

View File

@ -26,6 +26,8 @@ from typing import Optional
import pyqtgraph as pg
from ._axes import Axis
def invertQTransform(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
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__(
self,
@ -86,6 +102,8 @@ class PlotItem(pg.PlotItem):
enableMenu=enableMenu,
kargs=kargs,
)
self.name = name
self.chart_widget = None
# self.setAxisItems(
# axisItems,
# default_axes=default_axes,
@ -209,7 +227,12 @@ class PlotItem(pg.PlotItem):
# 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 = pg.AxisItem(orientation=name, parent=self)
axis = Axis(
self,
orientation=name,
parent=self,
)
axis.linkToView(self.vb)

View File

@ -416,12 +416,26 @@ class CompleterView(QTreeView):
section: str,
values: Sequence[str],
clear_all: bool = False,
reverse: bool = False,
) -> None:
'''
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()
if clear_all:
# XXX: rewrite the model from scratch if caller requests it
@ -598,22 +612,34 @@ class SearchWidget(QtWidgets.QWidget):
self.show()
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
loaded with active data) feeds in the results section.
'''
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(
'cache',
list(reversed(godw._chart_cache)),
list(fqsns),
# 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]]:
'''Return the current completer tree selection as
'''
Return the current completer tree selection as
a tuple ``(parent: str, child: str)`` if valid, else ``None``.
'''
@ -663,12 +689,13 @@ class SearchWidget(QtWidgets.QWidget):
provider, symbol = value
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(
provider,
[symbol],
'info',
fqsns=[fqsn],
loglevel='info',
)
# 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
# LIFO order. this is normally only done internally by
# the chart on new symbols being loaded into memory
godw.set_chart_symbol(
fqsn, (
godw.set_chart_symbols(
(fqsn,), (
godw.hist_linked,
godw.rt_linked,
)
)
self.show_only_cache_entries()
self.show_cache_entries(only=True)
self.bar.focus()
return fqsn
@ -757,9 +784,10 @@ async def pack_matches(
with trio.CancelScope() as cs:
task_status.started(cs)
# 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
# print(f'results from {provider}: {results}')
@ -806,7 +834,7 @@ async def fill_results(
has_results: defaultdict[str, set[str]] = defaultdict(set)
# show cached feed list at startup
search.show_only_cache_entries()
search.show_cache_entries()
search.on_resize()
while True:
@ -860,8 +888,9 @@ async def fill_results(
# it hasn't already been searched with the current
# input pattern (in which case just look up the old
# results).
if (period >= pause) and (
provider not in already_has_results
if (
period >= pause
and provider not in already_has_results
):
# TODO: it may make more sense TO NOT search the
@ -869,7 +898,9 @@ async def fill_results(
# cpu-bound.
if provider != 'cache':
view.clear_section(
provider, status_field='-> searchin..')
provider,
status_field='-> searchin..',
)
await n.start(
pack_matches,
@ -890,11 +921,20 @@ async def fill_results(
# re-searching it's ``dict`` since it's easier
# but it also causes it to be slower then cached
# results from other providers on occasion.
if results and provider != 'cache':
if (
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:
view.clear_section(provider)
@ -937,7 +977,7 @@ async def handle_keyboard_input(
)
bar.focus()
search.show_only_cache_entries()
search.show_cache_entries()
await trio.sleep(0)
async for kbmsg in recv_chan:
@ -949,20 +989,21 @@ async def handle_keyboard_input(
if mods == Qt.ControlModifier:
ctl = True
if key in (Qt.Key_Enter, Qt.Key_Return):
if key in (
Qt.Key_Enter,
Qt.Key_Return
):
_search_enabled = False
await search.chart_current_item(clear_to_cache=True)
search.show_only_cache_entries()
search.show_cache_entries(only=True)
view.show_matches()
search.focus()
elif not ctl and not bar.text():
# if nothing in search text show the cache
view.set_section_entries(
'cache',
list(reversed(godwidget._chart_cache)),
clear_all=True,
)
# TODO: really should factor this somewhere..bc
# we're doin it in another spot as well..
search.show_cache_entries(only=True)
continue
# cancel and close
@ -1025,6 +1066,8 @@ async def handle_keyboard_input(
if parent_item and parent_item.text() == 'cache':
await search.chart_current_item(clear_to_cache=False)
# ACTUAL SEARCH BLOCK #
# where we fuzzy complete and fill out sections.
elif not ctl:
# relay to completer task
_search_enabled = True
@ -1035,13 +1078,21 @@ async def handle_keyboard_input(
async def search_simple_dict(
text: str,
source: dict,
) -> 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
# as in the case of the current app's local symbol cache
matches = fuzzy.extractBests(
text,
source.keys(),
tokens,
score_cutoff=90,
)