0

I'm trying to make a plot with 2 breaks in the x-axis. I can produce one break following the matplotlib

days = list(range(0,500))
values = list(np.random.randint(low = 10,high=100,size=len(days)))

fig = plt.figure(figsize=(5, 5))

f,(ax,ax2) = plt.subplots(1,2,sharey=True, facecolor='w')

ax.plot(days, values)
ax2.plot(days, values)

ax.set_xlim(0,100)  # x-axis range limited to 0 - 100 
ax2.set_xlim(250, 300)  # x-axis range limited to 250 - 300

# hide the spines between ax and ax2
ax.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax.yaxis.tick_left()
ax.tick_params(labelright='off')

d = .015 # how big to make the diagonal lines in axes coordinates
# arguments to pass plot, just so we don't keep repeating them
kwargs = dict(transform=ax.transAxes, color='k', clip_on=False)
ax.plot((1-d,1+d), (-d,+d), **kwargs)
ax.plot((1-d,1+d),(1-d,1+d), **kwargs)

kwargs.update(transform=ax2.transAxes)  # switch to the bottom axes
ax2.plot((-d,+d), (1-d,1+d), **kwargs)
ax2.plot((-d,+d), (-d,+d), **kwargs)

plt.show()

enter image description here

However, how can I modify this to have another break to have an ax3 between 400 and 500? Additionally, how can I scale the x-axis length to be reflective of the length of the interval? In the above I would ideally like the right side reduced as it is half the interval.

I have tried using the brokenaxes which looks like it would meet the criteria automatically scaling and allowing multiple breaks refer to here e.g. https://test-brokenaxes.readthedocs.io/en/latest/auto_examples/plot_usage.html#sphx-glr-auto-examples-plot-usage-py

However, when running that code I get

AttributeError: 'SubplotSpec' object has no attribute 'is_last_row'

Any assistance appreciated.

4
  • Looks like is_last_row became deprecated in 3.4, so I assume broken axes hasn't been updated to account for that. matplotlib.org/stable/api/_as_gen/… Commented Aug 31, 2021 at 10:49
  • And possibly related: I showed in this answer how you could make a broken axes plot with difference sized x-axis depending on the range: stackoverflow.com/questions/67718650/… Commented Aug 31, 2021 at 10:52
  • Thanks, looks definitely along the lines I need. Could you add another break to what you presented as there is left and right position, can you add a centre? @tmdavison Commented Aug 31, 2021 at 11:11
  • Yes, I've just added an answer showing how to add a central axis Commented Aug 31, 2021 at 13:41

1 Answer 1

3

You can define the width of the different subplots using the gridspec_kw argument to plt.subplots. In there, we define the width_ratios. In this case, you have the first and third subplots twice as wide as the middle one, so we can use (2,1,2) for the ratios.

We then need to make sure we turn off the correct spines: so for the left axes (ax1), we turn off the right spine. In the middle (ax2) we turn off both left and right, and on the right axes (ax3), we just turn off the left spine.

On the right axes, I move the y-axis ticks to the right hand side using ax3.yaxis.tick_right()

In the middle axes, I hide the ticks with ax2.tick_params(axis='y', length=0) --- note we can't just use something like ax2.set_yticks([]) here because that would affect the other axes, since we use sharey=True.

I've then taken the code to draw the diagonal lines from my other answer here, and added extra lines for the second break in the x axis.

All together, that looks like this:

import matplotlib.pyplot as plt
import numpy as np

days = list(range(0,500))
values = list(np.random.randint(low = 10,high=100,size=len(days)))

# use width_ratios to define the width of each subplot
# depending on the range we want to plot
f, (ax1, ax2, ax3) = plt.subplots(1, 3, sharey=True, facecolor='w',
                                  gridspec_kw={'width_ratios': (2, 1, 2)})

ax1.plot(days, values)
ax2.plot(days, values)
ax3.plot(days, values)

ax1.set_xlim(0,100)  # x-axis range limited to 0 - 100 
ax2.set_xlim(250, 300)  # x-axis range limited to 250 - 300
ax3.set_xlim(400, 500)  # x-axis range limited to 400 - 500

# hide the spines between ax and ax2
ax1.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax2.spines['right'].set_visible(False)
ax3.spines['left'].set_visible(False)

# Move right hand axes ticks to right hand side
ax3.yaxis.tick_right()

# Turn off ticks on middle axes; so we don't affect the other
# axes ticks, let's just set the length to 0 here
ax2.tick_params(axis='y', length=0)

# Draw the diagonal lines to show broken axes
d = 2.  # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(marker=[(-1, -d), (1, d)], markersize=12,
              linestyle="none", color='k', mec='k', mew=1, clip_on=False)
ax1.plot([1, 1], [0, 1], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 0], [0, 1], transform=ax2.transAxes, **kwargs)
ax2.plot([1, 1], [0, 1], transform=ax2.transAxes, **kwargs)
ax3.plot([0, 0], [0, 1], transform=ax3.transAxes, **kwargs)

plt.savefig('2brokenaxes.png')

enter image description here

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you! Just as I need!
@tmdavidson is it possible to include two of the above into a single output figure?
sure, you would just need to change to plt.subplots(2, 3,...) if you want them on top of each other or plt.subplots(1, 6, ...) if you want them next to each other. Then catch all 6 axes returned from subplots, and just repeat the logic above for the second set of 3

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.