Merge pull request #259 from pikers/overlayed_dvlm

Overlayed $vlm
py3.10_support
goodboy 2022-01-26 13:47:52 -05:00 committed by GitHub
commit 8fe2bd6614
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 373 additions and 195 deletions

View File

@ -1,5 +1,5 @@
# piker: trading gear for hackers
# Copyright (C) 2018-present Tyler Goodlet (in stewardship of piker0)
# Copyright (C) Tyler Goodlet (in stewardship of pikers)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by

View File

@ -18,21 +18,26 @@
Anchor funtions for UI placement of annotions.
'''
from typing import Callable
from __future__ import annotations
from typing import Callable, TYPE_CHECKING
from PyQt5.QtCore import QPointF
from PyQt5.QtWidgets import QGraphicsPathItem
from ._label import Label
if TYPE_CHECKING:
from ._axes import PriceAxis
from ._chart import ChartPlotWidget
from ._label import Label
def marker_right_points(
chart: 'ChartPlotWidget', # noqa
chart: ChartPlotWidget, # noqa
marker_size: int = 20,
) -> (float, float, float):
'''Return x-dimension, y-axis-aware, level-line marker oriented scene values.
'''
Return x-dimension, y-axis-aware, level-line marker oriented scene
values.
X values correspond to set the end of a level line, end of
a paried level line marker, and the right most side of the "right"
@ -57,16 +62,17 @@ def vbr_left(
label: Label,
) -> Callable[..., float]:
"""Return a closure which gives the scene x-coordinate for the
leftmost point of the containing view box.
'''
Return a closure which gives the scene x-coordinate for the leftmost
point of the containing view box.
"""
'''
return label.vbr().left
def right_axis(
chart: 'ChartPlotWidget', # noqa
chart: ChartPlotWidget, # noqa
label: Label,
side: str = 'left',
@ -141,13 +147,13 @@ def gpath_pin(
return path_br.bottomRight() - QPointF(label.w, label.h / 6)
def pp_tight_and_right(
label: Label
) -> QPointF:
'''Place *just* right of the pp label.
'''
Place *just* right of the pp label.
'''
txt = label.txt
# txt = label.txt
return label.txt.pos() + QPointF(label.w - label.h/3, 0)

View File

@ -18,8 +18,8 @@
Chart axes graphics and behavior.
"""
import functools
from typing import List, Tuple, Optional
from functools import lru_cache
from typing import List, Tuple, Optional, Callable
from math import floor
import pandas as pd
@ -27,8 +27,10 @@ import pyqtgraph as pg
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QPointF
from ._style import DpiAwareFont, hcolor, _font
from ..data._source import float_digits
from ._label import Label
from ._style import DpiAwareFont, hcolor, _font
from ._interaction import ChartView
_axis_pen = pg.mkPen(hcolor('bracket'))
@ -42,7 +44,6 @@ class Axis(pg.AxisItem):
self,
linkedsplits,
typical_max_str: str = '100 000.000',
min_tick: int = 2,
**kwargs
) -> None:
@ -52,7 +53,6 @@ class Axis(pg.AxisItem):
# self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
self.linkedsplits = linkedsplits
self._min_tick = min_tick
self._dpi_font = _font
self.setTickFont(_font.font)
@ -74,7 +74,10 @@ class Axis(pg.AxisItem):
})
self.setTickFont(_font.font)
# NOTE: this is for surrounding "border"
self.setPen(_axis_pen)
# this is the text color
self.setTextPen(_axis_pen)
self.typical_br = _font._qfm.boundingRect(typical_max_str)
# size the pertinent axis dimension to a "typical value"
@ -83,40 +86,102 @@ class Axis(pg.AxisItem):
def size_to_values(self) -> None:
pass
def set_min_tick(self, size: int) -> None:
self._min_tick = size
def txt_offsets(self) -> Tuple[int, int]:
return tuple(self.style['tickTextOffset'])
class PriceAxis(Axis):
def __init__(
self,
*args,
min_tick: int = 2,
title: str = '',
formatter: Optional[Callable[[float], str]] = None,
**kwargs
) -> None:
super().__init__(*args, **kwargs)
self.formatter = formatter
self._min_tick: int = min_tick
self.title = None
def set_title(
self,
title: str,
view: Optional[ChartView] = None
) -> Label:
'''
Set a sane UX label using our built-in ``Label``.
'''
# XXX: built-in labels but they're huge, and placed weird..
# self.setLabel(title)
# self.showLabel()
label = self.title = Label(
view=view or self.linkedView(),
fmt_str=title,
color='bracket',
parent=self,
# update_on_range_change=False,
)
def below_axis() -> QPointF:
return QPointF(
0,
self.size().height(),
)
# XXX: doesn't work? have to pass it above
# label.txt.setParent(self)
label.scene_anchor = below_axis
label.render()
label.show()
label.update()
return label
def set_min_tick(
self,
size: int
) -> None:
self._min_tick = size
def size_to_values(self) -> None:
# self.typical_br = _font._qfm.boundingRect(typical_max_str)
self.setWidth(self.typical_br.width())
# XXX: drop for now since it just eats up h space
def tickStrings(
self,
vals,
scale,
spacing,
):
vals: tuple[float],
scale: float,
spacing: float,
# TODO: figure out how to enforce min tick spacing by passing
# it into the parent type
digits = max(float_digits(spacing * scale), self._min_tick)
) -> list[str]:
# TODO: figure out how to enforce min tick spacing by passing it
# into the parent type
digits = max(
float_digits(spacing * scale),
self._min_tick,
)
if self.title:
self.title.update()
# print(f'vals: {vals}\nscale: {scale}\nspacing: {spacing}')
# print(f'digits: {digits}')
return [
('{value:,.{digits}f}').format(
digits=digits,
value=v,
).replace(',', ' ') for v in vals
]
if not self.formatter:
return [
('{value:,.{digits}f}').format(
digits=digits,
value=v,
).replace(',', ' ') for v in vals
]
else:
return list(map(self.formatter, vals))
class DynamicDateAxis(Axis):
@ -136,6 +201,7 @@ class DynamicDateAxis(Axis):
def _indexes_to_timestrs(
self,
indexes: List[int],
) -> List[str]:
chart = self.linkedsplits.chart
@ -165,9 +231,10 @@ class DynamicDateAxis(Axis):
def tickStrings(
self,
values: tuple[float],
scale,
spacing,
):
scale: float,
spacing: float,
) -> list[str]:
# info = self.tickStrings.cache_info()
# print(info)
return self._indexes_to_timestrs(values)
@ -220,6 +287,8 @@ class AxisLabel(pg.GraphicsObject):
self.path = None
self.rect = None
self._pw = self.pixelWidth()
def paint(
self,
p: QtGui.QPainter,
@ -269,9 +338,10 @@ class AxisLabel(pg.GraphicsObject):
def boundingRect(self): # noqa
"""Size the graphics space from the text contents.
'''
Size the graphics space from the text contents.
"""
'''
if self.label_str:
self._size_br_from_str(self.label_str)
@ -287,23 +357,32 @@ class AxisLabel(pg.GraphicsObject):
return QtCore.QRectF()
# return self.rect or QtCore.QRectF()
# TODO: but the input probably needs to be the "len" of
# the current text value:
@lru_cache
def _size_br_from_str(
self,
value: str
def _size_br_from_str(self, value: str) -> None:
"""Do our best to render the bounding rect to a set margin
) -> tuple[float, float]:
'''
Do our best to render the bounding rect to a set margin
around provided string contents.
"""
'''
# size the filled rect to text and/or parent axis
# if not self._txt_br:
# # XXX: this can't be c
# # XXX: this can't be called until stuff is rendered?
# self._txt_br = self._dpifont.boundingRect(value)
txt_br = self._txt_br = self._dpifont.boundingRect(value)
txt_h, txt_w = txt_br.height(), txt_br.width()
# print(f'wsw: {self._dpifont.boundingRect(" ")}')
# allow subtypes to specify a static width and height
h, w = self.size_hint()
# print(f'axis size: {self._parent.size()}')
# print(f'axis geo: {self._parent.geometry()}')
self.rect = QtCore.QRectF(
0, 0,
@ -314,7 +393,7 @@ class AxisLabel(pg.GraphicsObject):
# hb = self.path.controlPointRect()
# hb_size = hb.size()
return self.rect
return (self.rect.width(), self.rect.height())
# _common_text_flags = (
# QtCore.Qt.TextDontClip |
@ -342,6 +421,7 @@ class XAxisLabel(AxisLabel):
abs_pos: QPointF, # scene coords
value: float, # data for text
offset: int = 0 # if have margins, k?
) -> None:
timestrs = self._parent._indexes_to_timestrs([int(value)])
@ -356,17 +436,19 @@ class XAxisLabel(AxisLabel):
w = self.boundingRect().width()
self.setPos(QPointF(
abs_pos.x() - w/2,
y_offset/2,
))
self.setPos(
QPointF(
abs_pos.x() - w/2 - self._pw,
y_offset/2,
)
)
self.update()
def _draw_arrow_path(self):
y_offset = self._parent.style['tickTextOffset'][1]
path = QtGui.QPainterPath()
h, w = self.rect.height(), self.rect.width()
middle = w/2 - 0.5
middle = w/2 - self._pw * 0.5
aw = h/2
left = middle - aw
right = middle + aw
@ -410,8 +492,12 @@ class YAxisLabel(AxisLabel):
self.x_offset, y_offset = self._parent.txt_offsets()
def size_hint(self) -> Tuple[float, float]:
# size to parent axis width
return None, self._parent.width()
# size to parent axis width(-ish)
wsh = self._dpifont.boundingRect(' ').height() / 2
return (
None,
self._parent.size().width() - wsh,
)
def update_label(
self,
@ -432,16 +518,19 @@ class YAxisLabel(AxisLabel):
br = self.boundingRect()
h = br.height()
self.setPos(QPointF(
x_offset,
abs_pos.y() - h / 2 - self._y_margin / 2
))
self.setPos(
QPointF(
x_offset,
abs_pos.y() - h / 2 - self._pw,
)
)
self.update()
def update_on_resize(self, vr, r):
"""Tiis is a ``.sigRangeChanged()`` handler.
'''
This is a ``.sigRangeChanged()`` handler.
"""
'''
index, last = self._last_datum
if index is not None:
self.update_from_data(index, last)
@ -451,11 +540,13 @@ class YAxisLabel(AxisLabel):
index: int,
value: float,
_save_last: bool = True,
) -> None:
"""Update the label's text contents **and** position from
'''
Update the label's text contents **and** position from
a view box coordinate datum.
"""
'''
if _save_last:
self._last_datum = (index, value)
@ -469,7 +560,7 @@ class YAxisLabel(AxisLabel):
path = QtGui.QPainterPath()
h = self.rect.height()
path.moveTo(0, 0)
path.lineTo(-x_offset - h/4, h/2.)
path.lineTo(-x_offset - h/4, h/2. - self._pw/2)
path.lineTo(0, h)
path.closeSubpath()
self.path = path

View File

@ -479,14 +479,20 @@ class LinkedSplits(QWidget):
axisItems=axes,
**cpw_kwargs,
)
cpw.hideAxis('left')
cpw.hideAxis('bottom')
if self.xaxis_chart:
self.xaxis_chart.hideAxis('bottom')
# presuming we only want it at the true bottom of all charts.
# XXX: uses new api from our ``pyqtgraph`` fork.
# https://github.com/pikers/pyqtgraph/tree/plotitemoverlay_onto_pg_master
_ = self.xaxis_chart.removeAxis('bottom', unlink=False)
assert 'bottom' not in self.xaxis_chart.plotItem.axes
# _ = self.xaxis_chart.removeAxis('bottom', unlink=False)
# assert 'bottom' not in self.xaxis_chart.plotItem.axes
self.xaxis_chart = cpw
cpw.showAxis('bottom')
if self.xaxis_chart is None:
self.xaxis_chart = cpw
@ -726,11 +732,6 @@ class ChartPlotWidget(pg.PlotWidget):
self._static_yrange = static_yrange # for "known y-range style"
self._view_mode: str = 'follow'
# show only right side axes
self.hideAxis('left')
self.showAxis('right')
# self.showAxis('left')
# show background grid
self.showGrid(x=False, y=True, alpha=0.3)
@ -862,53 +863,58 @@ class ChartPlotWidget(pg.PlotWidget):
def overlay_plotitem(
self,
name: str,
index: Optional[int] = None,
axis_title: Optional[str] = None,
axis_side: str = 'right',
axis_kwargs: dict = {},
) -> pg.PlotItem:
# Custom viewbox impl
cv = self.mk_vb(name)
cv.chart = self
# xaxis = DynamicDateAxis(
# orientation='bottom',
# linkedsplits=self.linked,
# )
allowed_sides = {'left', 'right'}
if axis_side not in allowed_sides:
raise ValueError(f'``axis_side``` must be in {allowed_sides}')
yaxis = PriceAxis(
orientation='right',
orientation=axis_side,
linkedsplits=self.linked,
**axis_kwargs,
)
plotitem = pg.PlotItem(
pi = pg.PlotItem(
parent=self.plotItem,
name=name,
enableMenu=False,
viewBox=cv,
axisItems={
# 'bottom': xaxis,
'right': yaxis,
axis_side: yaxis,
},
default_axes=[],
)
# plotitem.setAxisItems(
# add_to_layout=False,
# axisItems={
# 'bottom': xaxis,
# 'right': yaxis,
# },
# )
# plotite.hideAxis('right')
# plotite.hideAxis('bottom')
# plotitem.addItem(curve)
pi.hideButtons()
cv.enable_auto_yrange()
# plotitem.enableAutoRange(axis='y')
plotitem.hideButtons()
# compose this new plot's graphics with the current chart's
# existing one but with separate axes as neede and specified.
self.pi_overlay.add_plotitem(
plotitem,
pi,
index=index,
# only link x-axes,
link_axes=(0,),
)
return plotitem
# add axis title
# TODO: do we want this API to still work?
# raxis = pi.getAxis('right')
axis = self.pi_overlay.get_axis(pi, axis_side)
axis.set_title(axis_title or name, view=pi.getViewBox())
return pi
def draw_curve(
self,
@ -1014,7 +1020,8 @@ class ChartPlotWidget(pg.PlotWidget):
# add y-axis "last" value label
last = self._ysticks[name] = YAxisLabel(
chart=self,
parent=self.getAxis('right'),
# parent=self.getAxis('right'),
parent=self.pi_overlay.get_axis(self.plotItem, 'right'),
# TODO: pass this from symbol data
digits=digits,
opacity=1,

View File

@ -43,8 +43,8 @@ log = get_logger(__name__)
# latency (in terms of perceived lag in cross hair) so really be sure
# there's an improvement if you want to change it!
_mouse_rate_limit = 58 # TODO; should we calc current screen refresh rate?
_debounce_delay = 1 / 60
_mouse_rate_limit = 120 # TODO; should we calc current screen refresh rate?
_debounce_delay = 1 / 40
_ch_label_opac = 1
@ -369,7 +369,13 @@ class Cursor(pg.GraphicsObject):
self,
plot: 'ChartPlotWidget', # noqa
digits: int = 0,
) -> None:
'''
Add chart to tracked set such that a cross-hair and possibly
curve tracking cursor can be drawn on the plot.
'''
# add ``pg.graphicsItems.InfiniteLine``s
# vertical and horizonal lines and a y-axis label
@ -382,7 +388,8 @@ class Cursor(pg.GraphicsObject):
yl = YAxisLabel(
chart=plot,
parent=plot.getAxis('right'),
# parent=plot.getAxis('right'),
parent=plot.pi_overlay.get_axis(plot.plotItem, 'right'),
digits=digits or self.digits,
opacity=_ch_label_opac,
bg_color=self.label_color,
@ -424,19 +431,25 @@ class Cursor(pg.GraphicsObject):
# ONLY create an x-axis label for the cursor
# if this plot owns the 'bottom' axis.
if 'bottom' in plot.plotItem.axes:
self.xaxis_label = XAxisLabel(
# if 'bottom' in plot.plotItem.axes:
if plot.linked.xaxis_chart is plot:
xlabel = self.xaxis_label = XAxisLabel(
parent=self.plots[plot_index].getAxis('bottom'),
# parent=self.plots[plot_index].pi_overlay.get_axis(plot.plotItem, 'bottom'),
opacity=_ch_label_opac,
bg_color=self.label_color,
)
# place label off-screen during startup
self.xaxis_label.setPos(self.plots[0].mapFromView(QPointF(0, 0)))
xlabel.setPos(
self.plots[0].mapFromView(QPointF(0, 0))
)
xlabel.show()
def add_curve_cursor(
self,
plot: 'ChartPlotWidget', # noqa
curve: 'PlotCurveItem', # noqa
) -> LineDot:
# if this plot contains curves add line dot "cursors" to denote
# the current sample under the mouse
@ -493,24 +506,27 @@ class Cursor(pg.GraphicsObject):
ix = round(x) # since bars are centered around index
# px perfect...
line_offset = self._lw / 2
# round y value to nearest tick step
m = self._y_incr_mult
iy = round(y * m) / m
# px perfect...
line_offset = self._lw / 2
vl_y = iy - line_offset
# update y-range items
if iy != last_iy:
if self._y_label_update:
self.graphics[self.active_plot]['yl'].update_label(
abs_pos=plot.mapFromView(QPointF(ix, iy)),
# abs_pos=plot.mapFromView(QPointF(ix, iy)),
abs_pos=plot.mapFromView(QPointF(ix, vl_y)),
value=iy
)
# only update horizontal xhair line if label is enabled
self.graphics[plot]['hl'].setY(iy)
# self.graphics[plot]['hl'].setY(iy)
self.graphics[plot]['hl'].setY(vl_y)
# update all trackers
for item in self._trackers:
@ -541,21 +557,18 @@ class Cursor(pg.GraphicsObject):
# left axis offset width for calcuating
# absolute x-axis label placement.
left_axis_width = 0
left = axes.get('left')
if left:
left_axis_width = left['item'].width()
if 'bottom' in axes:
left = axes.get('left')
if left:
left_axis_width = left['item'].width()
# map back to abs (label-local) coordinates
self.xaxis_label.update_label(
abs_pos=(
plot.mapFromView(QPointF(vl_x, iy)) -
QPointF(left_axis_width, 0)
),
value=ix,
)
# map back to abs (label-local) coordinates
self.xaxis_label.update_label(
abs_pos=(
plot.mapFromView(QPointF(vl_x, iy)) -
QPointF(left_axis_width, 0)
),
value=ix,
)
self._datum_xy = ix, iy

View File

@ -115,13 +115,14 @@ async def update_linked_charts_graphics(
vlm_chart: Optional[ChartPlotWidget] = None,
) -> None:
'''The 'main' (price) chart real-time update loop.
'''
The 'main' (price) chart real-time update loop.
Receive from the primary instrument quote stream and update the OHLC
chart.
'''
# TODO: bunch of stuff:
# TODO: bunch of stuff (some might be done already, can't member):
# - I'm starting to think all this logic should be
# done in one place and "graphics update routines"
# should not be doing any length checking and array diffing.
@ -181,13 +182,34 @@ async def update_linked_charts_graphics(
view = chart.view
last_quote = time.time()
# async def iter_drain_quotes():
# # NOTE: all code below this loop is expected to be synchronous
# # and thus draw instructions are not picked up jntil the next
# # wait / iteration.
# async for quotes in stream:
# while True:
# try:
# moar = stream.receive_nowait()
# except trio.WouldBlock:
# yield quotes
# break
# else:
# for sym, quote in moar.items():
# ticks_frame = quote.get('ticks')
# if ticks_frame:
# quotes[sym].setdefault(
# 'ticks', []).extend(ticks_frame)
# print('pulled extra')
# yield quotes
# async for quotes in iter_drain_quotes():
async for quotes in stream:
now = time.time()
quote_period = time.time() - last_quote
quote_rate = round(
1/quote_period, 1) if quote_period > 0 else float('inf')
if (
quote_period <= 1/_quote_throttle_rate
@ -196,7 +218,8 @@ async def update_linked_charts_graphics(
and quote_rate >= _quote_throttle_rate * 1.5
):
log.warning(f'High quote rate {symbol.key}: {quote_rate}')
last_quote = now
last_quote = time.time()
# chart isn't active/shown so skip render cycle and pause feed(s)
if chart.linked.isHidden():
@ -621,9 +644,15 @@ async def display_symbol_data(
await trio.sleep(0)
linkedsplits.resize_sidepanes()
# NOTE: we pop the volume chart from the subplots set so
# that it isn't double rendered in the display loop
# above since we do a maxmin calc on the volume data to
# determine if auto-range adjustements should be made.
linkedsplits.subplots.pop('volume', None)
# TODO: make this not so shit XD
# close group status
sbar._status_groups[loading_sym_key][1]()
# let the app run.
# let the app run.. bby
await trio.sleep_forever()

View File

@ -33,7 +33,9 @@ import pyqtgraph as pg
import trio
from trio_typing import TaskStatus
from ._axes import PriceAxis
from .._cacheables import maybe_open_context
from ..calc import humanize
from ..data._sharedmem import (
ShmArray,
maybe_open_shm_array,
@ -653,7 +655,7 @@ async def open_vlm_displays(
last_val_sticky.update_from_data(-1, value)
chart.update_curve_from_array(
vlm_curve = chart.update_curve_from_array(
'volume',
shm.array,
)
@ -661,73 +663,100 @@ async def open_vlm_displays(
# size view to data once at outset
chart.view._set_yrange()
if not dvlm:
return
# add axis title
axis = chart.getAxis('right')
axis.set_title(' vlm')
# spawn and overlay $ vlm on the same subchart
shm, started = await admin.start_engine_task(
'dolla_vlm',
# linked.symbol.front_feed(), # data-feed symbol key
{ # fsp engine conf
'func_name': 'dolla_vlm',
'zero_on_step': True,
'params': {
'price_func': {
'default_value': 'chl3',
if dvlm:
# spawn and overlay $ vlm on the same subchart
shm, started = await admin.start_engine_task(
'dolla_vlm',
# linked.symbol.front_feed(), # data-feed symbol key
{ # fsp engine conf
'func_name': 'dolla_vlm',
'zero_on_step': True,
'params': {
'price_func': {
'default_value': 'chl3',
},
},
},
},
# loglevel,
)
# profiler(f'created shm for fsp actor: {display_name}')
# loglevel,
)
# profiler(f'created shm for fsp actor: {display_name}')
await started.wait()
await started.wait()
pi = chart.overlay_plotitem(
'dolla_vlm',
)
# add custom auto range handler
pi.vb._maxmin = partial(maxmin, name='dolla_vlm')
pi = chart.overlay_plotitem(
'dolla_vlm',
index=0, # place axis on inside (nearest to chart)
axis_title=' $vlm',
axis_side='right',
axis_kwargs={
'typical_max_str': ' 100.0 M ',
'formatter': partial(
humanize,
digits=2,
),
},
curve, _ = chart.draw_curve(
)
name='dolla_vlm',
data=shm.array,
# add custom auto range handler
pi.vb._maxmin = partial(maxmin, name='dolla_vlm')
array_key='dolla_vlm',
overlay=pi,
color='charcoal',
step_mode=True,
# **conf.get('chart_kwargs', {})
)
# TODO: is there a way to "sync" the dual axes such that only
# one curve is needed?
# curve.hide()
curve, _ = chart.draw_curve(
# TODO: we need a better API to do this..
# specially store ref to shm for lookup in display loop
# since only a placeholder of `None` is entered in
# ``.draw_curve()``.
chart._overlays['dolla_vlm'] = shm
name='dolla_vlm',
data=shm.array,
# XXX: old dict-style config before it was moved into the helper task
# 'dolla_vlm': {
# 'func_name': 'dolla_vlm',
# 'zero_on_step': True,
# 'overlay': 'volume',
# 'separate_axes': True,
# 'params': {
# 'price_func': {
# 'default_value': 'chl3',
# # tell target ``Edit`` widget to not allow
# # edits for now.
# 'widget_kwargs': {'readonly': True},
# },
# },
# 'chart_kwargs': {'step_mode': True}
# },
array_key='dolla_vlm',
overlay=pi,
# color='bracket',
# TODO: this color or dark volume
# color='charcoal',
step_mode=True,
# **conf.get('chart_kwargs', {})
)
# TODO: is there a way to "sync" the dual axes such that only
# one curve is needed?
# hide the original vlm curve since the $vlm one is now
# displayed and the curves are effectively the same minus
# liquidity events (well at least on low OHLC periods - 1s).
vlm_curve.hide()
# }
# TODO: we need a better API to do this..
# specially store ref to shm for lookup in display loop
# since only a placeholder of `None` is entered in
# ``.draw_curve()``.
chart._overlays['dolla_vlm'] = shm
# XXX: old dict-style config before it was moved into the
# helper task
# 'dolla_vlm': {
# 'func_name': 'dolla_vlm',
# 'zero_on_step': True,
# 'overlay': 'volume',
# 'separate_axes': True,
# 'params': {
# 'price_func': {
# 'default_value': 'chl3',
# # tell target ``Edit`` widget to not allow
# # edits for now.
# 'widget_kwargs': {'readonly': True},
# },
# },
# 'chart_kwargs': {'step_mode': True}
# },
# }
for name, axis_info in pi.axes.items():
# lol this sux XD
axis = axis_info['item']
if isinstance(axis, PriceAxis):
axis.size_to_values()
# built-in vlm fsps
for display_name, conf in {

View File

@ -34,7 +34,7 @@ from ._style import (
class Label:
"""
'''
A plain ol' "scene label" using an underlying ``QGraphicsTextItem``.
After hacking for many days on multiple "label" systems inside
@ -50,10 +50,8 @@ class Label:
small, re-usable label components that can actually be used to build
production grade UIs...
"""
'''
def __init__(
self,
view: pg.ViewBox,
fmt_str: str,
@ -63,6 +61,7 @@ class Label:
font_size: str = 'small',
opacity: float = 1,
fields: dict = {},
parent: pg.GraphicsObject = None,
update_on_range_change: bool = True,
) -> None:
@ -71,11 +70,13 @@ class Label:
self._fmt_str = fmt_str
self._view_xy = QPointF(0, 0)
self.scene_anchor: Optional[Callable[..., QPointF]] = None
self.scene_anchor: Optional[
Callable[..., QPointF]
] = None
self._x_offset = x_offset
txt = self.txt = QtWidgets.QGraphicsTextItem()
txt = self.txt = QtWidgets.QGraphicsTextItem(parent=parent)
txt.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
vb.scene().addItem(txt)
@ -86,7 +87,6 @@ class Label:
)
dpi_font.configure_to_dpi()
txt.setFont(dpi_font.font)
txt.setOpacity(opacity)
# register viewbox callbacks
@ -109,7 +109,7 @@ class Label:
# self.setTextInteractionFlags(QtGui.Qt.TextEditorInteraction)
@property
def color(self):
def color(self) -> str:
return self._hcolor
@color.setter
@ -118,9 +118,10 @@ class Label:
self._hcolor = color
def update(self) -> None:
'''Update this label either by invoking its
user defined anchoring function, or by positioning
to the last recorded data view coordinates.
'''
Update this label either by invoking its user defined anchoring
function, or by positioning to the last recorded data view
coordinates.
'''
# move label in scene coords to desired position
@ -234,7 +235,8 @@ class Label:
class FormatLabel(QLabel):
'''Kinda similar to above but using the widget apis.
'''
Kinda similar to above but using the widget apis.
'''
def __init__(
@ -273,8 +275,8 @@ class FormatLabel(QLabel):
QSizePolicy.Expanding,
QSizePolicy.Expanding,
)
self.setAlignment(Qt.AlignVCenter
| Qt.AlignLeft
self.setAlignment(
Qt.AlignVCenter | Qt.AlignLeft
)
self.setText(self.fmt_str)

View File

@ -334,10 +334,11 @@ class LevelLine(pg.InfiniteLine):
w: QtWidgets.QWidget
) -> None:
"""Core paint which we override (yet again)
'''
Core paint which we override (yet again)
from pg..
"""
'''
p.setRenderHint(p.Antialiasing)
# these are in viewbox coords