From cf835e3702f56cdc981c33308c55a1eafb8c7c8f Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Sat, 3 Oct 2020 17:03:07 +0200 Subject: [PATCH 01/23] implement a function `axline_transaxes` for plotting a line through a point in axes coordinates and with a slope in data coordinates fixes #18625 --- lib/matplotlib/axes/_axes.py | 49 +++++++++++++++++++++++++++++++ lib/matplotlib/lines.py | 28 ++++++++++++++++++ lib/matplotlib/tests/test_axes.py | 13 ++++++++ 3 files changed, 90 insertions(+) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 58348483cac9..e6e6fbe710ac 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -876,6 +876,55 @@ def _to_points(xy1, xy2, slope): self._request_autoscale_view() return line + def axline_transaxes(self, xy1, slope, **kwargs): + """ + Add an infinitely long straight line. + + The line is defined by one point in axes coordinates *xy1* and a + *slope* in data coordinates. + + This should only be used with linear scales; the *slope* has no clear + meaning for all other scales, and thus the behavior is undefined. + + Parameters + ---------- + xy1 : (float, float) + Point for the line to pass through, in axes coordinates. + slope : float + The slope of the line, in data coordinates. + + Returns + ------- + `.Line2D` + + Other Parameters + ---------------- + **kwargs + Valid kwargs are `.Line2D` properties, with the exception of + 'transform': + + %(_Line2D_docstr)s + + See Also + -------- + axhline : for horizontal lines + axvline : for vertical lines + + Examples + -------- + Draw a thick red line passing through (0, 0) + (the lower left corner of the viewport) with slope 1:: + + >>> axline_transaxes((0, 0), 1, linewidth=4, color='r') + """ + if "transform" in kwargs: + raise TypeError("'transform' is not allowed as a kwarg; " + "axline_transaxes generates its own transform") + + line = mlines._AxLineTransAxes(xy1, slope, **kwargs) + self.add_line(line) + return line + @docstring.dedent_interpd def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs): """ diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 1aea828908e6..35ad179868ae 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1440,6 +1440,34 @@ def draw(self, renderer): super().draw(renderer) +class _AxLineTransAxes(_AxLine): + """ + A helper class that implements `~.Axes.axline_transaxes`, by recomputing + the artist transform at draw time. + """ + + def __init__(self, xy1, slope, **kwargs): + super().__init__([0, 1], [0, 1], **kwargs) + self._slope = slope + self._xy1 = xy1 + + def get_transform(self): + ax = self.axes + (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim) + x, y = (ax.transAxes + ax.transData.inverted()).transform(self._xy1) + + # find intersections with view limits in either direction, + # and draw between the middle two points. + _, start, stop, _ = sorted([ + (vxlo, y + (vxlo - x) * self._slope), + (vxhi, y + (vxhi - x) * self._slope), + (x + (vylo - y) / self._slope, vylo), + (x + (vyhi - y) / self._slope, vyhi), + ]) + return (BboxTransformTo(Bbox([start, stop])) + + ax.transLimits + ax.transAxes) + + class VertexSelector: """ Manage the callbacks to maintain a list of selected vertices for diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 9093a91b106d..f66eb0e01922 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4072,6 +4072,19 @@ def test_axline(fig_test, fig_ref): ax.axvline(-0.5, color='C5') +@check_figures_equal() +def test_axline_transform(fig_test, fig_ref): + ax = fig_test.subplots() + ax.set(xlim=(-1, 1), ylim=(-1, 1)) + ax.axline_transaxes((0, 0), slope=1) + ax.axline_transaxes((1, 0.5), slope=1, color='C1') + + ax = fig_ref.subplots() + ax.set(xlim=(-1, 1), ylim=(-1, 1)) + ax.plot([-1, 1], [-1, 1]) + ax.plot([0, 1], [-1, 0], color='C1') + + def test_axline_args(): """Exactly one of *xy2* and *slope* must be specified.""" fig, ax = plt.subplots() From 527aa460d25dfbd43229bdae6308b162db9eb902 Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Sat, 3 Oct 2020 18:58:46 +0200 Subject: [PATCH 02/23] improve docs --- lib/matplotlib/axes/_axes.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index e6e6fbe710ac..2d4e2a9653b0 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -880,8 +880,10 @@ def axline_transaxes(self, xy1, slope, **kwargs): """ Add an infinitely long straight line. - The line is defined by one point in axes coordinates *xy1* and a - *slope* in data coordinates. + The line is defined by a *slope* in data coordinates, set by the + current x and y limits of the axes, and a point in axes-relative + coordinates, which is in the same place on the axes regardless of x + and y limits. This should only be used with linear scales; the *slope* has no clear meaning for all other scales, and thus the behavior is undefined. @@ -890,6 +892,8 @@ def axline_transaxes(self, xy1, slope, **kwargs): ---------- xy1 : (float, float) Point for the line to pass through, in axes coordinates. + ``(0, 0)`` is the bottom left corner of the axes, ``(1, 1)`` is + the upper right. slope : float The slope of the line, in data coordinates. From 45e5bc77005fe74ce187363026ee92b8ba944d45 Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Sat, 3 Oct 2020 19:10:08 +0200 Subject: [PATCH 03/23] improve tests --- lib/matplotlib/tests/test_axes.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index f66eb0e01922..250cb160aa3a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4073,16 +4073,38 @@ def test_axline(fig_test, fig_ref): @check_figures_equal() -def test_axline_transform(fig_test, fig_ref): +def test_axline_transaxes(fig_test, fig_ref): ax = fig_test.subplots() ax.set(xlim=(-1, 1), ylim=(-1, 1)) ax.axline_transaxes((0, 0), slope=1) ax.axline_transaxes((1, 0.5), slope=1, color='C1') + ax.axline_transaxes((0.5, 0.5), slope=0, color='C2') ax = fig_ref.subplots() ax.set(xlim=(-1, 1), ylim=(-1, 1)) ax.plot([-1, 1], [-1, 1]) ax.plot([0, 1], [-1, 0], color='C1') + ax.plot([-1, 1], [0, 0], color='C2') + + +@check_figures_equal() +def test_axline_transaxes_panzoom(fig_test, fig_ref): + # test that it is robust against pan/zoom and + # figure resize after plotting + ax = fig_test.subplots() + ax.set(xlim=(-1, 1), ylim=(-1, 1)) + ax.axline_transaxes((0, 0), slope=1) + ax.axline_transaxes((0.5, 0.5), slope=2, color='C1') + ax.axline_transaxes((0.5, 0.5), slope=0, color='C2') + ax.set(xlim=(0, 5), ylim=(0, 10)) + fig_test.set_size_inches(3, 3) + + ax = fig_ref.subplots() + ax.set(xlim=(0, 5), ylim=(0, 10)) + fig_ref.set_size_inches(3, 3) + ax.plot([0, 5], [0, 5]) + ax.plot([0, 5], [0, 10], color='C1') + ax.plot([0, 5], [5, 5], color='C2') def test_axline_args(): From bd8e82bd22316044541b24a5047d855a7b399293 Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Sat, 3 Oct 2020 19:21:30 +0200 Subject: [PATCH 04/23] fix special case for slope=0 --- lib/matplotlib/lines.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 35ad179868ae..9018eced5376 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1456,14 +1456,18 @@ def get_transform(self): (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim) x, y = (ax.transAxes + ax.transData.inverted()).transform(self._xy1) - # find intersections with view limits in either direction, - # and draw between the middle two points. - _, start, stop, _ = sorted([ - (vxlo, y + (vxlo - x) * self._slope), - (vxhi, y + (vxhi - x) * self._slope), - (x + (vylo - y) / self._slope, vylo), - (x + (vyhi - y) / self._slope, vyhi), - ]) + if np.isclose(self._slope, 0): + start = vxlo, y + stop = vxhi, y + else: + # find intersections with view limits in either direction, + # and draw between the middle two points. + _, start, stop, _ = sorted([ + (vxlo, y + (vxlo - x) * self._slope), + (vxhi, y + (vxhi - x) * self._slope), + (x + (vylo - y) / self._slope, vylo), + (x + (vyhi - y) / self._slope, vyhi), + ]) return (BboxTransformTo(Bbox([start, stop])) + ax.transLimits + ax.transAxes) From da040d0c68f5086ff0b939514a02d7d0dc0091e8 Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Sat, 3 Oct 2020 20:32:29 +0200 Subject: [PATCH 05/23] implement simpler solution for calculation of axline_transaxes --- lib/matplotlib/lines.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 9018eced5376..a4a7dea4103d 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -18,7 +18,7 @@ from .markers import MarkerStyle from .path import Path from .transforms import ( - Affine2D, Bbox, BboxTransformFrom, BboxTransformTo, TransformedPath) + Affine2D, Bbox, BboxTransformFrom, BboxTransformTo, TransformedPath, AffineDeltaTransform) # Imported here for backward compatibility, even though they don't # really belong. @@ -1453,23 +1453,15 @@ def __init__(self, xy1, slope, **kwargs): def get_transform(self): ax = self.axes - (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim) - x, y = (ax.transAxes + ax.transData.inverted()).transform(self._xy1) + x, y = self._xy1 - if np.isclose(self._slope, 0): - start = vxlo, y - stop = vxhi, y - else: - # find intersections with view limits in either direction, - # and draw between the middle two points. - _, start, stop, _ = sorted([ - (vxlo, y + (vxlo - x) * self._slope), - (vxhi, y + (vxhi - x) * self._slope), - (x + (vylo - y) / self._slope, vylo), - (x + (vyhi - y) / self._slope, vyhi), - ]) - return (BboxTransformTo(Bbox([start, stop])) - + ax.transLimits + ax.transAxes) + dx, dy = AffineDeltaTransform(ax.transData + ax.transAxes.inverted()).transform([1, self._slope]) + slope_axes = dy / dx + + start = 0, slope_axes * (0 - x) + y + stop = 1, slope_axes * (1 - x) + y + + return BboxTransformTo(Bbox([start, stop])) + ax.transAxes class VertexSelector: From 02b8c59cb97f509e0aab81f2e253f046595c7935 Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Sat, 3 Oct 2020 20:35:28 +0200 Subject: [PATCH 06/23] fix flake8 errors --- lib/matplotlib/lines.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index a4a7dea4103d..5a751a8f8e5a 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -18,7 +18,8 @@ from .markers import MarkerStyle from .path import Path from .transforms import ( - Affine2D, Bbox, BboxTransformFrom, BboxTransformTo, TransformedPath, AffineDeltaTransform) + Affine2D, Bbox, BboxTransformFrom, BboxTransformTo, TransformedPath, + AffineDeltaTransform) # Imported here for backward compatibility, even though they don't # really belong. @@ -1455,7 +1456,8 @@ def get_transform(self): ax = self.axes x, y = self._xy1 - dx, dy = AffineDeltaTransform(ax.transData + ax.transAxes.inverted()).transform([1, self._slope]) + dx, dy = AffineDeltaTransform(ax.transData + ax.transAxes.inverted())\ + .transform([1, self._slope]) slope_axes = dy / dx start = 0, slope_axes * (0 - x) + y From 9292f1860220eb1d574060c08d34fe8caa133b48 Mon Sep 17 00:00:00 2001 From: johan12345 Date: Tue, 20 Oct 2020 19:33:48 +0200 Subject: [PATCH 07/23] replace separate function axline_transaxes with new functionality for axline --- lib/matplotlib/axes/_axes.py | 90 ++++++------------------------ lib/matplotlib/lines.py | 92 +++++++++++++++---------------- lib/matplotlib/tests/test_axes.py | 14 +++-- 3 files changed, 69 insertions(+), 127 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 2d4e2a9653b0..b459a5018304 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -808,6 +808,12 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs): all other scales, and thus the behavior is undefined. Please specify the line using the points *xy1*, *xy2* for non-linear scales. + As part of the ``kwargs``, a 'transform' can be passed. In the case + that a point and a slope are given, the transformation will only be + used for the point and not for the slope, i.e. the slope stays in data + coordinates. This can be used e.g. with ``ax.transAxes`` for drawing + grid lines with a fixed slope. + Parameters ---------- xy1, xy2 : (float, float) @@ -823,8 +829,7 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs): Other Parameters ---------------- **kwargs - Valid kwargs are `.Line2D` properties, with the exception of - 'transform': + Valid kwargs are `.Line2D` properties %(_Line2D_docstr)s @@ -839,30 +844,22 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs): >>> axline((0, 0), (1, 1), linewidth=4, color='r') """ - def _to_points(xy1, xy2, slope): - """ - Check for a valid combination of input parameters and convert - to two points, if necessary. - """ - if (xy2 is None and slope is None or - xy2 is not None and slope is not None): - raise TypeError( - "Exactly one of 'xy2' and 'slope' must be given") - if xy2 is None: - x1, y1 = xy1 - xy2 = (x1, y1 + 1) if np.isinf(slope) else (x1 + 1, y1 + slope) - return xy1, xy2 + if (xy2 is None and slope is None or + xy2 is not None and slope is not None): + raise TypeError( + "Exactly one of 'xy2' and 'slope' must be given") - if "transform" in kwargs: - raise TypeError("'transform' is not allowed as a kwarg; " - "axline generates its own transform") if slope is not None and (self.get_xscale() != 'linear' or self.get_yscale() != 'linear'): raise TypeError("'slope' cannot be used with non-linear scales") datalim = [xy1] if xy2 is None else [xy1, xy2] - (x1, y1), (x2, y2) = _to_points(xy1, xy2, slope) - line = mlines._AxLine([x1, x2], [y1, y2], **kwargs) + if "transform" in kwargs: + #TODO: datalim = (kwargs["transform"] + # + self.transData.inverted()).transform(datalim)? + datalim = [] + + line = mlines._AxLine(xy1, xy2, slope, **kwargs) # Like add_line, but correctly handling data limits. self._set_artist_props(line) if line.get_clip_path() is None: @@ -876,59 +873,6 @@ def _to_points(xy1, xy2, slope): self._request_autoscale_view() return line - def axline_transaxes(self, xy1, slope, **kwargs): - """ - Add an infinitely long straight line. - - The line is defined by a *slope* in data coordinates, set by the - current x and y limits of the axes, and a point in axes-relative - coordinates, which is in the same place on the axes regardless of x - and y limits. - - This should only be used with linear scales; the *slope* has no clear - meaning for all other scales, and thus the behavior is undefined. - - Parameters - ---------- - xy1 : (float, float) - Point for the line to pass through, in axes coordinates. - ``(0, 0)`` is the bottom left corner of the axes, ``(1, 1)`` is - the upper right. - slope : float - The slope of the line, in data coordinates. - - Returns - ------- - `.Line2D` - - Other Parameters - ---------------- - **kwargs - Valid kwargs are `.Line2D` properties, with the exception of - 'transform': - - %(_Line2D_docstr)s - - See Also - -------- - axhline : for horizontal lines - axvline : for vertical lines - - Examples - -------- - Draw a thick red line passing through (0, 0) - (the lower left corner of the viewport) with slope 1:: - - >>> axline_transaxes((0, 0), 1, linewidth=4, color='r') - """ - if "transform" in kwargs: - raise TypeError("'transform' is not allowed as a kwarg; " - "axline_transaxes generates its own transform") - - line = mlines._AxLineTransAxes(xy1, slope, **kwargs) - self.add_line(line) - return line - @docstring.dedent_interpd def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs): """ diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 5a751a8f8e5a..2dda9fd9f25f 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1406,34 +1406,55 @@ class _AxLine(Line2D): transform at draw time. """ + def __init__(self, xy1, xy2, slope, **kwargs): + super().__init__([0, 1], [0, 1], **kwargs) + self._slope = slope + self._xy1 = xy1 + self._xy2 = xy2 + self._transform = kwargs.get("transform") + def get_transform(self): ax = self.axes - (x1, y1), (x2, y2) = ax.transScale.transform([*zip(*self.get_data())]) - dx = x2 - x1 - dy = y2 - y1 - if np.allclose(x1, x2): - if np.allclose(y1, y2): - raise ValueError( - f"Cannot draw a line through two identical points " - f"(x={self.get_xdata()}, y={self.get_ydata()})") - # First send y1 to 0 and y2 to 1. - return (Affine2D.from_values(1, 0, 0, 1 / dy, 0, -y1 / dy) - + ax.get_xaxis_transform(which="grid")) - if np.allclose(y1, y2): - # First send x1 to 0 and x2 to 1. - return (Affine2D.from_values(1 / dx, 0, 0, 1, -x1 / dx, 0) - + ax.get_yaxis_transform(which="grid")) + if self._transform is not None: + points_transform = self._transform + ax.transData.inverted() + else: + points_transform = ax.transScale + + if self._xy2 is not None: + # two points were given + (x1, y1), (x2, y2) = \ + points_transform.transform([self._xy1, self._xy2]) + dx = x2 - x1 + dy = y2 - y1 + if np.allclose(x1, x2): + if np.allclose(y1, y2): + raise ValueError( + f"Cannot draw a line through two identical points " + f"(x={self.get_xdata()}, y={self.get_ydata()})") + slope = np.inf + else: + slope = dy / dx + else: + # one point and a slope were given + x1, y1 = points_transform.transform(self._xy1) + slope = self._slope (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim) # General case: find intersections with view limits in either # direction, and draw between the middle two points. - _, start, stop, _ = sorted([ - (vxlo, y1 + (vxlo - x1) * dy / dx), - (vxhi, y1 + (vxhi - x1) * dy / dx), - (x1 + (vylo - y1) * dx / dy, vylo), - (x1 + (vyhi - y1) * dx / dy, vyhi), - ]) - return (BboxTransformFrom(Bbox([*zip(*self.get_data())])) - + BboxTransformTo(Bbox([start, stop])) + if np.isclose(slope, 0): + start = vxlo, y1 + stop = vxhi, y1 + elif np.isinf(slope): + start = x1, vylo + stop = x1, vyhi + else: + _, start, stop, _ = sorted([ + (vxlo, y1 + (vxlo - x1) * slope), + (vxhi, y1 + (vxhi - x1) * slope), + (x1 + (vylo - y1) / slope, vylo), + (x1 + (vyhi - y1) / slope, vyhi), + ]) + return (BboxTransformTo(Bbox([start, stop])) + ax.transLimits + ax.transAxes) def draw(self, renderer): @@ -1441,31 +1462,6 @@ def draw(self, renderer): super().draw(renderer) -class _AxLineTransAxes(_AxLine): - """ - A helper class that implements `~.Axes.axline_transaxes`, by recomputing - the artist transform at draw time. - """ - - def __init__(self, xy1, slope, **kwargs): - super().__init__([0, 1], [0, 1], **kwargs) - self._slope = slope - self._xy1 = xy1 - - def get_transform(self): - ax = self.axes - x, y = self._xy1 - - dx, dy = AffineDeltaTransform(ax.transData + ax.transAxes.inverted())\ - .transform([1, self._slope]) - slope_axes = dy / dx - - start = 0, slope_axes * (0 - x) + y - stop = 1, slope_axes * (1 - x) + y - - return BboxTransformTo(Bbox([start, stop])) + ax.transAxes - - class VertexSelector: """ Manage the callbacks to maintain a list of selected vertices for diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 250cb160aa3a..5f8ba5bf073b 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4076,15 +4076,17 @@ def test_axline(fig_test, fig_ref): def test_axline_transaxes(fig_test, fig_ref): ax = fig_test.subplots() ax.set(xlim=(-1, 1), ylim=(-1, 1)) - ax.axline_transaxes((0, 0), slope=1) - ax.axline_transaxes((1, 0.5), slope=1, color='C1') - ax.axline_transaxes((0.5, 0.5), slope=0, color='C2') + ax.axline((0, 0), slope=1, transform=ax.transAxes) + ax.axline((1, 0.5), slope=1, color='C1', transform=ax.transAxes) + ax.axline((0.5, 0.5), slope=0, color='C2', transform=ax.transAxes) + ax.axline((0.5, 0), (0.5, 1), color='C3', transform=ax.transAxes) ax = fig_ref.subplots() ax.set(xlim=(-1, 1), ylim=(-1, 1)) ax.plot([-1, 1], [-1, 1]) ax.plot([0, 1], [-1, 0], color='C1') ax.plot([-1, 1], [0, 0], color='C2') + ax.plot([0, 0], [-1, 1], color='C3') @check_figures_equal() @@ -4093,9 +4095,9 @@ def test_axline_transaxes_panzoom(fig_test, fig_ref): # figure resize after plotting ax = fig_test.subplots() ax.set(xlim=(-1, 1), ylim=(-1, 1)) - ax.axline_transaxes((0, 0), slope=1) - ax.axline_transaxes((0.5, 0.5), slope=2, color='C1') - ax.axline_transaxes((0.5, 0.5), slope=0, color='C2') + ax.axline((0, 0), slope=1, transform=ax.transAxes) + ax.axline((0.5, 0.5), slope=2, color='C1', transform=ax.transAxes) + ax.axline((0.5, 0.5), slope=0, color='C2', transform=ax.transAxes) ax.set(xlim=(0, 5), ylim=(0, 10)) fig_test.set_size_inches(3, 3) From f6ee501be52e3d732f9e155ebc83be272010154c Mon Sep 17 00:00:00 2001 From: johan12345 Date: Tue, 20 Oct 2020 19:53:40 +0200 Subject: [PATCH 08/23] remove unused imports --- lib/matplotlib/lines.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 2dda9fd9f25f..36ee1768244b 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -17,9 +17,7 @@ from .colors import is_color_like, get_named_colors_mapping from .markers import MarkerStyle from .path import Path -from .transforms import ( - Affine2D, Bbox, BboxTransformFrom, BboxTransformTo, TransformedPath, - AffineDeltaTransform) +from .transforms import Bbox, BboxTransformTo, TransformedPath # Imported here for backward compatibility, even though they don't # really belong. From 0b0ea4e8fc3f72e7d14b5ad3dc32abf545954e1e Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Wed, 21 Oct 2020 16:16:32 +0200 Subject: [PATCH 09/23] Do not change data limits if a transform is passed --- lib/matplotlib/axes/_axes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b459a5018304..035ed286d3c2 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -855,8 +855,8 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs): datalim = [xy1] if xy2 is None else [xy1, xy2] if "transform" in kwargs: - #TODO: datalim = (kwargs["transform"] - # + self.transData.inverted()).transform(datalim)? + # if a transform is passed (i.e. line points are not in data space), + # data limits should not be adjusted. datalim = [] line = mlines._AxLine(xy1, xy2, slope, **kwargs) From 6d93918f4f01c32956d43a3c3fc166ffe7fa3835 Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Wed, 21 Oct 2020 16:19:48 +0200 Subject: [PATCH 10/23] fix flake8 error --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 035ed286d3c2..d7c5f8d520ab 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -855,7 +855,7 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs): datalim = [xy1] if xy2 is None else [xy1, xy2] if "transform" in kwargs: - # if a transform is passed (i.e. line points are not in data space), + # if a transform is passed (i.e. line points not in data space), # data limits should not be adjusted. datalim = [] From d7c31c3069958efe7ceb3dbe9c8fcfa34142483d Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Mon, 26 Oct 2020 07:42:19 +0100 Subject: [PATCH 11/23] Shorten docstring Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/axes/_axes.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index d7c5f8d520ab..bd8c83f5d92e 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -808,11 +808,10 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs): all other scales, and thus the behavior is undefined. Please specify the line using the points *xy1*, *xy2* for non-linear scales. - As part of the ``kwargs``, a 'transform' can be passed. In the case - that a point and a slope are given, the transformation will only be - used for the point and not for the slope, i.e. the slope stays in data - coordinates. This can be used e.g. with ``ax.transAxes`` for drawing - grid lines with a fixed slope. + The *transform* keyword argument only applies to the points *xy1*, + *xy2*. The *slope* (if given) is always in data coordinates. This can + be used e.g. with ``ax.transAxes`` for drawing grid lines with a fixed + slope. Parameters ---------- From da0b3449512d60096d7891e3846f5c3495572d4a Mon Sep 17 00:00:00 2001 From: johan12345 Date: Mon, 26 Oct 2020 07:46:30 +0100 Subject: [PATCH 12/23] move arguments check from axline function into _AxLine class --- lib/matplotlib/axes/_axes.py | 5 ----- lib/matplotlib/lines.py | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index bd8c83f5d92e..767e3e9f56cc 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -843,11 +843,6 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs): >>> axline((0, 0), (1, 1), linewidth=4, color='r') """ - if (xy2 is None and slope is None or - xy2 is not None and slope is not None): - raise TypeError( - "Exactly one of 'xy2' and 'slope' must be given") - if slope is not None and (self.get_xscale() != 'linear' or self.get_yscale() != 'linear'): raise TypeError("'slope' cannot be used with non-linear scales") diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 36ee1768244b..6620e50f921c 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1406,6 +1406,12 @@ class _AxLine(Line2D): def __init__(self, xy1, xy2, slope, **kwargs): super().__init__([0, 1], [0, 1], **kwargs) + + if (xy2 is None and slope is None or + xy2 is not None and slope is not None): + raise TypeError( + "Exactly one of 'xy2' and 'slope' must be given") + self._slope = slope self._xy1 = xy1 self._xy2 = xy2 From fe54cd10fedf5ed0d3b0973f38f70d7c38677cdd Mon Sep 17 00:00:00 2001 From: johan12345 Date: Mon, 26 Oct 2020 07:48:48 +0100 Subject: [PATCH 13/23] remove whitespace --- lib/matplotlib/lines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 6620e50f921c..1c0328921231 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1406,7 +1406,7 @@ class _AxLine(Line2D): def __init__(self, xy1, xy2, slope, **kwargs): super().__init__([0, 1], [0, 1], **kwargs) - + if (xy2 is None and slope is None or xy2 is not None and slope is not None): raise TypeError( From db447482e5d863586dd928c892f84ac0aa6c7a8c Mon Sep 17 00:00:00 2001 From: johan12345 Date: Tue, 27 Oct 2020 22:57:23 +0100 Subject: [PATCH 14/23] add next_whats_new entry --- doc/users/next_whats_new/axline_transform.rst | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 doc/users/next_whats_new/axline_transform.rst diff --git a/doc/users/next_whats_new/axline_transform.rst b/doc/users/next_whats_new/axline_transform.rst new file mode 100644 index 000000000000..39cfe455387d --- /dev/null +++ b/doc/users/next_whats_new/axline_transform.rst @@ -0,0 +1,6 @@ +axline supports *transform* parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The *transform* keyword argument only applies to the points *xy1*, +*xy2*. The *slope* (if given) is always in data coordinates. This can +be used e.g. with ``ax.transAxes`` for drawing grid lines with a fixed +slope. \ No newline at end of file From 690177ed18264666fcb0a71bae1055d9e6323936 Mon Sep 17 00:00:00 2001 From: johan12345 Date: Tue, 27 Oct 2020 23:13:13 +0100 Subject: [PATCH 15/23] add demo for axline with transform parameter --- examples/pyplots/axline.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/pyplots/axline.py b/examples/pyplots/axline.py index c1890c186393..847f2076da69 100644 --- a/examples/pyplots/axline.py +++ b/examples/pyplots/axline.py @@ -27,6 +27,18 @@ plt.legend(fontsize=14) plt.show() +# `~.axes.Axes.axline` can also be used with a `transform` parameter, which +# applies to the point, but not to the slope. This can be useful for drawing +# diagonal grid lines with a fixed slope, which stay in place when the +# plot limits are moved. + +for pos in np.linspace(-2, 1, 10): + plt.axline((pos, 0), slope=0.5, color='k', transform=plt.gca().transAxes) + +plt.ylim([0, 1]) +plt.xlim([0, 1]) +plt.show() + ############################################################################# # # ------------ From 257be32479a765eeb59526e42886c6de7603a592 Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Fri, 30 Oct 2020 08:25:29 +0100 Subject: [PATCH 16/23] Update examples/pyplots/axline.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- examples/pyplots/axline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/pyplots/axline.py b/examples/pyplots/axline.py index 847f2076da69..ddc6cf186365 100644 --- a/examples/pyplots/axline.py +++ b/examples/pyplots/axline.py @@ -27,6 +27,7 @@ plt.legend(fontsize=14) plt.show() +############################################################################## # `~.axes.Axes.axline` can also be used with a `transform` parameter, which # applies to the point, but not to the slope. This can be useful for drawing # diagonal grid lines with a fixed slope, which stay in place when the From 425ee2d78f1320fa52fa746a892f760f7b664518 Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Fri, 30 Oct 2020 08:26:10 +0100 Subject: [PATCH 17/23] Update doc/users/next_whats_new/axline_transform.rst Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- doc/users/next_whats_new/axline_transform.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/users/next_whats_new/axline_transform.rst b/doc/users/next_whats_new/axline_transform.rst index 39cfe455387d..03d952febca4 100644 --- a/doc/users/next_whats_new/axline_transform.rst +++ b/doc/users/next_whats_new/axline_transform.rst @@ -1,6 +1,7 @@ axline supports *transform* parameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The *transform* keyword argument only applies to the points *xy1*, +`~.Axes.axline` now supports the *transform* parameter, which applies to +the points *xy1*, *xy2*. *xy2*. The *slope* (if given) is always in data coordinates. This can be used e.g. with ``ax.transAxes`` for drawing grid lines with a fixed -slope. \ No newline at end of file +slope. From 4649b9fc0521e90f64f1faeb80e9ab9f3a5f5923 Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Fri, 30 Oct 2020 09:43:00 +0100 Subject: [PATCH 18/23] fix docs warning --- examples/pyplots/axline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/pyplots/axline.py b/examples/pyplots/axline.py index ddc6cf186365..94694e4e7a4e 100644 --- a/examples/pyplots/axline.py +++ b/examples/pyplots/axline.py @@ -28,7 +28,7 @@ plt.show() ############################################################################## -# `~.axes.Axes.axline` can also be used with a `transform` parameter, which +# `~.axes.Axes.axline` can also be used with a ``transform`` parameter, which # applies to the point, but not to the slope. This can be useful for drawing # diagonal grid lines with a fixed slope, which stay in place when the # plot limits are moved. From 82e63a98cd7209382732fa77bd3738a30260767e Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Fri, 30 Oct 2020 14:56:16 +0100 Subject: [PATCH 19/23] what's new: re-wrap and fix duplicated *xy2* --- doc/users/next_whats_new/axline_transform.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/users/next_whats_new/axline_transform.rst b/doc/users/next_whats_new/axline_transform.rst index 03d952febca4..9b66c8573f89 100644 --- a/doc/users/next_whats_new/axline_transform.rst +++ b/doc/users/next_whats_new/axline_transform.rst @@ -1,7 +1,6 @@ axline supports *transform* parameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ `~.Axes.axline` now supports the *transform* parameter, which applies to -the points *xy1*, *xy2*. -*xy2*. The *slope* (if given) is always in data coordinates. This can -be used e.g. with ``ax.transAxes`` for drawing grid lines with a fixed +the points *xy1*, *xy2*. The *slope* (if given) is always in data coordinates. +This can be used e.g. with ``ax.transAxes`` for drawing grid lines with a fixed slope. From 21deaf481081d3df622adfdf5382765bba660b53 Mon Sep 17 00:00:00 2001 From: johan12345 Date: Thu, 3 Dec 2020 08:34:15 +0100 Subject: [PATCH 20/23] remove unnecessary set of self._transform (already done by super.__init__()) --- lib/matplotlib/lines.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 1c0328921231..989cb6e8bf59 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1415,7 +1415,6 @@ def __init__(self, xy1, xy2, slope, **kwargs): self._slope = slope self._xy1 = xy1 self._xy2 = xy2 - self._transform = kwargs.get("transform") def get_transform(self): ax = self.axes From a2584afa75737675a02a59fbd25e8b9c28fcde7d Mon Sep 17 00:00:00 2001 From: johan12345 Date: Sun, 6 Dec 2020 22:00:08 +0100 Subject: [PATCH 21/23] remove unused code --- lib/matplotlib/lines.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 989cb6e8bf59..f892ec433397 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1418,10 +1418,7 @@ def __init__(self, xy1, xy2, slope, **kwargs): def get_transform(self): ax = self.axes - if self._transform is not None: - points_transform = self._transform + ax.transData.inverted() - else: - points_transform = ax.transScale + points_transform = self._transform + ax.transData.inverted() if self._xy2 is not None: # two points were given From a5aa3490f03e1b63e58c26063794b850a40b1611 Mon Sep 17 00:00:00 2001 From: johan12345 Date: Sun, 6 Dec 2020 22:14:44 +0100 Subject: [PATCH 22/23] improve test coverage by testing for ValueError --- lib/matplotlib/tests/test_axes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 5f8ba5bf073b..8993658d5f88 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4123,6 +4123,10 @@ def test_axline_args(): ax.set_yscale('log') with pytest.raises(TypeError): ax.axline((0, 0), slope=1) + ax.set_yscale('linear') + with pytest.raises(ValueError): + ax.axline((0, 0), (0, 0)) # two identical points are not allowed + plt.draw() @image_comparison(['vlines_basic', 'vlines_with_nan', 'vlines_masked'], From 91e851e9645bf1286e370c2223962c1a5e4f400c Mon Sep 17 00:00:00 2001 From: johan12345 Date: Sun, 6 Dec 2020 22:16:13 +0100 Subject: [PATCH 23/23] fix exception description --- lib/matplotlib/lines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index f892ec433397..a3b20594308a 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1430,7 +1430,7 @@ def get_transform(self): if np.allclose(y1, y2): raise ValueError( f"Cannot draw a line through two identical points " - f"(x={self.get_xdata()}, y={self.get_ydata()})") + f"(x={(x1, x2)}, y={(y1, y2)})") slope = np.inf else: slope = dy / dx