More WIP, implement `BarItems` rendering in `Flow.update_graphics()`
							parent
							
								
									4877aee729
								
							
						
					
					
						commit
						bafa1a02a5
					
				| 
						 | 
				
			
			@ -23,6 +23,8 @@ incremental update.
 | 
			
		|||
 | 
			
		||||
'''
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
from functools import partial
 | 
			
		||||
import time
 | 
			
		||||
from typing import (
 | 
			
		||||
    Optional,
 | 
			
		||||
    Callable,
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +32,7 @@ from typing import (
 | 
			
		|||
 | 
			
		||||
import msgspec
 | 
			
		||||
import numpy as np
 | 
			
		||||
from numpy.lib import recfunctions as rfn
 | 
			
		||||
import pyqtgraph as pg
 | 
			
		||||
from PyQt5.QtGui import QPainterPath
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +40,7 @@ from ..data._sharedmem import (
 | 
			
		|||
    ShmArray,
 | 
			
		||||
    # attach_shm_array
 | 
			
		||||
)
 | 
			
		||||
from .._profile import pg_profile_enabled, ms_slower_then
 | 
			
		||||
from ._ohlc import (
 | 
			
		||||
    BarItems,
 | 
			
		||||
    gen_qpath,
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +50,12 @@ from ._curve import (
 | 
			
		|||
)
 | 
			
		||||
from ._compression import (
 | 
			
		||||
    ohlc_flatten,
 | 
			
		||||
    ds_m4,
 | 
			
		||||
)
 | 
			
		||||
from ..log import get_logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = get_logger(__name__)
 | 
			
		||||
 | 
			
		||||
# class FlowsTable(msgspec.Struct):
 | 
			
		||||
#     '''
 | 
			
		||||
