Flip contents label stuff into a type
parent
aade0e5ea1
commit
416f027c5f
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue