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.pre_viz_calls
							parent
							
								
									d6ae75d743
								
							
						
					
					
						commit
						d71045400e
					
				| 
						 | 
				
			
			@ -28,10 +28,7 @@ from PyQt5.QtWidgets import QGraphicsItem
 | 
			
		|||
from PyQt5.QtCore import (
 | 
			
		||||
    Qt,
 | 
			
		||||
    QLineF,
 | 
			
		||||
    QSizeF,
 | 
			
		||||
    QRectF,
 | 
			
		||||
    # QRect,
 | 
			
		||||
    QPointF,
 | 
			
		||||
)
 | 
			
		||||
from PyQt5.QtGui import (
 | 
			
		||||
    QPainter,
 | 
			
		||||
| 
						 | 
				
			
			@ -89,9 +86,9 @@ class Curve(pg.GraphicsObject):
 | 
			
		|||
    '''
 | 
			
		||||
 | 
			
		||||
    # sub-type customization methods
 | 
			
		||||
    sub_br: Optional[Callable] = None
 | 
			
		||||
    sub_paint: Optional[Callable] = None
 | 
			
		||||
    declare_paintables: Optional[Callable] = None
 | 
			
		||||
    sub_paint: Optional[Callable] = None
 | 
			
		||||
    # sub_br: Optional[Callable] = None
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        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(pen, width=2)
 | 
			
		||||
 | 
			
		||||
        # self._last_line: Optional[QLineF] = None
 | 
			
		||||
        self._last_line = QLineF()
 | 
			
		||||
        self._last_w: float = 1
 | 
			
		||||
 | 
			
		||||
        # flat-top style histogram-like discrete curve
 | 
			
		||||
        # self._step_mode: bool = step_mode
 | 
			
		||||
| 
						 | 
				
			
			@ -231,8 +226,8 @@ class Curve(pg.GraphicsObject):
 | 
			
		|||
            self.path.clear()
 | 
			
		||||
 | 
			
		||||
            if self.fast_path:
 | 
			
		||||
                # self.fast_path.clear()
 | 
			
		||||
                self.fast_path = None
 | 
			
		||||
                self.fast_path.clear()
 | 
			
		||||
                # self.fast_path = None
 | 
			
		||||
 | 
			
		||||
    @cm
 | 
			
		||||
    def reset_cache(self) -> None:
 | 
			
		||||
| 
						 | 
				
			
			@ -252,77 +247,81 @@ class Curve(pg.GraphicsObject):
 | 
			
		|||
            self.boundingRect = self._path_br
 | 
			
		||||
            return self._path_br()
 | 
			
		||||
 | 
			
		||||
    # Qt docs: https://doc.qt.io/qt-5/qgraphicsitem.html#boundingRect
 | 
			
		||||
    def _path_br(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Post init ``.boundingRect()```.
 | 
			
		||||
 | 
			
		||||
        '''
 | 
			
		||||
        # hb = self.path.boundingRect()
 | 
			
		||||
        hb = self.path.controlPointRect()
 | 
			
		||||
        hb_size = hb.size()
 | 
			
		||||
 | 
			
		||||
        fp = self.fast_path
 | 
			
		||||
        if fp:
 | 
			
		||||
            fhb = fp.controlPointRect()
 | 
			
		||||
            hb_size = fhb.size() + hb_size
 | 
			
		||||
 | 
			
		||||
        # print(f'hb_size: {hb_size}')
 | 
			
		||||
 | 
			
		||||
        # if self._last_step_rect:
 | 
			
		||||
        #     hb_size += self._last_step_rect.size()
 | 
			
		||||
 | 
			
		||||
        # 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
 | 
			
		||||
            # default unit step in each direction.
 | 
			
		||||
 | 
			
		||||
            # 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.
 | 
			
		||||
            w += self._last_w
 | 
			
		||||
            # ll = self._last_line
 | 
			
		||||
            h += 1  # ll.y2() - ll.y1()
 | 
			
		||||
 | 
			
		||||
        # br = QPointF(
 | 
			
		||||
        #     self._vr[-1],
 | 
			
		||||
        #     # tl.x() + w,
 | 
			
		||||
        #     tl.y() + h,
 | 
			
		||||
        # )
 | 
			
		||||
 | 
			
		||||
        br = QRectF(
 | 
			
		||||
 | 
			
		||||
            # top left
 | 
			
		||||
            # hb.topLeft()
 | 
			
		||||
            # tl,
 | 
			
		||||
            QPointF(hb.topLeft()),
 | 
			
		||||
 | 
			
		||||
            # br,
 | 
			
		||||
            # total size
 | 
			
		||||
            # QSizeF(hb_size)
 | 
			
		||||
            # hb_size,
 | 
			
		||||
            QSizeF(w, h)
 | 
			
		||||
        profiler = Profiler(
 | 
			
		||||
            msg=f'Curve.boundingRect(): `{self._name}`',
 | 
			
		||||
            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')
 | 
			
		||||
 | 
			
		||||
        # TODO: if/when we get fast path appends working in the
 | 
			
		||||
        # `Renderer`, then we might need to actually use this..
 | 
			
		||||
        # fp = self.fast_path
 | 
			
		||||
        # if fp:
 | 
			
		||||
        #     fhb = fp.controlPointRect()
 | 
			
		||||
        #     # hb_size = fhb.size() + hb_size
 | 
			
		||||
        #     br = pr.united(fhb)
 | 
			
		||||
 | 
			
		||||
        # 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?
 | 
			
		||||
 | 
			
		||||
        # sbr = self.sub_br
 | 
			
		||||
        # if sbr:
 | 
			
		||||
        #     # w, h = self.sub_br(w, h)
 | 
			
		||||
        #     sub_br = sbr()
 | 
			
		||||
        #     br = br.united(sub_br)
 | 
			
		||||
 | 
			
		||||
        # assume plain line graphic and use
 | 
			
		||||
        # default unit step in each direction.
 | 
			
		||||
        ll = self._last_line
 | 
			
		||||
        y1, y2 = ll.y1(), ll.y2()
 | 
			
		||||
        x1, x2 = ll.x1(), ll.x2()
 | 
			
		||||
 | 
			
		||||
        ymn = min(y1, y2, mn_y)
 | 
			
		||||
        ymx = max(y1, y2, mx_y)
 | 
			
		||||
        most_left = min(x1, x2, most_left)
 | 
			
		||||
        most_right = max(x1, x2, most_right)
 | 
			
		||||
 | 
			
		||||
        profiler('calc last line vertices')
 | 
			
		||||
        # ll_br = QRectF(
 | 
			
		||||
        #     x1,
 | 
			
		||||
        #     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
 | 
			
		||||
 | 
			
		||||
        return QRectF(
 | 
			
		||||
            most_left,
 | 
			
		||||
            ymn,
 | 
			
		||||
            most_right - most_left + 1,
 | 
			
		||||
            ymx,
 | 
			
		||||
        )
 | 
			
		||||
        # print(f'bounding rect: {br}')
 | 
			
		||||
        return br
 | 
			
		||||
 | 
			
		||||
    def paint(
 | 
			
		||||
        self,
 | 
			
		||||
| 
						 | 
				
			
			@ -359,6 +358,7 @@ class Curve(pg.GraphicsObject):
 | 
			
		|||
 | 
			
		||||
        fp = self.fast_path
 | 
			
		||||
        if fp:
 | 
			
		||||
            # print("DRAWING PATH")
 | 
			
		||||
            p.drawPath(fp)
 | 
			
		||||
            profiler('.drawPath(fast_path)')
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -450,17 +450,20 @@ class StepCurve(Curve):
 | 
			
		|||
        y = src_data[array_key]
 | 
			
		||||
 | 
			
		||||
        x_last = x[-1]
 | 
			
		||||
        x_2last = x[-2]
 | 
			
		||||
        y_last = y[-1]
 | 
			
		||||
        step_size = x_last - x_2last
 | 
			
		||||
        half_step = step_size / 2
 | 
			
		||||
 | 
			
		||||
        # lol, commenting this makes step curves
 | 
			
		||||
        # all "black" for me :eyeroll:..
 | 
			
		||||
        self._last_line = QLineF(
 | 
			
		||||
            x_last - w, 0,
 | 
			
		||||
            x_last + w, 0,
 | 
			
		||||
            x_2last, 0,
 | 
			
		||||
            x_last, 0,
 | 
			
		||||
        )
 | 
			
		||||
        self._last_step_rect = QRectF(
 | 
			
		||||
            x_last - w, 0,
 | 
			
		||||
            x_last + w, y_last,
 | 
			
		||||
            x_last - half_step, 0,
 | 
			
		||||
            step_size, y_last,
 | 
			
		||||
        )
 | 
			
		||||
        return x, y
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -475,11 +478,8 @@ class StepCurve(Curve):
 | 
			
		|||
        p.fillRect(self._last_step_rect, self._brush)
 | 
			
		||||
        profiler('.fillRect()')
 | 
			
		||||
 | 
			
		||||
    def sub_br(
 | 
			
		||||
        self,
 | 
			
		||||
        path_w: float,
 | 
			
		||||
        path_h: float,
 | 
			
		||||
 | 
			
		||||
    ) -> (float, float):
 | 
			
		||||
        # passthrough
 | 
			
		||||
        return path_w, path_h
 | 
			
		||||
    # def sub_br(
 | 
			
		||||
    #     self,
 | 
			
		||||
    #     parent_br: QRectF | None = None,
 | 
			
		||||
    # ) -> QRectF:
 | 
			
		||||
    #     return self._last_step_rect
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,8 +25,15 @@ from typing import (
 | 
			
		|||
 | 
			
		||||
import numpy as np
 | 
			
		||||
import pyqtgraph as pg
 | 
			
		||||
from PyQt5 import QtCore, QtGui, QtWidgets
 | 
			
		||||
from PyQt5.QtCore import QLineF, QPointF
 | 
			
		||||
from PyQt5 import (
 | 
			
		||||
    QtGui,
 | 
			
		||||
    QtWidgets,
 | 
			
		||||
)
 | 
			
		||||
from PyQt5.QtCore import (
 | 
			
		||||
    QLineF,
 | 
			
		||||
    QRectF,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from PyQt5.QtGui import QPainterPath
 | 
			
		||||
 | 
			
		||||
from .._profile import pg_profile_enabled, ms_slower_then
 | 
			
		||||
| 
						 | 
				
			
			@ -114,8 +121,13 @@ class BarItems(pg.GraphicsObject):
 | 
			
		|||
        # we expect the downsample curve report this.
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    # Qt docs: https://doc.qt.io/qt-5/qgraphicsitem.html#boundingRect
 | 
			
		||||
    def boundingRect(self):
 | 
			
		||||
        # Qt docs: https://doc.qt.io/qt-5/qgraphicsitem.html#boundingRect
 | 
			
		||||
        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
 | 
			
		||||
        # like `pg.PlotCurveItem` does? In theory it's just
 | 
			
		||||
| 
						 | 
				
			
			@ -135,32 +147,53 @@ class BarItems(pg.GraphicsObject):
 | 
			
		|||
            hb.topLeft(),
 | 
			
		||||
            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
 | 
			
		||||
        mx_y = hb_br.y()
 | 
			
		||||
        mn_y = hb_tl.y()
 | 
			
		||||
 | 
			
		||||
        last_lines = self._last_bar_lines
 | 
			
		||||
        # OHLC line segments: [hl, o, c]
 | 
			
		||||
        last_lines: tuple[QLineF] | None = self._last_bar_lines
 | 
			
		||||
        if last_lines:
 | 
			
		||||
            body_line = self._last_bar_lines[0]
 | 
			
		||||
            if body_line:
 | 
			
		||||
                mx_y = max(mx_y, max(body_line.y1(), body_line.y2()))
 | 
			
		||||
                mn_y = min(mn_y, min(body_line.y1(), body_line.y2()))
 | 
			
		||||
            (
 | 
			
		||||
                hl,
 | 
			
		||||
                o,
 | 
			
		||||
                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
 | 
			
		||||
            QPointF(
 | 
			
		||||
                hb_tl.x(),
 | 
			
		||||
                mn_y,
 | 
			
		||||
            ),
 | 
			
		||||
 | 
			
		||||
            # bottom right
 | 
			
		||||
            QPointF(
 | 
			
		||||
                hb_br.x() + 1,
 | 
			
		||||
                mx_y,
 | 
			
		||||
            )
 | 
			
		||||
                profiler('calc last bar vertices')
 | 
			
		||||
            # TODO: see if this br uniting works faster?
 | 
			
		||||
            # 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,
 | 
			
		||||
            most_right - most_left + 1,
 | 
			
		||||
            mx_y - mn_y,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def paint(
 | 
			
		||||
| 
						 | 
				
			
			@ -213,11 +246,15 @@ class BarItems(pg.GraphicsObject):
 | 
			
		|||
 | 
			
		||||
        # relevant fields
 | 
			
		||||
        ohlc = src_data[fields]
 | 
			
		||||
        last_row = ohlc[-1:]
 | 
			
		||||
        # last_row = ohlc[-1:]
 | 
			
		||||
 | 
			
		||||
        # individual values
 | 
			
		||||
        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"
 | 
			
		||||
        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
 | 
			
		||||
            # because i've seen it do this to bars i - 3 back?
 | 
			
		||||
 | 
			
		||||
        # return ohlc['time'], ohlc['close']
 | 
			
		||||
        return ohlc['index'], ohlc['close']
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue