Skip to content

Order of ax.spines[].set_position() and ax.yaxis.set_major_formatter() produces different results #2941

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
mscross opened this issue Mar 29, 2014 · 6 comments · Fixed by #3104

Comments

@mscross
Copy link
Contributor

mscross commented Mar 29, 2014

In the first example, ax.spines['right'].set_position() is called first, and the negative signs on the right y-axis are smaller hyphen-minus symbols because they were passed through ax.yaxis.set_major_formatter(). in the second example, the order is reversed, and the formatter is not applied or is reset (i.e., the negative signs are the larger unicode character \u2212).

I was under the impression that matplotlib settings were order-agnostic before plt.show()- is this a bug?

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FormatStrFormatter, MultipleLocator

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('time (s)')
# Make the y-axis label and tick labels match the line color.
ax1.set_ylabel('exp', color='b')
for tl in ax1.get_yticklabels():
    tl.set_color('b')

ymajorFormatter = FormatStrFormatter("%.1f")

ax2 = ax1.twinx()
s2 = np.sin(2*np.pi*t)
ax2.plot(t, s2, 'r.')
ax2.set_ylabel('sin', color='r')
for tl in ax2.get_yticklabels():
    tl.set_color('r')

# ax2.yaxis.set_major_formatter(ymajorFormatter)
ax2.spines['right'].set_position(('axes', 1.1))
ax2.yaxis.set_major_formatter(ymajorFormatter)
plt.show()

spine_thenformat

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('time (s)')
# Make the y-axis label and tick labels match the line color.
ax1.set_ylabel('exp', color='b')
for tl in ax1.get_yticklabels():
    tl.set_color('b')

ymajorFormatter = FormatStrFormatter("%.1f")

ax2 = ax1.twinx()
s2 = np.sin(2*np.pi*t)
ax2.plot(t, s2, 'r.')
ax2.set_ylabel('sin', color='r')
for tl in ax2.get_yticklabels():
    tl.set_color('r')

ax2.yaxis.set_major_formatter(ymajorFormatter)
ax2.spines['right'].set_position(('axes', 1.1))
# ax2.yaxis.set_major_formatter(ymajorFormatter)
plt.show()

format_thenspine

@tacaswell
Copy link
Member

The major formatter is definitely not getting propagated through the set_position call:

In [15]: ax2.yaxis.get_major_formatter()
Out[15]: <matplotlib.ticker.ScalarFormatter at 0x55d0410>

In [16]: ymajorFormatter
Out[16]: <matplotlib.ticker.FormatStrFormatter at 0x55bce90>

However I am not sure what the 'correct' behavior is.

@tacaswell tacaswell added this to the v1.4.x milestone Mar 31, 2014
@JDWarner
Copy link

My understanding was basically that unless two calls were mutually exclusive (e.g., the same parameter getting set twice) all commands prior to plt.show() or equivalent should be order agnostic.

If this is the design goal, it seems this should propagate through changing the position of the spines. Users would expect spine position and formatting of the axis label to be independent.

@tacaswell
Copy link
Member

@JDWarner duh, yes, you are right. Looking at this again I was confused about what was going on (thought that the non-commuting operation was setting the ticks to be on the right axis, not the left, which is not what is happening).

Re-milestoned and labeled accordingly.

@tacaswell
Copy link
Member

Digging into this, it seems the offending line (https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/spines.py#L382) clear the axis (which resets everything to the defaults) after adjusting the spine position.

This looks like it was a feature of the original introduction of this code in 2009 (43ca13b) !

I suspect the fix is to just remove the self.axis.cla() block. @astraw, you have a comment?

@astraw
Copy link
Contributor

astraw commented Apr 12, 2014

I don't think this is a bug per se, just a wart.

I don't remember exactly why the call to self.axis.cla() was necessary, but I remember that it was. You're welcome to play with removing it. Check especially the spine_placement_demo.py example and make sure that zooming and panning continues to work. And spine placement code should also work with the basemap projections.

By design, the matplotlib API is not order-independent. For example, this code shows that most basic plotting commands get drawn in the order they were called:

import matplotlib.pyplot as plt

fig=plt.figure()
ax1=fig.add_subplot(211)
ax1.plot([1,2,3],[4,5,4],'b-')
ax1.plot([1.5],[4.5],'ro',ms=20.)

ax2=fig.add_subplot(212)
ax2.plot([1.5],[4.5],'ro',ms=20.)
ax2.plot([1,2,3],[4,5,4],'b-')

plt.show()

figure_1

@JDWarner
Copy link

@astraw Granted, as I noted before when one or more command either references the same space or is mutually exclusive the order matters. Ergo the zorder= kwarg.

The behavior in this issue, however, is not mutually exclusive nor is it intuitive to the user. When setting independent (or seemingly independent) features about a figure/axis, it's not expected that the order would matter.

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

Successfully merging a pull request may close this issue.

5 participants