From 4e4696866292aaf6f5b4b7cdcb58433673f71faf Mon Sep 17 00:00:00 2001 From: Chris Barnes Date: Mon, 20 May 2019 13:17:55 -0400 Subject: [PATCH 1/2] TST: Axes.indicate_inset failure See #14275 inset_ax argument is meant to be optional, but currently fails if not given. --- lib/matplotlib/tests/test_axes.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index e3a68fd9ace8..57f86a252e04 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5959,6 +5959,35 @@ def test_tick_padding_tightbbox(): assert bb.y0 < bb2.y0 +def test_inset(): + """ + Ensure that inset_ax argument is indeed optional + """ + dx, dy = 0.05, 0.05 + # generate 2 2d grids for the x & y bounds + y, x = np.mgrid[slice(1, 5 + dy, dy), + slice(1, 5 + dx, dx)] + z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) + + fig, ax = plt.subplots() + ax.pcolormesh(x, y, z) + ax.set_aspect(1.) + ax.apply_aspect() + # we need to apply_aspect to make the drawing below work. + + xlim = [1.5, 2.15] + ylim = [2, 2.5] + + rect = [xlim[0], ylim[0], xlim[1] - xlim[0], ylim[1] - ylim[0]] + + rec, connectors = ax.indicate_inset(bounds=rect) + assert connectors is None + fig.canvas.draw() + xx = np.array([[1.5, 2.], + [2.15, 2.5]]) + assert np.all(rec.get_bbox().get_points() == xx) + + def test_zoom_inset(): dx, dy = 0.05, 0.05 # generate 2 2d grids for the x & y bounds @@ -5981,6 +6010,7 @@ def test_zoom_inset(): axin1.set_aspect(ax.get_aspect()) rec, connectors = ax.indicate_inset_zoom(axin1) + assert len(connectors) == 4 fig.canvas.draw() xx = np.array([[1.5, 2.], [2.15, 2.5]]) From 86f5fb04b68e04e95adf4303276dc2d14d0c7e37 Mon Sep 17 00:00:00 2001 From: Chris Barnes Date: Mon, 20 May 2019 11:49:16 -0400 Subject: [PATCH 2/2] FIX: Axes.indicate_inset* methods See #14275 1. Methods now return a tuple as documented, instead of a list. 2. indicate_inset() now does not error when optional inset_ax is not passed, but returns None instead of a tuple. --- doc/api/next_api_changes/2019-05-21-CB.rst | 13 ++++++ lib/matplotlib/axes/_axes.py | 49 ++++++++++++++-------- 2 files changed, 44 insertions(+), 18 deletions(-) create mode 100644 doc/api/next_api_changes/2019-05-21-CB.rst diff --git a/doc/api/next_api_changes/2019-05-21-CB.rst b/doc/api/next_api_changes/2019-05-21-CB.rst new file mode 100644 index 000000000000..39d0cdd29ff8 --- /dev/null +++ b/doc/api/next_api_changes/2019-05-21-CB.rst @@ -0,0 +1,13 @@ +``matplotlib.axes.Axes.indicate_inset`` returns a 4-tuple as documented +----------------------------------------------------------------------- + +In <= 3.1.0, ``matplotlib.axes.Axes.indicate_inset`` and +``matplotlib.axes.Axes.indicate_inset_zoom`` were documented as returning +a 4-tuple of ``matplotlib.patches.ConnectionPatch`` es, where in fact they +returned a 4-length list. + +They now correctly return a 4-tuple. +``matplotlib.axes.Axes.indicate_inset`` would previously raise an error if +the optional *inset_ax* was not supplied; it now completes successfully, +and returns *None* instead of the tuple of ``ConnectionPatch`` es. + diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 61a54c7eee41..195b0ba73ca3 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -8,12 +8,11 @@ import numpy as np from numpy import ma -from matplotlib import _preprocess_data, rcParams +import matplotlib.category as _ # <-registers a category unit converter import matplotlib.cbook as cbook import matplotlib.collections as mcoll import matplotlib.colors as mcolors import matplotlib.contour as mcontour -import matplotlib.category as _ # <-registers a category unit converter import matplotlib.dates as _ # <-registers a date unit converter import matplotlib.docstring as docstring import matplotlib.image as mimage @@ -21,8 +20,8 @@ import matplotlib.lines as mlines import matplotlib.markers as mmarkers import matplotlib.mlab as mlab -import matplotlib.path as mpath import matplotlib.patches as mpatches +import matplotlib.path as mpath import matplotlib.quiver as mquiver import matplotlib.stackplot as mstack import matplotlib.streamplot as mstream @@ -31,9 +30,10 @@ import matplotlib.ticker as mticker import matplotlib.transforms as mtransforms import matplotlib.tri as mtri -from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer +from matplotlib import _preprocess_data, rcParams from matplotlib.axes._base import _AxesBase, _process_plot_format from matplotlib.axes._secondary_axes import SecondaryAxis +from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer try: from numpy.lib.histograms import histogram_bin_edges @@ -514,9 +514,12 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None, rectangle_patch : `.Patches.Rectangle` Rectangle artist. - connector_lines : 4-tuple of `.Patches.ConnectionPatch` - One for each of four connector lines. Two are set with visibility - to *False*, but the user can set the visibility to True if the + connector_lines : optional 4-tuple of `.Patches.ConnectionPatch` + Each of four connector lines coming from the given rectangle + on this axes in the order lower left, upper left, lower right, + upper right: *None* if *inset_ax* is *None*. + Two are set with visibility to *False*, + but the user can set the visibility to *True* if the automatic choice is not deemed correct. """ @@ -535,25 +538,31 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None, zorder=zorder, label=label, transform=transform, **kwargs) self.add_patch(rectpatch) + connects = [] + if inset_ax is not None: # want to connect the indicator to the rect.... - connects = [] xr = [bounds[0], bounds[0]+bounds[2]] yr = [bounds[1], bounds[1]+bounds[3]] for xc in range(2): for yc in range(2): xyA = (xc, yc) xyB = (xr[xc], yr[yc]) - connects += [mpatches.ConnectionPatch(xyA, xyB, + connects.append( + mpatches.ConnectionPatch( + xyA, xyB, 'axes fraction', 'data', axesA=inset_ax, axesB=self, arrowstyle="-", - zorder=zorder, edgecolor=edgecolor, alpha=alpha)] + zorder=zorder, edgecolor=edgecolor, alpha=alpha + ) + ) self.add_patch(connects[-1]) # decide which two of the lines to keep visible.... pos = inset_ax.get_position() bboxins = pos.transformed(self.figure.transFigure) rectbbox = mtransforms.Bbox.from_bounds( - *bounds).transformed(transform) + *bounds + ).transformed(transform) x0 = rectbbox.x0 < bboxins.x0 x1 = rectbbox.x1 < bboxins.x1 y0 = rectbbox.y0 < bboxins.y0 @@ -563,7 +572,7 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None, connects[2].set_visible(x1 == y0) connects[3].set_visible(x1 ^ y1) - return rectpatch, connects + return rectpatch, tuple(connects) if connects else None def indicate_inset_zoom(self, inset_ax, **kwargs): """ @@ -583,7 +592,7 @@ def indicate_inset_zoom(self, inset_ax, **kwargs): chosen so as to not overlap with the indicator box. **kwargs - Other *kwargs* are passed on to `.Axes.inset_rectangle` + Other *kwargs* are passed on to `.Axes.indicate_inset` Returns ------- @@ -591,17 +600,21 @@ def indicate_inset_zoom(self, inset_ax, **kwargs): Rectangle artist. connector_lines : 4-tuple of `.Patches.ConnectionPatch` - One for each of four connector lines. Two are set with visibility - to *False*, but the user can set the visibility to True if the - automatic choice is not deemed correct. + Each of four connector lines coming from the rectangle drawn on + this axis, in the order lower left, upper left, lower right, + upper right. + Two are set with visibility to *False*, but the user can + set the visibility to *True* if the automatic choice is not deemed + correct. """ xlim = inset_ax.get_xlim() ylim = inset_ax.get_ylim() - rect = [xlim[0], ylim[0], xlim[1] - xlim[0], ylim[1] - ylim[0]] + rect = (xlim[0], ylim[0], xlim[1] - xlim[0], ylim[1] - ylim[0]) rectpatch, connects = self.indicate_inset( - rect, inset_ax, **kwargs) + rect, inset_ax, **kwargs + ) return rectpatch, connects