Add scrolling from right and cross-hair
Modify the default ``ViewBox`` scroll to zoom behaviour such that whatever right-most point is visible is used as the "center" for zooming. Add a "traditional" cross-hair cursor.bar_select
parent
c8afdb0adc
commit
730241bb8a
|
@ -39,11 +39,10 @@ def run_qtrio(
|
|||
if app is None:
|
||||
app = PyQt5.QtWidgets.QApplication([])
|
||||
|
||||
# This code is from Nathaniel:
|
||||
|
||||
# This is substantially faster than using a signal... for some
|
||||
# This code is from Nathaniel, and I quote:
|
||||
# "This is substantially faster than using a signal... for some
|
||||
# reason Qt signal dispatch is really slow (and relies on events
|
||||
# underneath anyway, so this is strictly less work)
|
||||
# underneath anyway, so this is strictly less work)."
|
||||
REENTER_EVENT = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
||||
|
||||
class ReenterEvent(QtCore.QEvent):
|
||||
|
|
|
@ -260,32 +260,54 @@ class YAxisLabel(AxisLabel):
|
|||
self.setPos(new_pos)
|
||||
|
||||
|
||||
class ScrollFromRightView(pg.ViewBox):
|
||||
|
||||
def wheelEvent(self, ev, axis=None):
|
||||
if axis in (0, 1):
|
||||
mask = [False, False]
|
||||
mask[axis] = self.state['mouseEnabled'][axis]
|
||||
else:
|
||||
mask = self.state['mouseEnabled'][:]
|
||||
|
||||
# actual scaling factor
|
||||
s = 1.02 ** (ev.delta() * self.state['wheelScaleFactor'])
|
||||
s = [(None if m is False else s) for m in mask]
|
||||
|
||||
# XXX: scroll "around" the right most element in the view
|
||||
# center = pg.Point(
|
||||
# fn.invertQTransform(self.childGroup.transform()).map(ev.pos())
|
||||
# )
|
||||
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)
|
||||
|
||||
|
||||
# TODO: convert this to a ``ViewBox`` type giving us
|
||||
# control over mouse scrolling and a context menu
|
||||
# This is a sub-class of ``GracphicView`` which can
|
||||
# take a ``background`` color setting.
|
||||
class CustomPlotWidget(pg.PlotWidget):
|
||||
"""``GraphicsView`` subtype containing a single ``PlotItem``.
|
||||
|
||||
(Could be replaced with a ``pg.GraphicsLayoutWidget`` if we
|
||||
eventually want multiple plots managed together).
|
||||
"""
|
||||
|
||||
sig_mouse_leave = QtCore.Signal(object)
|
||||
sig_mouse_enter = QtCore.Signal(object)
|
||||
|
||||
# def wheelEvent(self, ev, axis=None):
|
||||
# if axis in (0, 1):
|
||||
# mask = [False, False]
|
||||
# mask[axis] = self.state['mouseEnabled'][axis]
|
||||
# else:
|
||||
# mask = self.state['mouseEnabled'][:]
|
||||
|
||||
# # actual scaling factor
|
||||
# s = 1.02 ** (ev.delta() * self.state['wheelScaleFactor'])
|
||||
# s = [(None if m is False else s) for m in mask]
|
||||
|
||||
# self._resetTarget()
|
||||
# self.scaleBy(s, center)
|
||||
# ev.accept()
|
||||
# self.sigRangeChangedManually.emit(mask)
|
||||
|
||||
def enterEvent(self, ev): # noqa
|
||||
pg.PlotWidget.enterEvent(self, ev)
|
||||
self.sig_mouse_enter.emit(self)
|
||||
|
||||
def leaveEvent(self, ev): # noqa
|
||||
pg.PlotWidget.leaveEvent(self, ev)
|
||||
self.sig_mouse_leave.emit(self)
|
||||
self.scene().leaveEvent(ev)
|
||||
|
||||
|
@ -441,6 +463,8 @@ class CrossHairItem(pg.GraphicsObject):
|
|||
|
||||
|
||||
class BarItem(pg.GraphicsObject):
|
||||
# XXX: From the customGraphicsItem.py example:
|
||||
# The only required methods are paint() and boundingRect()
|
||||
|
||||
w = 0.5
|
||||
|
||||
|
@ -452,7 +476,19 @@ class BarItem(pg.GraphicsObject):
|
|||
super().__init__()
|
||||
self.generatePicture()
|
||||
|
||||
# TODO: this is the routine to be retriggered for redraw
|
||||
@timeit
|
||||
def generatePicture(self):
|
||||
# pre-computing a QPicture object allows paint() to run much
|
||||
# more quickly, rather than re-drawing the shapes every time.
|
||||
self.picture = QtGui.QPicture()
|
||||
p = QtGui.QPainter(self.picture)
|
||||
self._generate(p)
|
||||
p.end()
|
||||
|
||||
def _generate(self, p):
|
||||
# XXX: overloaded method to allow drawing other candle types
|
||||
|
||||
high_to_low = np.array(
|
||||
[QtCore.QLineF(q.id, q.low, q.id, q.high) for q in Quotes]
|
||||
)
|
||||
|
@ -476,18 +512,14 @@ class BarItem(pg.GraphicsObject):
|
|||
p.setPen(self.bear_brush)
|
||||
p.drawLines(*lines[short_bars])
|
||||
|
||||
# TODO: this is the routine to be retriggered for redraw
|
||||
@timeit
|
||||
def generatePicture(self):
|
||||
self.picture = QtGui.QPicture()
|
||||
p = QtGui.QPainter(self.picture)
|
||||
self._generate(p)
|
||||
p.end()
|
||||
|
||||
def paint(self, p, *args):
|
||||
p.drawPicture(0, 0, self.picture)
|
||||
|
||||
def boundingRect(self):
|
||||
# boundingRect _must_ indicate the entire area that will be
|
||||
# drawn on or else we will get artifacts and possibly crashing.
|
||||
# (in this case, QPicture does all the work of computing the
|
||||
# bouning rect for us)
|
||||
return QtCore.QRectF(self.picture.boundingRect())
|
||||
|
||||
|
||||
|
@ -529,7 +561,13 @@ def _configure_quotes_chart(
|
|||
|
||||
chart.hideAxis('left')
|
||||
chart.showAxis('right')
|
||||
|
||||
# adds all bar/candle graphics objects for each
|
||||
# data point in the np array buffer to
|
||||
# be drawn on next render cycle
|
||||
chart.addItem(_get_chart_points(style))
|
||||
|
||||
# set panning limits
|
||||
chart.setLimits(
|
||||
xMin=Quotes[0].id,
|
||||
xMax=Quotes[-1].id,
|
||||
|
@ -537,8 +575,8 @@ def _configure_quotes_chart(
|
|||
yMin=Quotes.low.min() * 0.98,
|
||||
yMax=Quotes.high.max() * 1.02,
|
||||
)
|
||||
chart.showGrid(x=True, y=True)
|
||||
chart.setCursor(QtCore.Qt.BlankCursor)
|
||||
chart.showGrid(x=True, y=True, alpha=0.4)
|
||||
chart.setCursor(QtCore.Qt.CrossCursor)
|
||||
|
||||
# assign callback for rescaling y-axis automatically
|
||||
# based on y-range contents
|
||||
|
@ -607,6 +645,7 @@ class QuotesChart(QtGui.QWidget):
|
|||
ind.addItem(curve)
|
||||
ind.hideAxis('left')
|
||||
ind.showAxis('right')
|
||||
# XXX: never do this lol
|
||||
# ind.setAspectLocked(1)
|
||||
ind.setXLink(self.chart)
|
||||
ind.setLimits(
|
||||
|
@ -616,8 +655,8 @@ class QuotesChart(QtGui.QWidget):
|
|||
yMin=Quotes.open.min() * 0.98,
|
||||
yMax=Quotes.open.max() * 1.02,
|
||||
)
|
||||
ind.showGrid(x=True, y=True)
|
||||
ind.setCursor(QtCore.Qt.BlankCursor)
|
||||
ind.showGrid(x=True, y=True, alpha=0.4)
|
||||
ind.setCursor(QtCore.Qt.CrossCursor)
|
||||
|
||||
def _update_sizes(self):
|
||||
min_h_ind = int(self.height() * 0.3 / len(self.indicators))
|
||||
|
@ -655,7 +694,8 @@ class QuotesChart(QtGui.QWidget):
|
|||
self.chart = CustomPlotWidget(
|
||||
parent=self.splitter,
|
||||
axisItems={'bottom': self.xaxis, 'right': PriceAxis()},
|
||||
enableMenu=False,
|
||||
viewBox=ScrollFromRightView,
|
||||
# enableMenu=False,
|
||||
)
|
||||
self.chart.getPlotItem().setContentsMargins(*CHART_MARGINS)
|
||||
self.chart.setFrameStyle(QtGui.QFrame.StyledPanel | QtGui.QFrame.Plain)
|
||||
|
@ -668,7 +708,7 @@ class QuotesChart(QtGui.QWidget):
|
|||
parent=self.splitter,
|
||||
axisItems={'bottom': self.xaxis_ind, 'right': PriceAxis()},
|
||||
# axisItems={'top': self.xaxis_ind, 'right': PriceAxis()},
|
||||
enableMenu=False,
|
||||
# enableMenu=False,
|
||||
)
|
||||
ind.setFrameStyle(QtGui.QFrame.StyledPanel | QtGui.QFrame.Plain)
|
||||
ind.getPlotItem().setContentsMargins(*CHART_MARGINS)
|
||||
|
|
Loading…
Reference in New Issue