6

I am trying to use a colorbar to label discrete, coded values plotted using imshow. I can achieve the colorbar that I want using the boundaries and values keywords, which makes the maximum value of the colorbar effectively 1 greater than the maximum value of the data being plotted.

Now I want ticks to be in the middle of each color range in the colorbar, but cannot specify a tick position for the largest color block in the colorbar, seemingly because it is outside of the data value limits.

Here's a quick block of code to demonstrate the problem:

data = np.tile(np.arange(4), 2)
fig = plt.figure()
ax = fig.add_subplot(121)
ax.imshow(data[None], aspect='auto')
cax = fig.add_subplot(122)
cbar = fig.colorbar(ax.images[0], cax=cax, boundaries=[0,1,2,3,4], values=[0,1,2,3])
cbar.set_ticks([.5, 1.5, 2.5, 3.5])
cbar.set_ticklabels(['one', 'two', 'three', 'four'])

Note the missing tick where 'four' should be. What's the right way to do this?

amcmorl
  • 131
  • 1
  • 5
  • I have since hacked a solution by not using Figure.colorbar, instead plotting `range(max(data) + 1)` in the cax axes, and then setting the axis ticks and labels as required. It would, however, be nice if there was a clean way to do this using colorbar. – amcmorl Sep 24 '12 at 20:01

2 Answers2

7

To summarize, this works for me:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import cm
from matplotlib import colors

data = np.tile(np.arange(4), 2)
fig = plt.figure()
ax = fig.add_subplot(121)
cmap = cm.get_cmap('jet', 4)
bounds = np.arange(5)
vals = bounds[:-1]
norm = colors.BoundaryNorm(bounds, cmap.N)
ax.imshow(data[None], aspect='auto', interpolation='nearest', cmap=cmap, norm=norm)

cax = fig.add_subplot(122)
cbar = fig.colorbar(ax.images[0], cax=cax, boundaries=bounds, values=vals)
cbar.set_ticks(vals + .5)
cbar.set_ticklabels(['one', 'two', 'three', 'four'])

The solution was to specify the colormap explicitly for the image using get_cmap and bounded by BoundaryNorm. Then specifying the tick positions just works. The resulting plot is:

discrete colorbar example

amcmorl
  • 131
  • 1
  • 5
3

You are not using the same colormap in imshow and cbar. As your data and cbar is defined in the same way (same limits etc.) so you do not realize the inconsistency in the above example. You should define the colormap first.

Let's say you want to divide your data into 4-discrete colors, then you can use

import numpy as np
import pylab as plt
from matplotlib import colors, cm

data = np.tile(np.arange(4), 2)
fig = plt.figure()
ax = fig.add_subplot(121)
cax = fig.add_subplot(122)
cmap = cm.get_cmap('jet', 4) # 4 discrete color
im=ax.imshow(data[None], aspect='auto',cmap=cmap)
cbar = fig.colorbar(ax.images[0], cax=cax, cmap=cmap)
plt.show()

enter image description here

You can now put the ticks according to your need.

In case you want to define the bounds as well as the colors in these bounds then you can use ListedColormap as follows:

data = np.tile(np.arange(4), 2)
fig = plt.figure()
ax = fig.add_subplot(121)
cax = fig.add_subplot(122)
cmap = colors.ListedColormap(['b','g','y','r'])
bounds=[0,1,2,3,4]
norm = colors.BoundaryNorm(bounds, cmap.N)
im=ax.imshow(data[None], aspect='auto',cmap=cmap, norm=norm)
cbar = fig.colorbar(im, cax=cax, cmap=cmap, norm=norm, boundaries=bounds, ticks=[0.5,1.5,2.5,3.5],)
plt.show()

enter image description here

imsc
  • 7,492
  • 7
  • 47
  • 69
  • Actually, this is not quite correct: by default the colorbar does take the colormap of the mappable (in this case the image in `ax`; look in matplotlib's colorbar.py:Colobar.__init__), so specifying the colormap in the call to colorbar is not necessary. Specifying the truncated cmap using `get_cmap`, and applying the `BoundaryNorm` normalization did fix the problem. Thanks for those pointers. – amcmorl Sep 25 '12 at 13:25
  • Of course the default `colormap` is `jet`. What I meant by defining the `colormap` was to put the `bounds` so that limits in both plots are same - essentially what you are doing now. – imsc Sep 25 '12 at 15:16