8000 Axes.axline: implement support transform argument (for points but not slope) by johan12345 · Pull Request #18647 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Axes.axline: implement support transform argument (for points but not slope) #18647

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cf835e3
implement a function `axline_transaxes`
johan12345 Oct 3, 2020
527aa46
improve docs
johan12345 Oct 3, 2020
45e5bc7
improve tests
johan12345 Oct 3, 2020
bd8e82b
fix special case for slope=0
johan12345 Oct 3, 2020
da040d0
implement simpler solution for calculation of axline_transaxes
johan12345 Oct 3, 2020
02b8c59
fix flake8 errors
johan12345 Oct 3, 2020
9292f18
replace separate function axline_transaxes with new functionality for…
johan12345 Oct 20, 2020
f6ee501
remove unused imports
johan12345 Oct 20, 2020
0b0ea4e
Do not change data limits if a transform is passed
johan12345 Oct 21, 2020
6d93918
fix flake8 error
johan12345 Oct 21, 2020
d7c31c3
Shorten docstring
johan12345 Oct 26, 2020
da0b344
move arguments check from axline function into _AxLine class 8000
johan12345 Oct 26, 2020
fe54cd1
remove whitespace
johan12345 Oct 26, 2020
db44748
add next_whats_new entry
johan12345 Oct 27, 2020
690177e
add demo for axline with transform parameter
johan12345 Oct 27, 2020
257be32
Update examples/pyplots/axline.py
johan12345 Oct 30, 2020
425ee2d
Update doc/users/next_whats_new/axline_transform.rst
johan12345 Oct 30, 2020
4649b9f
fix docs warning
johan12345 Oct 30, 2020
82e63a9
what's new: re-wrap and fix duplicated *xy2*
johan12345 Oct 30, 2020
21deaf4
remove unnecessary set of self._transform
johan12345 Dec 3, 2020
a2584af
remove unused code
johan12345 Dec 6, 2020
a5aa349
improve test coverage by testing for ValueError
johan12345 Dec 6, 2020
91e851e
fix exception description
johan12345 Dec 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
28 changes: 28 additions & 0 deletions lib/matplotlib/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be simpler to calculate the slope into axes coordinates and simply draw the line in axes co-ordinates?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I tried to stay close to the implementation of axline (as the original idea was to add this behavior into that function if a transform is passed, see #18625), but this may be an easier option.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, but it won't really get much simpler because we still need to somehow convert to data coordinates to calculate at which edges of the viewport (top/bottom or left/right) the ends of the line would be. That will of course depend on the current x/y limits.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wont the line clip if you just make it go from x_axes = -1 to 2?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right, it does. I implemented a simpler solution in da040d0, however it breaks the tests even though the plots visually seem to look the same. The difference seems to be very small, not sure why this is happening 😕

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could probably safely up the tolerance if this is just roundoff from passing the positions through the transform stack...

@check_figures_equal(extensions=['png'], tol=0.19)


# 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
Expand Down
13 changes: 13 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be useful to add a test where you call the method, and then subsequently resize the figure (simulating a manual resize) and show this still yields the expected line. Also that its robust to panning and zooming.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I added some an additional test in 45e5bc7 which uses set_xlim/set_ylim and set_size_inches to simulate resize and pan/zoom, is that what you meant?

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()
Expand Down
0