Skip to content

[Bug]: secondary_xaxis produces ticks at incorrect locations #29067

Closed

Description

@dkweiss31

Bug summary

It is possible I'm doing this incorrectly, but for a very simple example secondary_xaxis puts tick marks at incorrect locations. Modifying slightly the interpolation example from here https://matplotlib.org/stable/gallery/subplots_axes_and_figures/secondary_axis.html:

Code for reproduction

fig, ax = plt.subplots(constrained_layout=True)
xdata = np.arange(0, 11, 0.4)
ydata = np.random.randn(len(xdata))
ax.plot(xdata, ydata, label='Plotted data')
ax.set_xlabel('X [m]')
ax.legend()
xnew = xdata**2


def forward(x):
    return np.interp(x, xdata, xnew)


def inverse(x):
    return np.interp(x, xnew, xdata)

secax = ax.secondary_xaxis('top', functions=(forward, inverse))
secax.xaxis.set_minor_locator(AutoMinorLocator())
secax.set_xlabel('$X_{other}$')

plt.show()

Actual outcome

image

Expected outcome

Notice that e.g. 0 on the lower axis is not aligned with 0 on the top and 10 on the bottom is not aligned with 100 on the top.

Additional information

No response

Operating system

OS/X

Matplotlib Version

3.9.2

Matplotlib Backend

module://matplotlib_inline.backend_inline

Python version

3.10.14

Jupyter version

7.2.2

Installation

pip

Activity

  1. jklymak commented on Nov 4, 2024

    @jklymak
    Member

    Your interpolation won't go lower than zero, but the main axis has negative values so the secondary axes is not one to one. Your forward and reverse should take care of negative values, probably leaving them the same or doing -x**2

  2. jklymak commented on Nov 4, 2024

    @jklymak
    Member

    Or just set_xlim(min=0)...

  3. dkweiss31 commented on Nov 4, 2024

    @dkweiss31
    ContributorAuthor

    Thanks for the quick reply @jklymak ! You're right about the negative values but I was hoping that the top x axis would not go negative and start at 0, same as the bottom x axis. As you suggest the following works code works by specifying the limits (note that if I don't specify the right xlim, the top x axis is still not correct). However it doesn't have the nice buffer of white space on the left and right that we are used to. Is there a way to retain that nice behavior with secondary_xaxis?

    fig, ax = plt.subplots(constrained_layout=True)
    xdata = np.arange(0, 11, 0.4)
    ydata = np.random.randn(len(xdata))
    ax.plot(xdata, ydata, label='Plotted data')
    ax.set_xlim(left=xdata[0], right=xdata[-1])
    ax.axvline(10, color="grey")
    ax.set_xlabel('X [m]')
    ax.legend()
    xnew = xdata**2
    
    def forward(x):
        return np.interp(x, xdata, xnew)
    
    
    def inverse(x):
        return np.interp(x, xnew, xdata)
    
    secax = ax.secondary_xaxis('top', functions=(forward, inverse))
    secax.xaxis.set_minor_locator(AutoMinorLocator())
    secax.set_xlabel('$X_{other}$')
    
    plt.show()

    Producing
    image

  4. jklymak commented on Nov 4, 2024

    @jklymak
    Member

    There is an explicit note about this in https://matplotlib.org/stable/gallery/subplots_axes_and_figures/secondary_axis.html#sphx-glr-gallery-subplots-axes-and-figures-secondary-axis-py:

    In order to properly handle the data margins, the mapping functions 
    (forward and inverse in this example) need to be defined beyond the nominal plot limits.
    
    In the specific case of the numpy linear interpolation, 
    [numpy.interp](https://numpy.org/doc/stable/reference/generated/numpy.interp.html#numpy.interp), this 
    condition can be arbitrarily enforced by providing optional keyword arguments left, right such that values 
    outside the data range are mapped well outside the plot limits.
    
  5. jklymak commented on Nov 4, 2024

    @jklymak
    Member

    Feel free to carry on the discussion. However, I don't think there is much we can do here - you need to define forward and inverse to handle beyond 0 and 11 if you want margins to work. How exactly you do that is application dependent.

  6. dkweiss31 commented on Nov 4, 2024

    @dkweiss31
    ContributorAuthor

    I'm not sure that's the only issue here: it seems that this functionality doesn't play nice with interp. The following simple example which does away with the negative complications works fine for the first set of functions and doesn't for the second set:

    import matplotlib.pyplot as plt
    import numpy as np
    
    fig, ax = plt.subplots(constrained_layout=True)
    xdata = np.arange(2, 11, 0.4)
    ydata = np.random.randn(len(xdata))
    ax.plot(xdata, ydata, label='Plotted data')
    ax.axvline(10, color="grey")
    ax.set_xlabel('X [m]')
    ax.legend()
    xnew = 2 * xdata
    
    # def forward(x):
    #     return 2 * x
    #
    #
    # def inverse(x):
    #     return x / 2
    
    
    def forward(x):
        return np.interp(x, xdata, xnew)
    
    
    def inverse(x):
        return np.interp(x, xnew, xdata)
    
    secax = ax.secondary_xaxis('top', functions=(forward, inverse))
    secax.set_xlabel('$X_{other}$')
    
    plt.show()

    first set:
    image
    second set (interp)
    image

  7. jklymak commented on Nov 4, 2024

    @jklymak
    Member

    As above, you need to tell np.interp what to do for x<2 and x>11.

    def forward(x):
        xdatan = np.concatenate(([-50], xdata, [50]))
        xnewn = np.concatenate(([-50*2], xnew, [50*2]))
    
        return np.interp(x, xdatan, xnewn)
    
    
    def inverse(x):
        xdatan = np.concatenate(([-50], xdata, [50]))
        xnewn = np.concatenate(([-50*2], xnew, [50*2]))
        return np.interp(x, xnewn, xdatan)
    
  8. dkweiss31 commented on Nov 4, 2024

    @dkweiss31
    ContributorAuthor

    Ahh I see: that worked, thanks! Unless I'm mistaken about what the tutorial is trying to show, it might be suffering from the same problem I was running into. If there is interest I'd be happy to submit a PR to update the tutorial based on our discussion.

  9. reopened this on Nov 4, 2024
  10. jklymak commented on Nov 4, 2024

    @jklymak
    Member

    Ah I see, I think you are correct. Fixing the tutorial would be super helpful!

  11. added this to the v3.10.0 milestone on Nov 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions