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

Skip to content

abline() - for drawing arbitrary lines on a plot, given specifications. #5253

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

Closed
naught101 opened this issue Oct 16, 2015 · 20 comments · Fixed by #15330
Closed

abline() - for drawing arbitrary lines on a plot, given specifications. #5253

naught101 opened this issue Oct 16, 2015 · 20 comments · Fixed by #15330
Assignees
Milestone

Comments

@naught101
Copy link

It would be really nice to have some function that could draw arbitrarily angled straight lines on a graph, along the lines of ggplot's abline

for example, these might be ways to draw infinite lines:

pl.abline(intercept=0, slope=1)  # 1:1 line from the origin

pl. abline(a=[0, 0], b=[1, 1])  # same line as above, using 2 points

pl.abline(a=[0, 0], angle=45)  # same line again, using 1 point and an angle

pl.abline(a=[0, 0], gradient=1)  # same line again, using 1 point and the gradient

These are all infinite lines (they extend to the bounds of the graph, regardless of where their specified points lie).

There are a few questions on stack overflow indicating how popular this might be, eg:
http://stackoverflow.com/questions/22104256/does-matplotlib-have-a-function-for-drawing-diagonal-lines-in-axis-coordinates

@WeatherGod
Copy link
Member

For those who aren't clicking through to the link, the proposal is mostly a
generalized axhline()/axvline(). It goes a bit further to also allow
arbitrary placement of simple line segments.

Just yesterday, I was doing a correlation plot, and I was thinking to
myself how valuable it would be to have an easy way to add that 1-to-1 line
that extended infinitely no matter where I panned the display, rather than
finding the maximum over both data and then only having a segment.

On Fri, Oct 16, 2015 at 4:14 AM, naught101 [email protected] wrote:

It would be really nice to have some function that could draw arbitrarily
angled straight lines on a graph, along the lines of ggplot's abline
http://docs.ggplot2.org/current/geom_abline.html

for example, these might be ways to draw infinite lines:

pl.abline(intercept=0, slope=1) # 1:1 line from the origin

pl. abline(a=[0, 0], b=[1, 1]) # same line as above, using 2 points

pl.abline(a=[0, 0], angle=45) # same line again, using 1 point and an angle

These are all infinite lines (they extend to the bounds of the graph,
regardless of where their specified points lie).

There are a few questions on stack overflow indicating how popular this
might be, eg:

http://stackoverflow.com/questions/22104256/does-matplotlib-have-a-function-for-drawing-diagonal-lines-in-axis-coordinates


Reply to this email directly or view it on GitHub
#5253.

@mdboom
Copy link
Member

mdboom commented Oct 16, 2015

I'm definitely open to including this feature. Someone may need to step up to implement it, however.

@naught101
Copy link
Author

Cool. I might try and give it a go. Any tips on where to start would be helpful. In particular, I'm not sure how a (theoretically) infinite line would interact with how MPL defines the plot boundaries. Does the line just get re-drawn to the boundaries on every zoom/pan?

@WeatherGod
Copy link
Member

I would look at how Axes.axhline() and Axes.axvline() are defined in
lib/matplotlib/axes/_axes.py. Essentially, the Line2D object gets defined
with a special transform that follows the coordinate system of the grid
rather than the data. I see abline() as being a generalization of axvline()
and axhline(), so eventually, those two methods could call abline().

On Mon, Oct 19, 2015 at 9:26 PM, naught101 [email protected] wrote:

Cool. I might try and give it a go. Any tips on where to start would be
helpful. In particular, I'm not sure how a (theoretically) infinite line
would interact with how MPL defines the plot boundaries. Does the line just
get re-drawn to the boundaries on every zoom/pan?


Reply to this email directly or view it on GitHub
#5253 (comment)
.

@efiring
Copy link
Member

efiring commented Oct 20, 2015

On 2015/10/20 3:19 AM, Benjamin Root wrote:

