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

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

Conversation

johan12345
Copy link
Contributor

@johan12345 johan12345 commented Oct 3, 2020

PR Summary

for plotting a line through a point in axes coordinates and with a slope in data coordinates
see #18625 for details and example.

PR Checklist

  • Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (run flake8 on changed files to check).
  • New features are documented, with examples if plot related.
  • Documentation is sphinx and numpydoc compliant (the docs should build without error).
  • Conforms to Matplotlib style conventions (install flake8-docstrings and pydocstyle<4 and run flake8 --docstring-convention=all).
  • New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).

for plotting  a line through a point in axes coordinates and with a slope in data coordinates
fixes matplotlib#18625
@jklymak
Copy link
Member

jklymak commented Oct 3, 2020

When we originally put in axline, we had a big back and forth about how to specify the line, and it was decided that two points were just as good as point/slope. So I'm not sure this feature is particularly needed, though its actually how I would have preferred specifying the line.

@johan12345
Copy link
Contributor Author

Point and slope is already available in axline. This is specifically about specifying the point in axes coordinates instead of data coordinates, so that the line stays at the same position when moving the viewport.

@jklymak
Copy link
Member

jklymak commented Oct 3, 2020

oops, sorry, I see...

@@ -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?

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)

@jklymak
Copy link
Member

jklymak commented Oct 3, 2020

Overall this seems good, and I guess I agree that we can't just make this an example. I'm not at all sure about the name chosen?

Be sure to ping us for reviews if this starts slipping down the PR list. Things get forgotten....

@jklymak jklymak added this to the v3.4.0 milestone Oct 3, 2020
@johan12345
Copy link
Contributor Author

I'm not at all sure about the name chosen?

Yeah, I'm not sure either. Some other ideas: axline_fixed (i.e. fixed to the viewport), axline_axcoord (i.e. axes coordinates)

@anntzer
Copy link
Contributor

anntzer commented Oct 3, 2020

From the peanut gallery, I would rather add a special case in the codepath of axline (even something as direct as if transform == self.transAxes and <a slope was passed>: return ... (and document it) rather than another toplevel method, this will avoid the API growing further and further with more variants (axline_transfigure, etc.).

@timhoffm
Copy link
Member

timhoffm commented Oct 3, 2020

I'd like to see use-cases to guide us in the naming and exact functionality.

Proposed so far:

  • A regular skewed grid filling the viewport.

I currently don't see any others: The slope is in data space, but the positions are in Axes coordinates, so the positions are data-independent; i.e. the lines only describe "a data direction" (maybe that's already a hint for a name). If it's just a direction without position, would we not anyway want a grid to indicate the direction homogeneously across all positions? If so, we might want to instead make a function that directly creates the grid (e.g. directional_grid()).

@jklymak
Copy link
Member

jklymak commented Oct 3, 2020

I agree with the above, though I somewhat cringe at the idea of a grid API versus just a single line. Specifying a sloping grid in axes co-ordinates in a general way is not trivial.

I also agree that maybe this should be handled by axline. I think the original discussion made it clear that directly using transform was problematic, but maybe renaming as point_transform would alleviate that concern?

@jklymak
Copy link
Member

jklymak commented Oct 4, 2020

I'll add this one to the agenda of the weekly call since the API choices seem complicated.

@jklymak
Copy link
Member

jklymak commented Oct 20, 2020

@johan12345 We discussed this on the weekly developer call.

The consensus was that this can and should be handled with the transform argument to axline. If the user provides a point and slope, the point would be transformed by the transform argument. If the user provides two points, the line would be in the transformed space. The default transform would still be ax.transData, but ax.transAxes should give the behaviour you are after. Its not clear to me if this should be limited so that strange transforms are prevented, but, I've not thought about it personally.

Hopefully that is not too annoying, as you have gone a ways down this path, but the consensus was that adding a transform was the easiest API...

@jklymak
Copy link
Member

jklymak commented Oct 20, 2020

Ooops sorry I see Tim commented on the original issue. Sorry for the noise.

@johan12345
Copy link
Contributor Author

@jklymak Thanks! Yes, as written in the original issue #18625 I have now tried to adapt axline to do this (9292f18). With that, the alternative solution for the calculation of axline_transaxes implemented da040d0 was discarded, to have a common solution based on the previous implementation of axline.

One thing that is not implemented for the case where a transformation is passed is the adjustment of data limits:

What I'm not sure about is the adjustment of data limits. For the moment, I set it to simply not adjust the data limits if a transform is passed (because at least for this use case I think it doesn't really make sense to adjust them).

What do you think?

@jklymak
Copy link
Member

jklymak commented Oct 21, 2020

I mean if the line is in Axes space (but with a data slope) I don't think the intention is for it to be treated as data so the limits should not change? But I don't 100% understand the use of the feature....

@johan12345
Copy link
Contributor Author

johan12345 commented Oct 27, 2020

Sure, next_whats_new was added in db44748 and an example in 690177e.

@johan12345
Copy link
Contributor Author

Thanks @timhoffm!

(already done by super.__init__())
@QuLogic QuLogic closed this Dec 3, 2020
@QuLogic QuLogic reopened this Dec 3, 2020
@QuLogic
Copy link
Member

QuLogic commented Dec 4, 2020

So code coverage is complaining, and though it's pointing to a weird place, it is correctly finding a bug: the ._transform is never None. This is because axline calls self.set_artist_props(line) which will set the transform to transData if not set. (Note that it checks a separate property ._transformSet so setting ._transform only would still have done this.)

However, this turns out to work, since transform - transData == IdentityTransform, and axline doesn't support alternate scales, so transScale is also an identity.

So perhaps that check is unnecessary and handling None transform is not needed.

@johan12345
Copy link
Contributor Author

Well, that is probably what changed when I removed the explicit setting of self._transform... Okay, I can remove that as well.

@QuLogic
Copy link
Member

QuLogic commented Dec 4, 2020

No, as I said, it checks ._transformSet, which would not have been changed by setting ._transform in the old code.

@johan12345
Copy link
Contributor Author

Ah, okay. I removed the unused code in a2584af, and also improved the test coverage a bit more in a5aa349.

@QuLogic QuLogic merged commit b556e62 into matplotlib:master Dec 15, 2020
@QuLogic
Copy link
Member

QuLogic commented Dec 15, 2020

Thanks @johan12345! Congratulations on your first PR to Matplotlib 🎉 We hope to hear from you again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants