Add basic optional polyline support, draft out downsampling routine
							parent
							
								
									da5d2ef331
								
							
						
					
					
						commit
						93d2c715e7
					
				| 
						 | 
					@ -23,6 +23,7 @@ from typing import Optional
 | 
				
			||||||
import numpy as np
 | 
					import numpy as np
 | 
				
			||||||
import pyqtgraph as pg
 | 
					import pyqtgraph as pg
 | 
				
			||||||
from PyQt5 import QtGui, QtWidgets
 | 
					from PyQt5 import QtGui, QtWidgets
 | 
				
			||||||
 | 
					from PyQt5.QtWidgets import QGraphicsItem
 | 
				
			||||||
from PyQt5.QtCore import (
 | 
					from PyQt5.QtCore import (
 | 
				
			||||||
    Qt,
 | 
					    Qt,
 | 
				
			||||||
    QLineF,
 | 
					    QLineF,
 | 
				
			||||||
| 
						 | 
					@ -94,6 +95,38 @@ _line_styles: dict[str, int] = {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def downsample(
 | 
				
			||||||
 | 
					    x: np.ndarray,
 | 
				
			||||||
 | 
					    y: np.ndarray,
 | 
				
			||||||
 | 
					    bins: int,
 | 
				
			||||||
 | 
					    method: str = 'peak',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					) -> tuple[np.ndarray, np.ndarray]:
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    Downsample x/y data for lesser curve graphics gen.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The "peak" method is originally copied verbatim from
 | 
				
			||||||
 | 
					    ``pyqtgraph.PlotDataItem.getDisplayDataset()``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    match method:
 | 
				
			||||||
 | 
					        case 'peak':
 | 
				
			||||||
 | 
					            ds = bins
 | 
				
			||||||
 | 
					            n = len(x) // ds
 | 
				
			||||||
 | 
					            x1 = np.empty((n, 2))
 | 
				
			||||||
 | 
					            # start of x-values; try to select a somewhat centered point
 | 
				
			||||||
 | 
					            stx = ds//2
 | 
				
			||||||
 | 
					            x1[:] = x[stx:stx+n*ds:ds, np.newaxis]
 | 
				
			||||||
 | 
					            x = x1.reshape(n*2)
 | 
				
			||||||
 | 
					            y1 = np.empty((n, 2))
 | 
				
			||||||
 | 
					            y2 = y[:n*ds].reshape((n, ds))
 | 
				
			||||||
 | 
					            y1[:, 0] = y2.max(axis=1)
 | 
				
			||||||
 | 
					            y1[:, 1] = y2.min(axis=1)
 | 
				
			||||||
 | 
					            y = y1.reshape(n*2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return x, y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO: got a feeling that dropping this inheritance gets us even more speedups
 | 
					# TODO: got a feeling that dropping this inheritance gets us even more speedups
 | 
				
			||||||
class FastAppendCurve(pg.PlotCurveItem):
 | 
					class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
| 
						 | 
					@ -116,17 +149,23 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
        fill_color: Optional[str] = None,
 | 
					        fill_color: Optional[str] = None,
 | 
				
			||||||
        style: str = 'solid',
 | 
					        style: str = 'solid',
 | 
				
			||||||
        name: Optional[str] = None,
 | 
					        name: Optional[str] = None,
 | 
				
			||||||
 | 
					        use_polyline: bool = False,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        **kwargs
 | 
					        **kwargs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._name = name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # TODO: we can probably just dispense with the parent since
 | 
					        # TODO: we can probably just dispense with the parent since
 | 
				
			||||||
        # we're basically only using the pen setting now...
 | 
					        # we're basically only using the pen setting now...
 | 
				
			||||||
        super().__init__(*args, **kwargs)
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
        self._name = name
 | 
					 | 
				
			||||||
        self._xrange: tuple[int, int] = self.dataBounds(ax=0)
 | 
					        self._xrange: tuple[int, int] = self.dataBounds(ax=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # self._last_draw = time.time()
 | 
				
			||||||
 | 
					        self._use_poly = use_polyline
 | 
				
			||||||
 | 
					        self.poly = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # all history of curve is drawn in single px thickness
 | 
					        # all history of curve is drawn in single px thickness
 | 
				
			||||||
        pen = pg.mkPen(hcolor(color))
 | 
					        pen = pg.mkPen(hcolor(color))
 | 
				
			||||||
        pen.setStyle(_line_styles[style])
 | 
					        pen.setStyle(_line_styles[style])
 | 
				
			||||||
| 
						 | 
					@ -153,12 +192,14 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
        # interactions slower (such as zooming) and if so maybe if/when
 | 
					        # interactions slower (such as zooming) and if so maybe if/when
 | 
				
			||||||
        # we implement a "history" mode for the view we disable this in
 | 
					        # we implement a "history" mode for the view we disable this in
 | 
				
			||||||
        # that mode?
 | 
					        # that mode?
 | 
				
			||||||
        if step_mode:
 | 
					        if step_mode or self._use_poly:
 | 
				
			||||||
            # don't enable caching by default for the case where the
 | 
					            # don't enable caching by default for the case where the
 | 
				
			||||||
            # only thing drawn is the "last" line segment which can
 | 
					            # only thing drawn is the "last" line segment which can
 | 
				
			||||||
            # have a weird artifact where it won't be fully drawn to its
 | 
					            # have a weird artifact where it won't be fully drawn to its
 | 
				
			||||||
            # endpoint (something we saw on trade rate curves)
 | 
					            # endpoint (something we saw on trade rate curves)
 | 
				
			||||||
            self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
 | 
					            self.setCacheMode(
 | 
				
			||||||
 | 
					                QGraphicsItem.DeviceCoordinateCache
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update_from_array(
 | 
					    def update_from_array(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
| 
						 | 
					@ -195,6 +236,14 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
            x_out, y_out = x[:-1], y[:-1]
 | 
					            x_out, y_out = x[:-1], y[:-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.path is None or prepend_length > 0:
 | 
					        if self.path is None or prepend_length > 0:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self._use_poly:
 | 
				
			||||||
 | 
					                self.poly = pg.functions.arrayToQPolygonF(
 | 
				
			||||||
 | 
					                    x_out,
 | 
				
			||||||
 | 
					                    y_out,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
                self.path = pg.functions.arrayToQPath(
 | 
					                self.path = pg.functions.arrayToQPath(
 | 
				
			||||||
                    x_out,
 | 
					                    x_out,
 | 
				
			||||||
                    y_out,
 | 
					                    y_out,
 | 
				
			||||||
| 
						 | 
					@ -242,11 +291,18 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
                new_y = y[-append_length - 2:-1]
 | 
					                new_y = y[-append_length - 2:-1]
 | 
				
			||||||
                # print((new_x, new_y))
 | 
					                # print((new_x, new_y))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self._use_poly:
 | 
				
			||||||
 | 
					                union_poly = pg.functions.arrayToQPolygonF(
 | 
				
			||||||
 | 
					                    new_x,
 | 
				
			||||||
 | 
					                    new_y,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
                append_path = pg.functions.arrayToQPath(
 | 
					                append_path = pg.functions.arrayToQPath(
 | 
				
			||||||
                    new_x,
 | 
					                    new_x,
 | 
				
			||||||
                    new_y,
 | 
					                    new_y,
 | 
				
			||||||
                    connect='all',
 | 
					                    connect='all',
 | 
				
			||||||
                # finiteCheck=False,
 | 
					                    finiteCheck=False,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            path = self.path
 | 
					            path = self.path
 | 
				
			||||||
| 
						 | 
					@ -254,6 +310,8 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
            # other merging ideas:
 | 
					            # other merging ideas:
 | 
				
			||||||
            # https://stackoverflow.com/questions/8936225/how-to-merge-qpainterpaths
 | 
					            # https://stackoverflow.com/questions/8936225/how-to-merge-qpainterpaths
 | 
				
			||||||
            if self._step_mode:
 | 
					            if self._step_mode:
 | 
				
			||||||
 | 
					                assert not self._use_poly, 'Dunno howw this worx yet'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # path.addPath(append_path)
 | 
					                # path.addPath(append_path)
 | 
				
			||||||
                self.path.connectPath(append_path)
 | 
					                self.path.connectPath(append_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -268,20 +326,27 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
                #     # path.addPath(append_path)
 | 
					                #     # path.addPath(append_path)
 | 
				
			||||||
                #     # path.closeSubpath()
 | 
					                #     # path.closeSubpath()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                if self._use_poly:
 | 
				
			||||||
 | 
					                    self.poly = self.poly.united(union_poly)
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    # print(f"append_path br: {append_path.boundingRect()}")
 | 
					                    # print(f"append_path br: {append_path.boundingRect()}")
 | 
				
			||||||
                    # self.path.moveTo(new_x[0], new_y[0])
 | 
					                    # self.path.moveTo(new_x[0], new_y[0])
 | 
				
			||||||
                # self.path.connectPath(append_path)
 | 
					                    self.path.connectPath(append_path)
 | 
				
			||||||
                path.connectPath(append_path)
 | 
					                    # path.connectPath(append_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # XXX: lol this causes a hang..
 | 
				
			||||||
 | 
					                    # self.path = self.path.simplified()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.disable_cache()
 | 
					            self.disable_cache()
 | 
				
			||||||
            flip_cache = True
 | 
					            flip_cache = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (
 | 
					        # XXX: do we need this any more?
 | 
				
			||||||
            self._step_mode
 | 
					        # if (
 | 
				
			||||||
        ):
 | 
					        #     self._step_mode
 | 
				
			||||||
            self.disable_cache()
 | 
					        # ):
 | 
				
			||||||
            flip_cache = True
 | 
					        #     self.disable_cache()
 | 
				
			||||||
 | 
					        #     flip_cache = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # print(f"update br: {self.path.boundingRect()}")
 | 
					            # print(f"update br: {self.path.boundingRect()}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -318,7 +383,7 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if flip_cache:
 | 
					        if flip_cache:
 | 
				
			||||||
            # XXX: seems to be needed to avoid artifacts (see above).
 | 
					            # XXX: seems to be needed to avoid artifacts (see above).
 | 
				
			||||||
            self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
 | 
					            self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def disable_cache(self) -> None:
 | 
					    def disable_cache(self) -> None:
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
| 
						 | 
					@ -334,15 +399,22 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
        Compute and then cache our rect.
 | 
					        Compute and then cache our rect.
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
 | 
					        if self._use_poly:
 | 
				
			||||||
 | 
					            if self.poly is None:
 | 
				
			||||||
 | 
					                return QtGui.QPolygonF().boundingRect()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                br = self.boundingRect = self.poly.boundingRect
 | 
				
			||||||
 | 
					                return br()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
            if self.path is None:
 | 
					            if self.path is None:
 | 
				
			||||||
                return QtGui.QPainterPath().boundingRect()
 | 
					                return QtGui.QPainterPath().boundingRect()
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                # dynamically override this method after initial
 | 
					                # dynamically override this method after initial
 | 
				
			||||||
                # path is created to avoid requiring the above None check
 | 
					                # path is created to avoid requiring the above None check
 | 
				
			||||||
            self.boundingRect = self._br
 | 
					                self.boundingRect = self._path_br
 | 
				
			||||||
            return self._br()
 | 
					                return self._path_br()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _br(self):
 | 
					    def _path_br(self):
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
        Post init ``.boundingRect()```.
 | 
					        Post init ``.boundingRect()```.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -373,7 +445,9 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        profiler = pg.debug.Profiler(disabled=not pg_profile_enabled())
 | 
					        profiler = pg.debug.Profiler(
 | 
				
			||||||
 | 
					            # disabled=False, #not pg_profile_enabled(),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        # p.setRenderHint(p.Antialiasing, True)
 | 
					        # p.setRenderHint(p.Antialiasing, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
| 
						 | 
					@ -395,12 +469,26 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
            p.setPen(self.opts['pen'])
 | 
					            p.setPen(self.opts['pen'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # else:
 | 
					        # else:
 | 
				
			||||||
 | 
					        if self._use_poly:
 | 
				
			||||||
 | 
					            assert self.poly
 | 
				
			||||||
 | 
					            p.drawPolyline(self.poly)
 | 
				
			||||||
 | 
					            profiler('.drawPolyline()')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
            p.drawPath(self.path)
 | 
					            p.drawPath(self.path)
 | 
				
			||||||
            profiler('.drawPath()')
 | 
					            profiler('.drawPath()')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # TODO: try out new work from `pyqtgraph` main which
 | 
					        # TODO: try out new work from `pyqtgraph` main which should
 | 
				
			||||||
        # should repair horrid perf:
 | 
					        # repair horrid perf (pretty sure i did and it was still
 | 
				
			||||||
 | 
					        # horrible?):
 | 
				
			||||||
        # https://github.com/pyqtgraph/pyqtgraph/pull/2032
 | 
					        # https://github.com/pyqtgraph/pyqtgraph/pull/2032
 | 
				
			||||||
        # if self._fill:
 | 
					        # if self._fill:
 | 
				
			||||||
        #     brush = self.opts['brush']
 | 
					        #     brush = self.opts['brush']
 | 
				
			||||||
        #     p.fillPath(self.path, brush)
 | 
					        #     p.fillPath(self.path, brush)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # now = time.time()
 | 
				
			||||||
 | 
					        # print(f'DRAW RATE {1/(now - self._last_draw)}')
 | 
				
			||||||
 | 
					        # self._last_draw = now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# import time
 | 
				
			||||||
 | 
					# _last_draw: float = time.time()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue