Skip to content

twinx / twiny inherit autoscale behavior for shared axis #7904

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 1 commit into from
Jan 29, 2017

Conversation

naoyak
Copy link
Contributor

@naoyak naoyak commented Jan 21, 2017

Fixes #6860

Creating twinx() or twiny() should inherit the autoscaling behavior from the parent Axes instance, but only for the shared axis. Previously, twinx() would autoscale on the x-axis regardless of the setting on the original Axes, meaning that plotting on the twin would look incorrect with the (visible) original x-axis.

@naoyak naoyak closed this Jan 21, 2017
@naoyak naoyak reopened this Jan 21, 2017
self.yaxis.tick_left()
ax2.xaxis.set_visible(False)
ax2.set
Copy link
Member

Choose a reason for hiding this comment

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

what does this line do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oops, made a goof there. removed.

@@ -145,6 +145,24 @@ def test_twinx_cla():
assert ax.yaxis.get_visible()


@cleanup
def test_twin_inherit_autoscale():
Copy link
Member

Choose a reason for hiding this comment

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

As much as I dislike image tests, do you have one of those, or some other inspection of the axis to check that the ticks are acting the way you expect?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I could create something motivated by the example in #6860 (i.e. that the plotted points should reflect that the original (visible) axis and twinned (invisible) axis share the same autoscaling behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added a simple image comparison.

@story645
Copy link
Member

story645 commented Jan 23, 2017

Also, how does this intersect with #7528?

@naoyak naoyak force-pushed the twin-axes-autoscale branch from ed9b984 to b01c31d Compare January 23, 2017 22:57
@naoyak
Copy link
Contributor Author

naoyak commented Jan 23, 2017

mm, i have to check out #7528. I think I need to understand a bit better what _make_twin_axes() is doing...

@naoyak
Copy link
Contributor Author

naoyak commented Jan 24, 2017

I don't think this PR conflicts directly with #7528 in terms of functionality, but maybe testing can be consolidated. Seems like there's some confusion on whether the shared axis should be the same axis or a copy (whose attributes can be changed independently), both from the API perspective and in terms of the internals.

@naoyak naoyak closed this Jan 24, 2017
@naoyak naoyak reopened this Jan 24, 2017
@naoyak naoyak force-pushed the twin-axes-autoscale branch from b01c31d to fcf88d4 Compare January 24, 2017 21:08
@naoyak naoyak changed the title twinx / twiny inherit autoscale behavior for shared axis [MRG] twinx / twiny inherit autoscale behavior for shared axis Jan 25, 2017
@NelleV
Copy link
Member

NelleV commented Jan 25, 2017

This looks good to me. I'd just add a bit of documentation on the twinx and twiny method to explain this behaviour.

@naoyak naoyak force-pushed the twin-axes-autoscale branch from 423bf47 to c5ea558 Compare January 25, 2017 18:52
@naoyak
Copy link
Contributor Author

naoyak commented Jan 25, 2017

@NelleV thanks, added a note about the autoscale setting to the docstrings.

@NelleV NelleV changed the title [MRG] twinx / twiny inherit autoscale behavior for shared axis [MRG+1] twinx / twiny inherit autoscale behavior for shared axis Jan 25, 2017
@NelleV
Copy link
Member

NelleV commented Jan 25, 2017

Thanks! It looks good to me.

@@ -3902,18 +3902,18 @@ def _make_twin_axes(self, *kl, **kwargs):

