From 789c77f9b2fdabe7b160c336dfbe346447aaf457 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Wed, 20 Apr 2022 11:42:49 -0400 Subject: [PATCH] Add `BarItems.draw_last()` and disable `.update_from_array()` --- piker/ui/_ohlc.py | 592 +++++++++++++++++++++++++--------------------- 1 file changed, 316 insertions(+), 276 deletions(-) diff --git a/piker/ui/_ohlc.py b/piker/ui/_ohlc.py index bf56f5f5..328d62b9 100644 --- a/piker/ui/_ohlc.py +++ b/piker/ui/_ohlc.py @@ -244,7 +244,7 @@ class BarItems(pg.GraphicsObject): self.fast_path = QtGui.QPainterPath() self._xrange: tuple[int, int] - self._yrange: tuple[float, float] + # self._yrange: tuple[float, float] self._vrange = None # TODO: don't render the full backing array each time @@ -281,10 +281,10 @@ class BarItems(pg.GraphicsObject): # self.start_index = len(ohlc) index = ohlc['index'] self._xrange = (index[0], index[-1]) - self._yrange = ( - np.nanmax(ohlc['high']), - np.nanmin(ohlc['low']), - ) + # self._yrange = ( + # np.nanmax(ohlc['high']), + # np.nanmin(ohlc['low']), + # ) # up to last to avoid double draw of last bar self._last_bar_lines = bar_from_ohlc_row(last, self.w) @@ -325,286 +325,326 @@ class BarItems(pg.GraphicsObject): else: return 0 - def update_from_array( + # def update_from_array( + # self, + + # # full array input history + # ohlc: np.ndarray, + + # # pre-sliced array data that's "in view" + # ohlc_iv: np.ndarray, + + # view_range: Optional[tuple[int, int]] = None, + # profiler: Optional[pg.debug.Profiler] = None, + + # ) -> None: + # ''' + # Update the last datum's bar graphic from input data array. + + # This routine should be interface compatible with + # ``pg.PlotCurveItem.setData()``. Normally this method in + # ``pyqtgraph`` seems to update all the data passed to the + # graphics object, and then update/rerender, but here we're + # assuming the prior graphics havent changed (OHLC history rarely + # does) so this "should" be simpler and faster. + + # This routine should be made (transitively) as fast as possible. + + # ''' + # profiler = profiler or pg.debug.Profiler( + # disabled=not pg_profile_enabled(), + # gt=ms_slower_then, + # delayed=True, + # ) + + # # index = self.start_index + # istart, istop = self._xrange + # # ds_istart, ds_istop = self._ds_xrange + + # index = ohlc['index'] + # first_index, last_index = index[0], index[-1] + + # # length = len(ohlc) + # # prepend_length = istart - first_index + # # append_length = last_index - istop + + # # ds_prepend_length = ds_istart - first_index + # # ds_append_length = last_index - ds_istop + + # flip_cache = False + + # x_gt = 16 + # if self._ds_line: + # uppx = self._ds_line.x_uppx() + # else: + # uppx = 0 + + # should_line = self._in_ds + # if ( + # self._in_ds + # and uppx < x_gt + # ): + # should_line = False + + # elif ( + # not self._in_ds + # and uppx >= x_gt + # ): + # should_line = True + + # profiler('ds logic complete') + + # if should_line: + # # update the line graphic + # # x, y = self._ds_line_xy = ohlc_flatten(ohlc_iv) + # x, y = self._ds_line_xy = ohlc_flatten(ohlc) + # x_iv, y_iv = self._ds_line_xy = ohlc_flatten(ohlc_iv) + # profiler('flattening bars to line') + + # # TODO: we should be diffing the amount of new data which + # # needs to be downsampled. Ideally we actually are just + # # doing all the ds-ing in sibling actors so that the data + # # can just be read and rendered to graphics on events of our + # # choice. + # # diff = do_diff(ohlc, new_bit) + # curve = self._ds_line + # curve.update_from_array( + # x=x, + # y=y, + # x_iv=x_iv, + # y_iv=y_iv, + # view_range=None, # hack + # profiler=profiler, + # ) + # profiler('updated ds line') + + # if not self._in_ds: + # # hide bars and show line + # self.hide() + # # XXX: is this actually any faster? + # # self._pi.removeItem(self) + + # # TODO: a `.ui()` log level? + # log.info( + # f'downsampling to line graphic {self._name}' + # ) + + # # self._pi.addItem(curve) + # curve.show() + # curve.update() + # self._in_ds = True + + # # stop here since we don't need to update bars path any more + # # as we delegate to the downsample line with updates. + + # else: + # # we should be in bars mode + + # if self._in_ds: + # # flip back to bars graphics and hide the downsample line. + # log.info(f'showing bars graphic {self._name}') + + # curve = self._ds_line + # curve.hide() + # # self._pi.removeItem(curve) + + # # XXX: is this actually any faster? + # # self._pi.addItem(self) + # self.show() + # self._in_ds = False + + # # generate in_view path + # self.path = gen_qpath( + # ohlc_iv, + # 0, + # self.w, + # # path=self.path, + # ) + + # # TODO: to make the downsampling faster + # # - allow mapping only a range of lines thus only drawing as + # # many bars as exactly specified. + # # - move ohlc "flattening" to a shmarr + # # - maybe move all this embedded logic to a higher + # # level type? + + # # if prepend_length: + # # # new history was added and we need to render a new path + # # prepend_bars = ohlc[:prepend_length] + + # # if ds_prepend_length: + # # ds_prepend_bars = ohlc[:ds_prepend_length] + # # pre_x, pre_y = ohlc_flatten(ds_prepend_bars) + # # fx = np.concatenate((pre_x, fx)) + # # fy = np.concatenate((pre_y, fy)) + # # profiler('ds line prepend diff complete') + + # # if append_length: + # # # generate new graphics to match provided array + # # # path appending logic: + # # # we need to get the previous "current bar(s)" for the time step + # # # and convert it to a sub-path to append to the historical set + # # # new_bars = ohlc[istop - 1:istop + append_length - 1] + # # append_bars = ohlc[-append_length - 1:-1] + # # # print(f'ohlc bars to append size: {append_bars.size}\n') + + # # if ds_append_length: + # # ds_append_bars = ohlc[-ds_append_length - 1:-1] + # # post_x, post_y = ohlc_flatten(ds_append_bars) + # # print( + # # f'ds curve to append sizes: {(post_x.size, post_y.size)}' + # # ) + # # fx = np.concatenate((fx, post_x)) + # # fy = np.concatenate((fy, post_y)) + + # # profiler('ds line append diff complete') + + # profiler('array diffs complete') + + # # does this work? + # last = ohlc[-1] + # # fy[-1] = last['close'] + + # # # incremental update and cache line datums + # # self._ds_line_xy = fx, fy + + # # maybe downsample to line + # # ds = self.maybe_downsample() + # # if ds: + # # # if we downsample to a line don't bother with + # # # any more path generation / updates + # # self._ds_xrange = first_index, last_index + # # profiler('downsampled to line') + # # return + + # # print(in_view.size) + + # # if self.path: + # # self.path = path + # # self.path.reserve(path.capacity()) + # # self.path.swap(path) + + # # path updates + # # if prepend_length: + # # # XXX: SOMETHING IS MAYBE FISHY HERE what with the old_path + # # # y value not matching the first value from + # # # ohlc[prepend_length + 1] ??? + # # prepend_path = gen_qpath(prepend_bars, 0, self.w) + # # old_path = self.path + # # self.path = prepend_path + # # self.path.addPath(old_path) + # # profiler('path PREPEND') + + # # if append_length: + # # append_path = gen_qpath(append_bars, 0, self.w) + + # # self.path.moveTo( + # # float(istop - self.w), + # # float(append_bars[0]['open']) + # # ) + # # self.path.addPath(append_path) + + # # profiler('path APPEND') + # # fp = self.fast_path + # # if fp is None: + # # self.fast_path = append_path + + # # else: + # # fp.moveTo( + # # float(istop - self.w), float(new_bars[0]['open']) + # # ) + # # fp.addPath(append_path) + + # # self.setCacheMode(QtWidgets.QGraphicsItem.NoCache) + # # flip_cache = True + + # self._xrange = first_index, last_index + + # # trigger redraw despite caching + # self.prepareGeometryChange() + + # self.draw_last(last) + + # # # generate new lines objects for updatable "current bar" + # # self._last_bar_lines = bar_from_ohlc_row(last, self.w) + + # # # last bar update + # # i, o, h, l, last, v = last[ + # # ['index', 'open', 'high', 'low', 'close', 'volume'] + # # ] + # # # assert i == self.start_index - 1 + # # # assert i == last_index + # # body, larm, rarm = self._last_bar_lines + + # # # XXX: is there a faster way to modify this? + # # rarm.setLine(rarm.x1(), last, rarm.x2(), last) + + # # # writer is responsible for changing open on "first" volume of bar + # # larm.setLine(larm.x1(), o, larm.x2(), o) + + # # if l != h: # noqa + + # # if body is None: + # # body = self._last_bar_lines[0] = QLineF(i, l, i, h) + # # else: + # # # update body + # # body.setLine(i, l, i, h) + + # # # XXX: pretty sure this is causing an issue where the bar has + # # # a large upward move right before the next sample and the body + # # # is getting set to None since the next bar is flat but the shm + # # # array index update wasn't read by the time this code runs. Iow + # # # we're doing this removal of the body for a bar index that is + # # # now out of date / from some previous sample. It's weird + # # # though because i've seen it do this to bars i - 3 back? + + # profiler('last bar set') + + # self.update() + # profiler('.update()') + + # if flip_cache: + # self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) + + # # profiler.finish() + + def draw_last( self, - - # full array input history - ohlc: np.ndarray, - - # pre-sliced array data that's "in view" - ohlc_iv: np.ndarray, - - view_range: Optional[tuple[int, int]] = None, - profiler: Optional[pg.debug.Profiler] = None, + last: np.ndarray, ) -> None: - ''' - Update the last datum's bar graphic from input data array. + # generate new lines objects for updatable "current bar" + self._last_bar_lines = bar_from_ohlc_row(last, self.w) - This routine should be interface compatible with - ``pg.PlotCurveItem.setData()``. Normally this method in - ``pyqtgraph`` seems to update all the data passed to the - graphics object, and then update/rerender, but here we're - assuming the prior graphics havent changed (OHLC history rarely - does) so this "should" be simpler and faster. + # last bar update + i, o, h, l, last, v = last[ + ['index', 'open', 'high', 'low', 'close', 'volume'] + ] + # assert i == self.start_index - 1 + # assert i == last_index + body, larm, rarm = self._last_bar_lines - This routine should be made (transitively) as fast as possible. + # XXX: is there a faster way to modify this? + rarm.setLine(rarm.x1(), last, rarm.x2(), last) - ''' - profiler = profiler or pg.debug.Profiler( - disabled=not pg_profile_enabled(), - gt=ms_slower_then, - delayed=True, - ) + # writer is responsible for changing open on "first" volume of bar + larm.setLine(larm.x1(), o, larm.x2(), o) - # index = self.start_index - istart, istop = self._xrange - # ds_istart, ds_istop = self._ds_xrange + if l != h: # noqa - index = ohlc['index'] - first_index, last_index = index[0], index[-1] + if body is None: + body = self._last_bar_lines[0] = QLineF(i, l, i, h) + else: + # update body + body.setLine(i, l, i, h) - # length = len(ohlc) - # prepend_length = istart - first_index - # append_length = last_index - istop - - # ds_prepend_length = ds_istart - first_index - # ds_append_length = last_index - ds_istop - - flip_cache = False - - x_gt = 16 - if self._ds_line: - uppx = self._ds_line.x_uppx() - else: - uppx = 0 - - should_line = self._in_ds - if ( - self._in_ds - and uppx < x_gt - ): - should_line = False - - elif ( - not self._in_ds - and uppx >= x_gt - ): - should_line = True - - profiler('ds logic complete') - - if should_line: - # update the line graphic - # x, y = self._ds_line_xy = ohlc_flatten(ohlc_iv) - x, y = self._ds_line_xy = ohlc_flatten(ohlc) - x_iv, y_iv = self._ds_line_xy = ohlc_flatten(ohlc_iv) - profiler('flattening bars to line') - - # TODO: we should be diffing the amount of new data which - # needs to be downsampled. Ideally we actually are just - # doing all the ds-ing in sibling actors so that the data - # can just be read and rendered to graphics on events of our - # choice. - # diff = do_diff(ohlc, new_bit) - curve = self._ds_line - curve.update_from_array( - x=x, - y=y, - x_iv=x_iv, - y_iv=y_iv, - view_range=None, # hack - profiler=profiler, - ) - profiler('updated ds line') - - if not self._in_ds: - # hide bars and show line - self.hide() - # XXX: is this actually any faster? - # self._pi.removeItem(self) - - # TODO: a `.ui()` log level? - log.info( - f'downsampling to line graphic {self._name}' - ) - - # self._pi.addItem(curve) - curve.show() - curve.update() - self._in_ds = True - - # stop here since we don't need to update bars path any more - # as we delegate to the downsample line with updates. - - else: - # we should be in bars mode - - if self._in_ds: - # flip back to bars graphics and hide the downsample line. - log.info(f'showing bars graphic {self._name}') - - curve = self._ds_line - curve.hide() - # self._pi.removeItem(curve) - - # XXX: is this actually any faster? - # self._pi.addItem(self) - self.show() - self._in_ds = False - - # generate in_view path - self.path = gen_qpath( - ohlc_iv, - 0, - self.w, - # path=self.path, - ) - - # TODO: to make the downsampling faster - # - allow mapping only a range of lines thus only drawing as - # many bars as exactly specified. - # - move ohlc "flattening" to a shmarr - # - maybe move all this embedded logic to a higher - # level type? - - # if prepend_length: - # # new history was added and we need to render a new path - # prepend_bars = ohlc[:prepend_length] - - # if ds_prepend_length: - # ds_prepend_bars = ohlc[:ds_prepend_length] - # pre_x, pre_y = ohlc_flatten(ds_prepend_bars) - # fx = np.concatenate((pre_x, fx)) - # fy = np.concatenate((pre_y, fy)) - # profiler('ds line prepend diff complete') - - # if append_length: - # # generate new graphics to match provided array - # # path appending logic: - # # we need to get the previous "current bar(s)" for the time step - # # and convert it to a sub-path to append to the historical set - # # new_bars = ohlc[istop - 1:istop + append_length - 1] - # append_bars = ohlc[-append_length - 1:-1] - # # print(f'ohlc bars to append size: {append_bars.size}\n') - - # if ds_append_length: - # ds_append_bars = ohlc[-ds_append_length - 1:-1] - # post_x, post_y = ohlc_flatten(ds_append_bars) - # print( - # f'ds curve to append sizes: {(post_x.size, post_y.size)}' - # ) - # fx = np.concatenate((fx, post_x)) - # fy = np.concatenate((fy, post_y)) - - # profiler('ds line append diff complete') - - profiler('array diffs complete') - - # does this work? - last = ohlc[-1] - # fy[-1] = last['close'] - - # # incremental update and cache line datums - # self._ds_line_xy = fx, fy - - # maybe downsample to line - # ds = self.maybe_downsample() - # if ds: - # # if we downsample to a line don't bother with - # # any more path generation / updates - # self._ds_xrange = first_index, last_index - # profiler('downsampled to line') - # return - - # print(in_view.size) - - # if self.path: - # self.path = path - # self.path.reserve(path.capacity()) - # self.path.swap(path) - - # path updates - # if prepend_length: - # # XXX: SOMETHING IS MAYBE FISHY HERE what with the old_path - # # y value not matching the first value from - # # ohlc[prepend_length + 1] ??? - # prepend_path = gen_qpath(prepend_bars, 0, self.w) - # old_path = self.path - # self.path = prepend_path - # self.path.addPath(old_path) - # profiler('path PREPEND') - - # if append_length: - # append_path = gen_qpath(append_bars, 0, self.w) - - # self.path.moveTo( - # float(istop - self.w), - # float(append_bars[0]['open']) - # ) - # self.path.addPath(append_path) - - # profiler('path APPEND') - # fp = self.fast_path - # if fp is None: - # self.fast_path = append_path - - # else: - # fp.moveTo( - # float(istop - self.w), float(new_bars[0]['open']) - # ) - # fp.addPath(append_path) - - # self.setCacheMode(QtWidgets.QGraphicsItem.NoCache) - # flip_cache = True - - self._xrange = first_index, last_index - - # trigger redraw despite caching - self.prepareGeometryChange() - - # generate new lines objects for updatable "current bar" - self._last_bar_lines = bar_from_ohlc_row(last, self.w) - - # last bar update - i, o, h, l, last, v = last[ - ['index', 'open', 'high', 'low', 'close', 'volume'] - ] - # assert i == self.start_index - 1 - # assert i == last_index - body, larm, rarm = self._last_bar_lines - - # XXX: is there a faster way to modify this? - rarm.setLine(rarm.x1(), last, rarm.x2(), last) - - # writer is responsible for changing open on "first" volume of bar - larm.setLine(larm.x1(), o, larm.x2(), o) - - if l != h: # noqa - - if body is None: - body = self._last_bar_lines[0] = QLineF(i, l, i, h) - else: - # update body - body.setLine(i, l, i, h) - - # XXX: pretty sure this is causing an issue where the bar has - # a large upward move right before the next sample and the body - # is getting set to None since the next bar is flat but the shm - # array index update wasn't read by the time this code runs. Iow - # we're doing this removal of the body for a bar index that is - # now out of date / from some previous sample. It's weird - # though because i've seen it do this to bars i - 3 back? - - profiler('last bar set') - - self.update() - profiler('.update()') - - if flip_cache: - self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) - - # profiler.finish() + # XXX: pretty sure this is causing an issue where the bar has + # a large upward move right before the next sample and the body + # is getting set to None since the next bar is flat but the shm + # array index update wasn't read by the time this code runs. Iow + # we're doing this removal of the body for a bar index that is + # now out of date / from some previous sample. It's weird + # though because i've seen it do this to bars i - 3 back? def boundingRect(self): # Qt docs: https://doc.qt.io/qt-5/qgraphicsitem.html#boundingRect