Flip contents label stuff into a type
parent
aade0e5ea1
commit
416f027c5f
|
@ -2,6 +2,7 @@
|
||||||
High level Qt chart widgets.
|
High level Qt chart widgets.
|
||||||
"""
|
"""
|
||||||
from typing import Tuple, Dict, Any, Optional
|
from typing import Tuple, Dict, Any, Optional
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui
|
from PyQt5 import QtCore, QtGui
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -15,6 +16,7 @@ from ._axes import (
|
||||||
)
|
)
|
||||||
from ._graphics import (
|
from ._graphics import (
|
||||||
CrossHair,
|
CrossHair,
|
||||||
|
ContentsLabel,
|
||||||
BarItems,
|
BarItems,
|
||||||
h_line,
|
h_line,
|
||||||
)
|
)
|
||||||
|
@ -27,7 +29,6 @@ from ._style import (
|
||||||
_min_points_to_show,
|
_min_points_to_show,
|
||||||
_bars_from_right_in_follow_mode,
|
_bars_from_right_in_follow_mode,
|
||||||
_bars_to_left_in_follow_mode,
|
_bars_to_left_in_follow_mode,
|
||||||
_font,
|
|
||||||
)
|
)
|
||||||
from ..data._source import Symbol
|
from ..data._source import Symbol
|
||||||
from .. import brokers
|
from .. import brokers
|
||||||
|
@ -326,7 +327,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
self.showAxis('right')
|
self.showAxis('right')
|
||||||
|
|
||||||
# show background grid
|
# 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?
|
# use cross-hair for cursor?
|
||||||
# self.setCursor(QtCore.Qt.CrossCursor)
|
# self.setCursor(QtCore.Qt.CrossCursor)
|
||||||
|
@ -344,10 +345,12 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
def last_bar_in_view(self) -> bool:
|
def last_bar_in_view(self) -> bool:
|
||||||
self._array[-1]['index']
|
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):
|
if index >= 0 and index < len(self._array):
|
||||||
|
array = self._array
|
||||||
|
|
||||||
for name, (label, update) in self._labels.items():
|
for name, (label, update) in self._labels.items():
|
||||||
update(index)
|
update(index, array)
|
||||||
|
|
||||||
def _set_xlimits(
|
def _set_xlimits(
|
||||||
self,
|
self,
|
||||||
|
@ -375,54 +378,6 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
rbar = min(r, len(self._array))
|
rbar = min(r, len(self._array))
|
||||||
return l, lbar, rbar, r
|
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(
|
def default_view(
|
||||||
self,
|
self,
|
||||||
index: int = -1,
|
index: int = -1,
|
||||||
|
@ -456,6 +411,34 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
padding=0,
|
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(
|
def draw_curve(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
|
@ -482,37 +465,21 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# register overlay curve with name
|
# register overlay curve with name
|
||||||
self._graphics[name] = curve
|
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:
|
if overlay:
|
||||||
# position bottom left if an overlay
|
anchor_at = ('bottom', 'right')
|
||||||
label.anchor(itemPos=(1, 1), parentPos=(1, 1), offset=(0, 3))
|
|
||||||
self._overlays[name] = curve
|
self._overlays[name] = curve
|
||||||
|
|
||||||
else:
|
else:
|
||||||
label.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, -4))
|
anchor_at = ('top', 'right')
|
||||||
|
|
||||||
# TODO: something instead of stickies for overlays
|
# TODO: something instead of stickies for overlays
|
||||||
# (we need something that avoids clutter on x-axis).
|
# (we need something that avoids clutter on x-axis).
|
||||||
self._add_sticky(name)
|
self._add_sticky(name)
|
||||||
|
|
||||||
def update(index: int) -> None:
|
label = ContentsLabel(chart=self, anchor_at=anchor_at)
|
||||||
data = self._array[index][name]
|
self._labels[name] = (label, partial(label.update_from_value, name))
|
||||||
label.setText(f"{name}: {data:.2f}")
|
|
||||||
|
|
||||||
label.show()
|
label.show()
|
||||||
self.scene().addItem(label)
|
self.update_contents_labels(len(data) - 1)
|
||||||
|
|
||||||
self._labels[name] = (label, update)
|
|
||||||
self._update_contents_label(len(data) - 1)
|
|
||||||
|
|
||||||
if self._cursor:
|
if self._cursor:
|
||||||
self._cursor.add_curve_cursor(self, curve)
|
self._cursor.add_curve_cursor(self, curve)
|
||||||
|
@ -909,7 +876,7 @@ async def chart_from_fsp(
|
||||||
)
|
)
|
||||||
|
|
||||||
# display contents labels asap
|
# display contents labels asap
|
||||||
chart._update_contents_label(len(shm.array) - 1)
|
chart.update_contents_labels(len(shm.array) - 1)
|
||||||
|
|
||||||
array = shm.array
|
array = shm.array
|
||||||
value = array[fsp_func_name][-1]
|
value = array[fsp_func_name][-1]
|
||||||
|
|
|
@ -6,10 +6,11 @@ from typing import List
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
|
# from numba import jit, float64, optional, int64
|
||||||
from PyQt5 import QtCore, QtGui
|
from PyQt5 import QtCore, QtGui
|
||||||
from PyQt5.QtCore import QLineF, QPointF
|
from PyQt5.QtCore import QLineF, QPointF
|
||||||
|
|
||||||
# from .quantdom.utils import timeit
|
# from .._profile import timeit
|
||||||
from ._style import _xaxis_at, hcolor, _font
|
from ._style import _xaxis_at, hcolor, _font
|
||||||
from ._axes import YAxisLabel, XAxisLabel
|
from ._axes import YAxisLabel, XAxisLabel
|
||||||
|
|
||||||
|
@ -56,6 +57,78 @@ class LineDot(pg.CurvePoint):
|
||||||
self.setFlag(self.ItemIgnoresTransformations)
|
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):
|
class CrossHair(pg.GraphicsObject):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -208,7 +281,7 @@ class CrossHair(pg.GraphicsObject):
|
||||||
opts['vl'].setX(ix)
|
opts['vl'].setX(ix)
|
||||||
|
|
||||||
# update the chart's "contents" label
|
# update the chart's "contents" label
|
||||||
plot._update_contents_label(ix)
|
plot.update_contents_labels(ix)
|
||||||
|
|
||||||
# update all subscribed curve dots
|
# update all subscribed curve dots
|
||||||
for cursor in opts.get('cursors', ()):
|
for cursor in opts.get('cursors', ()):
|
||||||
|
@ -235,6 +308,15 @@ class CrossHair(pg.GraphicsObject):
|
||||||
return self.plots[0].boundingRect()
|
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:
|
def _mk_lines_array(data: List, size: int) -> np.ndarray:
|
||||||
"""Create an ndarray to hold lines graphics objects.
|
"""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?
|
# TODO: `numba` this?
|
||||||
|
|
||||||
|
# @jit(
|
||||||
|
# # float64[:](
|
||||||
|
# # float64[:],
|
||||||
|
# # optional(float64),
|
||||||
|
# # optional(int16)
|
||||||
|
# # ),
|
||||||
|
# nopython=True,
|
||||||
|
# nogil=True
|
||||||
|
# )
|
||||||
def bars_from_ohlc(
|
def bars_from_ohlc(
|
||||||
data: np.ndarray,
|
data: np.ndarray,
|
||||||
w: float,
|
w: float,
|
||||||
|
|
Loading…
Reference in New Issue