Flip contents label stuff into a type

bar_select
Tyler Goodlet 2020-10-29 17:08:03 -04:00
parent aade0e5ea1
commit 416f027c5f
2 changed files with 135 additions and 76 deletions

View File

@ -2,6 +2,7 @@
High level Qt chart widgets.
"""
from typing import Tuple, Dict, Any, Optional
from functools import partial
from PyQt5 import QtCore, QtGui
import numpy as np
@ -15,6 +16,7 @@ from ._axes import (
)
from ._graphics import (
CrossHair,
ContentsLabel,
BarItems,
h_line,
)
@ -27,7 +29,6 @@ from ._style import (
_min_points_to_show,
_bars_from_right_in_follow_mode,
_bars_to_left_in_follow_mode,
_font,
)
from ..data._source import Symbol
from .. import brokers
@ -326,7 +327,7 @@ class ChartPlotWidget(pg.PlotWidget):
self.showAxis('right')
# show background grid
self.showGrid(x=True, y=True, alpha=0.4)
self.showGrid(x=True, y=True, alpha=0.5)
# use cross-hair for cursor?
# self.setCursor(QtCore.Qt.CrossCursor)
@ -344,10 +345,12 @@ class ChartPlotWidget(pg.PlotWidget):
def last_bar_in_view(self) -> bool:
self._array[-1]['index']
def _update_contents_label(self, index: int) -> None:
def update_contents_labels(self, index: int) -> None:
if index >= 0 and index < len(self._array):
array = self._array
for name, (label, update) in self._labels.items():
update(index)
update(index, array)
def _set_xlimits(
self,
@ -375,54 +378,6 @@ class ChartPlotWidget(pg.PlotWidget):
rbar = min(r, len(self._array))
return l, lbar, rbar, r
def draw_ohlc(
self,
name: str,
data: np.ndarray,
# XXX: pretty sure this is dumb and we don't need an Enum
style: pg.GraphicsObject = BarItems,
) -> pg.GraphicsObject:
"""Draw OHLC datums to chart.
"""
graphics = style(self.plotItem)
# adds all bar/candle graphics objects for each data point in
# the np array buffer to be drawn on next render cycle
self.addItem(graphics)
# draw after to allow self.scene() to work...
graphics.draw_from_data(data)
self._graphics[name] = graphics
# XXX: How to stack labels vertically?
# Ogi says: "use ..."
label = pg.LabelItem(
justify='left',
size=f'{_font.pixelSize()}px',
)
label.setParentItem(self._vb)
self.scene().addItem(label)
# keep close to top
label.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, -4))
def update(index: int) -> None:
label.setText(
"{name}[{index}] -> O:{} H:{} L:{} C:{} V:{}".format(
*self._array[index].item()[2:8],
name=name,
index=index,
)
)
self._labels[name] = (label, update)
self._update_contents_label(len(data) - 1)
label.show()
self._add_sticky(name)
return graphics
def default_view(
self,
index: int = -1,
@ -456,6 +411,34 @@ class ChartPlotWidget(pg.PlotWidget):
padding=0,
)
def draw_ohlc(
self,
name: str,
data: np.ndarray,
# XXX: pretty sure this is dumb and we don't need an Enum
style: pg.GraphicsObject = BarItems,
) -> pg.GraphicsObject:
"""Draw OHLC datums to chart.
"""
graphics = style(self.plotItem)
# adds all bar/candle graphics objects for each data point in
# the np array buffer to be drawn on next render cycle
self.addItem(graphics)
# draw after to allow self.scene() to work...
graphics.draw_from_data(data)
self._graphics[name] = graphics
label = ContentsLabel(chart=self, anchor_at=('top', 'left'))
self._labels[name] = (label, partial(label.update_from_ohlc, name))
label.show()
self.update_contents_labels(len(data) - 1)
self._add_sticky(name)
return graphics
def draw_curve(
self,
name: str,
@ -482,37 +465,21 @@ class ChartPlotWidget(pg.PlotWidget):
# register overlay curve with name
self._graphics[name] = curve
# XXX: How to stack labels vertically?
label = pg.LabelItem(
justify='left',
size=f'{_font.pixelSize()}px',
)
# anchor to the viewbox
label.setParentItem(self._vb)
# label.setParentItem(self.getPlotItem())
if overlay:
# position bottom left if an overlay
label.anchor(itemPos=(1, 1), parentPos=(1, 1), offset=(0, 3))
anchor_at = ('bottom', 'right')
self._overlays[name] = curve
else:
label.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, -4))
anchor_at = ('top', 'right')
# TODO: something instead of stickies for overlays
# (we need something that avoids clutter on x-axis).
self._add_sticky(name)
def update(index: int) -> None:
data = self._array[index][name]
label.setText(f"{name}: {data:.2f}")
label = ContentsLabel(chart=self, anchor_at=anchor_at)
self._labels[name] = (label, partial(label.update_from_value, name))
label.show()
self.scene().addItem(label)
self._labels[name] = (label, update)
self._update_contents_label(len(data) - 1)
self.update_contents_labels(len(data) - 1)
if self._cursor:
self._cursor.add_curve_cursor(self, curve)
@ -909,7 +876,7 @@ async def chart_from_fsp(
)
# display contents labels asap
chart._update_contents_label(len(shm.array) - 1)
chart.update_contents_labels(len(shm.array) - 1)
array = shm.array
value = array[fsp_func_name][-1]

View File

@ -6,10 +6,11 @@ from typing import List
import numpy as np
import pyqtgraph as pg
# from numba import jit, float64, optional, int64
from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import QLineF, QPointF
# from .quantdom.utils import timeit
# from .._profile import timeit
from ._style import _xaxis_at, hcolor, _font
from ._axes import YAxisLabel, XAxisLabel
@ -56,6 +57,78 @@ class LineDot(pg.CurvePoint):
self.setFlag(self.ItemIgnoresTransformations)
_corner_anchors = {
'top': 0,
'left': 0,
'bottom': 1,
'right': 1,
}
# XXX: fyi naming here is confusing / opposite to coords
_corner_margins = {
('top', 'left'): (-4, -5),
('top', 'right'): (4, -5),
('bottom', 'left'): (-4, 5),
('bottom', 'right'): (4, 5),
}
class ContentsLabel(pg.LabelItem):
"""Label anchored to a ``ViewBox`` typically for displaying
datum-wise points from the "viewed" contents.
"""
def __init__(
self,
chart: 'ChartPlotWidget', # noqa
anchor_at: str = ('top', 'right'),
justify_text: str = 'left',
size: str = f'{_font.pixelSize()}px',
) -> None:
super().__init__(justify=justify_text, size=size)
# anchor to viewbox
self.setParentItem(chart._vb)
chart.scene().addItem(self)
self.chart = chart
v, h = anchor_at
index = (_corner_anchors[h], _corner_anchors[v])
margins = _corner_margins[(v, h)]
self.anchor(itemPos=index, parentPos=index, offset=margins)
def update_from_ohlc(
self,
name: str,
index: int,
array: np.ndarray,
) -> None:
# this being "html" is the dumbest shit :eyeroll:
self.setText(
"<b>i</b>:{index}<br/>"
"<b>O</b>:{}<br/>"
"<b>H</b>:{}<br/>"
"<b>L</b>:{}<br/>"
"<b>C</b>:{}<br/>"
"<b>V</b>:{}".format(
# *self._array[index].item()[2:8],
*array[index].item()[2:8],
name=name,
index=index,
)
)
def update_from_value(
self,
name: str,
index: int,
array: np.ndarray,
) -> None:
data = array[index][name]
self.setText(f"{name}: {data:.2f}")
class CrossHair(pg.GraphicsObject):
def __init__(
@ -208,7 +281,7 @@ class CrossHair(pg.GraphicsObject):
opts['vl'].setX(ix)
# update the chart's "contents" label
plot._update_contents_label(ix)
plot.update_contents_labels(ix)
# update all subscribed curve dots
for cursor in opts.get('cursors', ()):
@ -235,6 +308,15 @@ class CrossHair(pg.GraphicsObject):
return self.plots[0].boundingRect()
# @jit(
# # float64[:](
# # float64[:],
# # optional(float64),
# # optional(int16)
# # ),
# nopython=True,
# nogil=True
# )
def _mk_lines_array(data: List, size: int) -> np.ndarray:
"""Create an ndarray to hold lines graphics objects.
"""
@ -246,6 +328,16 @@ def _mk_lines_array(data: List, size: int) -> np.ndarray:
# TODO: `numba` this?
# @jit(
# # float64[:](
# # float64[:],
# # optional(float64),
# # optional(int16)
# # ),
# nopython=True,
# nogil=True
# )
def bars_from_ohlc(
data: np.ndarray,
w: float,