def twinx(self):
"""
Create a twin Axes sharing the xaxis
Create a twin Axes instance sharing the x-axis with self
Copy link
Member

Choose a reason for hiding this comment

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

not clear what self is here? (Basically not sure this is any clearer than the original wording)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed

ticks on left and the returned axes will have ticks on the
right. To ensure tick marks of both axis align, see
`~matplotlib.ticker.LinearLocator`
Create a twin Axes instance for generating a plot with a shared
Copy link
Member

Choose a reason for hiding this comment

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

also not sure this is any clearer. docs support images, so maybe that's the way to go here?
https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes/_axes.py#L1660

Copy link
Contributor Author

Choose a reason for hiding this comment

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

An image would be a good idea. These docs feel like they are about implementation rather than the use case - I think what we want to say is "call this method if you want to create a plot with 1 x-axis and 2 y-axes". To me the extra xaxis object is an artifact which is hidden, but apparently there is another use case...

Copy link
Member

Choose a reason for hiding this comment

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

Since these are the dev docs, they kinda have to stay in implementation land. Your string is great, but more appropriate for a gallery example. Honestly, I'd just prefer the original docstring. I think with a small image, they're clear

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the docstring is going to focus on implementation, it still needs to be revised to reflect what's actually going on behind the scenes. twinx/twiny doesn't really "share" an axis in the sense of sharing the Axis object, but instead creates a 2nd, invisible one..I can try to encapsulate this aspect and revise.


Notes
------
-----
For those who are 'picking' artists while using twiny, pick
events are only called for the artists in the top-most axes.
Copy link
Member

Choose a reason for hiding this comment

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

top-most relative to what?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this means the "original" Axes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually I'm not really familiar with the Artist/picker API, I'm not really sure what top-most is in this context..

Copy link
Member

Choose a reason for hiding this comment

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

The Axes are rendered in some order (determined by the z-order and sorted in the Figure draw method). Which ever one draws last (has highest z-order) is the one that gets the pick event.

Copy link
Contributor

Choose a reason for hiding this comment

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

If I remember correctly, the new axis ends up on top. I remember having some issues with legends getting obscured by gridlines because of this.

@naoyak naoyak force-pushed the twin-axes-autoscale branch from c5ea558 to 3a14787 Compare January 26, 2017 05:34
@@ -3902,18 +3902,18 @@ def _make_twin_axes(self, *kl, **kwargs):

def twinx(self):
"""
Create a twin Axes sharing the xaxis
Create a twin Axes instance sharing the x-axis with the parent
Copy link
Member

Choose a reason for hiding this comment

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

with the parent what?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

is it that unclear? maybe "parent" isn't the exact terminology, but after all twinx() is a method called on an existing Axes. I am going to revert this part of the docstring edit.

self.yaxis.tick_left()
ax2.xaxis.set_visible(False)
ax2.patch.set_visible(False)
return ax2

