0

Basically, I want to achieve the same as in https://stackoverflow.com/a/58413766/6197439 - except with a two plots.

The example code for this is pasted below, and here is an animated gif of how it behaves:

Figure_1

The thing is:

  • I have to make the dual axis a twiny of ax2 (the bottom subplot) so it is drawn below the shared x axis at the bottom - else it gets drawn below the top plot (and overlapping the top of the bottom plot)
  • After start, I first drag in the bottom subplot - both axes follow as they should
  • If I zoom in the bottom subplot, both x-axes scale properly - but the twin axes does not have all the labels (that is why I have on_xlims_change, which helped fix that in the linked post, where there was only one plot - but here I cannot get it to work)
  • If then I drag in the top subplot - only the original x-axis moves, the dual/twinned cloned x-axis does not (the gif doesn't show that, but the same goes for zooming in top subplot as well)

I have tried using the callback on either and both ax and ax2, and I couldn't get an improved behavior - however, note that the gif shows the behavior as in the code posted here (where the callback is not used).

So, how can I make the dual/twinned x-axis follow the original shared x-axis - across both zoom and pan, in both the top and the bottom subplot?

The code:

#!/usr/bin/env python3

import matplotlib
print("matplotlib.__version__ {}".format(matplotlib.__version__))
import matplotlib.pyplot as plt

#
# Some toy data
x_seq = [x / 100.0 for x in range(1, 100)]
y_seq = [x**2 for x in x_seq]
y2_seq = [0.3*x**2 for x in x_seq]

#
# Scatter plot
fig, (ax, ax2) = plt.subplots(2, 1, sharex=True, figsize=(9, 6), dpi=120, gridspec_kw={'height_ratios': [2, 1]}) # two rows, one column
# Remove horizontal space between axes
fig.subplots_adjust(hspace=0)

ax.plot(x_seq, y_seq)
ax2.plot(x_seq, y2_seq)

# https://stackoverflow.com/questions/31803817/how-to-add-second-x-axis-at-the-bottom-of-the-first-one-in-matplotlib
ax22 = ax2.twiny() # instantiate a second axes that shares the same y-axis
# Move twinned axis ticks and label from top to bottom
ax22.xaxis.set_ticks_position("bottom")
ax22.xaxis.set_label_position("bottom")
# Offset the twin axis below the host
ax22.spines["bottom"].set_position(("axes", -0.1))

factor = 655
old_xlims = ax2.get_xlim()
new_xlims = (factor*old_xlims[0], factor*old_xlims[1])
old_tlocs = ax2.get_xticks()
new_tlocs = [i*factor for i in old_tlocs]
print("old_xlims {} new_xlims {} old_tlocs {} new_tlocs {}".format(old_xlims, new_xlims, old_tlocs, new_tlocs))
ax22.set_xticks(new_tlocs)
ax22.set_xlim(*new_xlims)

def on_xlims_change(axes):
  old_tlocs = axes.get_xticks()
  new_tlocs = [i*factor for i in old_tlocs]
  ax22.set_xticks(new_tlocs)

# ax.callbacks.connect('xlim_changed', on_xlims_change)
# ax2.callbacks.connect('xlim_changed', on_xlims_change)

#
# Show
plt.show()

sdbbs
  • 4,270
  • 5
  • 32
  • 87
  • 1
    `axes.get_xticks()` gets you the ticks *before* the change. I think I would try to share all three axes and just use a different formatter on the last one. – ImportanceOfBeingErnest Oct 16 '19 at 18:52
  • Thanks @ImportanceOfBeingErnest - I think I could use that advice, and get a working example, posted as an answer below - can you please check? – sdbbs Oct 17 '19 at 04:32
  • 1
    Yes, that looks good. In the long run I think the [`secondary_axis`](https://matplotlib.org/gallery/subplots_axes_and_figures/secondary_axis.html) should allow for (a) being positionned via `set_position` (similar to [this example](https://matplotlib.org/gallery/ticks_and_spines/spine_placement_demo.html)), (b) taking a different formatter (as asked for [here](https://discourse.matplotlib.org/t/matplotlib-users-bug-with-changing-formatter-and-locator-of-a-secondary-axis/20615)). – ImportanceOfBeingErnest Oct 17 '19 at 11:23

1 Answers1

0

I think I have a solution (code below):

Figure_1

.... thanks to the comment by @ImportanceOfBeingErnest :

axes.get_xticks() gets you the ticks before the change.

Well, now at least it makes sense, why it was so difficult to set it up :) Wish I found this info earlier ... People seem to have had a problem with it:

I think I would try to share all three axes

The only info I found on this is:

Apparently, one can use ax1.get_shared_x_axes().join(ax1, ax2) -> however, this join is not in the sense of "join"ing an array to string in Python, nor in the sense of appending to an array, it is in the sense of a (dis)join(t) set, apparently - so you can join three items, which is what I tried (and it seems to work):

ax.get_shared_x_axes().join(ax, ax2, ax22)

Is this correct?

and just use a different formatter on the last one.

There is decent info on that here:

So, finally, my code is:

#!/usr/bin/env python3

import matplotlib
print("matplotlib.__version__ {}".format(matplotlib.__version__))
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

#
# Some toy data
x_seq = [x / 100.0 for x in range(1, 100)]
y_seq = [x**2 for x in x_seq]
y2_seq = [0.3*x**2 for x in x_seq]

#
# Scatter plot
fig, (ax, ax2) = plt.subplots(2, 1, sharex=True, figsize=(9, 6), dpi=100, gridspec_kw={'height_ratios': [2, 1]}) # two rows, one column
# Remove horizontal space between axes
fig.subplots_adjust(hspace=0)

# https://stackoverflow.com/questions/31803817/how-to-add-second-x-axis-at-the-bottom-of-the-first-one-in-matplotlib
ax22 = ax2.twiny() # instantiate a second axes that shares the same y-axis
#~ ax.get_shared_x_axes().join(ax, ax22) # SO:42718823
ax.get_shared_x_axes().join(ax, ax2, ax22)
#~ ax.autoscale() # <-- needed if no axes limits are explicitely set. SO:42718823
# Move twinned axis ticks and label from top to bottom
ax22.xaxis.set_ticks_position("bottom")
ax22.xaxis.set_label_position("bottom")
# Offset the twin axis below the host
ax22.spines["bottom"].set_position(("axes", -0.1))

ax.plot(x_seq, y_seq)
ax2.plot(x_seq, y2_seq)

factor = 655

# FuncFormatter can be used as a decorator
@ticker.FuncFormatter
def major_formatter(x, pos):
  #return "[%.2f]" % x
  return int(factor*x)

ax22.xaxis.set_major_formatter(major_formatter)

#
# Show
plt.show()
sdbbs
  • 4,270
  • 5
  • 32
  • 87