diff options
Diffstat (limited to 'silx/gui/plot/backends/BackendMatplotlib.py')
-rwxr-xr-x[-rw-r--r--] | silx/gui/plot/backends/BackendMatplotlib.py | 496 |
1 files changed, 316 insertions, 180 deletions
diff --git a/silx/gui/plot/backends/BackendMatplotlib.py b/silx/gui/plot/backends/BackendMatplotlib.py index 7739329..075f6aa 100644..100755 --- a/silx/gui/plot/backends/BackendMatplotlib.py +++ b/silx/gui/plot/backends/BackendMatplotlib.py @@ -56,12 +56,13 @@ from matplotlib.collections import PathCollection, LineCollection from matplotlib.ticker import Formatter, ScalarFormatter, Locator from matplotlib.tri import Triangulation from matplotlib.collections import TriMesh +from matplotlib import path as mpath from . import BackendBase +from .. import items from .._utils import FLOAT32_MINPOS from .._utils.dtime_ticklayout import calcTicks, bestFormatString, timestamp - _PATCH_LINESTYLE = { "-": 'solid', "--": 'dashed', @@ -72,11 +73,54 @@ _PATCH_LINESTYLE = { } """Patches do not uses the same matplotlib syntax""" +_MARKER_PATHS = {} +"""Store cached extra marker paths""" + +_SPECIAL_MARKERS = { + 'tickleft': 0, + 'tickright': 1, + 'tickup': 2, + 'tickdown': 3, + 'caretleft': 4, + 'caretright': 5, + 'caretup': 6, + 'caretdown': 7, +} + def normalize_linestyle(linestyle): """Normalize known old-style linestyle, else return the provided value.""" return _PATCH_LINESTYLE.get(linestyle, linestyle) +def get_path_from_symbol(symbol): + """Get the path representation of a symbol, else None if + it is not provided. + + :param str symbol: Symbol description used by silx + :rtype: Union[None,matplotlib.path.Path] + """ + if symbol == u'\u2665': + path = _MARKER_PATHS.get(symbol, None) + if path is not None: + return path + vertices = numpy.array([ + [0,-99], + [31,-73], [47,-55], [55,-46], + [63,-37], [94,-2], [94,33], + [94,69], [71,89], [47,89], + [24,89], [8,74], [0,58], + [-8,74], [-24,89], [-47,89], + [-71,89], [-94,69], [-94,33], + [-94,-2], [-63,-37], [-55,-46], + [-47,-55], [-31,-73], [0,-99], + [0,-99]]) + codes = [mpath.Path.CURVE4] * len(vertices) + codes[0] = mpath.Path.MOVETO + codes[-1] = mpath.Path.CLOSEPOLY + path = mpath.Path(vertices, codes) + _MARKER_PATHS[symbol] = path + return path + return None class NiceDateLocator(Locator): """ @@ -162,7 +206,53 @@ class NiceAutoDateFormatter(Formatter): return tickStr -class _MarkerContainer(Container): +class _PickableContainer(Container): + """Artists container with a :meth:`contains` method""" + + def __init__(self, *args, **kwargs): + Container.__init__(self, *args, **kwargs) + self.__zorder = None + + @property + def axes(self): + """Mimin Artist.axes""" + for child in self.get_children(): + if hasattr(child, 'axes'): + return child.axes + return None + + def draw(self, *args, **kwargs): + """artist-like draw to broadcast draw to children""" + for child in self.get_children(): + child.draw(*args, **kwargs) + + def get_zorder(self): + """Mimic Artist.get_zorder""" + return self.__zorder + + def set_zorder(self, z): + """Mimic Artist.set_zorder to broadcast to children""" + if z != self.__zorder: + self.__zorder = z + for child in self.get_children(): + child.set_zorder(z) + + def contains(self, mouseevent): + """Mimic Artist.contains, and call it on all children. + + :param mouseevent: + :return: Picking status and associated information as a dict + :rtype: (bool,dict) + """ + # Goes through children from front to back and return first picked one. + for child in reversed(self.get_children()): + picked, info = child.contains(mouseevent) + if picked: + return picked, info + return False, {} + + +class _MarkerContainer(_PickableContainer): """Marker artists container supporting draw/remove and text position update :param artists: @@ -173,13 +263,14 @@ class _MarkerContainer(Container): :param y: Y coordinate of the marker (None for vertical lines) """ - def __init__(self, artists, x, y): + def __init__(self, artists, x, y, yAxis): self.line = artists[0] self.text = artists[1] if len(artists) > 1 else None self.x = x self.y = y + self.yAxis = yAxis - Container.__init__(self, artists) + _PickableContainer.__init__(self, artists) def draw(self, *args, **kwargs): """artist-like draw to broadcast draw to line and text""" @@ -214,6 +305,15 @@ class _MarkerContainer(Container): xmax -= 0.005 * delta self.text.set_x(xmax) + def contains(self, mouseevent): + """Mimic Artist.contains, and call it on the line Artist. + + :param mouseevent: + :return: Picking status and associated information as a dict + :rtype: (bool,dict) + """ + return self.line.contains(mouseevent) + class _DoubleColoredLinePatch(matplotlib.patches.Patch): """Matplotlib patch to display any patch using double color.""" @@ -294,7 +394,11 @@ class BackendMatplotlib(BackendBase.BackendBase): self.ax2 = self.ax.twinx() self.ax2.set_label("right") # Make sure background of Axes is displayed - self.ax2.patch.set_visible(True) + self.ax2.patch.set_visible(False) + self.ax.patch.set_visible(True) + + # Set axis zorder=0.5 so grid is displayed at 0.5 + self.ax.set_axisbelow(True) # disable the use of offsets try: @@ -306,10 +410,8 @@ class BackendMatplotlib(BackendBase.BackendBase): _logger.warning('Cannot disabled axes offsets in %s ' % matplotlib.__version__) - # critical for picking!!!! - self.ax2.set_zorder(0) self.ax2.set_autoscaley_on(True) - self.ax.set_zorder(1) + # this works but the figure color is left if self._matplotlibVersion < _parse_version('2'): self.ax.set_axis_bgcolor('none') @@ -317,7 +419,6 @@ class BackendMatplotlib(BackendBase.BackendBase): self.ax.set_facecolor('none') self.fig.sca(self.ax) - self._overlays = set() self._background = None self._colormaps = {} @@ -327,15 +428,67 @@ class BackendMatplotlib(BackendBase.BackendBase): self._enableAxis('right', False) self._isXAxisTimeSeries = False + def getItemsFromBackToFront(self, condition=None): + """Order as BackendBase + take into account matplotlib Axes structure""" + def axesOrder(item): + if item.isOverlay(): + return 2 + elif isinstance(item, items.YAxisMixIn) and item.getYAxis() == 'right': + return 1 + else: + return 0 + + return sorted( + BackendBase.BackendBase.getItemsFromBackToFront( + self, condition=condition), + key=axesOrder) + + def _overlayItems(self): + """Generator of backend renderer for overlay items""" + for item in self._plot.getItems(): + if (item.isOverlay() and + item.isVisible() and + item._backendRenderer is not None): + yield item._backendRenderer + + def _hasOverlays(self): + """Returns whether there is an overlay layer or not. + + The overlay layers contains overlay items and the crosshair. + + :rtype: bool + """ + if self._graphCursor: + return True # There is the crosshair + + for item in self._overlayItems(): + return True # There is at least one overlay item + return False + # Add methods - def addCurve(self, x, y, legend, + def _getMarkerFromSymbol(self, symbol): + """Returns a marker that can be displayed by matplotlib. + + :param str symbol: A symbol description used by silx + :rtype: Union[str,int,matplotlib.path.Path] + """ + path = get_path_from_symbol(symbol) + if path is not None: + return path + num = _SPECIAL_MARKERS.get(symbol, None) + if num is not None: + return num + # This symbol must be supported by matplotlib + return symbol + + def addCurve(self, x, y, color, symbol, linewidth, linestyle, yaxis, - xerror, yerror, z, selectable, - fill, alpha, symbolsize): - for parameter in (x, y, legend, color, symbol, linewidth, linestyle, - yaxis, z, selectable, fill, alpha, symbolsize): + xerror, yerror, z, + fill, alpha, symbolsize, baseline): + for parameter in (x, y, color, symbol, linewidth, linestyle, + yaxis, z, fill, alpha, symbolsize): assert parameter is not None assert yaxis in ('left', 'right') @@ -349,7 +502,7 @@ class BackendMatplotlib(BackendBase.BackendBase): else: axes = self.ax - picker = 3 if selectable else None + picker = 3 artists = [] # All the artists composing the curve @@ -368,7 +521,7 @@ class BackendMatplotlib(BackendBase.BackendBase): yerror.shape[1] == 1): yerror = numpy.ravel(yerror) - errorbars = axes.errorbar(x, y, label=legend, + errorbars = axes.errorbar(x, y, xerr=xerror, yerr=yerror, linestyle=' ', color=errorbarColor) artists += list(errorbars.get_children()) @@ -383,7 +536,7 @@ class BackendMatplotlib(BackendBase.BackendBase): if linestyle not in ["", " ", None]: # scatter plot with an actual line ... # we need to assign a color ... - curveList = axes.plot(x, y, label=legend, + curveList = axes.plot(x, y, linestyle=linestyle, color=actualColor[0], linewidth=linewidth, @@ -391,21 +544,24 @@ class BackendMatplotlib(BackendBase.BackendBase): marker=None) artists += list(curveList) + marker = self._getMarkerFromSymbol(symbol) scatter = axes.scatter(x, y, - label=legend, color=actualColor, - marker=symbol, + marker=marker, picker=picker, s=symbolsize**2) artists.append(scatter) if fill: + if baseline is None: + _baseline = FLOAT32_MINPOS + else: + _baseline = baseline artists.append(axes.fill_between( - x, FLOAT32_MINPOS, y, facecolor=actualColor[0], linestyle='')) + x, _baseline, y, facecolor=actualColor[0], linestyle='')) else: # Curve curveList = axes.plot(x, y, - label=legend, linestyle=linestyle, color=color, linewidth=linewidth, @@ -415,40 +571,35 @@ class BackendMatplotlib(BackendBase.BackendBase): artists += list(curveList) if fill: + if baseline is None: + _baseline = FLOAT32_MINPOS + else: + _baseline = baseline artists.append( - axes.fill_between(x, FLOAT32_MINPOS, y, facecolor=color)) + axes.fill_between(x, _baseline, y, facecolor=color)) for artist in artists: - artist.set_zorder(z) if alpha < 1: artist.set_alpha(alpha) - return Container(artists) + return _PickableContainer(artists) - def addImage(self, data, legend, - origin, scale, z, - selectable, draggable, - colormap, alpha): + def addImage(self, data, origin, scale, z, colormap, alpha): # Non-uniform image # http://wiki.scipy.org/Cookbook/Histograms # Non-linear axes # http://stackoverflow.com/questions/11488800/non-linear-axes-for-imshow-in-matplotlib - for parameter in (data, legend, origin, scale, z, - selectable, draggable): + for parameter in (data, origin, scale, z): assert parameter is not None origin = float(origin[0]), float(origin[1]) scale = float(scale[0]), float(scale[1]) height, width = data.shape[0:2] - picker = (selectable or draggable) - # All image are shown as RGBA image image = Image(self.ax, - label="__IMAGE__" + legend, interpolation='nearest', - picker=picker, - zorder=z, + picker=True, origin='lower') if alpha < 1: @@ -481,15 +632,10 @@ class BackendMatplotlib(BackendBase.BackendBase): self.ax.add_artist(image) return image - def addTriangles(self, x, y, triangles, legend, - color, z, selectable, alpha): - for parameter in (x, y, triangles, legend, color, - z, selectable, alpha): + def addTriangles(self, x, y, triangles, color, z, alpha): + for parameter in (x, y, triangles, color, z, alpha): assert parameter is not None - # 0 enables picking on filled triangle - picker = 0 if selectable else None - color = numpy.array(color, copy=False) assert color.ndim == 2 and len(color) == len(x) @@ -498,16 +644,14 @@ class BackendMatplotlib(BackendBase.BackendBase): collection = TriMesh( Triangulation(x, y, triangles), - label=legend, alpha=alpha, - picker=picker, - zorder=z) + picker=0) # 0 enables picking on filled triangle collection.set_color(color) self.ax.add_collection(collection) return collection - def addItem(self, x, y, legend, shape, color, fill, overlay, z, + def addItem(self, x, y, shape, color, fill, overlay, z, linestyle, linewidth, linebgcolor): if (linebgcolor is not None and shape not in ('rectangle', 'polygon', 'polylines')): @@ -520,20 +664,20 @@ class BackendMatplotlib(BackendBase.BackendBase): linestyle = normalize_linestyle(linestyle) if shape == "line": - item = self.ax.plot(x, y, label=legend, color=color, + item = self.ax.plot(x, y, color=color, linestyle=linestyle, linewidth=linewidth, marker=None)[0] elif shape == "hline": if hasattr(y, "__len__"): y = y[-1] - item = self.ax.axhline(y, label=legend, color=color, + item = self.ax.axhline(y, color=color, linestyle=linestyle, linewidth=linewidth) elif shape == "vline": if hasattr(x, "__len__"): x = x[-1] - item = self.ax.axvline(x, label=legend, color=color, + item = self.ax.axvline(x, color=color, linestyle=linestyle, linewidth=linewidth) elif shape == 'rectangle': @@ -568,7 +712,6 @@ class BackendMatplotlib(BackendBase.BackendBase): item = Polygon(points, closed=closed, fill=False, - label=legend, color=color, linestyle=linestyle, linewidth=linewidth) @@ -584,30 +727,32 @@ class BackendMatplotlib(BackendBase.BackendBase): else: raise NotImplementedError("Unsupported item shape %s" % shape) - item.set_zorder(z) - if overlay: item.set_animated(True) - self._overlays.add(item) return item - def addMarker(self, x, y, legend, text, color, - selectable, draggable, - symbol, linestyle, linewidth, constraint): - legend = "__MARKER__" + legend - + def addMarker(self, x, y, text, color, + symbol, linestyle, linewidth, constraint, yaxis): textArtist = None xmin, xmax = self.getGraphXLimits() - ymin, ymax = self.getGraphYLimits(axis='left') + ymin, ymax = self.getGraphYLimits(axis=yaxis) + + if yaxis == 'left': + ax = self.ax + elif yaxis == 'right': + ax = self.ax2 + else: + assert(False) + marker = self._getMarkerFromSymbol(symbol) if x is not None and y is not None: - line = self.ax.plot(x, y, label=legend, - linestyle=" ", - color=color, - marker=symbol, - markersize=10.)[-1] + line = ax.plot(x, y, + linestyle=" ", + color=color, + marker=marker, + markersize=10.)[-1] if text is not None: if symbol is None: @@ -616,43 +761,40 @@ class BackendMatplotlib(BackendBase.BackendBase): valign = 'top' text = " " + text - textArtist = self.ax.text(x, y, text, - color=color, - horizontalalignment='left', - verticalalignment=valign) + textArtist = ax.text(x, y, text, + color=color, + horizontalalignment='left', + verticalalignment=valign) elif x is not None: - line = self.ax.axvline(x, - label=legend, - color=color, - linewidth=linewidth, - linestyle=linestyle) + line = ax.axvline(x, + color=color, + linewidth=linewidth, + linestyle=linestyle) if text is not None: # Y position will be updated in updateMarkerText call - textArtist = self.ax.text(x, 1., " " + text, - color=color, - horizontalalignment='left', - verticalalignment='top') + textArtist = ax.text(x, 1., " " + text, + color=color, + horizontalalignment='left', + verticalalignment='top') elif y is not None: - line = self.ax.axhline(y, - label=legend, - color=color, - linewidth=linewidth, - linestyle=linestyle) + line = ax.axhline(y, + color=color, + linewidth=linewidth, + linestyle=linestyle) if text is not None: # X position will be updated in updateMarkerText call - textArtist = self.ax.text(1., y, " " + text, - color=color, - horizontalalignment='right', - verticalalignment='top') + textArtist = ax.text(1., y, " " + text, + color=color, + horizontalalignment='right', + verticalalignment='top') else: raise RuntimeError('A marker must at least have one coordinate') - if selectable or draggable: - line.set_picker(5) + line.set_picker(5) # All markers are overlays line.set_animated(True) @@ -660,24 +802,25 @@ class BackendMatplotlib(BackendBase.BackendBase): textArtist.set_animated(True) artists = [line] if textArtist is None else [line, textArtist] - container = _MarkerContainer(artists, x, y) + container = _MarkerContainer(artists, x, y, yaxis) container.updateMarkerText(xmin, xmax, ymin, ymax) - self._overlays.add(container) return container def _updateMarkers(self): xmin, xmax = self.ax.get_xbound() - ymin, ymax = self.ax.get_ybound() - for item in self._overlays: + ymin1, ymax1 = self.ax.get_ybound() + ymin2, ymax2 = self.ax2.get_ybound() + for item in self._overlayItems(): if isinstance(item, _MarkerContainer): - item.updateMarkerText(xmin, xmax, ymin, ymax) + if item.yAxis == 'left': + item.updateMarkerText(xmin, xmax, ymin1, ymax1) + else: + item.updateMarkerText(xmin, xmax, ymin2, ymax2) # Remove methods def remove(self, item): - # Warning: It also needs to remove extra stuff if added as for markers - self._overlays.discard(item) try: item.remove() except ValueError: @@ -699,7 +842,7 @@ class BackendMatplotlib(BackendBase.BackendBase): self._graphCursor = lineh, linev else: - if self._graphCursor is not None: + if self._graphCursor: lineh, linev = self._graphCursor lineh.remove() linev.remove() @@ -746,7 +889,37 @@ class BackendMatplotlib(BackendBase.BackendBase): if not self.ax2.lines: self._enableAxis('right', False) + def _drawOverlays(self): + """Draw overlays if any.""" + def condition(item): + return (item.isVisible() and + item._backendRenderer is not None and + item.isOverlay()) + + for item in self.getItemsFromBackToFront(condition=condition): + if (isinstance(item, items.YAxisMixIn) and + item.getYAxis() == 'right'): + axes = self.ax2 + else: + axes = self.ax + axes.draw_artist(item._backendRenderer) + + for item in self._graphCursor: + self.ax.draw_artist(item) + + def updateZOrder(self): + """Reorder all items with z order from 0 to 1""" + items = self.getItemsFromBackToFront( + lambda item: item.isVisible() and item._backendRenderer is not None) + count = len(items) + for index, item in enumerate(items): + zorder = 1. + index / count + if zorder != item._backendRenderer.get_zorder(): + item._backendRenderer.set_zorder(zorder) + def saveGraph(self, fileName, fileFormat, dpi): + self.updateZOrder() + # fileName can be also a StringIO or file instance if dpi is not None: self.fig.savefig(fileName, format=fileFormat, dpi=dpi) @@ -788,7 +961,9 @@ class BackendMatplotlib(BackendBase.BackendBase): def getGraphXLimits(self): if self._dirtyLimits and self.isKeepDataAspectRatio(): - self.replot() # makes sure we get the right limits + self.ax.apply_aspect() + self.ax2.apply_aspect() + self._dirtyLimits = False return self.ax.get_xbound() def setGraphXLimits(self, xmin, xmax): @@ -804,7 +979,9 @@ class BackendMatplotlib(BackendBase.BackendBase): return None if self._dirtyLimits and self.isKeepDataAspectRatio(): - self.replot() # makes sure we get the right limits + self.ax.apply_aspect() + self.ax2.apply_aspect() + self._dirtyLimits = False return ax.get_ybound() @@ -936,7 +1113,7 @@ class BackendMatplotlib(BackendBase.BackendBase): return xPixel, yPixel - def pixelToData(self, x, y, axis, check): + def pixelToData(self, x, y, axis): ax = self.ax2 if axis == "right" else self.ax # Convert from Qt origin (top) to matplotlib origin (bottom) @@ -944,14 +1121,6 @@ class BackendMatplotlib(BackendBase.BackendBase): inv = ax.transData.inverted() x, y = inv.transform_point((x, y)) - - if check: - xmin, xmax = self.getGraphXLimits() - ymin, ymax = self.getGraphYLimits(axis=axis) - - if x > xmax or x < xmin or y > ymax or y < ymin: - return None # (x, y) is out of plot area - return x, y def getPlotBoundsInPixels(self): @@ -996,12 +1165,12 @@ class BackendMatplotlib(BackendBase.BackendBase): else: dataBackgroundColor = backgroundColor - if self.ax2.axison: + if self.ax.axison: self.fig.patch.set_facecolor(backgroundColor) if self._matplotlibVersion < _parse_version('2'): - self.ax2.set_axis_bgcolor(dataBackgroundColor) + self.ax.set_axis_bgcolor(dataBackgroundColor) else: - self.ax2.set_facecolor(dataBackgroundColor) + self.ax.set_facecolor(dataBackgroundColor) else: self.fig.patch.set_facecolor(dataBackgroundColor) @@ -1033,6 +1202,12 @@ class BackendMatplotlib(BackendBase.BackendBase): line.set_color(gridColor) # axes.grid().set_markeredgecolor(gridColor) + def setBackgroundColors(self, backgroundColor, dataBackgroundColor): + self._synchronizeBackgroundColors() + + def setForegroundColors(self, foregroundColor, gridColor): + self._synchronizeForegroundColors() + class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): """QWidget matplotlib backend using a QtAgg canvas. @@ -1079,9 +1254,11 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): _MPL_TO_PLOT_BUTTONS = {1: 'left', 2: 'middle', 3: 'right'} def _onMousePress(self, event): - self._plot.onMousePress( - event.x, self._mplQtYAxisCoordConversion(event.y), - self._MPL_TO_PLOT_BUTTONS[event.button]) + 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) def _onMouseMove(self, event): if self._graphCursor: @@ -1102,9 +1279,11 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): event.x, self._mplQtYAxisCoordConversion(event.y)) def _onMouseRelease(self, event): - self._plot.onMouseRelease( - event.x, self._mplQtYAxisCoordConversion(event.y), - self._MPL_TO_PLOT_BUTTONS[event.button]) + 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) def _onMouseWheel(self, event): self._plot.onMouseWheel( @@ -1116,58 +1295,31 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): # picking - def _onPick(self, event): - # TODO not very nice and fragile, find a better way? - # Make a selection according to kind - if self._picked is None: - _logger.error('Internal picking error') - return - - label = event.artist.get_label() - if label.startswith('__MARKER__'): - self._picked.append({'kind': 'marker', 'legend': label[10:]}) + def pickItem(self, x, y, item): + mouseEvent = MouseEvent( + 'button_press_event', self, x, self._mplQtYAxisCoordConversion(y)) + mouseEvent.inaxes = item.axes + picked, info = item.contains(mouseEvent) - elif label.startswith('__IMAGE__'): - self._picked.append({'kind': 'image', 'legend': label[9:]}) + if not picked: + return None - elif isinstance(event.artist, TriMesh): + elif isinstance(item, TriMesh): # Convert selected triangle to data point indices - triangulation = event.artist._triangulation - indices = triangulation.get_masked_triangles()[event.ind[0]] + triangulation = item._triangulation + indices = triangulation.get_masked_triangles()[info['ind'][0]] # Sort picked triangle points by distance to mouse # from furthest to closest to put closest point last # This is to be somewhat consistent with last scatter point # being the top one. - dists = ((triangulation.x[indices] - event.mouseevent.xdata) ** 2 + - (triangulation.y[indices] - event.mouseevent.ydata) ** 2) - indices = indices[numpy.flip(numpy.argsort(dists))] - - self._picked.append({'kind': 'curve', 'legend': label, - 'indices': indices}) + xdata, ydata = self.pixelToData(x, y, axis='left') + dists = ((triangulation.x[indices] - xdata) ** 2 + + (triangulation.y[indices] - ydata) ** 2) + return indices[numpy.flip(numpy.argsort(dists), axis=0)] - else: # it's a curve, item have no picker for now - if not isinstance(event.artist, (PathCollection, Line2D)): - _logger.info('Unsupported artist, ignored') - return - - self._picked.append({'kind': 'curve', 'legend': label, - 'indices': event.ind}) - - def pickItems(self, x, y, kinds): - self._picked = [] - - # Weird way to do an explicit picking: Simulate a button press event - mouseEvent = MouseEvent('button_press_event', - self, x, self._mplQtYAxisCoordConversion(y)) - cid = self.mpl_connect('pick_event', self._onPick) - self.fig.pick(mouseEvent) - self.mpl_disconnect(cid) - - picked = [p for p in self._picked if p['kind'] in kinds] - self._picked = None - - return picked + else: # Returns indices if any + return info.get('ind', ()) # replot control @@ -1177,22 +1329,10 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): self.ax.get_xbound(), self.ax.get_ybound(), self.ax2.get_ybound()) FigureCanvasQTAgg.resizeEvent(self, event) - if self.isKeepDataAspectRatio() or self._overlays or self._graphCursor: + if self.isKeepDataAspectRatio() or self._hasOverlays(): # This is needed with matplotlib 1.5.x and 2.0.x self._plot._setDirtyPlot() - def _drawOverlays(self): - """Draw overlays if any.""" - if self._overlays or self._graphCursor: - # There is some overlays or crosshair - - # This assume that items are only on left/bottom Axes - for item in self._overlays: - self.ax.draw_artist(item) - - for item in self._graphCursor: - self.ax.draw_artist(item) - def draw(self): """Overload draw @@ -1201,6 +1341,8 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): This is directly called by matplotlib for widget resize. """ + self.updateZOrder() + # Starting with mpl 2.1.0, toggling autoscale raises a ValueError # in some situations. See #1081, #1136, #1163, if self._matplotlibVersion >= _parse_version("2.0.0"): @@ -1213,7 +1355,7 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): else: FigureCanvasQTAgg.draw(self) - if self._overlays or self._graphCursor: + if self._hasOverlays(): # Save background self._background = self.copy_from_bbox(self.fig.bbox) else: @@ -1257,7 +1399,7 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): if (_parse_version('1.5') <= self._matplotlibVersion < _parse_version('2.1') and not hasattr(self, '_firstReplot')): self._firstReplot = False - if self._overlays or self._graphCursor: + if self._hasOverlays(): qt.QTimer.singleShot(0, self.draw) # Request async draw # cursor @@ -1276,9 +1418,3 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): else: cursor = self._QT_CURSORS[cursor] FigureCanvasQTAgg.setCursor(self, qt.QCursor(cursor)) - - def setBackgroundColors(self, backgroundColor, dataBackgroundColor): - self._synchronizeBackgroundColors() - - def setForegroundColors(self, foregroundColor, gridColor): - self._synchronizeForegroundColors() |