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

Skip to content

Commit d83cfc5

Browse files
committed
Rewrite axline with custom Line2D subclass.
This makes it also work on non-linear scales. Also don't use add_line directly but manually copy most of add_line, to ensure data limits are correctly set.
1 parent 436d408 commit d83cfc5

File tree

7 files changed

+96
-61
lines changed

7 files changed

+96
-61
lines changed

doc/api/axes_api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Spans
8787
Axes.axhspan
8888
Axes.axvline
8989
Axes.axvspan
90+
Axes.axline
9091

9192
Spectral
9293
--------
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
New `axline` method
2-
-------------------
1+
New `~.axes.Axes.axline` method
2+
-------------------------------
33

44
A new `~.axes.Axes.axline` method has been added to draw infinitely long lines
55
that pass through two points.

examples/subplots_axes_and_figures/axhspan_demo.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,33 @@
44
============
55
66
Create lines or rectangles that span the axes in either the horizontal or
7-
vertical direction.
7+
vertical direction, and lines than span the axes with an arbitrary orientation.
88
"""
9+
910
import numpy as np
1011
import matplotlib.pyplot as plt
1112

1213
t = np.arange(-1, 2, .01)
1314
s = np.sin(2 * np.pi * t)
1415

15-
plt.plot(t, s)
16-
# Draw a thick red hline at y=0 that spans the xrange
17-
plt.axhline(linewidth=8, color='#d62728')
18-
19-
# Draw a default hline at y=1 that spans the xrange
20-
plt.axhline(y=1)
21-
22-
# Draw a default vline at x=1 that spans the yrange
23-
plt.axvline(x=1)
24-
25-
# Draw a thick blue vline at x=0 that spans the upper quadrant of the yrange
26-
plt.axvline(x=0, ymin=0.75, linewidth=8, color='#1f77b4')
27-
28-
# Draw a default hline at y=.5 that spans the middle half of the axes
29-
plt.axhline(y=.5, xmin=0.25, xmax=0.75)
30-
31-
plt.axhspan(0.25, 0.75, facecolor='0.5', alpha=0.5)
32-
33-
plt.axvspan(1.25, 1.55, facecolor='#2ca02c', alpha=0.5)
16+
fig, ax = plt.subplots()
17+
18+
ax.plot(t, s)
19+
# Thick red horizontal line at y=0 that spans the xrange.
20+
ax.axhline(linewidth=8, color='#d62728')
21+
# Horizontal line at y=1 that spans the xrange.
22+
ax.axhline(y=1)
23+
# Vertical line at x=1 that spans the yrange.
24+
ax.axvline(x=1)
25+
# Thick blue vertical line at x=0 that spans the upper quadrant of the yrange.
26+
ax.axvline(x=0, ymin=0.75, linewidth=8, color='#1f77b4')
27+
# Default hline at y=.5 that spans the middle half of the axes.
28+
ax.axhline(y=.5, xmin=0.25, xmax=0.75)
29+
# Infinite black line going through (0, 0) to (1, 1).
30+
ax.axline((0, 0), (1, 1), color='k')
31+
# 50%-gray rectangle spanning the axes' width from y=0.25 to y=0.75.
32+
ax.axhspan(0.25, 0.75, facecolor='0.5')
33+
# Green rectangle spanning the axes' height from x=1.25 to x=1.55.
34+
ax.axvspan(1.25, 1.55, facecolor='#2ca02c')
3435

3536
plt.show()

lib/matplotlib/axes/_axes.py

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -816,7 +816,7 @@ def axhline(self, y=0, xmin=0, xmax=1, **kwargs):
816816
--------
817817
hlines : Add horizontal lines in data coordinates.
818818
axhspan : Add a horizontal span (rectangle) across the axis.
819-
axhline : Add a line with an arbitrary slope.
819+
axline : Add a line with an arbitrary slope.
820820
821821
Examples
822822
--------
@@ -926,6 +926,10 @@ def axline(self, xy1, xy2, **kwargs):
926926
"""
927927
Add an infinitely long straight line that passes through two points.
928928
929+
This draws a straight line "on the screen", regardless of the x and y
930+
scales, and is thus also suitable for drawing exponential decays in
931+
semilog plots, power laws in loglog plots, etc.
932+
929933
Parameters
930934
----------
931935
xy1, xy2 : (float, float)
@@ -937,61 +941,41 @@ def axline(self, xy1, xy2, **kwargs):
937941
938942
Other Parameters
939943
----------------
940-
Valid kwargs are :class:`~matplotlib.lines.Line2D` properties,
941-
with the exception of 'transform':
944+
**kwargs
945+
Valid kwargs are :class:`~matplotlib.lines.Line2D` properties,
946+
with the exception of 'transform':
942947
943-
%(_Line2D_docstr)s
948+
%(_Line2D_docstr)s
944949
945950
Examples
946951
--------
947952
Draw a thick red line passing through (0, 0) and (1, 1)::
948953
949954
>>> axline((0, 0), (1, 1), linewidth=4, color='r')
950955
951-
952956
See Also
953957
--------
954958
axhline : for horizontal lines
955959
axvline : for vertical lines
956-
957-
Notes
958-
-----
959-
Currently this method does not work properly with non-linear axes.
960960
"""
961-
if not self.get_xscale() == self.get_yscale() == 'linear':
962-
raise NotImplementedError('axline() is only supported on '
963-
'linearly scaled axes')
964961

965962
if "transform" in kwargs:
966963
raise TypeError("'transform' is not allowed as a kwarg; "
967-
"axline generates its own transform.")
968-
964+
"axline generates its own transform")
969965
x1, y1 = xy1
970966
x2, y2 = xy2
971-
# If x values the same, we have a vertical line
972-
if np.allclose(x1, x2):
973-
if np.allclose(y1, y2):
974-
raise ValueError(
975-
'Cannot draw a line through two identical points '
976-
f'(got x1={x1}, x2={x2}, y1={y1}, y2={y2}).')
977-
line = self.axvline(x1, **kwargs)
978-
return line
979-
980-
slope = (y2 - y1) / (x2 - x1)
981-
intercept = y1 - (slope * x1)
982-
983-
xtrans = mtransforms.BboxTransformTo(self.viewLim)
984-
viewLimT = mtransforms.TransformedBbox(
985-
self.viewLim,
986-
mtransforms.Affine2D().rotate_deg(90).scale(-1, 1))
987-
ytrans = (mtransforms.BboxTransformTo(viewLimT) +
988-
mtransforms.Affine2D().scale(slope).translate(0, intercept))
989-
trans = mtransforms.blended_transform_factory(xtrans, ytrans)
990-
991-
line = mlines.Line2D([0, 1], [0, 1],
992-
transform=trans + self.transData,
993-
**kwargs)
994-
self.add_line(line)
967+
line = mlines._AxLine([x1, x2], [y1, y2], **kwargs)
968+
# Like add_line, but correctly handling data limits.
969+
self._set_artist_props(line)
970+
if line.get_clip_path() is None:
971+
line.set_clip_path(self.patch)
972+
if not line.get_label():
973+
line.set_label(f"_line{len(self.lines)}")
974+
self.lines.append(line)
975+
line._remove_method = self.lines.remove
976+
self.update_datalim([xy1, xy2])
977+
978+
self._request_autoscale_view()
995979
return line
996980

997981
@docstring.dedent_interpd

lib/matplotlib/lines.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
_to_unmasked_float_array, ls_mapper, ls_mapper_r, STEP_LOOKUP_MAP)
1616
from .markers import MarkerStyle
1717
from .path import Path
18-
from .transforms import Bbox, TransformedPath
18+
from .transforms import (
19+
Affine2D, Bbox, BboxTransformFrom, BboxTransformTo, TransformedPath)
1920

2021
# Imported here for backward compatibility, even though they don't
2122
# really belong.
@@ -1448,6 +1449,47 @@ def is_dashed(self):
14481449
return self._linestyle in ('--', '-.', ':')
14491450

14501451

1452+
class _AxLine(Line2D):
1453+
"""
1454+
A helper class that implements `~.Axes.axline`, by recomputing the artist
1455+
transform at draw time.
1456+
"""
1457+
1458+
def get_transform(self):
1459+
ax = self.axes
1460+
(x1, y1), (x2, y2) = ax.transScale.transform([*zip(*self.get_data())])
1461+
dx = x2 - x1
1462+
dy = y2 - y1
1463+
if np.allclose(x1, x2):
1464+
if np.allclose(y1, y2):
1465+
raise ValueError(
1466+
f"Cannot draw a line through two identical points "
1467+
f"(x={self.get_xdata()}, y={self.get_ydata()})")
1468+
# First send y1 to 0 and y2 to 1.
1469+
return (Affine2D.from_values(1, 0, 0, 1 / dy, 0, -y1 / dy)
1470+
+ ax.get_xaxis_transform(which="grid"))
1471+
if np.allclose(y1, y2):
1472+
# First send x1 to 0 and x2 to 1.
1473+
return (Affine2D.from_values(1 / dx, 0, 0, 1, -x1 / dx, 0)
1474+
+ ax.get_yaxis_transform(which="grid"))
1475+
(vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim)
1476+
# General case: find intersections with view limits in either
1477+
# direction, and draw between the middle two points.
1478+
_, start, stop, _ = sorted([
1479+
(vxlo, y1 + (vxlo - x1) * dy / dx),
1480+
(vxhi, y1 + (vxhi - x1) * dy / dx),
1481+
(x1 + (vylo - y1) * dx / dy, vylo),
1482+
(x1 + (vyhi - y1) * dx / dy, vyhi),
1483+
])
1484+
return (BboxTransformFrom(Bbox([*zip(*self.get_data())]))
1485+
+ BboxTransformTo(Bbox([start, stop]))
1486+
+ ax.transLimits + ax.transAxes)
1487+
1488+
def draw(self, renderer):
1489+
self._transformed_path = None # Force regen.
1490+
super().draw(renderer)
1491+
1492+
14511493
class VertexSelector:
14521494
"""
14531495
Manage the callbacks to maintain a list of selected vertices for

lib/matplotlib/pyplot.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2371,6 +2371,12 @@ def axis(*args, emit=True, **kwargs):
23712371
return gca().axis(*args, emit=emit, **kwargs)
23722372

23732373

2374+
# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
2375+
@docstring.copy(Axes.axline)
2376+
def axline(xy1, xy2, **kwargs):
2377+
return gca().axline(xy1, xy2, **kwargs)
2378+
2379+
23742380
# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
23752381
@docstring.copy(Axes.axvline)
23762382
def axvline(x=0, ymin=0, ymax=1, **kwargs):

tools/boilerplate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ def boilerplate_gen():
194194
'axhline',
195195
'axhspan',
196196
'axis',
197+
'axline',
197198
'axvline',
198199
'axvspan',
199200
'bar',

0 commit comments

Comments
 (0)