Make curve graphics timeframe agnostic
Ensure `.boundingRect()` calcs and `.draw_last_datum()` do geo-sizing based on source data instead of presuming some `1.0` unit steps in some spots; we need this to support an epoch index as is needed for overlays. Further, clean out a bunch of old bounding rect calc code and add some commented code for trying out `QRectF.united()` on the path + last datum curve segment. Turns out that approach is slower as per eyeballing the added profiler points.epoch_index
parent
ab1f15506d
commit
9d16299f60
|
@ -28,10 +28,7 @@ from PyQt5.QtWidgets import QGraphicsItem
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
Qt,
|
Qt,
|
||||||
QLineF,
|
QLineF,
|
||||||
QSizeF,
|
|
||||||
QRectF,
|
QRectF,
|
||||||
# QRect,
|
|
||||||
QPointF,
|
|
||||||
)
|
)
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
QPainter,
|
QPainter,
|
||||||
|
@ -89,9 +86,9 @@ class Curve(pg.GraphicsObject):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# sub-type customization methods
|
# sub-type customization methods
|
||||||
sub_br: Optional[Callable] = None
|
|
||||||
sub_paint: Optional[Callable] = None
|
|
||||||
declare_paintables: Optional[Callable] = None
|
declare_paintables: Optional[Callable] = None
|
||||||
|
sub_paint: Optional[Callable] = None
|
||||||
|
# sub_br: Optional[Callable] = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -140,9 +137,7 @@ class Curve(pg.GraphicsObject):
|
||||||
# self.last_step_pen = pg.mkPen(hcolor(color), width=2)
|
# self.last_step_pen = pg.mkPen(hcolor(color), width=2)
|
||||||
self.last_step_pen = pg.mkPen(pen, width=2)
|
self.last_step_pen = pg.mkPen(pen, width=2)
|
||||||
|
|
||||||
# self._last_line: Optional[QLineF] = None
|
|
||||||
self._last_line = QLineF()
|
self._last_line = QLineF()
|
||||||
self._last_w: float = 1
|
|
||||||
|
|
||||||
# flat-top style histogram-like discrete curve
|
# flat-top style histogram-like discrete curve
|
||||||
# self._step_mode: bool = step_mode
|
# self._step_mode: bool = step_mode
|
||||||
|
@ -231,8 +226,8 @@ class Curve(pg.GraphicsObject):
|
||||||
self.path.clear()
|
self.path.clear()
|
||||||
|
|
||||||
if self.fast_path:
|
if self.fast_path:
|
||||||
# self.fast_path.clear()
|
self.fast_path.clear()
|
||||||
self.fast_path = None
|
# self.fast_path = None
|
||||||
|
|
||||||
@cm
|
@cm
|
||||||
def reset_cache(self) -> None:
|
def reset_cache(self) -> None:
|
||||||
|
@ -252,77 +247,81 @@ class Curve(pg.GraphicsObject):
|
||||||
self.boundingRect = self._path_br
|
self.boundingRect = self._path_br
|
||||||
return self._path_br()
|
return self._path_br()
|
||||||
|
|
||||||
|
# Qt docs: https://doc.qt.io/qt-5/qgraphicsitem.html#boundingRect
|
||||||
def _path_br(self):
|
def _path_br(self):
|
||||||
'''
|
'''
|
||||||
Post init ``.boundingRect()```.
|
Post init ``.boundingRect()```.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# hb = self.path.boundingRect()
|
profiler = Profiler(
|
||||||
hb = self.path.controlPointRect()
|
msg=f'Curve.boundingRect(): `{self._name}`',
|
||||||
hb_size = hb.size()
|
disabled=not pg_profile_enabled(),
|
||||||
|
ms_threshold=ms_slower_then,
|
||||||
|
)
|
||||||
|
pr = self.path.controlPointRect()
|
||||||
|
hb_tl, hb_br = (
|
||||||
|
pr.topLeft(),
|
||||||
|
pr.bottomRight(),
|
||||||
|
)
|
||||||
|
mn_y = hb_tl.y()
|
||||||
|
mx_y = hb_br.y()
|
||||||
|
most_left = hb_tl.x()
|
||||||
|
most_right = hb_br.x()
|
||||||
|
profiler('calc path vertices')
|
||||||
|
|
||||||
fp = self.fast_path
|
# TODO: if/when we get fast path appends working in the
|
||||||
if fp:
|
# `Renderer`, then we might need to actually use this..
|
||||||
fhb = fp.controlPointRect()
|
# fp = self.fast_path
|
||||||
hb_size = fhb.size() + hb_size
|
# if fp:
|
||||||
|
# fhb = fp.controlPointRect()
|
||||||
|
# # hb_size = fhb.size() + hb_size
|
||||||
|
# br = pr.united(fhb)
|
||||||
|
|
||||||
# print(f'hb_size: {hb_size}')
|
# XXX: *was* a way to allow sub-types to extend the
|
||||||
|
# boundingrect calc, but in the one use case for a step curve
|
||||||
|
# doesn't seem like we need it as long as the last line segment
|
||||||
|
# is drawn as it is?
|
||||||
|
|
||||||
# if self._last_step_rect:
|
# sbr = self.sub_br
|
||||||
# hb_size += self._last_step_rect.size()
|
# if sbr:
|
||||||
|
# # w, h = self.sub_br(w, h)
|
||||||
|
# sub_br = sbr()
|
||||||
|
# br = br.united(sub_br)
|
||||||
|
|
||||||
# if self._line:
|
|
||||||
# br = self._last_step_rect.bottomRight()
|
|
||||||
|
|
||||||
# tl = QPointF(
|
|
||||||
# # self._vr[0],
|
|
||||||
# # hb.topLeft().y(),
|
|
||||||
# # 0,
|
|
||||||
# # hb_size.height() + 1
|
|
||||||
# )
|
|
||||||
|
|
||||||
# br = self._last_step_rect.bottomRight()
|
|
||||||
|
|
||||||
w = hb_size.width()
|
|
||||||
h = hb_size.height()
|
|
||||||
|
|
||||||
sbr = self.sub_br
|
|
||||||
if sbr:
|
|
||||||
w, h = self.sub_br(w, h)
|
|
||||||
else:
|
|
||||||
# assume plain line graphic and use
|
# assume plain line graphic and use
|
||||||
# default unit step in each direction.
|
# default unit step in each direction.
|
||||||
|
ll = self._last_line
|
||||||
|
y1, y2 = ll.y1(), ll.y2()
|
||||||
|
x1, x2 = ll.x1(), ll.x2()
|
||||||
|
|
||||||
# only on a plane line do we include
|
ymn = min(y1, y2, mn_y)
|
||||||
# and extra index step's worth of width
|
ymx = max(y1, y2, mx_y)
|
||||||
# since in the step case the end of the curve
|
most_left = min(x1, x2, most_left)
|
||||||
# actually terminates earlier so we don't need
|
most_right = max(x1, x2, most_right)
|
||||||
# this for the last step.
|
|
||||||
w += self._last_w
|
|
||||||
# ll = self._last_line
|
|
||||||
h += 1 # ll.y2() - ll.y1()
|
|
||||||
|
|
||||||
# br = QPointF(
|
profiler('calc last line vertices')
|
||||||
# self._vr[-1],
|
# ll_br = QRectF(
|
||||||
# # tl.x() + w,
|
# x1,
|
||||||
# tl.y() + h,
|
# ymn,
|
||||||
|
# # NOTE: a legacy snippet, not sure if it still applies?
|
||||||
|
# # only on a plane line do we include
|
||||||
|
# # and extra index step's worth of width
|
||||||
|
# # since in the step case the end of the curve
|
||||||
|
# # actually terminates earlier so we don't need
|
||||||
|
# # this for the last step.
|
||||||
|
# x2 - x1 + 1,
|
||||||
|
# ymx,
|
||||||
# )
|
# )
|
||||||
|
# br = br.united(ll_br)
|
||||||
|
# profiler('calc united rects')
|
||||||
|
# return br
|
||||||
|
|
||||||
br = QRectF(
|
return QRectF(
|
||||||
|
most_left,
|
||||||
# top left
|
ymn,
|
||||||
# hb.topLeft()
|
most_right - most_left + 1,
|
||||||
# tl,
|
ymx,
|
||||||
QPointF(hb.topLeft()),
|
|
||||||
|
|
||||||
# br,
|
|
||||||
# total size
|
|
||||||
# QSizeF(hb_size)
|
|
||||||
# hb_size,
|
|
||||||
QSizeF(w, h)
|
|
||||||
)
|
)
|
||||||
# print(f'bounding rect: {br}')
|
|
||||||
return br
|
|
||||||
|
|
||||||
def paint(
|
def paint(
|
||||||
self,
|
self,
|
||||||
|
@ -359,6 +358,7 @@ class Curve(pg.GraphicsObject):
|
||||||
|
|
||||||
fp = self.fast_path
|
fp = self.fast_path
|
||||||
if fp:
|
if fp:
|
||||||
|
# print("DRAWING PATH")
|
||||||
p.drawPath(fp)
|
p.drawPath(fp)
|
||||||
profiler('.drawPath(fast_path)')
|
profiler('.drawPath(fast_path)')
|
||||||
|
|
||||||
|
@ -450,17 +450,20 @@ class StepCurve(Curve):
|
||||||
y = src_data[array_key]
|
y = src_data[array_key]
|
||||||
|
|
||||||
x_last = x[-1]
|
x_last = x[-1]
|
||||||
|
x_2last = x[-2]
|
||||||
y_last = y[-1]
|
y_last = y[-1]
|
||||||
|
step_size = x_last - x_2last
|
||||||
|
half_step = step_size / 2
|
||||||
|
|
||||||
# lol, commenting this makes step curves
|
# lol, commenting this makes step curves
|
||||||
# all "black" for me :eyeroll:..
|
# all "black" for me :eyeroll:..
|
||||||
self._last_line = QLineF(
|
self._last_line = QLineF(
|
||||||
x_last - w, 0,
|
x_2last, 0,
|
||||||
x_last + w, 0,
|
x_last, 0,
|
||||||
)
|
)
|
||||||
self._last_step_rect = QRectF(
|
self._last_step_rect = QRectF(
|
||||||
x_last - w, 0,
|
x_last - half_step, 0,
|
||||||
x_last + w, y_last,
|
step_size, y_last,
|
||||||
)
|
)
|
||||||
return x, y
|
return x, y
|
||||||
|
|
||||||
|
@ -475,11 +478,8 @@ class StepCurve(Curve):
|
||||||
p.fillRect(self._last_step_rect, self._brush)
|
p.fillRect(self._last_step_rect, self._brush)
|
||||||
profiler('.fillRect()')
|
profiler('.fillRect()')
|
||||||
|
|
||||||
def sub_br(
|
# def sub_br(
|
||||||
self,
|
# self,
|
||||||
path_w: float,
|
# parent_br: QRectF | None = None,
|
||||||
path_h: float,
|
# ) -> QRectF:
|
||||||
|
# return self._last_step_rect
|
||||||
) -> (float, float):
|
|
||||||
# passthrough
|
|
||||||
return path_w, path_h
|
|
||||||
|
|
|
@ -25,8 +25,15 @@ from typing import (
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import (
|
||||||
from PyQt5.QtCore import QLineF, QPointF
|
QtGui,
|
||||||
|
QtWidgets,
|
||||||
|
)
|
||||||
|
from PyQt5.QtCore import (
|
||||||
|
QLineF,
|
||||||
|
QRectF,
|
||||||
|
)
|
||||||
|
|
||||||
from PyQt5.QtGui import QPainterPath
|
from PyQt5.QtGui import QPainterPath
|
||||||
|
|
||||||
from .._profile import pg_profile_enabled, ms_slower_then
|
from .._profile import pg_profile_enabled, ms_slower_then
|
||||||
|
@ -114,8 +121,13 @@ class BarItems(pg.GraphicsObject):
|
||||||
# we expect the downsample curve report this.
|
# we expect the downsample curve report this.
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def boundingRect(self):
|
|
||||||
# Qt docs: https://doc.qt.io/qt-5/qgraphicsitem.html#boundingRect
|
# Qt docs: https://doc.qt.io/qt-5/qgraphicsitem.html#boundingRect
|
||||||
|
def boundingRect(self):
|
||||||
|
profiler = Profiler(
|
||||||
|
msg=f'BarItems.boundingRect(): `{self._name}`',
|
||||||
|
disabled=not pg_profile_enabled(),
|
||||||
|
ms_threshold=ms_slower_then,
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: Can we do rect caching to make this faster
|
# TODO: Can we do rect caching to make this faster
|
||||||
# like `pg.PlotCurveItem` does? In theory it's just
|
# like `pg.PlotCurveItem` does? In theory it's just
|
||||||
|
@ -135,32 +147,53 @@ class BarItems(pg.GraphicsObject):
|
||||||
hb.topLeft(),
|
hb.topLeft(),
|
||||||
hb.bottomRight(),
|
hb.bottomRight(),
|
||||||
)
|
)
|
||||||
|
mn_y = hb_tl.y()
|
||||||
|
mx_y = hb_br.y()
|
||||||
|
most_left = hb_tl.x()
|
||||||
|
most_right = hb_br.x()
|
||||||
|
profiler('calc path vertices')
|
||||||
|
|
||||||
# need to include last bar height or BR will be off
|
# need to include last bar height or BR will be off
|
||||||
mx_y = hb_br.y()
|
# OHLC line segments: [hl, o, c]
|
||||||
mn_y = hb_tl.y()
|
last_lines: tuple[QLineF] | None = self._last_bar_lines
|
||||||
|
|
||||||
last_lines = self._last_bar_lines
|
|
||||||
if last_lines:
|
if last_lines:
|
||||||
body_line = self._last_bar_lines[0]
|
(
|
||||||
if body_line:
|
hl,
|
||||||
mx_y = max(mx_y, max(body_line.y1(), body_line.y2()))
|
o,
|
||||||
mn_y = min(mn_y, min(body_line.y1(), body_line.y2()))
|
c,
|
||||||
|
) = last_lines
|
||||||
|
most_right = c.x2() + 1
|
||||||
|
ymx = ymn = c.y2()
|
||||||
|
|
||||||
return QtCore.QRectF(
|
if hl:
|
||||||
|
y1, y2 = hl.y1(), hl.y2()
|
||||||
|
ymn = min(y1, y2)
|
||||||
|
ymx = max(y1, y2)
|
||||||
|
mx_y = max(ymx, mx_y)
|
||||||
|
mn_y = min(ymn, mn_y)
|
||||||
|
|
||||||
# top left
|
profiler('calc last bar vertices')
|
||||||
QPointF(
|
# TODO: see if this br uniting works faster?
|
||||||
hb_tl.x(),
|
# last_bar_rect = QRectF(
|
||||||
|
# o.x1(),
|
||||||
|
# ymn,
|
||||||
|
# c.x2() - o.x1() + 1,
|
||||||
|
# ymx,
|
||||||
|
# )
|
||||||
|
# tot_br = hb.united(last_bar_rect)
|
||||||
|
# print(
|
||||||
|
# f'last datum bar br: {last_bar_rect}\n'
|
||||||
|
# f'path br: {hb}\n'
|
||||||
|
# f'sum br: {tot_br}\n'
|
||||||
|
# )
|
||||||
|
# profiler('calc united rects')
|
||||||
|
# return tot_br
|
||||||
|
|
||||||
|
return QRectF(
|
||||||
|
most_left,
|
||||||
mn_y,
|
mn_y,
|
||||||
),
|
most_right - most_left + 1,
|
||||||
|
mx_y - mn_y,
|
||||||
# bottom right
|
|
||||||
QPointF(
|
|
||||||
hb_br.x() + 1,
|
|
||||||
mx_y,
|
|
||||||
)
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def paint(
|
def paint(
|
||||||
|
@ -213,11 +246,15 @@ class BarItems(pg.GraphicsObject):
|
||||||
|
|
||||||
# relevant fields
|
# relevant fields
|
||||||
ohlc = src_data[fields]
|
ohlc = src_data[fields]
|
||||||
last_row = ohlc[-1:]
|
# last_row = ohlc[-1:]
|
||||||
|
|
||||||
# individual values
|
# individual values
|
||||||
last_row = i, o, h, l, last = ohlc[-1]
|
last_row = i, o, h, l, last = ohlc[-1]
|
||||||
|
|
||||||
|
# times = src_data['time']
|
||||||
|
# if times[-1] - times[-2]:
|
||||||
|
# breakpoint()
|
||||||
|
|
||||||
# generate new lines objects for updatable "current bar"
|
# generate new lines objects for updatable "current bar"
|
||||||
self._last_bar_lines = bar_from_ohlc_row(last_row)
|
self._last_bar_lines = bar_from_ohlc_row(last_row)
|
||||||
|
|
||||||
|
@ -248,4 +285,5 @@ class BarItems(pg.GraphicsObject):
|
||||||
# date / from some previous sample. It's weird though
|
# date / from some previous sample. It's weird though
|
||||||
# because i've seen it do this to bars i - 3 back?
|
# because i've seen it do this to bars i - 3 back?
|
||||||
|
|
||||||
|
# return ohlc['time'], ohlc['close']
|
||||||
return ohlc['index'], ohlc['close']
|
return ohlc['index'], ohlc['close']
|
||||||
|
|
Loading…
Reference in New Issue