volc_nerd
volc_nerd

Reputation: 235

Matplotlib multiple broken axis

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.

Upvotes: 0

Views: 2832

Answers (1)

tmdavison
tmdavison

Reputation: 69116

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

Upvotes: 3

Related Questions