diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py
index 1718c4ee..80c9b369 100644
--- a/piker/ui/_chart.py
+++ b/piker/ui/_chart.py
@@ -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]
diff --git a/piker/ui/_graphics.py b/piker/ui/_graphics.py
index 19532693..88089812 100644
--- a/piker/ui/_graphics.py
+++ b/piker/ui/_graphics.py
@@ -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(
+ "i:{index}
"
+ "O:{}
"
+ "H:{}
"
+ "L:{}
"
+ "C:{}
"
+ "V:{}".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,