8000 Merge pull request #15330 from anntzer/axline · matplotlib/matplotlib@42e3f92 · GitHub
[go: up one dir, main page]

Skip to content

Commit 42e3f92

Browse files
authored
Merge pull request #15330 from anntzer/axline
Add axes method for drawing infinite lines.
2 parents cc73684 + d83cfc5 commit 42e3f92

File tree

8 files changed

+151
-21
lines changed

8 files changed

+151
-21
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
New `~.axes.Axes.axline` method
2+
-------------------------------
3+
4+
A new `~.axes.Axes.axline` method has been added to draw infinitely long lines
5+
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, < F438 span class="pl-s1">facecolor='#2ca02c')
3435

3536
plt.show()

lib/matplotlib/axes/_axes.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +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+
axline : Add a line with an arbitrary slope.
819820
820821
Examples
821822
--------
@@ -899,6 +900,7 @@ def axvline(self, x=0, ymin=0, ymax=1, **kwargs):
899900
--------
900901
vlines : Add vertical lines in data coordinates.
901902
axvspan : Add a vertical span (rectangle) across the axis.
903+
axline : Add a line with an abritrary slope.
902904
"""
903905

904906
if "transform" in kwargs:
@@ -919,6 +921,63 @@ def axvline(self, x=0, ymin=0, ymax=1, **kwargs):
919921
self._request_autoscale_view(scalex=scalex, scaley=False)
920922
return l
921923

924+
@docstring.dedent_interpd
925+
def axline(self, xy1, xy2, **kwargs):
926+
"""
927+
Add an infinitely long straight line that passes through two points.
928+
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+
933+
Parameters
934+
----------
935+
xy1, xy2 : (float, float)
936+
Points for the line to pass through.
937+
938+
Returns
939+
-------
940+
:class:`~matplotlib.lines.Line2D`
941+
942+
Other Parameters
943+
----------------
944+
**kwargs
945+
Valid kwargs are :class:`~matplotlib.lines.Line2D` properties,
946+
with the exception of 'transform':
947+
948+
%(_Line2D_docstr)s
949+
950+
Examples
951+
--------
952+
Draw a thick red line passing through (0, 0) and (1, 1)::
953+
954+
>>> axline((0, 0), (1, 1), linewidth=4, color='r')
955+
956+
See Also
957+
--------
958+
axhline : for horizontal lines
959+
axvline : for vertical lines
960+
"""
961+
962+
if "transform" in kwargs:
963+
raise TypeError("'transform' is not allowed as a kwarg; "
964+
"axline generates its own transform")
965+
x1, y1 = xy1
966+
x2, y2 = xy2
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()
979+
return line
980+
922981
@docstring.dedent_interpd
923982
def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs):
924983
"""

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.
@@ -1456,6 +1457,47 @@ def is_dashed(self):
14561457
return self._linestyle in ('--', '-.', ':')
14571458

14581459

1460+
class _AxLine(Line2D):
1461+
"""
1462+
A helper class that implements `~.Axes.axline`, by recomputing the artist
1463+
transform at draw time.
1464+
"""
1465+
1466+
def get_transform(self):
1467+
ax = self.axes
1468+
(x1, y1), (x2, y2) = ax.transScale.transform([*zip(*self.get_data())])
1469+
dx = x2 - x1
1470+
dy = y2 - y1
1471+
if np.allclose(x1, x2):
1472+
if np.allclose(y1, y2):
1473+
raise ValueError(
1474+
f"Cannot draw a line through two identical points "
1475+
f"(x={self.get_xdata()}, y={self.get_ydata()})")
1476+
# First send y1 to 0 and y2 to 1.
1477+
return (Affine2D.from_values(1, 0, 0, 1 / dy, 0, -y1 / dy)
1478+
+ ax.get_xaxis_transform(which="grid"))
1479+
if np.allclose(y1, y2):
1480+
# First send x1 to 0 and x2 to 1.
1481+
return (Affine2D.from_values(1 / dx, 0, 0, 1, -x1 / dx, 0)
1482+
+ ax.get_yaxis_transform(which="grid"))
1483+
(vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim)
1484+
# General case: find intersections with view limits in either
1485+
# direction, and draw between the middle two points.
1486+
_, start, stop, _ = sorted([
1487+
(vxlo, y1 + (vxlo - x1) * dy / dx),
1488+
(vxhi, y1 + (vxhi - x1) * dy / dx),
1489+
(x1 + (vylo - y1) * dx / dy, vylo),
1490+
(x1 + (vyhi - y1) * dx / dy, vyhi),
1491+
])
1492+
return (BboxTransformFrom(Bbox([*zip(*self.get_data())]))
1493+
+ BboxTransformTo(Bbox([start, stop]))
1494+
+ ax.transLimits + ax.transAxes)
1495+
1496+
def draw(self, renderer):
1497+
self._transformed_path = None # Force regen.
1498+
super().draw(renderer)
1499+
1500+
14591501
class VertexSelector:
14601502
"""
14611503
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):

lib/matplotlib/tests/test_axes.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3641,6 +3641,21 @@ def test_eb_line_zorder():
36413641
ax.set_title("errorbar zorder test")
36423642

36433643

3644+
@check_figures_equal()
3645+
def test_axline(fig_test, fig_ref):
3646+
ax = fig_test.subplots()
3647+
ax.set(xlim=(-1, 1), ylim=(-1, 1))
3648+
ax.axline((0, 0), (1, 1))
3649+
ax.axline((0, 0), (1, 0), color='C1')
3650+
ax.axline((0, 0.5), (1, 0.5), color='C2')
3651+
3652+
ax = fig_ref.subplots()
3653+
ax.set(xlim=(-1, 1), ylim=(-1, 1))
3654+
ax.plot([-1, 1], [-1, 1])
3655+
ax.axhline(0, color='C1')
3656+
ax.axhline(0.5, color='C2')
3657+
3658+
36443659
@image_comparison(['vlines_basic', 'vlines_with_nan', 'vlines_masked'],
36453660
extensions=['png'])
36463661
def test_vlines():

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)
0