summaryrefslogtreecommitdiff
path: root/silx/gui/plot/backends/BackendMatplotlib.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/backends/BackendMatplotlib.py')
-rwxr-xr-xsilx/gui/plot/backends/BackendMatplotlib.py149
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(