diff --git a/piker/ui/_curve.py b/piker/ui/_curve.py index cf987203..60353f08 100644 --- a/piker/ui/_curve.py +++ b/piker/ui/_curve.py @@ -45,74 +45,77 @@ log = get_logger(__name__) # TODO: numba this instead.. -def step_path_arrays_from_1d( - x: np.ndarray, - y: np.ndarray, - include_endpoints: bool = False, +# def step_path_arrays_from_1d( +# x: np.ndarray, +# y: np.ndarray, +# include_endpoints: bool = True, -) -> (np.ndarray, np.ndarray): - ''' - Generate a "step mode" curve aligned with OHLC style bars - such that each segment spans each bar (aka "centered" style). +# ) -> (np.ndarray, np.ndarray): +# ''' +# Generate a "step mode" curve aligned with OHLC style bars +# such that each segment spans each bar (aka "centered" style). - ''' - y_out = y.copy() - x_out = x.copy() +# ''' +# # y_out = y.copy() +# # x_out = x.copy() - # x2 = np.empty( - # # the data + 2 endpoints on either end for - # # "termination of the path". - # (len(x) + 1, 2), - # # we want to align with OHLC or other sampling style - # # bars likely so we need fractinal values - # dtype=float, - # ) +# # x2 = np.empty( +# # # the data + 2 endpoints on either end for +# # # "termination of the path". +# # (len(x) + 1, 2), +# # # we want to align with OHLC or other sampling style +# # # bars likely so we need fractinal values +# # dtype=float, +# # ) - x2 = np.broadcast_to( - x[:, None], - ( - x_out.size, - # 4, # only ohlc - 2, - ), - ) + np.array([-0.5, 0.5]) +# x2 = np.broadcast_to( +# x[:, None], +# ( +# x.size + 1, +# # 4, # only ohlc +# 2, +# ), +# ) + np.array([-0.5, 0.5]) - # x2[0] = x[0] - 0.5 - # x2[1] = x[0] + 0.5 - # x2[0, 0] = x[0] - 0.5 - # x2[0, 1] = x[0] + 0.5 - # x2[1:] = x[:, np.newaxis] + 0.5 - # import pdbpp - # pdbpp.set_trace() +# # x2[0] = x[0] - 0.5 +# # x2[1] = x[0] + 0.5 +# # x2[0, 0] = x[0] - 0.5 +# # x2[0, 1] = x[0] + 0.5 +# # x2[1:] = x[:, np.newaxis] + 0.5 +# # import pdbpp +# # pdbpp.set_trace() - # flatten to 1-d - # x_out = x2.reshape(x2.size) - x_out = x2 +# # flatten to 1-d +# # x_out = x2.reshape(x2.size) +# # x_out = x2 - # we create a 1d with 2 extra indexes to - # hold the start and (current) end value for the steps - # on either end - y2 = np.empty((len(y), 2), dtype=y.dtype) - y2[:] = y[:, np.newaxis] - y2[-1] = 0 - - y_out = y2 - -# y_out = np.empty( -# 2*len(y) + 2, -# dtype=y.dtype +# # we create a 1d with 2 extra indexes to +# # hold the start and (current) end value for the steps +# # on either end +# y2 = np.empty( +# (len(y) + 1, 2), +# dtype=y.dtype, # ) +# y2[:] = y[:, np.newaxis] +# # y2[-1] = 0 - # flatten and set 0 endpoints - # y_out[1:-1] = y2.reshape(y2.size) - # y_out[0] = 0 - # y_out[-1] = 0 +# # y_out = y2 - if not include_endpoints: - return x_out[:-1], y_out[:-1] +# # y_out = np.empty( +# # 2*len(y) + 2, +# # dtype=y.dtype +# # ) - else: - return x_out, y_out +# # flatten and set 0 endpoints +# # y_out[1:-1] = y2.reshape(y2.size) +# # y_out[0] = 0 +# # y_out[-1] = 0 + +# if not include_endpoints: +# return x2[:-1], y2[:-1] + +# else: +# return x2, y2 _line_styles: dict[str, int] = { @@ -158,6 +161,8 @@ class FastAppendCurve(pg.GraphicsObject): self._y = self.yData = y self._x = self.xData = x self._vr: Optional[tuple] = None + self._avr: Optional[tuple] = None + self._br = None self._name = name self.path: Optional[QtGui.QPainterPath] = None @@ -171,6 +176,7 @@ class FastAppendCurve(pg.GraphicsObject): # self._xrange: tuple[int, int] = self.dataBounds(ax=0) self._xrange: Optional[tuple[int, int]] = None + # self._x_iv_range = None # self._last_draw = time.time() self._in_ds: bool = False @@ -283,6 +289,10 @@ class FastAppendCurve(pg.GraphicsObject): view_range: Optional[tuple[int, int]] = None, profiler: Optional[pg.debug.Profiler] = None, + draw_last: bool = True, + slice_to_head: int = -1, + do_append: bool = True, + should_redraw: bool = False, ) -> QtGui.QPainterPath: ''' @@ -297,7 +307,7 @@ class FastAppendCurve(pg.GraphicsObject): disabled=not pg_profile_enabled(), gt=ms_slower_then, ) - # flip_cache = False + flip_cache = False if self._xrange: istart, istop = self._xrange @@ -330,7 +340,7 @@ class FastAppendCurve(pg.GraphicsObject): new_sample_rate = False should_ds = self._in_ds showing_src_data = self._in_ds - should_redraw = False + # should_redraw = False # if a view range is passed, plan to draw the # source ouput that's "in view" of the chart. @@ -342,32 +352,60 @@ class FastAppendCurve(pg.GraphicsObject): # print(f'{self._name} vr: {view_range}') # by default we only pull data up to the last (current) index - x_out, y_out = x_iv[:-1], y_iv[:-1] + x_out, y_out = x_iv[:slice_to_head], y_iv[:slice_to_head] profiler(f'view range slice {view_range}') - ivl, ivr = view_range + vl, vr = view_range - probably_zoom_change = False + # last_ivr = self._x_iv_range + # ix_iv, iy_iv = self._x_iv_range = (x_iv[0], x_iv[-1]) + + zoom_or_append = False last_vr = self._vr + last_ivr = self._avr + if last_vr: - livl, livr = last_vr + # relative slice indices + lvl, lvr = last_vr + # abs slice indices + al, ar = last_ivr + + # append_length = int(x[-1] - istop) + # append_length = int(x_iv[-1] - ar) + + # left_change = abs(x_iv[0] - al) >= 1 + # right_change = abs(x_iv[-1] - ar) >= 1 + if ( - ivl < livl - or (ivr - livr) > 2 + # likely a zoom view change + (vr - lvr) > 2 or vl < lvl + # append / prepend update + # we had an append update where the view range + # didn't change but the data-viewed (shifted) + # underneath, so we need to redraw. + # or left_change and right_change and last_vr == view_range + + # not (left_change and right_change) and ivr + # ( + # or abs(x_iv[ivr] - livr) > 1 ): - probably_zoom_change = True + zoom_or_append = True + + # if last_ivr: + # liivl, liivr = last_ivr if ( view_range != last_vr and ( append_length > 1 - or probably_zoom_change + or zoom_or_append ) ): should_redraw = True # print("REDRAWING BRUH") self._vr = view_range + self._avr = x_iv[0], x_iv[slice_to_head] # x_last = x_iv[-1] # y_last = y_iv[-1] @@ -382,7 +420,7 @@ class FastAppendCurve(pg.GraphicsObject): # or self._in_ds # ): # by default we only pull data up to the last (current) index - x_out, y_out = x[:-1], y[:-1] + x_out, y_out = x[:slice_to_head], y[:slice_to_head] if prepend_length > 0: should_redraw = True @@ -434,12 +472,12 @@ class FastAppendCurve(pg.GraphicsObject): # step mode: draw flat top discrete "step" # over the index space for each datum. # if self._step_mode: + # self.disable_cache() + # flip_cache = True # x_out, y_out = step_path_arrays_from_1d( # x_out, # y_out, # ) - # # self.disable_cache() - # # flip_cache = True # # TODO: numba this bish # profiler('generated step arrays') @@ -460,7 +498,7 @@ class FastAppendCurve(pg.GraphicsObject): self._in_ds = False - elif should_ds and px_width and uppx: + elif should_ds and uppx and px_width > 1: x_out, y_out = self.downsample( x_out, y_out, @@ -477,11 +515,9 @@ class FastAppendCurve(pg.GraphicsObject): finiteCheck=False, path=self.path, ) + self.prepareGeometryChange() profiler( - 'generated fresh path\n' - f'should_redraw: {should_redraw}\n' - f'should_ds: {should_ds}\n' - f'new_sample_rate: {new_sample_rate}\n' + f'generated fresh path. (should_redraw: {should_redraw} should_ds: {should_ds} new_sample_rate: {new_sample_rate})' ) # profiler(f'DRAW PATH IN VIEW -> {self._name}') @@ -514,26 +550,29 @@ class FastAppendCurve(pg.GraphicsObject): elif ( append_length > 0 + and do_append + and not should_redraw # and not view_range ): - new_x = x[-append_length - 2:-1] - new_y = y[-append_length - 2:-1] + print(f'{self._name} append len: {append_length}') + new_x = x[-append_length - 2:slice_to_head] + new_y = y[-append_length - 2:slice_to_head] profiler('sliced append path') # if self._step_mode: - # new_x, new_y = step_path_arrays_from_1d( - # new_x, - # new_y, - # ) - # # [1:] since we don't need the vertical line normally at - # # the beginning of the step curve taking the first (x, - # # y) poing down to the x-axis **because** this is an - # # appended path graphic. - # new_x = new_x[1:] - # new_y = new_y[1:] + # # new_x, new_y = step_path_arrays_from_1d( + # # new_x, + # # new_y, + # # ) + # # # [1:] since we don't need the vertical line normally at + # # # the beginning of the step curve taking the first (x, + # # # y) poing down to the x-axis **because** this is an + # # # appended path graphic. + # # new_x = new_x[1:] + # # new_y = new_y[1:] - # # self.disable_cache() - # # flip_cache = True + # self.disable_cache() + # flip_cache = True # profiler('generated step data') @@ -563,7 +602,7 @@ class FastAppendCurve(pg.GraphicsObject): # an attempt at trying to make append-updates faster.. if self.fast_path is None: self.fast_path = append_path - self.fast_path.reserve(int(6e3)) + # self.fast_path.reserve(int(6e3)) else: self.fast_path.connectPath(append_path) size = self.fast_path.capacity() @@ -596,19 +635,20 @@ class FastAppendCurve(pg.GraphicsObject): # self.disable_cache() # flip_cache = True - self.draw_last(x, y) - profiler('draw last segment') + if draw_last: + self.draw_last(x, y) + profiler('draw last segment') + + + # if flip_cache: + # # # XXX: seems to be needed to avoid artifacts (see above). + # self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) # trigger redraw of path # do update before reverting to cache mode - # self.prepareGeometryChange() self.update() profiler('.update()') - # if flip_cache: - # # XXX: seems to be needed to avoid artifacts (see above). - # self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) - def draw_last( self, x: np.ndarray, @@ -624,10 +664,14 @@ class FastAppendCurve(pg.GraphicsObject): self._last_line = QLineF( x_last - 0.5, 0, x_last + 0.5, 0, + # x_last, 0, + # x_last, 0, ) self._last_step_rect = QRectF( x_last - 0.5, 0, x_last + 0.5, y_last + # x_last, 0, + # x_last, y_last ) # print( # f"path br: {self.path.boundingRect()}", @@ -640,6 +684,8 @@ class FastAppendCurve(pg.GraphicsObject): x_last, y_last ) + self.update() + # XXX: lol brutal, the internals of `CurvePoint` (inherited by # our `LineDot`) required ``.getData()`` to work.. def getData(self): @@ -685,7 +731,7 @@ class FastAppendCurve(pg.GraphicsObject): # XXX: pretty annoying but, without this there's little # artefacts on the append updates to the curve... self.setCacheMode(QtWidgets.QGraphicsItem.NoCache) - self.prepareGeometryChange() + # self.prepareGeometryChange() def boundingRect(self): ''' @@ -705,6 +751,7 @@ class FastAppendCurve(pg.GraphicsObject): ''' hb = self.path.controlPointRect() + # hb = self.path.boundingRect() hb_size = hb.size() fp = self.fast_path @@ -713,17 +760,47 @@ class FastAppendCurve(pg.GraphicsObject): 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 + # ) + + # if self._last_step_rect: + # br = self._last_step_rect.bottomRight() + + # else: + # hb_size += QSizeF(1, 1) w = hb_size.width() + 1 h = hb_size.height() + 1 + # 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) ) + self._br = br # print(f'bounding rect: {br}') return br @@ -740,6 +817,7 @@ class FastAppendCurve(pg.GraphicsObject): disabled=not pg_profile_enabled(), gt=ms_slower_then, ) + self.prepareGeometryChange() if ( self._step_mode diff --git a/piker/ui/_flows.py b/piker/ui/_flows.py index b150a2d1..03d95a35 100644 --- a/piker/ui/_flows.py +++ b/piker/ui/_flows.py @@ -34,6 +34,13 @@ import numpy as np from numpy.lib import recfunctions as rfn import pyqtgraph as pg from PyQt5.QtGui import QPainterPath +from PyQt5.QtCore import ( + # Qt, + QLineF, + # QSizeF, + QRectF, + # QPointF, +) from ..data._sharedmem import ( ShmArray, @@ -465,7 +472,7 @@ class Flow(msgspec.Struct): # , frozen=True): self.gy[ ishm_first:iflat_first ] = rfn.structured_to_unstructured( - self.shm.array[fields][:iflat_first] + self.shm._array[fields][ishm_first:iflat_first] ) self._iflat_first = ishm_first @@ -516,6 +523,8 @@ class Flow(msgspec.Struct): # , frozen=True): y_iv=y_iv, view_range=(ivl, ivr), # hack profiler=profiler, + # should_redraw=False, + # do_append=False, ) curve.show() profiler('updated ds curve') @@ -578,21 +587,36 @@ class Flow(msgspec.Struct): # , frozen=True): # graphics.draw_last(last) else: - + # ``FastAppendCurve`` case: array_key = array_key or self.name - # ``FastAppendCurve`` case: if graphics._step_mode and self.gy is None: + self._iflat_first = self.shm._first.value # create a flattened view onto the OHLC array # which can be read as a line-style format shm = self.shm # fields = ['index', array_key] - i = shm._array['index'] - out = shm._array[array_key] + i = shm._array['index'].copy() + out = shm._array[array_key].copy() - self.gx, self.gy = step_path_arrays_from_1d(i, out) + self.gx = np.broadcast_to( + i[:, None], + (i.size, 2), + ) + np.array([-0.5, 0.5]) + + + # self.gy = np.broadcast_to( + # out[:, None], (out.size, 2), + # ) + self.gy = np.empty((len(out), 2), dtype=out.dtype) + self.gy[:] = out[:, np.newaxis] + + # start y at origin level + self.gy[0, 0] = 0 + + # self.gx, self.gy = step_path_arrays_from_1d(i, out) # flat = self.gy = self.shm.unstruct_view(fields) # self.gy = self.shm.ustruct(fields) @@ -635,17 +659,29 @@ class Flow(msgspec.Struct): # , frozen=True): self.shm._first.value ) + il = max(iflat - 1, 0) + # check for shm prepend updates since last read. if iflat_first != ishm_first: - # write newly prepended data to flattened copy - _gx, self.gy[ - ishm_first:iflat_first - ] = step_path_arrays_from_1d( - self.shm.array['index'][:iflat_first], - self.shm.array[array_key][:iflat_first], + print(f'prepend {array_key}') + + i_prepend = self.shm._array['index'][ishm_first:iflat_first] + y_prepend = self.shm._array[array_key][ishm_first:iflat_first] + + y2_prepend = np.broadcast_to( + y_prepend[:, None], (y_prepend.size, 2), ) + + # write newly prepended data to flattened copy + self.gy[ishm_first:iflat_first] = y2_prepend + # ] = step_path_arrays_from_1d( + # ] = step_path_arrays_from_1d( + # i_prepend, + # y_prepend, + # ) self._iflat_first = ishm_first + # # flat = self.gy = self.shm.unstruct_view(fields) # self.gy = self.shm.ustruct(fields) # # self._iflat_last = self.shm._last.value @@ -654,40 +690,112 @@ class Flow(msgspec.Struct): # , frozen=True): # # do an update for the most recent prepend # # index # iflat = ishm_first - if iflat != ishm_last: - _x, to_update = step_path_arrays_from_1d( - self.shm._array[iflat:ishm_last]['index'], - self.shm._array[iflat:ishm_last][array_key], + append_diff = ishm_last - iflat + # if iflat != ishm_last: + if append_diff: + + # slice up to the last datum since last index/append update + new_x = self.shm._array[il:ishm_last]['index']#.copy() + new_y = self.shm._array[il:ishm_last][array_key]#.copy() + + # _x, to_update = step_path_arrays_from_1d(new_x, new_y) + + # new_x2 = = np.broadcast_to( + # new_x2[:, None], + # (new_x2.size, 2), + # ) + np.array([-0.5, 0.5]) + + new_y2 = np.broadcast_to( + new_y[:, None], (new_y.size, 2), ) + # new_y2 = np.empty((len(new_y), 2), dtype=new_y.dtype) + # new_y2[:] = new_y[:, np.newaxis] + + # import pdbpp + # pdbpp.set_trace() + + # print( + # f'updating step curve {to_update}\n' + # f'last array val: {new_x}, {new_y}' + # ) # to_update = rfn.structured_to_unstructured( # self.shm._array[iflat:ishm_last][fields] # ) - # import pdbpp - # pdbpp.set_trace() - self.gy[iflat:ishm_last-1] = to_update - self.gy[-1] = 0 - print(f'updating step curve {to_update}') + # if not to_update.any(): + # if new_y.any() and not to_update.any(): + # import pdbpp + # pdbpp.set_trace() + + # print(f'{array_key} new values new_x:{new_x}, new_y:{new_y}') + # head, last = to_update[:-1], to_update[-1] + self.gy[il:ishm_last] = new_y2 + + gy = self.gy[il:ishm_last] + + # self.gy[-1] = to_update[-1] profiler('updated step curve data') - # slice out up-to-last step contents - x_step = self.gx[ishm_first:ishm_last] - x = x_step.reshape(-1) - y_step = self.gy[ishm_first:ishm_last] - y = y_step.reshape(-1) - profiler('sliced step data') + # print( + # f'append size: {append_diff}\n' + # f'new_x: {new_x}\n' + # f'new_y: {new_y}\n' + # f'new_y2: {new_y2}\n' + # f'new gy: {gy}\n' + # ) - # update local last-index tracking - self._iflat_last = ishm_last + # update local last-index tracking + self._iflat_last = ishm_last + + # ( + # iflat_first, + # iflat, + # ishm_last, + # ishm_first, + # ) = ( + # self._iflat_first, + # self._iflat_last, + # self.shm._last.value, + # self.shm._first.value + # ) + # graphics.draw_last(last['index'], last[array_key]) + + # slice out up-to-last step contents + x_step = self.gx[ishm_first:ishm_last+2] + # x_step[-1] = last['index'] + # x_step[-1] = last['index'] + # to 1d + x = x_step.reshape(-1) + + y_step = self.gy[ishm_first:ishm_last+2] + lasts = self.shm.array[['index', array_key]] + last = lasts[array_key][-1] + y_step[-1] = last + # to 1d + y = y_step.reshape(-1) + # y[-1] = 0 + + # s = 6 + # print(f'lasts: {x[-2*s:]}, {y[-2*s:]}') + + profiler('sliced step data') # reshape to 1d for graphics rendering # y = y_flat.reshape(-1) # x = x_flat.reshape(-1) # do all the same for only in-view data - y_iv = y_step[ivl:ivr].reshape(-1) - x_iv = x_step[ivl:ivr].reshape(-1) + ys_iv = y_step[ivl:ivr+1] + xs_iv = x_step[ivl:ivr+1] + y_iv = ys_iv.reshape(ys_iv.size) + x_iv = xs_iv.reshape(xs_iv.size) + # print( + # f'ys_iv : {ys_iv[-s:]}\n' + # f'y_iv: {y_iv[-s:]}\n' + # f'xs_iv: {xs_iv[-s:]}\n' + # f'x_iv: {x_iv[-s:]}\n' + # ) # y_iv = y_iv_flat.reshape(-1) # x_iv = x_iv_flat.reshape(-1) profiler('flattened ustruct in-view OHLC data') @@ -696,7 +804,49 @@ class Flow(msgspec.Struct): # , frozen=True): # x, y = ohlc_flatten(array) # x_iv, y_iv = ohlc_flatten(in_view) # profiler('flattened OHLC data') - graphics.reset_cache() + + x_last = array['index'][-1] + y_last = array[array_key][-1] + graphics._last_line = QLineF( + x_last - 0.5, 0, + x_last + 0.5, 0, + # x_last, 0, + # x_last, 0, + ) + graphics._last_step_rect = QRectF( + x_last - 0.5, 0, + x_last + 0.5, y_last, + # x_last, 0, + # x_last, y_last + ) + # graphics.update() + + graphics.update_from_array( + x=x, + y=y, + + x_iv=x_iv, + y_iv=y_iv, + + view_range=(ivl, ivr) if use_vr else None, + + draw_last=False, + slice_to_head=-2, + + should_redraw=bool(append_diff), + # do_append=False, + + **kwargs + ) + # graphics.reset_cache() + # print( + # f"path br: {graphics.path.boundingRect()}\n", + # # f"fast path br: {graphics.fast_path.boundingRect()}", + # f"last rect br: {graphics._last_step_rect}\n", + # f"full br: {graphics._br}\n", + # ) + + # graphics.boundingRect() else: x = array['index'] @@ -704,17 +854,20 @@ class Flow(msgspec.Struct): # , frozen=True): x_iv = in_view['index'] y_iv = in_view[array_key] - graphics.update_from_array( - x=x, - y=y, + # graphics.draw_last(x, y) + profiler('draw last segment {array_key}') - x_iv=x_iv, - y_iv=y_iv, + graphics.update_from_array( + x=x, + y=y, - view_range=(ivl, ivr) if use_vr else None, + x_iv=x_iv, + y_iv=y_iv, - **kwargs - ) + view_range=(ivl, ivr) if use_vr else None, + + **kwargs + ) return graphics