I would look at how Axes.axhline() and Axes.axvline() are defined in
lib/matplotlib/axes/_axes.py. Essentially, the Line2D object gets defined
with a special transform that follows the coordinate system of the grid
rather than the data. I see abline() as being a generalization of axvline()
and axhline(), so eventually, those two methods could call abline().

I don't think this will work. axvline and axhline use blended
transforms: axes coordinates for one axis and data for the other. If I
understand correctly, the abline in ggplot2 is entirely in data
coordinates, so it would use the normal transData transform. (The Stack
Overflow discussion is confusing, and does not seem consistent with abline.)

@naught101
Copy link
Author

Hrm.. I'm not sure what would be the most useful: to have an abline that works only in data coordinates (which presumably would draw spirals in polar coordinates, etc.), or one that works only in grid coordinates. I can't really see the latter being that useful outside of cartesian data coordinates anyway. But yeah, the blended transforms thing definitely won't work.

I guess you can get the bounds of the axes, then transform those back into data coordinates, calculate your line segment by intersecting it with the rectangle, and then draw it in data coordinates?

@QuLogic
Copy link
Member

QuLogic commented Oct 21, 2015

Start with (0,0)->(1,1) line and apply following transforms:

  • Blended transform of:
    • Scale on x-axis with BboxTransformTo(ax.dataLim) (so you get (x0, 0) -> (x1, 1))
    • Scale on y-axis with BboxTransform(transpose of ax.dataLim) (so you get (x0, x0) -> (x1, x1)) + Affine2D().scale(m).translate(b) (to get (x0, m*x0+b)->(x1, m*x1+b))
  • Apply regular data transform afterwards.

The hard part is getting the transpose of ax.dataLim. I don't think there're any transforms that will do that for you, but maybe TransformedBbox(ax.dataLim, Affine2D().rotate_deg(90).scale(-1, 1))?

This also might not be the most efficient method...

@WeatherGod
Copy link
Member

I think Elliot has the right idea.

On Tue, Oct 20, 2015 at 10:58 PM, Elliott Sales de Andrade <
[email protected]> wrote:

Start with (0,0)->(1,1) line and apply following transforms:

  • Blended transform of:
    • Scale on x-axis with BboxTransformTo(ax.dataLim) (so you get (x0,
      0) -> (x1, 1))
    • Scale on y-axis with BboxTransform(transpose of ax.dataLim) (so
      you get (x0, x0) -> (x1, x1)) + Affine2D.scale(m).translate(b) (to
      get (x0, m_x0+b)->(x1, m_x1+b))
  • Apply regular data transform afterwards.

The hard part is getting the transpose of ax.dataLim. I don't think
there's any transforms that will do that for you, but maybe TransformedBbox(ax.dataLim,
Affine2D().rotate_deg(90).scale(-1, 1))?


Reply to this email directly or view it on GitHub
#5253 (comment)
.

@anntzer
Copy link
Contributor

anntzer commented Jun 2, 2016

I recently had a similar need, this time to add a text annotation to an axvline that would stay at the top of the figure (say 5% from the edge) regardless of zooming.

Perhaps an idea would be to provide a function to transform an Axes object into another overlaid, invisible (by default) Axes where one or both of the coordinates would use grid coordinates? Say, ax.with_grid_coords(x=False, y=False). (It would probably not make sense to provide a function that goes in the opposite direction.) Then axvline could be simply implented as

ax.with_grid_coords(y=True).plot([xpos, xpos], [0, 1])

(not that you necessarily want to do that) and my desired annotation would be

ax.with_grid_coords(y=True).text(xpos, .95, text)

(I know about the transform kwarg to text, but it's not obvious to me how to get the axes transform in one direction but the data transform in the other.)

However, this would not solve the OP's issue. On that note, I wonder whether it would be better to just add the functionality to plot (yes, I know, it's getting crowded there), via an infinite kwarg (default to False; makes the last segments of the path on both sides infinite if True, or just on one side if "left" or "right").

@QuLogic
Copy link
Member

QuLogic commented Jun 2, 2016

I recently had a similar need, this time to add a text annotation to an axvline that would stay at the top of the figure (say 5% from the edge) regardless of zooming.

This can be done easily with a blended transform of ax.transData for the x-coordinate and ax.transAxes for the y-coordinate. I'm not sure from which example I got this, but I've used it for exactly the same thing for years.

@anntzer
Copy link
Contributor

anntzer commented Jun 2, 2016

Can you point out to a code example? Thanks.

@QuLogic
Copy link
Member

QuLogic commented Jun 2, 2016

It's kind of implicitly included in axes_zoom_effect and more explicitly mentioned at the end of fill_between_demo.

But it's really a matter of supplying the transform for the text:

from matplotlib.transforms import blended_transform_factory
trans = blended_transform_factory(ax.transData, ax.transAxes)
ax.text(123,  # Data coordinates
        0.95,  # Fraction of Axes
        'text',
        transform=trans)

or you can use annotate to give it a little offset from the line

from matplotlib.transforms import blended_transform_factory
trans = blended_transform_factory(ax.transData, ax.transAxes)
ax.annotate('text',
            xy=123, 1), # (Data coordinates, Fraction of Axes)
            xycoords=trans,
            xytext=(2, -12),  # (a few points to right, one line (default font size) below top (might need adjustment)
            textcoords='offset points')

@anntzer
Copy link
Contributor

anntzer commented Jun 2, 2016

I guess reading the transformations tutorial would have pointed me to ax.get_xaxis_transform(), which would have gotten the job done even faster. Thanks, and sorry for hijacking the thread.

I still think that my infinite kwarg to plot may be a good alternative to abline though.

@tacaswell tacaswell added this to the unassigned milestone Jun 3, 2016
@syrte
Copy link

syrte commented Jul 30, 2016

A solution with transform would be nice. However if it's very hard to make implementation, why we stick on transform? As I see callback with ax.callbacks.connect is not that bad. Would anyone say more about this?

If ax.callbacks.connect is acceptable, the function in this post is already near to what we need. We may just add some wrap to accept more general inputs.
And here's my version with some enhancement. It will always make a line between xlims or ylims depending on the slope.

@syrte
Copy link

syrte commented Jul 30, 2016

@anntzer
I would vote for abline or a new function with other name.
As I see infinite keywords is not a good idea for plot, the meaning of infinite is vague for a line with more than 2 points (it might be a curve).

@gagabla
Copy link

gagabla commented Nov 6, 2016

I am constantly missing such a function. I fit straight lines to data points and always have the problem of drawing the result of the fitting. Picking points on the line "far enough outside" such that the line is always visible is a pain. To add to the list of possible arguments for a function like abline i would like to add:

pl.abline(a=[0, 0], direction=[0.70710678, 0.70710678])  # same line again, using 1 point and a direction vector

@eldond
Copy link

eldond commented Nov 23, 2016

What do you think of this implementation? http://stackoverflow.com/a/14348481/6605826 I would name it axdline to go with axhline and axvline.

@tacaswell
Copy link
Member

There are some issues with those details (like it should not be adding it's self to an axes or touching pyplot in the __init__) and it probably should also over-ride draw so that the draw_artist is not needed.

There should also be some discussion about how this should interact with non-linear scales on the axes. Only taking points at the edges will result in a 'linear on the screen' line independent of the scales.

@tacaswell
Copy link
Member

Also, this might as well be coupled to a general 'plot a function' artist. The simple case (evaluate at evenly spaced points) is not too bad. There was a PR to add this (#1143) this seems to have fallen by the way side (iirc it got hung up on smart sampling to deal with diverging functions).

@QuLogic
Copy link
Member

QuLogic commented Nov 24, 2016

The transform I outlined above seems to work quite well, though I'm not sure about non-linear scales.

https://youtu.be/DJjbFMoAw68

@QuLogic QuLogic self-assigned this Nov 24, 2016
@QuLogic QuLogic modified the milestones: 2.1 (next point release), unassigned Nov 24, 2016
@tacaswell tacaswell modified the milestones: 2.1 (next point release), 2.2 (next next feature release) Oct 3, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment