diff --git a/piker/ui/_graphics.py b/piker/ui/_graphics.py
index e1dbdaf2..83cbd5b5 100644
--- a/piker/ui/_graphics.py
+++ b/piker/ui/_graphics.py
@@ -33,14 +33,15 @@ from ._style import (
_xaxis_at,
hcolor,
_font,
+ _down_2_font_inches_we_like,
)
from ._axes import YAxisLabel, XAxisLabel, YSticky
# XXX: these settings seem to result in really decent mouse scroll
# 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 = 60 # calc current screen refresh rate?
+# there's an improvement if you want to change it!
+_mouse_rate_limit = 60 # TODO; should we calc current screen refresh rate?
_debounce_delay = 1 / 2e3
_ch_label_opac = 1
@@ -53,6 +54,7 @@ class LineDot(pg.CurvePoint):
self,
curve: pg.PlotCurveItem,
index: int,
+ plot: 'ChartPlotWidget',
pos=None,
size: int = 2, # in pxs
color: str = 'default_light',
@@ -64,6 +66,7 @@ class LineDot(pg.CurvePoint):
pos=pos,
rotate=False,
)
+ self._plot = plot
# TODO: get pen from curve if not defined?
cdefault = hcolor(color)
@@ -83,6 +86,31 @@ class LineDot(pg.CurvePoint):
# keep a static size
self.setFlag(self.ItemIgnoresTransformations)
+ def event(
+ self,
+ ev: QtCore.QEvent,
+ ) -> None:
+ # print((ev, type(ev)))
+ if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve() is None:
+ return False
+
+ # if ev.propertyName() == 'index':
+ # print(ev)
+ # # self.setProperty
+
+ (x, y) = self.curve().getData()
+ index = self.property('index')
+ # first = self._plot._ohlc[0]['index']
+ # first = x[0]
+ # i = index - first
+ i = index - x[0]
+ if i > 0:
+ newPos = (index, y[i])
+ QtGui.QGraphicsItem.setPos(self, *newPos)
+ return True
+
+ return False
+
_corner_anchors = {
'top': 0,
@@ -94,8 +122,10 @@ _corner_anchors = {
_corner_margins = {
('top', 'left'): (-4, -5),
('top', 'right'): (4, -5),
- ('bottom', 'left'): (-4, 5),
- ('bottom', 'right'): (4, 5),
+
+ # TODO: pretty sure y here needs to be 2x font height
+ ('bottom', 'left'): (-4, 14),
+ ('bottom', 'right'): (4, 14),
}
@@ -132,15 +162,19 @@ class ContentsLabel(pg.LabelItem):
array: np.ndarray,
) -> None:
# this being "html" is the dumbest shit :eyeroll:
+ first = array[0]['index']
+
self.setText(
"i:{index}
"
"O:{}
"
"H:{}
"
"L:{}
"
"C:{}
"
- "V:{}".format(
- # *self._array[index].item()[2:8],
- *array[index].item()[2:8],
+ "V:{}
"
+ "wap:{}".format(
+ *array[index - first][
+ ['open', 'high', 'low', 'close', 'volume', 'bar_wap']
+ ],
name=name,
index=index,
)
@@ -152,8 +186,9 @@ class ContentsLabel(pg.LabelItem):
index: int,
array: np.ndarray,
) -> None:
- if index < len(array):
- data = array[index][name]
+ first = array[0]['index']
+ if index < array[-1]['index'] and index > first:
+ data = array[index - first][name]
self.setText(f"{name}: {data:.2f}")
@@ -250,7 +285,7 @@ class CrossHair(pg.GraphicsObject):
) -> LineDot:
# if this plot contains curves add line dot "cursors" to denote
# the current sample under the mouse
- cursor = LineDot(curve, index=len(plot._ohlc))
+ cursor = LineDot(curve, index=plot._ohlc[-1]['index'], plot=plot)
plot.addItem(cursor)
self.graphics[plot].setdefault('cursors', []).append(cursor)
return cursor
@@ -312,8 +347,9 @@ class CrossHair(pg.GraphicsObject):
plot.update_contents_labels(ix)
# update all subscribed curve dots
+ # first = plot._ohlc[0]['index']
for cursor in opts.get('cursors', ()):
- cursor.setIndex(ix)
+ cursor.setIndex(ix) # - first)
# update the label on the bottom of the crosshair
self.xaxis_label.update_label(
@@ -375,7 +411,7 @@ def lines_from_ohlc(row: np.ndarray, w: float) -> Tuple[QLineF]:
return [hl, o, c]
-# @timeit
+@timeit
@jit(
# TODO: for now need to construct this manually for readonly arrays, see
# https://github.com/numba/numba/issues/4511
@@ -450,7 +486,7 @@ def path_arrays_from_ohlc(
@timeit
def gen_qpath(
data,
- start,
+ start, # XXX: do we need this?
w,
) -> QtGui.QPainterPath:
@@ -478,13 +514,16 @@ class BarItems(pg.GraphicsObject):
super().__init__()
self.last_bar = QtGui.QPicture()
- self.history = QtGui.QPicture()
+ # self.history = QtGui.QPicture()
self.path = QtGui.QPainterPath()
- self._h_path = QtGui.QGraphicsPathItem(self.path)
+ # self._h_path = QtGui.QGraphicsPathItem(self.path)
self._pi = plotitem
+ self._xrange: Tuple[int, int]
+ self._yrange: Tuple[float, float]
+
# XXX: not sure this actually needs to be an array other
# then for the old tina mode calcs for up/down bars below?
# lines container
@@ -495,14 +534,15 @@ class BarItems(pg.GraphicsObject):
self._last_bar_lines: Optional[Tuple[QLineF, ...]] = None
# track the current length of drawable lines within the larger array
- self.index: int = 0
+ self.start_index: int = 0
+ self.stop_index: int = 0
- # @timeit
+ @timeit
def draw_from_data(
self,
data: np.ndarray,
start: int = 0,
- ):
+ ) -> QtGui.QPainterPath:
"""Draw OHLC datum graphics from a ``np.ndarray``.
This routine is usually only called to draw the initial history.
@@ -511,19 +551,37 @@ class BarItems(pg.GraphicsObject):
# save graphics for later reference and keep track
# of current internal "last index"
- self.index = len(data)
+ # self.start_index = len(data)
+ index = data['index']
+ self._xrange = (index[0], index[-1])
+ self._yrange = (
+ np.nanmax(data['high']),
+ np.nanmin(data['low']),
+ )
# up to last to avoid double draw of last bar
self._last_bar_lines = lines_from_ohlc(data[-1], self.w)
# create pics
- self.draw_history()
+ # self.draw_history()
self.draw_last_bar()
# trigger render
# https://doc.qt.io/qt-5/qgraphicsitem.html#update
self.update()
+ return self.path
+
+ # def update_ranges(
+ # self,
+ # xmn: int,
+ # xmx: int,
+ # ymn: float,
+ # ymx: float,
+ # ) -> None:
+ # ...
+
+
def draw_last_bar(self) -> None:
"""Currently this draws lines to a cached ``QPicture`` which
is supposed to speed things up on ``.paint()`` calls (which
@@ -535,17 +593,17 @@ class BarItems(pg.GraphicsObject):
p.drawLines(*tuple(filter(bool, self._last_bar_lines)))
p.end()
- @timeit
- def draw_history(self) -> None:
- # TODO: avoid having to use a ```QPicture` to calc the
- # ``.boundingRect()``, use ``QGraphicsPathItem`` instead?
- # https://doc.qt.io/qt-5/qgraphicspathitem.html
- # self._h_path.setPath(self.path)
+ # @timeit
+ # def draw_history(self) -> None:
+ # # TODO: avoid having to use a ```QPicture` to calc the
+ # # ``.boundingRect()``, use ``QGraphicsPathItem`` instead?
+ # # https://doc.qt.io/qt-5/qgraphicspathitem.html
+ # # self._h_path.setPath(self.path)
- p = QtGui.QPainter(self.history)
- p.setPen(self.bars_pen)
- p.drawPath(self.path)
- p.end()
+ # p = QtGui.QPainter(self.history)
+ # p.setPen(self.bars_pen)
+ # p.drawPath(self.path)
+ # p.end()
@timeit
def update_from_array(
@@ -564,14 +622,42 @@ class BarItems(pg.GraphicsObject):
This routine should be made (transitively) as fast as possible.
"""
- index = self.index
- length = len(array)
- extra = length - index
+ # index = self.start_index
+ istart, istop = self._xrange
+
+ index = array['index']
+ first_index, last_index = index[0], index[-1]
+
+ # length = len(array)
+ prepend_length = istart - first_index
+ append_length = last_index - istop
# TODO: allow mapping only a range of lines thus
# only drawing as many bars as exactly specified.
- if extra > 0:
+ if prepend_length:
+ # breakpoint()
+ # new history was added and we need to render a new path
+ new_bars = array[:prepend_length]
+ prepend_path = gen_qpath(new_bars, 0, self.w)
+
+ # XXX: SOMETHING IS FISHY HERE what with the old_path
+ # y value not matching the first value from
+ # array[prepend_length + 1] ???
+
+ # update path
+ old_path = self.path
+ self.path = prepend_path
+ # self.path.moveTo(float(index - self.w), float(new_bars[0]['open']))
+ # self.path.moveTo(
+ # float(istart - self.w),
+ # # float(array[prepend_length + 1]['open'])
+ # float(array[prepend_length]['open'])
+ # )
+ self.path.addPath(old_path)
+ # self.draw_history()
+
+ if append_length:
# generate new lines objects for updatable "current bar"
self._last_bar_lines = lines_from_ohlc(array[-1], self.w)
self.draw_last_bar()
@@ -580,29 +666,61 @@ class BarItems(pg.GraphicsObject):
# path appending logic:
# we need to get the previous "current bar(s)" for the time step
# and convert it to a sub-path to append to the historical set
- new_history_istart = length - 2
- to_history = array[new_history_istart:new_history_istart + extra]
- new_history_qpath = gen_qpath(to_history, 0, self.w)
+ # new_bars = array[istop - 1:istop + append_length - 1]
+ new_bars = array[-append_length - 1:-1]
+ append_path = gen_qpath(new_bars, 0, self.w)
+ self.path.moveTo(float(istop - self.w), float(new_bars[0]['open']))
+ self.path.addPath(append_path)
- # move to position of placement for the next bar in history
- # and append new sub-path
- new_bars = array[index:index + extra]
- self.path.moveTo(float(index - self.w), float(new_bars[0]['open']))
- self.path.addPath(new_history_qpath)
+ # self.draw_history()
- self.index += extra
+ self._xrange = first_index, last_index
- self.draw_history()
+ # if extra > 0:
+ # index = array['index']
+ # first, last = index[0], indext[-1]
- if just_history:
- self.update()
- return
+ # # if first < self.start_index:
+ # # length = self.start_index - first
+ # # prepend_path = gen_qpath(array[:sef:
+
+ # # generate new lines objects for updatable "current bar"
+ # self._last_bar_lines = lines_from_ohlc(array[-1], self.w)
+ # self.draw_last_bar()
+
+ # # generate new graphics to match provided array
+ # # path appending logic:
+ # # we need to get the previous "current bar(s)" for the time step
+ # # and convert it to a sub-path to append to the historical set
+ # new_history_istart = length - 2
+
+ # to_history = array[new_history_istart:new_history_istart + extra]
+
+ # new_history_qpath = gen_qpath(to_history, 0, self.w)
+
+ # # move to position of placement for the next bar in history
+ # # and append new sub-path
+ # new_bars = array[index:index + extra]
+
+ # # x, y coordinates for start of next open/left arm
+ # self.path.moveTo(float(index - self.w), float(new_bars[0]['open']))
+
+ # self.path.addPath(new_history_qpath)
+
+ # self.start_index += extra
+
+ # self.draw_history()
+
+ if just_history:
+ self.update()
+ return
# last bar update
i, o, h, l, last, v = array[-1][
['index', 'open', 'high', 'low', 'close', 'volume']
]
- assert i == self.index - 1
+ # assert i == self.start_index - 1
+ assert i == last_index
body, larm, rarm = self._last_bar_lines
# XXX: is there a faster way to modify this?
@@ -660,12 +778,14 @@ class BarItems(pg.GraphicsObject):
# @timeit
def boundingRect(self):
- # TODO: can we do rect caching to make this faster
+ # Qt docs: https://doc.qt.io/qt-5/qgraphicsitem.html#boundingRect
+
+ # TODO: Can we do rect caching to make this faster
# like `pg.PlotCurveItem` does? In theory it's just
# computing max/min stuff again like we do in the udpate loop
- # anyway.
+ # anyway. Not really sure it's necessary since profiling already
+ # shows this method is faf.
- # Qt docs: https://doc.qt.io/qt-5/qgraphicsitem.html#boundingRect
# boundingRect _must_ indicate the entire area that will be
# drawn on or else we will get artifacts and possibly crashing.
# (in this case, QPicture does all the work of computing the
@@ -673,15 +793,15 @@ class BarItems(pg.GraphicsObject):
# compute aggregate bounding rectangle
lb = self.last_bar.boundingRect()
- hb = self.history.boundingRect()
+ hb = self.path.boundingRect()
# hb = self._h_path.boundingRect()
return QtCore.QRectF(
# top left
QtCore.QPointF(hb.topLeft()),
# total size
- # QtCore.QSizeF(QtCore.QSizeF(lb.size()) + hb.size())
- QtCore.QSizeF(lb.size() + hb.size())
+ QtCore.QSizeF(QtCore.QSizeF(lb.size()) + hb.size())
+ # QtCore.QSizeF(lb.size() + hb.size())
)
@@ -834,7 +954,7 @@ class L1Labels:
chart: 'ChartPlotWidget', # noqa
digits: int = 2,
size_digits: int = 0,
- font_size_inches: float = 4 / 53.,
+ font_size_inches: float = _down_2_font_inches_we_like,
) -> None:
self.chart = chart
@@ -888,7 +1008,9 @@ def level_line(
digits: int = 1,
# size 4 font on 4k screen scaled down, so small-ish.
- font_size_inches: float = 4 / 53.,
+ font_size_inches: float = _down_2_font_inches_we_like,
+
+ show_label: bool = True,
**linelabelkwargs
) -> LevelLine:
@@ -908,6 +1030,7 @@ def level_line(
**linelabelkwargs
)
label.update_from_data(0, level)
+
# TODO: can we somehow figure out a max value from the parent axis?
label._size_br_from_str(label.label_str)
@@ -923,4 +1046,7 @@ def level_line(
chart.plotItem.addItem(line)
+ if not show_label:
+ label.hide()
+
return line