1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
|
import jupyter_rfb
import numpy as np
from .. import functions as fn
from .. import graphicsItems, widgets
from ..Qt import QtCore, QtGui
__all__ = ['GraphicsLayoutWidget', 'PlotWidget']
KMLUT = {
x : getattr(QtCore.Qt.KeyboardModifier, x + "Modifier")
for x in ["Shift", "Control", "Alt", "Meta"]
}
MBLUT = {
k : getattr(QtCore.Qt.MouseButton, v + "Button")
for (k, v) in zip(
range(6),
["No", "Left", "Right", "Middle", "Back", "Forward"]
)
}
TYPLUT = {
"pointer_down" : QtCore.QEvent.Type.MouseButtonPress,
"pointer_up" : QtCore.QEvent.Type.MouseButtonRelease,
"pointer_move" : QtCore.QEvent.Type.MouseMove,
}
def get_buttons(evt_buttons):
NoButton = QtCore.Qt.MouseButton.NoButton
btns = NoButton
for x in evt_buttons:
btns |= MBLUT.get(x, NoButton)
return btns
def get_modifiers(evt_modifiers):
NoModifier = QtCore.Qt.KeyboardModifier.NoModifier
mods = NoModifier
for x in evt_modifiers:
mods |= KMLUT.get(x, NoModifier)
return mods
class GraphicsView(jupyter_rfb.RemoteFrameBuffer):
"""jupyter_rfb.RemoteFrameBuffer sub-class that wraps around
:class:`GraphicsView <pyqtgraph.GraphicsView>`.
Generally speaking, there is no Qt event loop running. The implementation works by
requesting a render() of the scene. Thus things that would work for exporting
purposes would be expected to work here. Things that are not part of the scene
would not work, e.g. context menus, tooltips.
This class should not be used directly. Its corresponding sub-classes
:class:`GraphicsLayoutWidget <pyqtgraph.jupyter.GraphicsLayoutWidget>` and
:class:`PlotWidget <pyqtgraph.jupyter.PlotWidget>` should be used instead."""
def __init__(self, **kwds):
super().__init__(**kwds)
self.gfxView = widgets.GraphicsView.GraphicsView()
self.logical_size = int(self.css_width[:-2]), int(self.css_height[:-2])
self.pixel_ratio = 1.0
# self.gfxView.resize(*self.logical_size)
# self.gfxView.show()
# self.gfxView.resizeEvent(None)
def get_frame(self):
w, h = self.logical_size
dpr = self.pixel_ratio
buf = np.empty((int(h * dpr), int(w * dpr), 4), dtype=np.uint8)
qimg = fn.ndarray_to_qimage(buf, QtGui.QImage.Format.Format_RGBX8888)
qimg.fill(QtCore.Qt.GlobalColor.transparent)
qimg.setDevicePixelRatio(dpr)
painter = QtGui.QPainter(qimg)
self.gfxView.render(painter, self.gfxView.viewRect(), self.gfxView.rect())
painter.end()
return buf
def handle_event(self, event):
event_type = event["event_type"]
if event_type == "resize":
oldSize = QtCore.QSize(*self.logical_size)
self.logical_size = int(event["width"]), int(event["height"])
self.pixel_ratio = event["pixel_ratio"]
self.gfxView.resize(*self.logical_size)
newSize = QtCore.QSize(*self.logical_size)
self.gfxView.resizeEvent(QtGui.QResizeEvent(newSize, oldSize))
elif event_type in ["pointer_down", "pointer_up", "pointer_move"]:
btn = MBLUT.get(event["button"], None)
if btn is None: # ignore unsupported buttons
return
pos = QtCore.QPointF(event["x"], event["y"])
btns = get_buttons(event["buttons"])
mods = get_modifiers(event["modifiers"])
typ = TYPLUT[event_type]
evt = QtGui.QMouseEvent(typ, pos, pos, btn, btns, mods)
QtCore.QCoreApplication.sendEvent(self.gfxView.viewport(), evt)
self.request_draw()
elif event_type == "wheel":
pos = QtCore.QPointF(event["x"], event["y"])
pixdel = QtCore.QPoint()
scale = -1.0 # map JavaScript wheel to Qt wheel
angdel = QtCore.QPoint(int(event["dx"] * scale), int(event["dy"] * scale))
btns = get_buttons([])
mods = get_modifiers(event["modifiers"])
phase = QtCore.Qt.ScrollPhase.NoScrollPhase
inverted = False
evt = QtGui.QWheelEvent(pos, pos, pixdel, angdel, btns, mods, phase, inverted)
QtCore.QCoreApplication.sendEvent(self.gfxView.viewport(), evt)
def connect_viewbox_redraw(vb, request_draw):
# connecting these signals is enough to support zoom/pan
# but not enough to support the various graphicsItems
# that react to mouse events
vb.sigRangeChanged.connect(request_draw)
# zoom / pan
vb.sigRangeChangedManually.connect(request_draw)
# needed for "auto" button
vb.sigStateChanged.connect(request_draw)
# note that all cases of sig{X,Y}RangeChanged being emitted
# are also followed by sigRangeChanged or sigStateChanged
vb.sigTransformChanged.connect(request_draw)
class GraphicsLayoutWidget(GraphicsView):
"""jupyter_rfb analogue of
:class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>`."""
def __init__(self, **kwds):
super().__init__(**kwds)
self.gfxLayout = graphicsItems.GraphicsLayout.GraphicsLayout()
for n in [
'nextRow', 'nextCol', 'nextColumn', 'addItem', 'getItem',
'addLayout', 'addLabel', 'removeItem', 'itemIndex', 'clear'
]:
setattr(self, n, getattr(self.gfxLayout, n))
self.gfxView.setCentralItem(self.gfxLayout)
def addPlot(self, *args, **kwds):
kwds["enableMenu"] = False
plotItem = self.gfxLayout.addPlot(*args, **kwds)
connect_viewbox_redraw(plotItem.getViewBox(), self.request_draw)
return plotItem
def addViewBox(self, *args, **kwds):
kwds["enableMenu"] = False
vb = self.gfxLayout.addViewBox(*args, **kwds)
connect_viewbox_redraw(vb, self.request_draw)
return vb
class PlotWidget(GraphicsView):
"""jupyter_rfb analogue of
:class:`PlotWidget <pyqtgraph.PlotWidget>`."""
def __init__(self, **kwds):
super().__init__(**kwds)
plotItem = graphicsItems.PlotItem.PlotItem(enableMenu=False)
self.gfxView.setCentralItem(plotItem)
connect_viewbox_redraw(plotItem.getViewBox(), self.request_draw)
self.plotItem = plotItem
def getPlotItem(self):
return self.plotItem
def __getattr__(self, attr):
# kernel crashes if we don't skip attributes starting with '_'
if attr.startswith('_'):
return super().__getattr__(attr)
# implicitly wrap methods from plotItem
if hasattr(self.plotItem, attr):
m = getattr(self.plotItem, attr)
if hasattr(m, '__call__'):
return m
raise AttributeError(attr)
|