def twiny(self):
"""
Create a twin Axes sharing the yaxis
Create a twin Axes instance sharing the y-axis with the parent
Copy link
Member

Choose a reason for hiding this comment

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

same as above

@naoyak naoyak force-pushed the twin-axes-autoscale branch 2 times, most recently from ead4817 to 1492d30 Compare January 26, 2017 21:58
@naoyak
Copy link
Contributor Author

naoyak commented Jan 27, 2017

OK, I've revised the twinx and twiny docstrings to reflect the changes in this PR and clarified the wording to the best of my ability.

However this makes me wonder if the implementation is flawed in that the scales of the twinned axis will diverge if autoscale is turned on, since plotting is done on either the original or the new Axes. Would it make more sense to share the same Axis object?

`~matplotlib.ticker.LinearLocator`

Returns
-------
Axis
Copy link
Member

Choose a reason for hiding this comment

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

wow, good catch!

@story645
Copy link
Member

This is making my head hurt. I'm wondering, and you likely know better than me at this point, but what I'm getting from you is that one y is hooked into the displayed x and the other is y hooked into the hidden x? if so, I agree that they they should just both be hooked into a single x, but I wonder if that's possible...

@naoyak
Copy link
Contributor Author

naoyak commented Jan 27, 2017

Actually, sorry it seems to work fine with the separate xaxis instances - the autoscaling does stay in sync between the "original" and the "twinned" x-axes. So while the implementation is still confusing to me, I think this will work OK.

import numpy as np
import matplotlib.pyplot as plt

fig, ax1 = plt.subplots()
t = np.arange(0.01, 10.0, 0.01)
s1 = np.exp(t)
ax1.plot(t, s1, 'b-')
ax1.set_xlabel('original x-axis')
ax1.set_ylabel('exp', color='b')
ax1.tick_params('y', colors='b')
ax1.tick_params('x', colors='b')

ax2 = ax1.twinx()
ax2.set_ylabel('sin', color='r')
ax2.tick_params('y', colors='r')
ax2.tick_params('x', colors='r')

# Manually set the twinned x-axis visible and shift it down
ax2.xaxis.set_visible(True)
ax2.spines['bottom'].set_position(('axes', -0.2))
ax2.set_xlabel('twinned x-axis')
t2 = np.arange(0.01, 20.0, 0.01)
s2 = np.sin(2 * np.pi * t2)
ax2.plot(t2, s2, 'r.')

plt.show()

download

@story645
Copy link
Member

Don't apologize! There are loads of moving pieces here and it gets confusing quickly. Ok, then far as I can tell, the goal of #7376 is to treat original and twin x as two semi-independent axis, in that they should have the same min & max but can be labeled individually. And so making a shared x-axis would probably make that functionality impossible. @madphysicist wanna chime in?

@naoyak
Copy link
Contributor Author

naoyak commented Jan 27, 2017

Yep, making a shared xAxis instance would seem to preclude the functionality in #7376. Fortunately we don't have to go down that road to resolve the relevant issues here 😄

This should be ready now - if it gets merged @madphysicist will have to sort a merge conflict in the docstring, but nothing functionally impactful, I think.

@story645
Copy link
Member

Looks good to me, just waiting on gitter.im for how to deal with the appveyor failure that has nothing to do with your test.

`~matplotlib.ticker.LinearLocator`

Returns
-------
Axis
The newly created axis
Axes
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that this should be lowercase.

x-axis positioned opposite to the original one (i.e. at top). The
y-axis autoscale setting will be inherited from the original Axes.
To ensure that the tick marks of both x-axes align, see
`~matplotlib.ticker.LinearLocator`
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure that I understand the last sentence. Specifically, how does looking at the documentation for LinearLocator ensure tick alignment? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the original wording - do you have any suggestions for a better sentence? (or you could incorporate it in your PR since that deals with tickers)

Copy link
Contributor

Choose a reason for hiding this comment

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

I will try to figure out what that sentence means exactly and attempt to improve it.


Returns
-------
Axis
The newly created axis
Axes
Copy link
Contributor

Choose a reason for hiding this comment

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

Lowercase?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

revised

@naoyak naoyak force-pushed the twin-axes-autoscale branch from 1492d30 to 0c0c3d6 Compare January 27, 2017 19:03
@madphysicist
Copy link
Contributor

@naoyak Until fairly recently, I thought that twinning actually meant sharing the axis. Since that is not the case, your earlier comment makes me question this whole PR:

However this makes me wonder if the implementation is flawed in that the scales of the twinned axis will diverge if autoscale is turned on, since plotting is done on either the original or the new Axes. Would it make more sense to share the same Axis object?

I don't think that sharing the axis object is an option. I think there may need to be a special provision in autoscaling that sets the widest possible bounds for the entire chain of twinned axes. Inheriting autoscaling is definitely a necessary part of this, but autoscaling itself may need to be redefined to special-case axes with twins. I think that fixing that issue that way will also address the concerns about my PR and autoscaling that @efiring brought up.

@naoyak
Copy link
Contributor Author

naoyak commented Jan 27, 2017

@madphysicist

I think there may need to be a special provision in autoscaling that sets the widest possible bounds for the entire chain of twinned axes.

I think that's what the current implementation does, actually - I am not sure how this is accomplished under the hood, though. Per discussion above I realized that sharing the Axis object is unnecessary so that is no longer an issue.

At this point this PR only sets the autoscale setting equal for the twinned axis instances and doesn't touch anything else. I think it's safe to merge without messing with your PR, what do you think?

@madphysicist
Copy link
Contributor

I think that you are safe to merge. I am working on some autoscaling tests right now. I am pretty sure there will be no conflict but it will be interesting to see how our PRs play together.

@story645 story645 changed the title [MRG+1] twinx / twiny inherit autoscale behavior for shared axis [MRG+2] twinx / twiny inherit autoscale behavior for shared axis Jan 29, 2017
@NelleV NelleV merged commit 85d76be into matplotlib:master Jan 29, 2017
@naoyak naoyak deleted the twin-axes-autoscale branch January 29, 2017 04:39
@QuLogic QuLogic changed the title [MRG+2] twinx / twiny inherit autoscale behavior for shared axis \twinx / twiny inherit autoscale behavior for shared axis Jan 29, 2017
@QuLogic QuLogic changed the title \twinx / twiny inherit autoscale behavior for shared axis twinx / twiny inherit autoscale behavior for shared axis Jan 29, 2017
@QuLogic
Copy link
Member

QuLogic commented Jan 29, 2017

#6860 wants a backport.

@QuLogic QuLogic added this to the v2.1 milestone Aug 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The original xlim changed by twinx
6 participants