diff --git a/piker/ui/_interaction.py b/piker/ui/_interaction.py new file mode 100644 index 00000000..bde6a4c6 --- /dev/null +++ b/piker/ui/_interaction.py @@ -0,0 +1,77 @@ +""" +UX interaction customs. +""" +import pyqtgraph as pg +from pyqtgraph import functions as fn + +from ..log import get_logger +from ._style import _min_points_to_show + + +log = get_logger(__name__) + + +class ChartView(pg.ViewBox): + """Price chart view box with interaction behaviors you'd expect from + any interactive platform: + + - zoom on mouse scroll that auto fits y-axis + - no vertical scrolling + - zoom to a "fixed point" on the y-axis + """ + def __init__( + self, + parent=None, + **kwargs, + ): + super().__init__(parent=parent, **kwargs) + # disable vertical scrolling + self.setMouseEnabled(x=True, y=False) + self.linked_charts = None + + def wheelEvent(self, ev, axis=None): + """Override "center-point" location for scrolling. + + This is an override of the ``ViewBox`` method simply changing + the center of the zoom to be the y-axis. + + TODO: PR a method into ``pyqtgraph`` to make this configurable + """ + + if axis in (0, 1): + mask = [False, False] + mask[axis] = self.state['mouseEnabled'][axis] + else: + mask = self.state['mouseEnabled'][:] + + # don't zoom more then the min points setting + l, lbar, rbar, r = self.linked_charts.chart.bars_range() + vl = r - l + + if ev.delta() > 0 and vl <= _min_points_to_show: + log.trace("Max zoom bruh...") + return + if ev.delta() < 0 and vl >= len(self.linked_charts._array): + log.trace("Min zoom bruh...") + return + + # actual scaling factor + s = 1.015 ** (ev.delta() * -1 / 20) # self.state['wheelScaleFactor']) + s = [(None if m is False else s) for m in mask] + + # center = pg.Point( + # fn.invertQTransform(self.childGroup.transform()).map(ev.pos()) + # ) + + # XXX: scroll "around" the right most element in the view + furthest_right_coord = self.boundingRect().topRight() + center = pg.Point( + fn.invertQTransform( + self.childGroup.transform() + ).map(furthest_right_coord) + ) + + self._resetTarget() + self.scaleBy(s, center) + ev.accept() + self.sigRangeChangedManually.emit(mask)