13

I'd like to draw a (vertical) colorbar, which has two different scales (corresponding to two different units for the same quantity) on each side. Think Fahrenheit on one side and Celsius on the other side. Obviously, I'd need to specify the ticks for each side individually.

Any idea how I can do this?

andreas-h
  • 10,679
  • 18
  • 60
  • 78

4 Answers4

14

That should get you started:

import matplotlib.pyplot as plt
import numpy as np

# generate random data
x = np.random.randint(0,200,(10,10))
plt.pcolormesh(x)

# create the colorbar
# the aspect of the colorbar is set to 'equal', we have to set it to 'auto',
# otherwise twinx() will do weird stuff.
cbar = plt.colorbar()
pos = cbar.ax.get_position()
cbar.ax.set_aspect('auto')

# create a second axes instance and set the limits you need
ax2 = cbar.ax.twinx()
ax2.set_ylim([-2,1])

# resize the colorbar (otherwise it overlays the plot)
pos.x0 +=0.05
cbar.ax.set_position(pos)
ax2.set_position(pos)

plt.show()

enter image description here

hitzg
  • 12,133
  • 52
  • 54
  • You don't need to resize the colorbar, just set axisbelow to False: cbar.ax.set_axisbelow(False) # 1. axis ax2 = cbar.ax.twinx() ax2.set_ylim([low,high]) # low high for limts ax2.set_axisbelow(False) # 2. axis – Andreas Jul 17 '15 at 13:41
  • @Andreas I don't understand what you mean: [`set_axisbelow`](http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.set_axisbelow) has no influence on the size or position of the axes instance. – hitzg Jul 17 '15 at 13:51
  • Ahh, I see what you mean, I thought it was to offset the 2. axis - so just ignore this in your context, sorry for the confusion – Andreas Jul 21 '15 at 11:00
11

If you create a subplot for the colorbar, you can create a twin axes for that subplot and manipulate it like a normal axes.

import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-1,2.7)
X,Y = np.meshgrid(x,x)
Z = np.exp(-X**2-Y**2)*.9+0.1

fig, (ax, cax) = plt.subplots(ncols=2, gridspec_kw={"width_ratios":[15,1]})

im =ax.imshow(Z, vmin=0.1, vmax=1)
cbar = plt.colorbar(im, cax=cax)
cax2 = cax.twinx()

ticks=np.arange(0.1,1.1,0.1)
iticks=1./np.array([10,3,2,1.5,1])
cbar.set_ticks(ticks)
cbar.set_label("z")
cbar.ax.yaxis.set_label_position("left")
cax2.set_ylim(0.1,1)
cax2.set_yticks(iticks)
cax2.set_yticklabels(1./iticks)
cax2.set_ylabel("1/z")

plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
2

Note that in newer version of matplotlib, the above answers no long work (as @Ryan Skene pointed out). I'm using v3.3.2. The secondary_yaxis function works for the colorbars in the same way as for regular plot axes and gives one colorbar with two scales: https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.secondary_yaxis.html#matplotlib.axes.Axes.secondary_yaxis

import matplotlib.pyplot as plt
import numpy as np

# generate random data
x = np.random.randint(0,200,(10,10)) #let's assume these are temperatures in Fahrenheit
im = plt.imshow(x)

# create the colorbar
cbar = plt.colorbar(im,pad=0.1) #you may need to adjust this padding for the secondary colorbar label[enter image description here][1]
cbar.set_label('Temperature ($^\circ$F)')

# define functions that relate the two colorbar scales
# e.g., Celcius to Fahrenheit and vice versa
def F_to_C(x):
    return (x-32)*5/9
def C_to_F(x):
    return (x*9/5)+32

# create a second axes
cbar2 = cbar.ax.secondary_yaxis('left',functions=(F_to_C,C_to_F))
cbar2.set_ylabel('Temperatrue ($\circ$C)')

plt.show()
0

I am using an inset axis for my colorbar and, for some reason, I found the above to answers no longer worked as of v3.4.2. The twinx took up the entire original subplot.

So I just replicated the inset axis (instead of using twinx) and increased the zorder on the original inset.

axkws = dict(zorder=2)
cax = inset_axes(
    ax, width="100%", height="100%", bbox_to_anchor=bbox, 
    bbox_transform=ax.transAxes, axes_kwargs=axkws
)
cbar = self.fig.colorbar(mpl.cm.ScalarMappable(cmap=cmap), cax=cax)
cbar.ax.yaxis.set_ticks_position('left')
    
caxx = inset_axes(
    ax, width="100%", height="100%", 
    bbox_to_anchor=bbox, bbox_transform=ax.transAxes
)
caxx.yaxis.set_ticks_position('right')
Ryan Skene
  • 864
  • 1
  • 12
  • 29