Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b556e62

Browse files
authored
Merge pull request #18647 from johan12345/master
Axes.axline: implement support transform argument (for points but not slope)
2 parents 772a315 + 91e851e commit b556e62

File tree

5 files changed

+119
-46
lines changed

5 files changed

+119
-46
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
axline supports *transform* parameter
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
`~.Axes.axline` now supports the *transform* parameter, which applies to
4+
the points *xy1*, *xy2*. The *slope* (if given) is always in data coordinates.
5+
This can be used e.g. with ``ax.transAxes`` for drawing grid lines with a fixed
6+
slope.

examples/pyplots/axline.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@
2727
plt.legend(fontsize=14)
2828
plt.show()
2929

30+
##############################################################################
31+
# `~.axes.Axes.axline` can also be used with a ``transform`` parameter, which
32+
# applies to the point, but not to the slope. This can be useful for drawing
33+
# diagonal grid lines with a fixed slope, which stay in place when the
34+
# plot limits are moved.
35+
36+
for pos in np.linspace(-2, 1, 10):
37+
plt.axline((pos, 0), slope=0.5, color='k', transform=plt.gca().transAxes)
38+
39+
plt.ylim([0, 1])
40+
plt.xlim([0, 1])
41+
plt.show()
42+
3043
#############################################################################
3144
#
3245
# ------------

lib/matplotlib/axes/_axes.py

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,11 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs):
814814
all other scales, and thus the behavior is undefined. Please specify
815815
the line using the points *xy1*, *xy2* for non-linear scales.
816816
817+
The *transform* keyword argument only applies to the points *xy1*,
818+
*xy2*. The *slope* (if given) is always in data coordinates. This can
819+
be used e.g. with ``ax.transAxes`` for drawing grid lines with a fixed
820+
slope.
821+
817822
Parameters
818823
----------
819824
xy1, xy2 : (float, float)
@@ -829,8 +834,7 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs):
829834
Other Parameters
830835
----------------
831836
**kwargs
832-
Valid kwargs are `.Line2D` properties, with the exception of
833-
'transform':
837+
Valid kwargs are `.Line2D` properties
834838
835839
%(_Line2D_docstr)s
836840
@@ -845,30 +849,17 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs):
845849
846850
>>> axline((0, 0), (1, 1), linewidth=4, color='r')
847851
"""
848-
def _to_points(xy1, xy2, slope):
849-
"""
850-
Check for a valid combination of input parameters and convert
851-
to two points, if necessary.
852-
"""
853-
if (xy2 is None and slope is None or
854-
xy2 is not None and slope is not None):
855-
raise TypeError(
856-
"Exactly one of 'xy2' and 'slope' must be given")
857-
if xy2 is None:
858-
x1, y1 = xy1
859-
xy2 = (x1, y1 + 1) if np.isinf(slope) else (x1 + 1, y1 + slope)
860-
return xy1, xy2
861-
862-
if "transform" in kwargs:
863-
raise TypeError("'transform' is not allowed as a kwarg; "
864-
"axline generates its own transform")
865852
if slope is not None and (self.get_xscale() != 'linear' or
866853
self.get_yscale() != 'linear'):
867854
raise TypeError("'slope' cannot be used with non-linear scales")
868855

869856
datalim = [xy1] if xy2 is None else [xy1, xy2]
870-
(x1, y1), (x2, y2) = _to_points(xy1, xy2, slope)
871-
line = mlines._AxLine([x1, x2], [y1, y2], **kwargs)
857+
if "transform" in kwargs:
858+
# if a transform is passed (i.e. line points not in data space),
859+
# data limits should not be adjusted.
860+
datalim = []
861+
862+
line = mlines._AxLine(xy1, xy2, slope, **kwargs)
872863
# Like add_line, but correctly handling data limits.
873864
self._set_artist_props(line)
874865
if line.get_clip_path() is None:

lib/matplotlib/lines.py

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
from .colors import is_color_like, get_named_colors_mapping
1717
from .markers import MarkerStyle
1818
from .path import Path
19-
from .transforms import (
20-
Affine2D, Bbox, BboxTransformFrom, BboxTransformTo, TransformedPath)
19+
from .transforms import Bbox, BboxTransformTo, TransformedPath
2120

2221
# Imported here for backward compatibility, even though they don't
2322
# really belong.
@@ -1412,34 +1411,57 @@ class _AxLine(Line2D):
14121411
transform at draw time.
14131412
"""
14141413

