Skip to content

ENH: Added share_tickers parameter to axes._AxesBase.twinx/y #7528

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

Conversation

madphysicist
Copy link
Contributor

Allow a copy of Axis.minor and Axis.major objects to be made when axes are twinned. This will allow two twinned axes to have different Formatter and Locator objects when they are both visible. The default of always sharing the same references (via a shared matplotlib.axis.Ticker container) has been preserved.

I made the keyword parameter share_tickers propagate all the way to axes._AxesBase.__init__ rather than implementing the copy in axes.AxesBase._make_twin_axes. The reason is that I would like to be able to modify the meaning of sharex and sharey directly in the constructor.

Fixes #7376.

This PR includes tests.

A couple of places that I would ask reviewers to look at:

  • I made one functional change that I think is OK, but may need to be reverted. In axes/_base.py, around line 1004, where the formatter and locator get reset, the original code called Axis.set_major/minor_formatter/locator. This would set the axis attribute of the locators and formatters to the second axis, not the original one that twinx/y was called on in the first place. I have gone ahead and changed that behavior for both the shared_ticker=True case and the False case. For the False case this is pretty obvious, since we do not want to change the axis attribute of the original formatter and locator at all. It should not cause any problems in the True case either, but it is technically a minor change in the functionality that I would like to bring attention to.
  • Did I add documentation for the share_tickers parameter correctly in axes/_base.py:473?
  • Did I add the handling of the parameter correctly? Specifically, it will affect the axis key in Figure._make_key. I think that this is correct because the axes with and without share_tickers will serve different purposes visually, so it should be OK to treat them as different objects.
  • I did not add an explicit test for the new copy constructor of axis.Ticker because it is really trivial. However, the code does get complete coverage in the tests that I wrote for twinx/y.

@madphysicist
Copy link
Contributor Author

@efiring. I made the parameter have to be explicitly specified by the user. The default is to share the ticker. Initially, even if the container is not shared, the actual Locator and Formatter are. It is then up to the savy user to mess things up for themselves.

@efiring
Copy link
Member

efiring commented Nov 28, 2016 via email

@madphysicist
Copy link
Contributor Author

Given the way I structured it, the user has to first manually disable the share_tickers option when twinning, then explicitly change one of the locators. I think that adding a warning in the docs about being careful with auto scaling should be enough since the default behavior is unchanged. Keep in mind that even with share_tickers disabled, the twinned axis will share a reference to the original locator until the user explicitly changes it. I just added a way to make such a change possible as a non default behavior.

@madphysicist
Copy link
Contributor Author

@efiring Hopefully the explicit warnings I added in the documentation will be sufficient. Perhaps I should add an actual warning to the low-level functions that set up auto-scaling that will be triggered if self._share* is non-None and self._share_tickers is False?

@codecov-io
Copy link

codecov-io commented Nov 29, 2016

Current coverage is 61.90% (diff: 100%)

Merging #7528 into master will decrease coverage by 4.52%

@@             master      #7528   diff @@
==========================================
  Files           109        173     +64   
  Lines         46780      56171   +9391   
  Methods           0          0           
  Messages          0          0           
  Branches          0          0           
==========================================
+ Hits          31075      34771   +3696   
- Misses        15705      21400   +5695   
  Partials          0          0           

Powered by Codecov. Last update dfd38f7...eb18ba8

@madphysicist madphysicist force-pushed the twin-ticker-copy branch 2 times, most recently from 54c9995 to 890c8b7 Compare December 1, 2016 23:07
@QuLogic
Copy link
Member

QuLogic commented Dec 2, 2016

I think you push some commits for #7482 on here accidentally.

@madphysicist
Copy link
Contributor Author

madphysicist commented Dec 2, 2016

@QuLogic. It was not entirely an accident. I wanted to make the documentation change in the example once (if) both branches get merged. That last commit should be in whichever branch gets merged last. My mistake was pushing it here early.

@madphysicist madphysicist reopened this Dec 2, 2016
@madphysicist
Copy link
Contributor Author

Sorry, pushing all kinds of buttons unintentionally in mobile version.

@madphysicist
Copy link
Contributor Author

@QuLogic. I undid the push. Once either PR gets accepted, I will add the example modifications to the other one. Thanks for the catch.

@madphysicist
Copy link
Contributor Author

While I realize that I need to add more tests for things like subplots using the new keyword, is the failure in coveralls something I should be worried about in general?

@naoyak
Copy link
Contributor

naoyak commented Jan 24, 2017

