Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes fromall commits
Commits
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
137 changes: 93 additions & 44 deletionslib/matplotlib/axes/_base.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -462,6 +462,13 @@ def __init__(self, fig, rect,
to share the x-axis with
*sharey* an class:`~matplotlib.axes.Axes` instance
to share the y-axis with
*share_tickers* [ *True* | *False* ] whether the major and
minor `Formatter` and `Locator` instances
are always shared (if `True`) or can be set
independently (if `False`) between this set
of axes and `sharex` and `sharey`. This
argument has no meaning if neither `sharex`
nor `sharey` are set. Defaults to `True`.
*title* the title string
*visible* [ *True* | *False* ] whether the axes is
visible
Expand All@@ -476,6 +483,14 @@ def __init__(self, fig, rect,
*yticklabels* sequence of strings
*yticks* sequence of floats
================ =========================================

.. warning::

Setting `share_tickers` to `False` and changing the
`Locator`s of a shared axis may not play with autoscaling.
Autoscaling may need to access the `Locator` object of the
base axis. Normally, with `share_tickers=True`, the axes
are guaranteed to share a `Locator` instance.
""" % {'scale': ' | '.join(
[repr(x) for x in mscale.get_scale_names()])}
martist.Artist.__init__(self)
Expand All@@ -493,6 +508,10 @@ def __init__(self, fig, rect,
self.set_anchor('C')
self._sharex = sharex
self._sharey = sharey
# share_tickers is only used as a modifier for sharex/y. It
# should not remain in kwargs by the time kwargs updates the
# instance dictionary.
self._share_tickers = kwargs.pop('share_tickers', True)
if sharex is not None:
self._shared_x_axes.join(self, sharex)
if sharex._adjustable == 'box':
Expand DownExpand Up@@ -968,50 +987,52 @@ def cla(self):
self.callbacks = cbook.CallbackRegistry()

if self._sharex is not None:
# major and minor are class instances with
# locator and formatter attributes
self.xaxis.major = self._sharex.xaxis.major
self.xaxis.minor = self._sharex.xaxis.minor
# The tickers need to exist but can be empty until after the
# call to Axis._set_scale since they will be overwritten
# anyway
self.xaxis.major = maxis.Ticker()
self.xaxis.minor = maxis.Ticker()

# Copy the axis limits
x0, x1 = self._sharex.get_xlim()
self.set_xlim(x0, x1, emit=False, auto=None)

# Save the current formatter/locator so we don't lose it
majf = self._sharex.xaxis.get_major_formatter()
minf = self._sharex.xaxis.get_minor_formatter()
majl = self._sharex.xaxis.get_major_locator()
minl = self._sharex.xaxis.get_minor_locator()

# This overwrites the current formatter/locator
self.xaxis._set_scale(self._sharex.xaxis.get_scale())

# Reset the formatter/locator
self.xaxis.set_major_formatter(majf)
self.xaxis.set_minor_formatter(minf)
self.xaxis.set_major_locator(majl)
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 Ithought it was, but the other behavior is what it used to do.

Copy link
ContributorAuthor

@madphysicistmadphysicistJan 27, 2017
edited
Loading

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 theTicker 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 sameTicker object.

I made this process a little more efficient:

  1. Set emptyTickers for the new axis (this one) (lines 993-4)
  2. Let_set_scale mess with the emptyTickers (line 1001)
  3. Reset theTickers to either a reference (lines 1006-7) or a copy (lines 1009-10) of the old axis'sTickers.

Since_share_tickers defaults toTrue, 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.

self.xaxis.major = self._sharex.xaxis.major
self.xaxis.minor = self._sharex.xaxis.minor
else:
self.xaxis.major.update_from(self._sharex.xaxis.major)
self.xaxis.minor.update_from(self._sharex.xaxis.minor)
else:
self.xaxis._set_scale('linear')

if self._sharey is not None:
self.yaxis.major = self._sharey.yaxis.major
self.yaxis.minor = self._sharey.yaxis.minor
# The tickers need to exist but can be empty until after the
# call to Axis._set_scale since they will be overwritten
# anyway
self.yaxis.major = maxis.Ticker()
self.yaxis.minor = maxis.Ticker()

# Copy the axis limits
y0, y1 = self._sharey.get_ylim()
self.set_ylim(y0, y1, emit=False, auto=None)

# Save the current formatter/locator so we don't lose it
majf = self._sharey.yaxis.get_major_formatter()
minf = self._sharey.yaxis.get_minor_formatter()
majl = self._sharey.yaxis.get_major_locator()
minl = self._sharey.yaxis.get_minor_locator()

# This overwrites the current formatter/locator
self.yaxis._set_scale(self._sharey.yaxis.get_scale())

# Reset the formatter/locator
self.yaxis.set_major_formatter(majf)
self.yaxis.set_minor_formatter(minf)
self.yaxis.set_major_locator(majl)
self.yaxis.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:
self.yaxis.major = self._sharey.yaxis.major
self.yaxis.minor = self._sharey.yaxis.minor
else:
self.yaxis.major.update_from(self._sharey.yaxis.major)
self.yaxis.minor.update_from(self._sharey.yaxis.minor)
else:
self.yaxis._set_scale('linear')

Expand DownExpand Up@@ -3898,15 +3919,29 @@ def _make_twin_axes(self, *kl, **kwargs):
ax2 = self.figure.add_axes(self.get_position(True), *kl, **kwargs)
return ax2

def twinx(self):
def twinx(self, share_tickers=True):
"""
Create a twin Axes sharing the xaxis
Create a twin Axes sharing the xaxis.

Create a new Axes instance with an invisible x-axis and an independent
y-axis positioned opposite to the original one (i.e. at right). The
x-axis autoscale setting will be inherited from the original Axes.
To ensure that the tick marks of both y-axes align, see
`~matplotlib.ticker.LinearLocator`
Create a new Axes instance with an invisible x-axis and an
independent y-axis positioned opposite to the original one (i.e.
at right). The x-axis autoscale setting will be inherited from
the original Axes. To ensure that the tick marks of both y-axes
align, see :class:`matplotlib.ticker.LinearLocator`.

`share_tickers` determines if the shared axis will always have
the same major and minor `Formatter` and `Locator` objects as
this one. This is usually desirable since the axes overlap.
However, if one of the axes is shifted so that they are both
visible, it may be useful to set this parameter to ``False``.

.. warning::

Setting `share_tickers` to `False` and modifying the
`Locator` of either axis may cause problems with
autoscaling. Autoscaling may require access to the
`Locator`, so the behavior will be undefined if the base and
twinned axis do not share a `Locator` instance.

Returns
-------
Expand All@@ -3918,7 +3953,7 @@ def twinx(self):
For those who are 'picking' artists while using twinx, pick
events are only called for the artists in the top-most axes.
"""
ax2 = self._make_twin_axes(sharex=self)
ax2 = self._make_twin_axes(sharex=self, share_tickers=share_tickers)
ax2.yaxis.tick_right()
ax2.yaxis.set_label_position('right')
ax2.yaxis.set_offset_position('right')
Expand All@@ -3928,15 +3963,29 @@ def twinx(self):
ax2.patch.set_visible(False)
return ax2

def twiny(self):
def twiny(self, share_tickers=True):
"""
Create a twin Axes sharing the yaxis
Create a twin Axes sharing the yaxis.

Create a new Axes instance with an invisible y-axis and an
independent 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 :class:`matplotlib.ticker.LinearLocator`

`share_tickers` determines if the shared axis will always have
the same major and minor `Formatter` and `Locator` objects as
this one. This is usually desirable since the axes overlap.
However, if one of the axes is shifted so that they are both
visible, it may be useful to set this parameter to ``False``.

.. warning::

Create a new Axes instance with an invisible y-axisandan independent
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 thatthetick marks of both x-axes align, see
`~matplotlib.ticker.LinearLocator`
Setting `share_tickers` to `False`andmodifying the
`Locator` of either axis may cause problems with
autoscaling. Autoscaling may require access to the
`Locator`, sothebehavior will be undefined if the base and
twinned axis do not share a `Locator` instance.

Returns
-------
Expand All@@ -3948,7 +3997,7 @@ def twiny(self):
For those who are 'picking' artists while using twiny, pick
events are only called for the artists in the top-most axes.
"""
ax2 = self._make_twin_axes(sharey=self)
ax2 = self._make_twin_axes(sharey=self, share_tickers=share_tickers)
ax2.xaxis.tick_top()
ax2.xaxis.set_label_position('top')
ax2.set_autoscaley_on(self.get_autoscaley_on())
Expand Down
8 changes: 8 additions & 0 deletionslib/matplotlib/axis.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -604,6 +604,14 @@ class Ticker(object):
locator = None
formatter = None

def update_from(self, ticker):
"""
Copies the formatter and locator of another ticker into this
one.
"""
self.locator = ticker.locator
self.formatter = ticker.formatter


class Axis(artist.Artist):
"""
Expand Down
53 changes: 53 additions & 0 deletionslib/matplotlib/tests/test_axes.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@
import matplotlib.markers as mmarkers
import matplotlib.patches as mpatches
import matplotlib.colors as mcolors
import matplotlib.ticker as mticker
from numpy.testing import assert_allclose, assert_array_equal
from matplotlib.cbook import IgnoredKeywordWarning
import matplotlib.colors as mcolors
Expand DownExpand Up@@ -143,6 +144,58 @@ def test_twinx_cla():
assert ax.patch.get_visible()
assert ax.yaxis.get_visible()

@cleanup
def test_twin_xy_sharing():
fig, ax = plt.subplots()

# Make some twinned axes to play with (with share_tickers=True)
ax2 = ax.twinx()
ax3 = ax2.twiny()
plt.draw()

assert ax.xaxis.major is ax2.xaxis.major
assert ax.xaxis.minor is ax2.xaxis.minor
assert ax2.yaxis.major is ax3.yaxis.major
assert ax2.yaxis.minor is ax3.yaxis.minor

# Verify that for share_tickers=True, the formatters and locators
# are identical no matter what
ax2.xaxis.set_major_formatter(mticker.PercentFormatter())
ax3.yaxis.set_major_locator(mticker.MaxNLocator())

assert ax.xaxis.get_major_formatter() is ax2.xaxis.get_major_formatter()
assert ax2.yaxis.get_major_locator() is ax3.yaxis.get_major_locator()

# Make some more twinned axes to play with (with share_tickers=False)
ax4 = ax.twinx(share_tickers=False)
ax5 = ax2.twiny(share_tickers=False)
plt.draw()

assert ax4 is not ax2
assert ax5 is not ax3

assert ax.xaxis.major is not ax4.xaxis.major
assert ax.xaxis.minor is not ax4.xaxis.minor
assert ax.xaxis.get_major_formatter() is ax4.xaxis.get_major_formatter()
assert ax.xaxis.get_minor_formatter() is ax4.xaxis.get_minor_formatter()
assert ax.xaxis.get_major_locator() is ax4.xaxis.get_major_locator()
assert ax.xaxis.get_minor_locator() is ax4.xaxis.get_minor_locator()

assert ax2.yaxis.major is not ax5.yaxis.major
assert ax2.yaxis.minor is not ax5.yaxis.minor
assert ax2.yaxis.get_major_formatter() is ax5.yaxis.get_major_formatter()
assert ax2.yaxis.get_minor_formatter() is ax5.yaxis.get_minor_formatter()
assert ax2.yaxis.get_major_locator() is ax5.yaxis.get_major_locator()
assert ax2.yaxis.get_minor_locator() is ax5.yaxis.get_minor_locator()

# Verify that for share_tickers=False, the formatters and locators
# can be changed independently
ax4.xaxis.set_minor_formatter(mticker.PercentFormatter())
ax5.yaxis.set_minor_locator(mticker.MaxNLocator())

assert ax.xaxis.get_minor_formatter() is not ax4.xaxis.get_minor_formatter()
assert ax2.yaxis.get_minor_locator() is not ax4.yaxis.get_minor_locator()


@image_comparison(baseline_images=['twin_autoscale'], extensions=['png'])
def test_twinx_axis_scales():
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp