2

I'm trying to enable sharing for both primary and secondary axis. The example plot is illustrated by the code below. The plot contains two horizontal axes, the primary axis grid is shown in green, while the other axis has red grid.

#!/usr/bin/python

# -*- coding: utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt

FIRST = 0.0
LAST  = 2.0
STEP  = 0.01

t = np.arange(FIRST, LAST, STEP)

s1 = np.sin(2*np.pi*t)
s2 = np.exp(-t)
s3 = s1*s2

###############################################################################

plt.rc('axes', grid=True)
plt.rc('grid', color='0.75', linestyle='-', linewidth=0.5)

fig3 = plt.figure()
ax1primary = plt.subplot2grid((4,3), (0,0), colspan=3, rowspan=2)
ax2primary = plt.subplot2grid((4,3), (2,0), colspan=3, sharex=ax1primary)
ax3primary = plt.subplot2grid((4,3), (3,0), colspan=3, sharex=ax1primary)

ax1primary.plot(t,s1)
ax1primary.set_yticks(np.arange(-0.9, 1.0, 0.3))
ax1primary.xaxis.grid(color='green')

ax2primary.plot(t[:150],s2[:150])
ax2primary.set_yticks(np.arange(0.3, 1, 0.2))
ax2primary.xaxis.grid(color='green')

ax3primary.plot(t[30:],s3[30:])
ax3primary.plot([0,2],[0.2,0.2],'m')
ax3primary.set_yticks(np.arange(-0.4, 0.7, 0.2))
ax3primary.xaxis.grid(color='green')

INDEX = t[np.where(abs(s3-0.2) < 0.005)[0]]
INDEX = np.append(INDEX, LAST)
INDEX = np.insert(INDEX, 0, FIRST)

ax1secondary = ax1primary.twiny()
ax1secondary.set_xticks(INDEX)
ax1secondary.xaxis.grid(color='red')

ax2secondary = ax2primary.twiny()
ax2secondary.set_xticks(INDEX)
ax2secondary.xaxis.grid(color='red')

ax3secondary = ax3primary.twiny()
ax3secondary.set_xticks(INDEX)
ax3secondary.xaxis.grid(color='red')

plt.tight_layout()
plt.subplots_adjust(hspace=0)

for ax in [ax1primary, ax2primary, ax2secondary, ax3secondary]:
    plt.setp(ax.get_xticklabels(), visible=False)

###############################################################################

plt.show()

On a static figure there is no issue. The problem becomes obvious when you start panning (or zooming) one of the subplots. The primary (green) axis stays perfectly in sync and moves within all subplots, but the secondary (red) axis gets misaligned and moves only within the active subplot.

Is there a way to fix this?


The behavior that I want to achieve is following:

I need one common "primary" x-axis (for all three subplots) with the ticks on the bottom of the figure and another common "secondary" x-axis (for all three subplots) with the ticks on the top of the figure. The primary axis is a standard regularly spaced axis, while the secondary axis shows the customized ticks (for example zero crossings) This is all satisfied in the example above. Now I need it to be satisfied also while panning and zooming subplots.

Boris L.
  • 936
  • 2
  • 12
  • 28
  • It's unclear to me what sort of behaviour you're looking for. `twiny` creates an independent x-axis with a shared y-axis, so if you pan inside your `ay1c`, `ay2c` and `ay3c` subplots the x-axes ought to move "out of sync" with their parent axes. On the other hand, your `ax1c`, `ax2c` and `ax3c` subplots were created with a shared x-axis but independent y-axes. Do you want all of your plots to share both x- and y-axes? – ali_m Sep 26 '16 at 12:01
  • I can understand it is confusing. I did not make a good choice of axis names. `ay1c`, `ay2c` and `ay3c` have nothing to do with y-axis (I will change the axis names in the example code above to remove the confusion. Thank you for this insight.). Nevertheless, the behavior I want to achieve is that I have one common x-axis (for all three subplots) with the ticks on the bottom of the figure and another common x-axis (for all three subplots) with the ticks on the top of the figure. – Boris L. Sep 26 '16 at 12:39
  • Then it sounds as though you want to use `twinx` rather than `twiny` to create `ay1c` etc. `twiny` gives you a common y-axis and an independent x-axis, whereas you want a common x-axis and independent y-axes. – ali_m Sep 26 '16 at 12:53
  • I have tried your suggestion. The result is that `twinx` creates the ticks on the right side of the figure. Can you show an example how to use `twinx` so that the ticks are on top of the figure? – Boris L. Sep 26 '16 at 13:02
  • What you're asking for doesn't really make sense to me. If you want the new plot to share an x-axis with the existing subplot then the new ticks should correspond to the new independent y-axis, and should therefore be on the right rather than on the top. Can you explain in more detail what you're trying to achieve? – ali_m Sep 26 '16 at 13:16
  • Let me try again... 1) at the bottom of the figure I have common "primary" x-axis (for all three subplots) with standard regularly spaced ticks 2) at the top of the figure I have common "secondary" x-axis (for all three subplots) with customized ticks that are associated with zero crossings of the function in the bottom subplot 3) that is all that I want to achieve, and I have achieved it 4) the problem is that when panning (or zooming) the axes do not move synchronously, i.e. I need the red grid to behave the same as the green grid (it should move together in all subplots) – Boris L. Sep 26 '16 at 13:34