@madphysicist do you have a screenshot of what this would look like (i.e. a shared axis with 2 distinct ticker formats)? I'm working on #7904 and wanted to be sure things would be consistent in terms of approach.

@madphysicist
Copy link
Contributor Author

@naoyak. Thanks for reaching out. I am glad to see you working on the auto-scaling, which I know next to nothing about. There is a screenshot of the expected behavior in a comment I made in the issue that started this PR. It also has the code used to generate it. The portion of the discussion following that comment also has some points by @efiring about autoscaling. I am not sure if they are pertinent to your PR though.

@naoyak
Copy link
Contributor

naoyak commented Jan 24, 2017

Awesome, the screenshot helps. I don't think our PRs will clash in any way but if you are close to finishing this one up, I might just wait and rebase on top.

@madphysicist
Copy link
Contributor Author

I agree that there will likely be no clash. I was wondering if you have any thoughts about this comment. If the twinned axes have different locators and autoscaling is turned on, is there really a possibility for conflict between the locators? If so, how would you recommend resolving it?

@naoyak
Copy link
Contributor

naoyak commented Jan 26, 2017

What do we mean here when we talk about a conflict in autoscaling? Do you mean that one of the twinned Axes would have autoscaling turned on, and the other has it turned off? In any case, new data would only apply to one of the Axes, I think.

@madphysicist
Copy link
Contributor Author

@naoyak Going off the comment I linked to in my previous post, there may be the possibility that the locator is used to determine bounds when autoscaling is turned on. Since my PR will allow different locators on each of the twinned axes, which one will determine the final bounds that are set (assuming that both have autoscaling on simultaneously, as they should)? I am not 100% convinced that there is a possibility for conflict at all, but I have not traced through the code enough or done any experiments to confirm one way or the other. I thought you might have more insight into the possibilities.

@tacaswell tacaswell added this to the 2.1 (next point release) milestone Jan 27, 2017
@@ -604,6 +604,14 @@ class Ticker(object):
locator = None
formatter = None

def __init__(self, ticker=None):
Copy link
Member

Choose a reason for hiding this comment

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

I have a slight preference to stay with the pattern the Artists use of an update_from method, but not enough to hold up the PR over.

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 have no problem doing it that way too. Functionally it won't make a difference what the name of the method is.

self.xaxis.set_minor_locator(minl)
# Reset the formatter/locator. Axis handle gets marked as
# stale in previous line, no need to repeat.
if self._share_tickers:
Copy link
Member

Choose a reason for hiding this comment

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

I am confused, it looks like this behavior is what I thought it was, but the other behavior is what it used to do.

Copy link
Contributor Author

@madphysicist madphysicist Jan 27, 2017

Choose a reason for hiding this comment

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

The old way of doing things was:

  1. Copy the Ticker objects to the new axis (lines 973-4)
  2. Save the formatters and locators (lines 979-982)
  3. Trash the formatters and locators with _set_scale (line 985)
  4. Restore the formatters and locators (lines 988-991)

This would restore for both the old and new axis since they always shared the same Ticker object.

I made this process a little more efficient:

  1. Set empty Tickers for the new axis (this one) (lines 993-4)
  2. Let _set_scale mess with the empty Tickers (line 1001)
  3. Reset the Tickers to either a reference (lines 1006-7) or a copy (lines 1009-10) of the old axis's Tickers.

Since _share_tickers defaults to True, the original behavior of having a reference to the original axis' Tickers is preserved unless explicitly asked for by the user. The key here is that _set_scale messes up the formatters and locators for its own purposes and it's effects have to be undone one way or another. I think my way is just simpler.

@madphysicist
Copy link
Contributor Author

@tacaswell I switched Ticker to use update_from and I think it makes the code look better overall. Also saves on an allocation when self._share_tickers is False.

@madphysicist
Copy link
Contributor Author

Looks like everything is working. However, I would like to add a couple of tests with subplots and autoscaling before this gets merged.

@madphysicist madphysicist force-pushed the twin-ticker-copy branch 3 times, most recently from 202f91b to 546713e Compare January 31, 2017 14:50
@tacaswell tacaswell modified the milestones: 2.1 (next point release), 2.2 (next next feature release) Aug 29, 2017
@dstansby
Copy link
Member

dstansby commented Apr 6, 2018

Closing in favour of discussion on #10976, and as a duplicate of #10960.

@dstansby dstansby closed this Apr 6, 2018
@dstansby dstansby removed this from the needs sorting milestone Apr 6, 2018
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.

7 participants