Skip to content

Commit c91537b

Browse files
committed
replace separate function axline_transaxes with new functionality for axline
1 parent 02b8c59 commit c91537b

File tree

3 files changed

+63
-127
lines changed

3 files changed

+63
-127
lines changed

lib/matplotlib/axes/_axes.py

Lines changed: 16 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,12 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs):
808808
all other scales, and thus the behavior is undefined. Please specify
809809
the line using the points *xy1*, *xy2* for non-linear scales.
810810
811+
As part of the ``kwargs``, a 'transform' can be passed. In the case
812+
that a point and a slope are given, the transformation will only be
813+
used for the point and not for the slope, i.e. the slope stays in data
814+
coordinates. This can be used e.g. with ``ax.transAxes`` for drawing grid
815+
lines with a fixed slope.
816+
811817
Parameters
812818
----------
813819
xy1, xy2 : (float, float)
@@ -823,8 +829,7 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs):
823829
Other Parameters
824830
----------------
825831
**kwargs
826-
Valid kwargs are `.Line2D` properties, with the exception of
827-
'transform':
832+
Valid kwargs are `.Line2D` properties
828833
829834
%(_Line2D_docstr)s
830835
@@ -839,30 +844,21 @@ def axline(self, xy1, xy2=None, *, slope=None, **kwargs):
839844
840845
>>> axline((0, 0), (1, 1), linewidth=4, color='r')
841846
"""
842-
def _to_points(xy1, xy2, slope):
843-
"""
844-
Check for a valid combination of input parameters and convert
845-
to two points, if necessary.
846-
"""
847-
if (xy2 is None and slope is None or
848-
xy2 is not None and slope is not None):
849-
raise TypeError(
850-
"Exactly one of 'xy2' and 'slope' must be given")
851-
if xy2 is None:
852-
x1, y1 = xy1
853-
xy2 = (x1, y1 + 1) if np.isinf(slope) else (x1 + 1, y1 + slope)
854-
return xy1, xy2
847+
if (xy2 is None and slope is None or
848+
xy2 is not None and slope is not None):
849+
raise TypeError(
850+
"Exactly one of 'xy2' and 'slope' must be given")
855851

856-
if "transform" in kwargs:
857-
raise TypeError("'transform' is not allowed as a kwarg; "
858-
"axline generates its own transform")
859852
if slope is not None and (self.get_xscale() != 'linear' or
860853
self.get_yscale() != 'linear'):
861854
raise TypeError("'slope' cannot be used with non-linear scales")
862855

863856
datalim = [xy1] if xy2 is None else [xy1, xy2]
864-
(x1, y1), (x2, y2) = _to_points(xy1, xy2, slope)
865-
line = mlines._AxLine([x1, x2], [y1, y2], **kwargs)
857+
if "transform" in kwargs:
858+
#TODO: datalim = (kwargs["transform"] + self.transData.inverted()).transform(datalim)?
859+
datalim = []
860+
861+
line = mlines._AxLine(xy1, xy2, slope, **kwargs)
866862
# Like add_line, but correctly handling data limits.
867863
self._set_artist_props(line)
868864
if line.get_clip_path() is None:
@@ -876,59 +872,6 @@ def _to_points(xy1, xy2, slope):
876872
self._request_autoscale_view()
877873
return line
878874

879-
def axline_transaxes(self, xy1, slope, **kwargs):
880-
"""
881-
Add an infinitely long straight line.
882-
883-
The line is defined by a *slope* in data coordinates, set by the
884-
current x and y limits of the axes, and a point in axes-relative
885-
coordinates, which is in the same place on the axes regardless of x
886-
and y limits.
887-
888-
This should only be used with linear scales; the *slope* has no clear
889-
meaning for all other scales, and thus the behavior is undefined.
890-
891-
Parameters
892-
----------
893-
xy1 : (float, float)
894-
Point for the line to pass through, in axes coordinates.
895-
``(0, 0)`` is the bottom left corner of the axes, ``(1, 1)`` is
896-
the upper right.
897-
slope : float
898-
The slope of the line, in data coordinates.
899-
900-
Returns
901-
-------
902-
`.Line2D`
903-
904-
Other Parameters
905-
----------------
906-
**kwargs
907-
Valid kwargs are `.Line2D` properties, with the exception of
908-
'transform':
909-
910-
%(_Line2D_docstr)s
911-
912-
See Also
913-
--------
914-
axhline : for horizontal lines
915-
axvline : for vertical lines
916-
917-
Examples
918-
--------
919-
Draw a thick red line passing through (0, 0)
920-
(the lower left corner of the viewport) with slope 1::
921-
922-
>>> axline_transaxes((0, 0), 1, linewidth=4, color='r')
923-
"""
924-
if "transform" in kwargs:
925-
raise TypeError("'transform' is not allowed as a kwarg; "
926-
"axline_transaxes generates its own transform")
927-
928-
line = mlines._AxLineTransAxes(xy1, slope, **kwargs)
929-
self.add_line(line)
930-
return line
931-
932875
@docstring.dedent_interpd
933876
def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs):
934877
"""

lib/matplotlib/lines.py

Lines changed: 41 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,66 +1406,59 @@ class _AxLine(Line2D):
14061406
transform at draw time.
14071407
"""
14081408

1409+
def __init__(self, xy1, xy2, slope, **kwargs):
1410+
super().__init__([0, 1], [0, 1], **kwargs)
1411+
self._slope = slope
1412+
self._xy1 = xy1
1413+
self._xy2 = xy2
1414+
self._transform = kwargs.get("transform")
1415+
14091416
def get_transform(self):
14101417
ax = self.axes
1411-
(x1, y1), (x2, y2) = ax.transScale.transform([*zip(*self.get_data())])
1412-
dx = x2 - x1
1413-
dy = y2 - y1
1414-
if np.allclose(x1, x2):
1415-
if np.allclose(y1, y2):
1416-
raise ValueError(
1417-
f"Cannot draw a line through two identical points "
1418-
f"(x={self.get_xdata()}, y={self.get_ydata()})")
1419-
# First send y1 to 0 and y2 to 1.
1420-
return (Affine2D.from_values(1, 0, 0, 1 / dy, 0, -y1 / dy)
1421-
+ ax.get_xaxis_transform(which="grid"))
1422-
if np.allclose(y1, y2):
1423-
# First send x1 to 0 and x2 to 1.
1424-
return (Affine2D.from_values(1 / dx, 0, 0, 1, -x1 / dx, 0)
1425-
+ ax.get_yaxis_transform(which="grid"))
1418+
if self._transform is not None:
1419+
points_transform = self._transform + ax.transData.inverted()
1420+
else:
1421+
points_transform = ax.transScale
1422+
1423+
if self._xy2 is not None:
1424+
# two points were given
1425+
(x1, y1), (x2, y2) = points_transform.transform([self._xy1, self._xy2])
1426+
dx = x2 - x1
1427+
dy = y2 - y1
1428+
if np.allclose(x1, x2):
1429+
if np.allclose(y1, y2):
1430+
raise ValueError(
1431+
f"Cannot draw a line through two identical points "
1432+
f"(x={self.get_xdata()}, y={self.get_ydata()})")
1433+
# First send y1 to 0 and y2 to 1.
1434+
return (Affine2D.from_values(1, 0, 0, 1 / dy, 0, -y1 / dy)
1435+
+ ax.get_xaxis_transform(which="grid"))
1436+
slope = dy / dx
1437+
else:
1438+
# one point and a slope were given
1439+
x1, y1 = points_transform.transform(self._xy1)
1440+
slope = self._slope
14261441
(vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim)
14271442
# General case: find intersections with view limits in either
14281443
# direction, and draw between the middle two points.
1429-
_, start, stop, _ = sorted([
1430-
(vxlo, y1 + (vxlo - x1) * dy / dx),
1431-
(vxhi, y1 + (vxhi - x1) * dy / dx),
1432-
(x1 + (vylo - y1) * dx / dy, vylo),
1433-
(x1 + (vyhi - y1) * dx / dy, vyhi),
1434-
])
1435-
return (BboxTransformFrom(Bbox([*zip(*self.get_data())]))
1436-
+ BboxTransformTo(Bbox([start, stop]))
1444+
if np.isclose(slope, 0):
1445+
start = vxlo, y1
1446+
stop = vxhi, y1
1447+
else:
1448+
_, start, stop, _ = sorted([
1449+
(vxlo, y1 + (vxlo - x1) * slope),
1450+
(vxhi, y1 + (vxhi - x1) * slope),
1451+
(x1 + (vylo - y1) / slope, vylo),
1452+
(x1 + (vyhi - y1) / slope, vyhi),
1453+
])
1454+
return (BboxTransformTo(Bbox([start, stop]))
14371455
+ ax.transLimits + ax.transAxes)
14381456

14391457
def draw(self, renderer):
14401458
self._transformed_path = None # Force regen.
14411459
super().draw(renderer)
14421460

14431461

1444-
class _AxLineTransAxes(_AxLine):
1445-
"""
1446-
A helper class that implements `~.Axes.axline_transaxes`, by recomputing
1447-
the artist transform at draw time.
1448-
"""
1449-
1450-
def __init__(self, xy1, slope, **kwargs):
1451-
super().__init__([0, 1], [0, 1], **kwargs)
1452-
self._slope = slope
1453-
self._xy1 = xy1
1454-
1455-
def get_transform(self):
1456-
ax = self.axes
1457-
x, y = self._xy1
1458-
1459-
dx, dy = AffineDeltaTransform(ax.transData + ax.transAxes.inverted())\
1460-
.transform([1, self._slope])
1461-
slope_axes = dy / dx
1462-
1463-
start = 0, slope_axes * (0 - x) + y
1464-
stop = 1, slope_axes * (1 - x) + y
1465-
1466-
return BboxTransformTo(Bbox([start, stop])) + ax.transAxes
1467-
1468-
14691462
class VertexSelector:
14701463
"""
14711464
Manage the callbacks to maintain a list of selected vertices for

lib/matplotlib/tests/test_axes.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4076,9 +4076,9 @@ def test_axline(fig_test, fig_ref):
40764076
def test_axline_transaxes(fig_test, fig_ref):
40774077
ax = fig_test.subplots()
40784078
ax.set(xlim=(-1, 1), ylim=(-1, 1))
4079-
ax.axline_transaxes((0, 0), slope=1)
4080-
ax.axline_transaxes((1, 0.5), slope=1, color='C1')
4081-
ax.axline_transaxes((0.5, 0.5), slope=0, color='C2')
4079+
ax.axline((0, 0), slope=1, transform=ax.transAxes)
4080+
ax.axline((1, 0.5), slope=1, color='C1', transform=ax.transAxes)
4081+
ax.axline((0.5, 0.5), slope=0, color='C2', transform=ax.transAxes)
40824082

40834083
ax = fig_ref.subplots()
40844084
ax.set(xlim=(-1, 1), ylim=(-1, 1))
@@ -4093,9 +4093,9 @@ def test_axline_transaxes_panzoom(fig_test, fig_ref):
40934093
# figure resize after plotting
40944094
ax = fig_test.subplots()
40954095
ax.set(xlim=(-1, 1), ylim=(-1, 1))
4096-
ax.axline_transaxes((0, 0), slope=1)
4097-
ax.axline_transaxes((0.5, 0.5), slope=2, color='C1')
4098-
ax.axline_transaxes((0.5, 0.5), slope=0, color='C2')
4096+
ax.axline((0, 0), slope=1, transform=ax.transAxes)
4097+
ax.axline((0.5, 0.5), slope=2, color='C1', transform=ax.transAxes)
4098+
ax.axline((0.5, 0.5), slope=0, color='C2', transform=ax.transAxes)
40994099
ax.set(xlim=(0, 5), ylim=(0, 10))
41004100
fig_test.set_size_inches(3, 3)
41014101

0 commit comments

Comments
 (0)