2 Answers2

3

Thanks for clarifying your question. The intended use of twiny is to create a second fully independent x-axis with its own scale and offset, which you would then plot into. However in your case you are only using the secondary x-axis created by twiny as a way to display a second set of custom x ticks, and you want this axis to always have exactly the same scale and offset as the parent x-axis.

One approach would be to create a callback that updates the limits of the secondary axis whenever the primary axis gets panned:

from matplotlib.backend_bases import NavigationToolbar2

parents = [ax1primary, ax2primary, ax3primary]
children = [ax1secondary, ax2secondary, ax3secondary]

def callback(event=None):
    # return immediately if the figure toolbar is not in "navigation mode"
    if not isinstance(parents[0].figure.canvas.manager.toolbar,
                      NavigationToolbar2):
        return
    for parent, child in zip(parents, children):
        child.set_xlim(*parent.get_xlim())
        child.set_ylim(*parent.get_ylim())

# connect the callback to the figure canvas
fig3.canvas.mpl_connect('motion_notify_event', callback)
ali_m
  • 71,714
  • 23
  • 223
  • 298
  • This looks like a promising solution, I will give it a try... Actually, I was hoping for something simple, like for example `ax2secondary = ax2primary.twiny(sharex=ax1secondary)` – Boris L. Sep 26 '16 at 18:20
1

Unfortunately the proposed callback solution is not robust enough. Most of the time panning works fine, but zooming is a disaster. Still, too often the grids get misaligned.

Until I find out how to improve the callback solution, I decided to code a custom grid and annotate the values within the plot.

#!/usr/bin/python

# -*- coding: utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt

FIRST = 0.0
LAST  = 2.0
STEP  = 0.01

t = np.arange(FIRST, LAST, STEP)

s1 = np.sin(2*np.pi*t)
s2 = np.exp(-t)
s3 = s1*s2

###############################################################################

plt.rc('axes', grid=True)
plt.rc('grid', color='0.75', linestyle='-', linewidth=0.5)

fig3 = plt.figure()
ax1primary = plt.subplot2grid((4,3), (0,0), colspan=3, rowspan=2)
ax2primary = plt.subplot2grid((4,3), (2,0), colspan=3, sharex=ax1primary)
ax3primary = plt.subplot2grid((4,3), (3,0), colspan=3, sharex=ax1primary)

ax1primary.plot(t,s1)
ax1primary.set_yticks(np.arange(-0.9, 1.0, 0.3))
ax1primary.xaxis.grid(color='green')
ax1primary.set_ylim(-1, 1)

ax2primary.plot(t[:150],s2[:150])
ax2primary.set_yticks(np.arange(0.3, 1, 0.2))
ax2primary.xaxis.grid(color='green')
ax2primary.set_ylim(0.2, 1)

ax3primary.plot(t[30:],s3[30:])
ax3primary.plot([0,2],[0.2,0.2],'m')
ax3primary.set_yticks(np.arange(-0.4, 0.7, 0.2))
ax3primary.xaxis.grid(color='green')
ax3primary.set_ylim(-0.6, 0.8)

INDEX = np.where(abs(s3-0.2) < 0.005)[0]

for i in range(0, len(INDEX)):
    ax1primary.annotate(t[INDEX[i]], xy=(t[INDEX[i]], 0))

ax1primary.plot([t[INDEX], t[INDEX]], [-1e9 * np.ones(len(INDEX)), 1e9 * np.ones(len(INDEX))], 'r')
ax2primary.plot([t[INDEX], t[INDEX]], [-1e9 * np.ones(len(INDEX)), 1e9 * np.ones(len(INDEX))], 'r')
ax3primary.plot([t[INDEX], t[INDEX]], [-1e9 * np.ones(len(INDEX)), 1e9 * np.ones(len(INDEX))], 'r')

plt.tight_layout()
plt.subplots_adjust(hspace=0)

for ax in [ax1primary, ax2primary]:
    plt.setp(ax.get_xticklabels(), visible=False)

###############################################################################

plt.show()
Boris L.
  • 936
  • 2
  • 12
  • 28