diff options
Diffstat (limited to 'silx/gui/plot/backends/BackendMatplotlib.py')
-rwxr-xr-x | silx/gui/plot/backends/BackendMatplotlib.py | 149 |
1 files changed, 77 insertions, 72 deletions
diff --git a/silx/gui/plot/backends/BackendMatplotlib.py b/silx/gui/plot/backends/BackendMatplotlib.py index 036e630..140672f 100755 --- a/silx/gui/plot/backends/BackendMatplotlib.py +++ b/silx/gui/plot/backends/BackendMatplotlib.py @@ -33,6 +33,7 @@ __date__ = "21/12/2018" import logging import datetime as dt +from typing import Tuple import numpy from pkg_resources import parse_version as _parse_version @@ -44,7 +45,7 @@ _logger = logging.getLogger(__name__) from ... import qt # First of all init matplotlib and set its backend -from ..matplotlib import FigureCanvasQTAgg +from ...utils.matplotlib import FigureCanvasQTAgg import matplotlib from matplotlib.container import Container from matplotlib.figure import Figure @@ -593,7 +594,7 @@ class BackendMatplotlib(BackendBase.BackendBase): if (len(color) == 4 and type(color[3]) in [type(1), numpy.uint8, numpy.int8]): - color = numpy.array(color, dtype=numpy.float) / 255. + color = numpy.array(color, dtype=numpy.float64) / 255. if yaxis == "right": axes = self.ax2 @@ -601,7 +602,7 @@ class BackendMatplotlib(BackendBase.BackendBase): else: axes = self.ax - picker = 3 + pickradius = 3 artists = [] # All the artists composing the curve @@ -627,7 +628,7 @@ class BackendMatplotlib(BackendBase.BackendBase): if hasattr(color, 'dtype') and len(color) == len(x): # scatter plot - if color.dtype not in [numpy.float32, numpy.float]: + if color.dtype not in [numpy.float32, numpy.float64]: actualColor = color / 255. else: actualColor = color @@ -639,7 +640,8 @@ class BackendMatplotlib(BackendBase.BackendBase): linestyle=linestyle, color=actualColor[0], linewidth=linewidth, - picker=picker, + picker=True, + pickradius=pickradius, marker=None) artists += list(curveList) @@ -647,7 +649,8 @@ class BackendMatplotlib(BackendBase.BackendBase): scatter = axes.scatter(x, y, color=actualColor, marker=marker, - picker=picker, + picker=True, + pickradius=pickradius, s=symbolsize**2) artists.append(scatter) @@ -665,7 +668,8 @@ class BackendMatplotlib(BackendBase.BackendBase): color=color, linewidth=linewidth, marker=symbol, - picker=picker, + picker=True, + pickradius=pickradius, markersize=symbolsize) artists += list(curveList) @@ -744,13 +748,13 @@ class BackendMatplotlib(BackendBase.BackendBase): color = numpy.array(color, copy=False) assert color.ndim == 2 and len(color) == len(x) - if color.dtype not in [numpy.float32, numpy.float]: + if color.dtype not in [numpy.float32, numpy.float64]: color = color.astype(numpy.float32) / 255. collection = TriMesh( Triangulation(x, y, triangles), alpha=alpha, - picker=0) # 0 enables picking on filled triangle + pickradius=0) # 0 enables picking on filled triangle collection.set_color(color) self.ax.add_collection(collection) @@ -893,7 +897,8 @@ class BackendMatplotlib(BackendBase.BackendBase): else: raise RuntimeError('A marker must at least have one coordinate') - line.set_picker(5) + line.set_picker(True) + line.set_pickradius(5) # All markers are overlays line.set_animated(True) @@ -1014,7 +1019,11 @@ class BackendMatplotlib(BackendBase.BackendBase): lambda item: item.isVisible() and item._backendRenderer is not None) count = len(items) for index, item in enumerate(items): - zorder = 1. + index / count + if item.getZValue() < 0.5: + # Make sure matplotlib z order is below the grid (with z=0.5) + zorder = 0.5 * index / count + else: # Make sure matplotlib z order is above the grid (> 0.5) + zorder = 1. + index / count if zorder != item._backendRenderer.get_zorder(): item._backendRenderer.set_zorder(zorder) @@ -1196,67 +1205,58 @@ class BackendMatplotlib(BackendBase.BackendBase): # Data <-> Pixel coordinates conversion - def _mplQtYAxisCoordConversion(self, y, asint=True): - """Qt origin (top) to/from matplotlib origin (bottom) conversion. + def _getDevicePixelRatio(self) -> float: + """Compatibility wrapper for devicePixelRatioF""" + return 1. - :param y: - :param bool asint: True to cast to int, False to keep as float + def _mplToQtPosition(self, x: float, y: float) -> Tuple[float, float]: + """Convert matplotlib "display" space coord to Qt widget logical pixel + """ + ratio = self._getDevicePixelRatio() + # Convert from matplotlib origin (bottom) to Qt origin (top) + # and apply device pixel ratio + return x / ratio, (self.fig.get_window_extent().height - y) / ratio - :rtype: float + def _qtToMplPosition(self, x: float, y: float) -> Tuple[float, float]: + """Convert Qt widget logical pixel to matplotlib "display" space coord """ - value = self.fig.get_window_extent().height - y - return int(value) if asint else value + ratio = self._getDevicePixelRatio() + # Apply device pixel ration and + # convert from Qt origin (top) to matplotlib origin (bottom) + return x * ratio, self.fig.get_window_extent().height - (y * ratio) def dataToPixel(self, x, y, axis): ax = self.ax2 if axis == "right" else self.ax - - pixels = ax.transData.transform_point((x, y)) - xPixel, yPixel = pixels.T - - # Convert from matplotlib origin (bottom) to Qt origin (top) - yPixel = self._mplQtYAxisCoordConversion(yPixel, asint=False) - - return xPixel, yPixel + displayPos = ax.transData.transform_point((x, y)).transpose() + return self._mplToQtPosition(*displayPos) def pixelToData(self, x, y, axis): ax = self.ax2 if axis == "right" else self.ax - - # Convert from Qt origin (top) to matplotlib origin (bottom) - y = self._mplQtYAxisCoordConversion(y, asint=False) - - inv = ax.transData.inverted() - x, y = inv.transform_point((x, y)) - return x, y + displayPos = self._qtToMplPosition(x, y) + return tuple(ax.transData.inverted().transform_point(displayPos)) def getPlotBoundsInPixels(self): bbox = self.ax.get_window_extent() # Warning this is not returning int... - return (int(bbox.xmin), - self._mplQtYAxisCoordConversion(bbox.ymax, asint=True), - int(bbox.width), - int(bbox.height)) + ratio = self._getDevicePixelRatio() + return tuple(int(value / ratio) for value in ( + bbox.xmin, + self.fig.get_window_extent().height - bbox.ymax, + bbox.width, + bbox.height)) - def setAxesDisplayed(self, displayed): - """Display or not the axes. + def setAxesMargins(self, left: float, top: float, right: float, bottom: float): + width, height = 1. - left - right, 1. - top - bottom + position = left, bottom, width, height + + # Toggle display of axes and viewbox rect + isFrameOn = position != (0., 0., 1., 1.) + self.ax.set_frame_on(isFrameOn) + self.ax2.set_frame_on(isFrameOn) + + self.ax.set_position(position) + self.ax2.set_position(position) - :param bool displayed: If `True` axes are displayed. If `False` axes - are not anymore visible and the margin used for them is removed. - """ - BackendBase.BackendBase.setAxesDisplayed(self, displayed) - if displayed: - # show axes and viewbox rect - self.ax.set_frame_on(True) - self.ax2.set_frame_on(True) - # set the default margins - self.ax.set_position([.15, .15, .75, .75]) - self.ax2.set_position([.15, .15, .75, .75]) - else: - # hide axes and viewbox rect - self.ax.set_frame_on(False) - self.ax2.set_frame_on(False) - # remove external margins - self.ax.set_position([0, 0, 1, 1]) - self.ax2.set_position([0, 0, 1, 1]) self._synchronizeBackgroundColors() self._synchronizeForegroundColors() self._plot._setDirtyPlot() @@ -1349,6 +1349,15 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): def postRedisplay(self): self._sigPostRedisplay.emit() + def _getDevicePixelRatio(self) -> float: + """Compatibility wrapper for devicePixelRatioF""" + if hasattr(self, 'devicePixelRatioF'): + ratio = self.devicePixelRatioF() + else: # Qt < 5.6 compatibility + ratio = float(self.devicePixelRatio()) + # Safety net: avoid returning 0 + return ratio if ratio != 0. else 1. + # Mouse event forwarding _MPL_TO_PLOT_BUTTONS = {1: 'left', 2: 'middle', 3: 'right'} @@ -1356,17 +1365,14 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): def _onMousePress(self, event): button = self._MPL_TO_PLOT_BUTTONS.get(event.button, None) if button is not None: - self._plot.onMousePress( - event.x, self._mplQtYAxisCoordConversion(event.y), - button) + x, y = self._mplToQtPosition(event.x, event.y) + self._plot.onMousePress(int(x), int(y), button) def _onMouseMove(self, event): + x, y = self._mplToQtPosition(event.x, event.y) if self._graphCursor: position = self._plot.pixelToData( - event.x, - self._mplQtYAxisCoordConversion(event.y), - axis='left', - check=True) + x, y, axis='left', check=True) lineh, linev = self._graphCursor if position is not None: linev.set_visible(True) @@ -1380,19 +1386,17 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): self._plot._setDirtyPlot(overlayOnly=True) # onMouseMove must trigger replot if dirty flag is raised - self._plot.onMouseMove( - event.x, self._mplQtYAxisCoordConversion(event.y)) + self._plot.onMouseMove(int(x), int(y)) def _onMouseRelease(self, event): button = self._MPL_TO_PLOT_BUTTONS.get(event.button, None) if button is not None: - self._plot.onMouseRelease( - event.x, self._mplQtYAxisCoordConversion(event.y), - button) + x, y = self._mplToQtPosition(event.x, event.y) + self._plot.onMouseRelease(int(x), int(y), button) def _onMouseWheel(self, event): - self._plot.onMouseWheel( - event.x, self._mplQtYAxisCoordConversion(event.y), event.step) + x, y = self._mplToQtPosition(event.x, event.y) + self._plot.onMouseWheel(int(x), int(y), event.step) def leaveEvent(self, event): """QWidget event handler""" @@ -1406,8 +1410,9 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): # picking def pickItem(self, x, y, item): + xDisplay, yDisplay = self._qtToMplPosition(x, y) mouseEvent = MouseEvent( - 'button_press_event', self, x, self._mplQtYAxisCoordConversion(y)) + 'button_press_event', self, int(xDisplay), int(yDisplay)) # Override axes and data position with the axes mouseEvent.inaxes = item.axes mouseEvent.xdata, mouseEvent.ydata = self.pixelToData( |