| 
						 | 
				
			
			@ -72,11 +81,63 @@ from ._compression import (
 | 
			
		|||
#     return cls(shm)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def rowarr_to_path(
 | 
			
		||||
    rows_array: np.ndarray,
 | 
			
		||||
    x_basis: np.ndarray,
 | 
			
		||||
    flow: Flow,
 | 
			
		||||
 | 
			
		||||
) -> QPainterPath:
 | 
			
		||||
 | 
			
		||||
    # TODO: we could in theory use ``numba`` to flatten
 | 
			
		||||
    # if needed?
 | 
			
		||||
 | 
			
		||||
    # to 1d
 | 
			
		||||
    y = rows_array.flatten()
 | 
			
		||||
 | 
			
		||||
    return pg.functions.arrayToQPath(
 | 
			
		||||
        # these get passed at render call time
 | 
			
		||||
        x=x_basis[:y.size],
 | 
			
		||||
        y=y,
 | 
			
		||||
        connect='all',
 | 
			
		||||
        finiteCheck=False,
 | 
			
		||||
        path=flow.path,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def ohlc_flat_view(
 | 
			
		||||
    ohlc_shm: ShmArray,
 | 
			
		||||
 | 
			
		||||
    # XXX: we bind this in currently..
 | 
			
		||||
    x_basis: np.ndarray,
 | 
			
		||||
 | 
			
		||||
    # vr: Optional[slice] = None,
 | 
			
		||||
 | 
			
		||||
) -> np.ndarray:
 | 
			
		||||
    '''
 | 
			
		||||
    Return flattened-non-copy view into an OHLC shm array.
 | 
			
		||||
 | 
			
		||||
    '''
 | 
			
		||||
    ohlc = ohlc_shm._array[['open', 'high', 'low', 'close']]
 | 
			
		||||
    # if vr:
 | 
			
		||||
    #     ohlc = ohlc[vr]
 | 
			
		||||
    #     x = x_basis[vr]
 | 
			
		||||
 | 
			
		||||
    unstructured = rfn.structured_to_unstructured(
 | 
			
		||||
        ohlc,
 | 
			
		||||
        copy=False,
 | 
			
		||||
    )
 | 
			
		||||
    # breakpoint()
 | 
			
		||||
    y = unstructured.flatten()
 | 
			
		||||
    x = x_basis[:y.size]
 | 
			
		||||
    return x, y
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Flow(msgspec.Struct):  # , frozen=True):
 | 
			
		||||
    '''
 | 
			
		||||
    (FinancialSignal-)Flow compound type which wraps a real-time
 | 
			
		||||
    graphics (curve) and its backing data stream together for high level
 | 
			
		||||
    access and control.
 | 
			
		||||
    (Financial Signal-)Flow compound type which wraps a real-time
 | 
			
		||||
    shm array stream with displayed graphics (curves, charts)
 | 
			
		||||
    for high level access and control as well as efficient incremental
 | 
			
		||||
    update.
 | 
			
		||||
 | 
			
		||||
    The intention is for this type to eventually be capable of shm-passing
 | 
			
		||||
    of incrementally updated graphics stream data between actors.
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +150,8 @@ class Flow(msgspec.Struct):  # , frozen=True):
 | 
			
		|||
 | 
			
		||||
    is_ohlc: bool = False
 | 
			
		||||
    render: bool = True  # toggle for display loop
 | 
			
		||||
    flat: Optional[ShmArray] = None
 | 
			
		||||
    x_basis: Optional[np.ndarray] = None
 | 
			
		||||
 | 
			
		||||
    _last_uppx: float = 0
 | 
			
		||||
    _in_ds: bool = False
 | 
			
		||||
| 
						 | 
				
			
			@ -96,6 +159,7 @@ class Flow(msgspec.Struct):  # , frozen=True):
 | 
			
		|||
    _graphics_tranform_fn: Optional[Callable[ShmArray, np.ndarray]] = None
 | 
			
		||||
 | 
			
		||||
    # map from uppx -> (downsampled data, incremental graphics)
 | 
			
		||||
    _src_r: Optional[Renderer] = None
 | 
			
		||||
    _render_table: dict[
 | 
			
		||||
        Optional[int],
 | 
			
		||||
        tuple[Renderer, pg.GraphicsItem],
 | 
			
		||||
| 
						 | 
				
			
			@ -215,7 +279,9 @@ class Flow(msgspec.Struct):  # , frozen=True):
 | 
			
		|||
            int, int, np.ndarray,
 | 
			
		||||
            int, int, np.ndarray,
 | 
			
		||||
    ]:
 | 
			
		||||
        # read call
 | 
			
		||||
        array = self.shm.array
 | 
			
		||||
 | 
			
		||||
        indexes = array['index']
 | 
			
		||||
        ifirst = indexes[0]
 | 
			
		||||
        ilast = indexes[-1]
 | 
			
		||||
| 
						 | 
				
			
			@ -245,6 +311,8 @@ class Flow(msgspec.Struct):  # , frozen=True):
 | 
			
		|||
        render: bool = True,
 | 
			
		||||
        array_key: Optional[str] = None,
 | 
			
		||||
 | 
			
		||||
        profiler=None,
 | 
			
		||||
 | 
			
		||||
        **kwargs,
 | 
			
		||||
 | 
			
		||||
    ) -> pg.GraphicsObject:
 | 
			
		||||
| 
						 | 
				
			
			@ -253,8 +321,19 @@ class Flow(msgspec.Struct):  # , frozen=True):
 | 
			
		|||
        render to graphics.
 | 
			
		||||
 | 
			
		||||
        '''
 | 
			
		||||
 | 
			
		||||
        profiler = profiler or pg.debug.Profiler(
 | 
			
		||||
            msg=f'Flow.update_graphics() for {self.name}',
 | 
			
		||||
            disabled=not pg_profile_enabled(),
 | 
			
		||||
            gt=ms_slower_then,
 | 
			
		||||
            delayed=True,
 | 
			
		||||
        )
 | 
			
		||||
        # shm read and slice to view
 | 
			
		||||
        read = xfirst, xlast, array, ivl, ivr, in_view = self.read()
 | 
			
		||||
        read = (
 | 
			
		||||
            xfirst, xlast, array,
 | 
			
		||||
            ivl, ivr, in_view,
 | 
			
		||||
        ) = self.read()
 | 
			
		||||
        profiler('read src shm data')
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            not in_view.size
 | 
			
		||||
| 
						 | 
				
			
			@ -265,100 +344,182 @@ class Flow(msgspec.Struct):  # , frozen=True):
 | 
			
		|||
        graphics = self.graphics
 | 
			
		||||
        if isinstance(graphics, BarItems):
 | 
			
		||||
 | 
			
		||||
            # ugh, not luvin dis, should we have just a designated
 | 
			
		||||
            # instance var?
 | 
			
		||||
            r = self._render_table.get('src')
 | 
			
		||||
            # if no source data renderer exists create one.
 | 
			
		||||
            r = self._src_r
 | 
			
		||||
            if not r:
 | 
			
		||||
                r = Renderer(
 | 
			
		||||
                # OHLC bars path renderer
 | 
			
		||||
                r = self._src_r = Renderer(
 | 
			
		||||
                    flow=self,
 | 
			
		||||
                    draw=gen_qpath,  # TODO: rename this to something with ohlc
 | 
			
		||||
                    # TODO: rename this to something with ohlc
 | 
			
		||||
                    draw_path=gen_qpath,
 | 
			
		||||
                    last_read=read,
 | 
			
		||||
                )
 | 
			
		||||
                self._render_table['src'] = (r, graphics)
 | 
			
		||||
 | 
			
		||||
                # create a flattened view onto the OHLC array
 | 
			
		||||
                # which can be read as a line-style format
 | 
			
		||||
                # shm = self.shm
 | 
			
		||||
                # self.flat = shm.unstruct_view(['open', 'high', 'low', 'close'])
 | 
			
		||||
                # import pdbpp
 | 
			
		||||
                # pdbpp.set_trace()
 | 
			
		||||
                # x = self.x_basis = (
 | 
			
		||||
                #     np.broadcast_to(
 | 
			
		||||
                #         shm._array['index'][:, None],
 | 
			
		||||
                #         # self.flat._array.shape,
 | 
			
		||||
                #         self.flat.shape,
 | 
			
		||||
                #     ) + np.array([-0.5, 0, 0, 0.5])
 | 
			
		||||
                # )
 | 
			
		||||
 | 
			
		||||
                ds_curve_r = Renderer(
 | 
			
		||||
                    flow=self,
 | 
			
		||||
                    draw=gen_qpath,  # TODO: rename this to something with ohlc
 | 
			
		||||
 | 
			
		||||
                    # just swap in the flat view
 | 
			
		||||
                    data_t=lambda array: self.flat.array,
 | 
			
		||||
                    # data_t=partial(
 | 
			
		||||
                    #     ohlc_flat_view,
 | 
			
		||||
                    #     self.shm,
 | 
			
		||||
                    # ),
 | 
			
		||||
                    last_read=read,
 | 
			
		||||
                    prerender_fn=ohlc_flatten,
 | 
			
		||||
                    draw_path=partial(
 | 
			
		||||
                        rowarr_to_path,
 | 
			
		||||
                        x_basis=None,
 | 
			
		||||
                    ),
 | 
			
		||||
 | 
			
		||||
                )
 | 
			
		||||
                curve = FastAppendCurve(
 | 
			
		||||
                    # y=y,
 | 
			
		||||
                    # x=x,
 | 
			
		||||
                    name='OHLC',
 | 
			
		||||
                    color=graphics._color,
 | 
			
		||||
                )
 | 
			
		||||
                curve.hide()
 | 
			
		||||
                self.plot.addItem(curve)
 | 
			
		||||
 | 
			
		||||
                # baseline "line" downsampled OHLC curve that should
 | 
			
		||||
                # kick on only when we reach a certain uppx threshold.
 | 
			
		||||
                self._render_table[0] = (
 | 
			
		||||
                    ds_curve_r,
 | 
			
		||||
                    FastAppendCurve(
 | 
			
		||||
                        y=y,
 | 
			
		||||
                        x=x,
 | 
			
		||||
                        name='OHLC',
 | 
			
		||||
                        color=self._color,
 | 
			
		||||
                    ),
 | 
			
		||||
                    curve,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            dsc_r, curve = self._render_table[0]
 | 
			
		||||
 | 
			
		||||
            # do checks for whether or not we require downsampling:
 | 
			
		||||
            # - if we're **not** downsampling then we simply want to
 | 
			
		||||
            #   render the bars graphics curve and update..
 | 
			
		||||
            # - if insteam we are in a downsamplig state then we to
 | 
			
		||||
            x_gt = 8
 | 
			
		||||
            uppx = curve.x_uppx()
 | 
			
		||||
            in_line = should_line = curve.isVisible()
 | 
			
		||||
            if (
 | 
			
		||||
                should_line
 | 
			
		||||
                and uppx < x_gt
 | 
			
		||||
            ):
 | 
			
		||||
                should_line = False
 | 
			
		||||
 | 
			
		||||
            elif (
 | 
			
		||||
                not should_line
 | 
			
		||||
                and uppx >= x_gt
 | 
			
		||||
            ):
 | 
			
		||||
                should_line = True
 | 
			
		||||
 | 
			
		||||
            profiler(f'ds logic complete line={should_line}')
 | 
			
		||||
 | 
			
		||||
            # do graphics updates
 | 
			
		||||
            if should_line:
 | 
			
		||||
                # start = time.time()
 | 
			
		||||
                # y = self.shm.unstruct_view(
 | 
			
		||||
                #     ['open', 'high', 'low', 'close'],
 | 
			
		||||
                # )
 | 
			
		||||
                # print(f'unstruct diff: {time.time() - start}')
 | 
			
		||||
                # profiler('read unstr view bars to line')
 | 
			
		||||
                # # start = self.flat._first.value
 | 
			
		||||
 | 
			
		||||
                # x = self.x_basis[:y.size].flatten()
 | 
			
		||||
                # y = y.flatten()
 | 
			
		||||
                # profiler('flattening bars to line')
 | 
			
		||||
                # path, last = dsc_r.render(read)
 | 
			
		||||
                # x, flat = ohlc_flat_view(
 | 
			
		||||
                #     ohlc_shm=self.shm,
 | 
			
		||||
                #     x_basis=x_basis,
 | 
			
		||||
                # )
 | 
			
		||||
                # y = y.flatten()
 | 
			
		||||
                # y_iv = y[ivl:ivr].flatten()
 | 
			
		||||
                # x_iv = x[ivl:ivr].flatten()
 | 
			
		||||
                # assert y.size == x.size
 | 
			
		||||
 | 
			
		||||
                x, y = self.flat = ohlc_flatten(array)
 | 
			
		||||
                x_iv, y_iv = ohlc_flatten(in_view)
 | 
			
		||||
                profiler('flattened OHLC data')
 | 
			
		||||
 | 
			
		||||
                curve.update_from_array(
 | 
			
		||||
                    x,
 | 
			
		||||
                    y,
 | 
			
		||||
                    x_iv=x_iv,
 | 
			
		||||
                   y_iv=y_iv,
 | 
			
		||||
                    view_range=None,  # hack
 | 
			
		||||
                    profiler=profiler,
 | 
			
		||||
                )
 | 
			
		||||
                profiler('updated ds curve')
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                # render incremental or in-view update
 | 
			
		||||
                # and apply ouput (path) to graphics.
 | 
			
		||||
                path, last = r.render(
 | 
			
		||||
                    read,
 | 
			
		||||
                    only_in_view=True,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                graphics.path = path
 | 
			
		||||
                graphics.draw_last(last)
 | 
			
		||||
 | 
			
		||||
                # NOTE: on appends we used to have to flip the coords
 | 
			
		||||
                # cache thought it doesn't seem to be required any more?
 | 
			
		||||
                # graphics.setCacheMode(QtWidgets.QGraphicsItem.NoCache)
 | 
			
		||||
                # graphics.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
 | 
			
		||||
 | 
			
		||||
                graphics.prepareGeometryChange()
 | 
			
		||||
                graphics.update()
 | 
			
		||||
 | 
			
		||||
            if (
 | 
			
		||||
                not in_line
 | 
			
		||||
                and should_line
 | 
			
		||||
            ):
 | 
			
		||||
                # change to line graphic
 | 
			
		||||
 | 
			
		||||
                log.info(
 | 
			
		||||
                    f'downsampling to line graphic {self.name}'
 | 
			
		||||
                )
 | 
			
		||||
                graphics.hide()
 | 
			
		||||
                # graphics.update()
 | 
			
		||||
                curve.show()
 | 
			
		||||
                curve.update()
 | 
			
		||||
 | 
			
		||||
            elif in_line and not should_line:
 | 
			
		||||
                log.info(f'showing bars graphic {self.name}')
 | 
			
		||||
                curve.hide()
 | 
			
		||||
                graphics.show()
 | 
			
		||||
                graphics.update()
 | 
			
		||||
 | 
			
		||||
            #   update our pre-downsample-ready data and then pass that
 | 
			
		||||
            #   new data the downsampler algo for incremental update.
 | 
			
		||||
            else:
 | 
			
		||||
                pass
 | 
			
		||||
                # do incremental update
 | 
			
		||||
 | 
			
		||||
            graphics.update_from_array(
 | 
			
		||||
                array,
 | 
			
		||||
                in_view,
 | 
			
		||||
                view_range=(ivl, ivr) if use_vr else None,
 | 
			
		||||
                # graphics.update_from_array(
 | 
			
		||||
                #     array,
 | 
			
		||||
                #     in_view,
 | 
			
		||||
                #     view_range=(ivl, ivr) if use_vr else None,
 | 
			
		||||
 | 
			
		||||
                **kwargs,
 | 
			
		||||
            )
 | 
			
		||||
                #     **kwargs,
 | 
			
		||||
                # )
 | 
			
		||||
 | 
			
		||||
            # generate and apply path to graphics obj
 | 
			
		||||
            graphics.path, last = r.render(only_in_view=True)
 | 
			
		||||
            graphics.draw_last(last)
 | 
			
		||||
                # generate and apply path to graphics obj
 | 
			
		||||
                # graphics.path, last = r.render(
 | 
			
		||||
                #     read,
 | 
			
		||||
                #     only_in_view=True,
 | 
			
		||||
                # )
 | 
			
		||||
                # graphics.draw_last(last)
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            # should_ds = False
 | 
			
		||||
            # should_redraw = False
 | 
			
		||||
 | 
			
		||||
            # # downsampling incremental state checking
 | 
			
		||||
            # uppx = bars.x_uppx()
 | 
			
		||||
            # px_width = bars.px_width()
 | 
			
		||||
            # uppx_diff = (uppx - self._last_uppx)
 | 
			
		||||
 | 
			
		||||
            # if self.renderer is None:
 | 
			
		||||
            #     self.renderer = Renderer(
 | 
			
		||||
            #         flow=self,
 | 
			
		||||
 | 
			
		||||
            # if not self._in_ds:
 | 
			
		||||
            #     # in not currently marked as downsampling graphics
 | 
			
		||||
            #     # then only draw the full bars graphic for datums "in
 | 
			
		||||
            #     # view".
 | 
			
		||||
 | 
			
		||||
            # # check for downsampling conditions
 | 
			
		||||
            # if (
 | 
			
		||||
            #     # std m4 downsample conditions
 | 
			
		||||
            #     px_width
 | 
			
		||||
            #     and uppx_diff >= 4
 | 
			
		||||
            #     or uppx_diff <= -3
 | 
			
		||||
            #     or self._step_mode and abs(uppx_diff) >= 4
 | 
			
		||||
 | 
			
		||||
            # ):
 | 
			
		||||
            #     log.info(
 | 
			
		||||
            #         f'{self._name} sampler change: {self._last_uppx} -> {uppx}'
 | 
			
		||||
            #     )
 | 
			
		||||
            #     self._last_uppx = uppx
 | 
			
		||||
            #     should_ds = True
 | 
			
		||||
 | 
			
		||||
            # elif (
 | 
			
		||||
            #     uppx <= 2
 | 
			
		||||
            #     and self._in_ds
 | 
			
		||||
            # ):
 | 
			
		||||
            #     # we should de-downsample back to our original
 | 
			
		||||
            #     # source data so we clear our path data in prep
 | 
			
		||||
            #     # to generate a new one from original source data.
 | 
			
		||||
            #     should_redraw = True
 | 
			
		||||
            #     should_ds = False
 | 
			
		||||
            # ``FastAppendCurve`` case:
 | 
			
		||||
 | 
			
		||||
            array_key = array_key or self.name
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -376,23 +537,64 @@ class Flow(msgspec.Struct):  # , frozen=True):
 | 
			
		|||
        return graphics
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def xy_downsample(
 | 
			
		||||
    x,
 | 
			
		||||
    y,
 | 
			
		||||
    px_width,
 | 
			
		||||
    uppx,
 | 
			
		||||
 | 
			
		||||
    x_spacer: float = 0.5,
 | 
			
		||||
 | 
			
		||||
) -> tuple[np.ndarray, np.ndarray]:
 | 
			
		||||
 | 
			
		||||
    # downsample whenever more then 1 pixels per datum can be shown.
 | 
			
		||||
    # always refresh data bounds until we get diffing
 | 
			
		||||
    # working properly, see above..
 | 
			
		||||
    bins, x, y = ds_m4(
 | 
			
		||||
        x,
 | 
			
		||||
        y,
 | 
			
		||||
        px_width=px_width,
 | 
			
		||||
        uppx=uppx,
 | 
			
		||||
        log_scale=bool(uppx)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # flatten output to 1d arrays suitable for path-graphics generation.
 | 
			
		||||
    x = np.broadcast_to(x[:, None], y.shape)
 | 
			
		||||
    x = (x + np.array(
 | 
			
		||||
        [-x_spacer, 0, 0, x_spacer]
 | 
			
		||||
    )).flatten()
 | 
			
		||||
    y = y.flatten()
 | 
			
		||||
 | 
			
		||||
    return x, y
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Renderer(msgspec.Struct):
 | 
			
		||||
 | 
			
		||||
    flow: Flow
 | 
			
		||||
 | 
			
		||||
    # called to render path graphics
 | 
			
		||||
    draw: Callable[np.ndarray, QPainterPath]
 | 
			
		||||
    draw_path: Callable[np.ndarray, QPainterPath]
 | 
			
		||||
 | 
			
		||||
    # called on input data but before
 | 
			
		||||
    prerender_fn: Optional[Callable[ShmArray, np.ndarray]] = None
 | 
			
		||||
    # called on input data but before any graphics format
 | 
			
		||||
    # conversions or processing.
 | 
			
		||||
    data_t: Optional[Callable[ShmArray, np.ndarray]] = None
 | 
			
		||||
    data_t_shm: Optional[ShmArray] = None
 | 
			
		||||
 | 
			
		||||
    # called on the final data (transform) output to convert
 | 
			
		||||
    # to "graphical data form" a format that can be passed to
 | 
			
		||||
    # the ``.draw()`` implementation.
 | 
			
		||||
    graphics_t: Optional[Callable[ShmArray, np.ndarray]] = None
 | 
			
		||||
    graphics_t_shm: Optional[ShmArray] = None
 | 
			
		||||
 | 
			
		||||
    # path graphics update implementation methods
 | 
			
		||||
    prepend_fn: Optional[Callable[QPainterPath, QPainterPath]] = None
 | 
			
		||||
    append_fn: Optional[Callable[QPainterPath, QPainterPath]] = None
 | 
			
		||||
 | 
			
		||||
    # last array view read
 | 
			
		||||
    last_read: Optional[np.ndarray] = None
 | 
			
		||||
 | 
			
		||||
    # output graphics rendering
 | 
			
		||||
    # output graphics rendering, the main object
 | 
			
		||||
    # processed in ``QGraphicsObject.paint()``
 | 
			
		||||
    path: Optional[QPainterPath] = None
 | 
			
		||||
 | 
			
		||||
    # def diff(
 | 
			
		||||
| 
						 | 
				
			
			@ -411,8 +613,10 @@ class Renderer(msgspec.Struct):
 | 
			
		|||
    def render(
 | 
			
		||||
        self,
 | 
			
		||||
 | 
			
		||||
        new_read,
 | 
			
		||||
 | 
			
		||||
        # only render datums "in view" of the ``ChartView``
 | 
			
		||||
        only_in_view: bool = True,
 | 
			
		||||
        only_in_view: bool = False,
 | 
			
		||||
 | 
			
		||||
    ) -> list[QPainterPath]:
 | 
			
		||||
        '''
 | 
			
		||||
| 
						 | 
				
			
			@ -428,28 +632,42 @@ class Renderer(msgspec.Struct):
 | 
			
		|||
 | 
			
		||||
        '''
 | 
			
		||||
        # do full source data render to path
 | 
			
		||||
        xfirst, xlast, array, ivl, ivr, in_view = self.last_read
 | 
			
		||||
        last_read = (
 | 
			
		||||
            xfirst, xlast, array,
 | 
			
		||||
            ivl, ivr, in_view,
 | 
			
		||||
        ) = self.last_read
 | 
			
		||||
 | 
			
		||||
        if only_in_view:
 | 
			
		||||
            # get latest data from flow shm
 | 
			
		||||
            self.last_read = (
 | 
			
		||||
                xfirst, xlast, array, ivl, ivr, in_view
 | 
			
		||||
            ) = self.flow.read()
 | 
			
		||||
 | 
			
		||||
            array = in_view
 | 
			
		||||
            # # get latest data from flow shm
 | 
			
		||||
            # self.last_read = (
 | 
			
		||||
            #     xfirst, xlast, array, ivl, ivr, in_view
 | 
			
		||||
            # ) = new_read
 | 
			
		||||
 | 
			
		||||
        if self.path is None or in_view:
 | 
			
		||||
        if self.path is None or only_in_view:
 | 
			
		||||
            # redraw the entire source data if we have either of:
 | 
			
		||||
            # - no prior path graphic rendered or,
 | 
			
		||||
            # - we always intend to re-render the data only in view
 | 
			
		||||
 | 
			
		||||
            if self.prerender_fn:
 | 
			
		||||
                array = self.prerender_fn(array)
 | 
			
		||||
            # data transform: convert source data to a format
 | 
			
		||||
            # expected to be incrementally updates and later rendered
 | 
			
		||||
            # to a more graphics native format.
 | 
			
		||||
            if self.data_t:
 | 
			
		||||
                array = self.data_t(array)
 | 
			
		||||
 | 
			
		||||
            hist, last = array[:-1], array[-1]
 | 
			
		||||
                # maybe allocate shm for data transform output
 | 
			
		||||
                # if self.data_t_shm is None:
 | 
			
		||||
                #     fshm = self.flow.shm
 | 
			
		||||
 | 
			
		||||
            # call path render func on history
 | 
			
		||||
            self.path = self.draw(hist)
 | 
			
		||||
                #     shm, opened = maybe_open_shm_array(
 | 
			
		||||
                #         f'{self.flow.name}_data_t',
 | 
			
		||||
                #         # TODO: create entry for each time frame
 | 
			
		||||
                #         dtype=array.dtype,
 | 
			
		||||
                #         readonly=False,
 | 
			
		||||
                #     )
 | 
			
		||||
                #     assert opened
 | 
			
		||||
                #     shm.push(array)
 | 
			
		||||
                #     self.data_t_shm = shm
 | 
			
		||||
 | 
			
		||||
        elif self.path:
 | 
			
		||||
            print(f'inremental update not supported yet {self.flow.name}')
 | 
			
		||||
| 
						 | 
				
			
			@ -459,4 +677,10 @@ class Renderer(msgspec.Struct):
 | 
			
		|||
            # do path generation for each segment
 | 
			
		||||
            # and then push into graphics object.
 | 
			
		||||
 | 
			
		||||
        hist, last = array[:-1], array[-1]
 | 
			
		||||
 | 
			
		||||
        # call path render func on history
 | 
			
		||||
        self.path = self.draw_path(hist)
 | 
			
		||||
 | 
			
		||||
        self.last_read = new_read
 | 
			
		||||
        return self.path, last
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue