From 6eef5e1b7c5ed7a57f645e8ee964f16247b198f0 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 4 Jul 2019 00:05:14 +0200 Subject: [PATCH] Fix locating the connector lines in inverted axes --- lib/matplotlib/axes/_axes.py | 80 +++++++++++++++---------------- lib/matplotlib/tests/test_axes.py | 26 ++++++++++ 2 files changed, 66 insertions(+), 40 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 195b0ba73ca3..db432cf3b04a 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -423,13 +423,12 @@ def inset_axes(self, bounds, *, transform=None, zorder=5, parent axes. **kwargs - - Other *kwargs* are passed on to the `axes.Axes` child axes. + Other *kwargs* are passed on to the `~.axes.Axes` child axes. Returns ------- - Axes - The created `.axes.Axes` instance. + ax + The created `~.axes.Axes` instance. Examples -------- @@ -468,8 +467,7 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None, """ Add an inset indicator to the axes. This is a rectangle on the plot at the position indicated by *bounds* that optionally has lines that - connect the rectangle to an inset axes - (`.Axes.inset_axes`). + connect the rectangle to an inset axes (`.Axes.inset_axes`). Warnings -------- @@ -499,10 +497,10 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None, Color of the rectangle and color of the connecting lines. Default is '0.5'. - alpha : number + alpha : float Transparency of the rectangle and connector lines. Default is 0.5. - zorder : number + zorder : float Drawing order of the rectangle and connector lines. Default is 4.99 (just below the default level of inset axes). @@ -511,19 +509,16 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None, Returns ------- - rectangle_patch : `.Patches.Rectangle` - Rectangle artist. + rectangle_patch : `.patches.Rectangle` + The indicator frame. - 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. + connector_lines : 4-tuple of `.patches.ConnectionPatch` + The four connector lines connecting to (lower_left, upper_left, + lower_right upper_right) corners of *inset_ax*. Two lines are + set with visibility to *False*, but the user can set the + visibility to True if the automatic choice is not deemed correct. """ - # to make the axes connectors work, we need to apply the aspect to # the parent axes. self.apply_aspect() @@ -532,31 +527,36 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None, transform = self.transData label = kwargs.pop('label', 'indicate_inset') - xy = (bounds[0], bounds[1]) - rectpatch = mpatches.Rectangle(xy, bounds[2], bounds[3], - facecolor=facecolor, edgecolor=edgecolor, alpha=alpha, - zorder=zorder, label=label, transform=transform, **kwargs) - self.add_patch(rectpatch) + x, y, width, height = bounds + rectangle_patch = mpatches.Rectangle( + (x, y), width, height, + facecolor=facecolor, edgecolor=edgecolor, alpha=alpha, + zorder=zorder, label=label, transform=transform, **kwargs) + self.add_patch(rectangle_patch) connects = [] if inset_ax is not None: - # want to connect the indicator to the rect.... - 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.append( - mpatches.ConnectionPatch( - xyA, xyB, - 'axes fraction', 'data', - axesA=inset_ax, axesB=self, arrowstyle="-", - zorder=zorder, edgecolor=edgecolor, alpha=alpha - ) - ) - self.add_patch(connects[-1]) + # connect the inset_axes to the rectangle + for xy_inset_ax in [(0, 0), (0, 1), (1, 0), (1, 1)]: + # inset_ax positions are in axes coordinates + # The 0, 1 values define the four edges if the inset_ax + # lower_left, upper_left, lower_right upper_right. + ex, ey = xy_inset_ax + if self.xaxis.get_inverted(): + ex = 1 - ex + if self.yaxis.get_inverted(): + ey = 1 - ey + xy_data = x + ex * width, y + ey * height + p = mpatches.ConnectionPatch(xy_inset_ax, xy_data, + coordsA='axes fraction', + coordsB='data', + axesA=inset_ax, axesB=self, + arrowstyle="-", zorder=zorder, + edgecolor=edgecolor, alpha=alpha) + connects.append(p) + self.add_patch(p) + # decide which two of the lines to keep visible.... pos = inset_ax.get_position() bboxins = pos.transformed(self.figure.transFigure) @@ -572,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, tuple(connects) if connects else None + return rectangle_patch, tuple(connects) if connects else None def indicate_inset_zoom(self, inset_ax, **kwargs): """ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 57f86a252e04..f1adb4e4d692 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -6021,6 +6021,32 @@ def test_zoom_inset(): xx, rtol=1e-4) +@pytest.mark.parametrize('x_inverted', [False, True]) +@pytest.mark.parametrize('y_inverted', [False, True]) +def test_indicate_inset_inverted(x_inverted, y_inverted): + """ + Test that the inset lines are correctly located with inverted data axes. + """ + fig, (ax1, ax2) = plt.subplots(1, 2) + + x = np.arange(10) + ax1.plot(x, x, 'o') + if x_inverted: + ax1.invert_xaxis() + if y_inverted: + ax1.invert_yaxis() + + rect, bounds = ax1.indicate_inset([2, 2, 5, 4], ax2) + lower_left, upper_left, lower_right, upper_right = bounds + + sign_x = -1 if x_inverted else 1 + sign_y = -1 if y_inverted else 1 + assert sign_x * (lower_right.xy2[0] - lower_left.xy2[0]) > 0 + assert sign_x * (upper_right.xy2[0] - upper_left.xy2[0]) > 0 + assert sign_y * (upper_left.xy2[1] - lower_left.xy2[1]) > 0 + assert sign_y * (upper_right.xy2[1] - lower_right.xy2[1]) > 0 + + def test_set_position(): fig, ax = plt.subplots() ax.set_aspect(3.)