1414+
def __init__(self, xy1, xy2, slope, **kwargs):
1415+
super().__init__([0, 1], [0, 1], **kwargs)
1416+
1417+
if (xy2 is None and slope is None or
1418+
xy2 is not None and slope is not None):
1419+
raise TypeError(
1420+
"Exactly one of 'xy2' and 'slope' must be given")
1421+
1422+
self._slope = slope
1423+
self._xy1 = xy1
1424+
self._xy2 = xy2
1425+
14151426
def get_transform(self):
14161427
ax = self.axes
1417-
(x1, y1), (x2, y2) = ax.transScale.transform([*zip(*self.get_data())])
1418-
dx = x2 - x1
1419-
dy = y2 - y1
1420-
if np.allclose(x1, x2):
1421-
if np.allclose(y1, y2):
1422-
raise ValueError(
1423-
f"Cannot draw a line through two identical points "
1424-
f"(x={self.get_xdata()}, y={self.get_ydata()})")
1425-
# First send y1 to 0 and y2 to 1.
1426-
return (Affine2D.from_values(1, 0, 0, 1 / dy, 0, -y1 / dy)
1427-
+ ax.get_xaxis_transform(which="grid"))
1428-
if np.allclose(y1, y2):
1429-
# First send x1 to 0 and x2 to 1.
1430-
return (Affine2D.from_values(1 / dx, 0, 0, 1, -x1 / dx, 0)
1431-
+ ax.get_yaxis_transform(which="grid"))
1428+
points_transform = self._transform + ax.transData.inverted()
1429+
1430+
if self._xy2 is not None:
1431+
# two points were given
1432+
(x1, y1), (x2, y2) = \
1433+
points_transform.transform([self._xy1, self._xy2])
1434+
dx = x2 - x1
1435+
dy = y2 - y1
1436+
if np.allclose(x1, x2):
1437+
if np.allclose(y1, y2):
1438+
raise ValueError(
1439+
f"Cannot draw a line through two identical points "
1440+
f"(x={(x1, x2)}, y={(y1, y2)})")
1441+
slope = np.inf
1442+
else:
1443+
slope = dy / dx
1444+
else:
1445+
# one point and a slope were given
1446+
x1, y1 = points_transform.transform(self._xy1)
1447+
slope = self._slope
14321448
(vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim)
14331449
# General case: find intersections with view limits in either
14341450
# direction, and draw between the middle two points.
1435-
_, start, stop, _ = sorted([
1436-
(vxlo, y1 + (vxlo - x1) * dy / dx),
1437-
(vxhi, y1 + (vxhi - x1) * dy / dx),
1438-
(x1 + (vylo - y1) * dx / dy, vylo),
1439-
(x1 + (vyhi - y1) * dx / dy, vyhi),
1440-
])
1441-
return (BboxTransformFrom(Bbox([*zip(*self.get_data())]))
1442-
+ BboxTransformTo(Bbox([start, stop]))
1451+
if np.isclose(slope, 0):
1452+
start = vxlo, y1
1453+
stop = vxhi, y1
1454+
elif np.isinf(slope):
1455+
start = x1, vylo
1456+
stop = x1, vyhi
1457+
else:
1458+
_, start, stop, _ = sorted([
1459+
(vxlo, y1 + (vxlo - x1) * slope),
1460+
(vxhi, y1 + (vxhi - x1) * slope),
1461+
(x1 + (vylo - y1) / slope, vylo),
1462+
(x1 + (vyhi - y1) / slope, vyhi),
1463+
])
1464+
return (BboxTransformTo(Bbox([start, stop]))
14431465
+ ax.transLimits + ax.transAxes)
14441466

14451467
def draw(self, renderer):

lib/matplotlib/tests/test_axes.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4089,6 +4089,43 @@ def test_axline(fig_test, fig_ref):
40894089
ax.axvline(-0.5, color='C5')
40904090

40914091

4092+
@check_figures_equal()
4093+
def test_axline_transaxes(fig_test, fig_ref):
4094+
ax = fig_test.subplots()
4095+
ax.set(xlim=(-1, 1), ylim=(-1, 1))
4096+
ax.axline((0, 0), slope=1, transform=ax.transAxes)
4097+
ax.axline((1, 0.5), slope=1, color='C1', transform=ax.transAxes)
4098+
ax.axline((0.5, 0.5), slope=0, color='C2', transform=ax.transAxes)
4099+
ax.axline((0.5, 0), (0.5, 1), color='C3', transform=ax.transAxes)
4100+
4101+
ax = fig_ref.subplots()
4102+
ax.set(xlim=(-1, 1), ylim=(-1, 1))
4103+
ax.plot([-1, 1], [-1, 1])
4104+
ax.plot([0, 1], [-1, 0], color='C1')
4105+
ax.plot([-1, 1], [0, 0], color='C2')
4106+
ax.plot([0, 0], [-1, 1], color='C3')
4107+
4108+
4109+
@check_figures_equal()
4110+
def test_axline_transaxes_panzoom(fig_test, fig_ref):
4111+
# test that it is robust against pan/zoom and
4112+
# figure resize after plotting
4113+
ax = fig_test.subplots()
4114+
ax.set(xlim=(-1, 1), ylim=(-1, 1))
4115+
ax.axline((0, 0), slope=1, transform=ax.transAxes)
4116+
ax.axline((0.5, 0.5), slope=2, color='C1', transform=ax.transAxes)
4117+
ax.axline((0.5, 0.5), slope=0, color='C2', transform=ax.transAxes)
4118+
ax.set(xlim=(0, 5), ylim=(0, 10))
4119+
fig_test.set_size_inches(3, 3)
4120+
4121+
ax = fig_ref.subplots()
4122+
ax.set(xlim=(0, 5), ylim=(0, 10))
4123+
fig_ref.set_size_inches(3, 3)
4124+
ax.plot([0, 5], [0, 5])
4125+
ax.plot([0, 5], [0, 10], color='C1')
4126+
ax.plot([0, 5], [5, 5], color='C2')
4127+
4128+
40924129
def test_axline_args():
40934130
"""Exactly one of *xy2* and *slope* must be specified."""
40944131
fig, ax = plt.subplots()
@@ -4103,6 +4140,10 @@ def test_axline_args():
41034140
ax.set_yscale('log')
41044141
with pytest.raises(TypeError):
41054142
ax.axline((0, 0), slope=1)
4143+
ax.set_yscale('linear')
4144+
with pytest.raises(ValueError):
4145+
ax.axline((0, 0), (0, 0)) # two identical points are not allowed
4146+
plt.draw()
41064147

41074148

41084149
@image_comparison(['vlines_basic', 'vlines_with_nan', 'vlines_masked'],

0 commit comments

Comments
 (0)