Correcting matplotlib colorbar ticks
Solution 1
You are suffering from an off-by-one error. You have 10 ticklabels spread among 11 colors. You might be able to correct the error by using np.linspace
instead of np.arange
. Using np.linspace
the third argument is the number of values desired. This reduces the amount of mental gymnastics needed to avoid the off-by-one error:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.cm as cm
import matplotlib.colors as mcolors
def colorbar_index(ncolors, cmap):
cmap = cmap_discretize(cmap, ncolors)
mappable = cm.ScalarMappable(cmap=cmap)
mappable.set_array([])
mappable.set_clim(-0.5, ncolors+0.5)
colorbar = plt.colorbar(mappable)
colorbar.set_ticks(np.linspace(0, ncolors, ncolors))
colorbar.set_ticklabels(range(ncolors))
def cmap_discretize(cmap, N):
"""Return a discrete colormap from the continuous colormap cmap.
cmap: colormap instance, eg. cm.jet.
N: number of colors.
Example
x = resize(arange(100), (5,100))
djet = cmap_discretize(cm.jet, 5)
imshow(x, cmap=djet)
"""
if type(cmap) == str:
cmap = plt.get_cmap(cmap)
colors_i = np.concatenate((np.linspace(0, 1., N), (0.,0.,0.,0.)))
colors_rgba = cmap(colors_i)
indices = np.linspace(0, 1., N+1)
cdict = {}
for ki,key in enumerate(('red','green','blue')):
cdict[key] = [ (indices[i], colors_rgba[i-1,ki], colors_rgba[i,ki])
for i in xrange(N+1) ]
# Return colormap object.
return mcolors.LinearSegmentedColormap(cmap.name + "_%d"%N, cdict, 1024)
fig, ax = plt.subplots()
A = np.random.random((10,10))*10
cmap = plt.get_cmap('YlGnBu')
ax.imshow(A, interpolation='nearest', cmap=cmap)
colorbar_index(ncolors=11, cmap=cmap)
plt.show()
Solution 2
You can control the placement and the labels by hand. I'll start with a linear cmap generated from cmap_discretize
on the page you linked:
import numpy as np
import pylab as plt
# The number of divisions of the cmap we have
k = 10
# Random test data
A = np.random.random((10,10))*k
c = cmap_discretize('jet', k)
# First show without
plt.subplot(121)
plt.imshow(A,interpolation='nearest',cmap=c)
plt.colorbar()
# Now label properly
plt.subplot(122)
plt.imshow(A,interpolation='nearest',cmap=c)
cb = plt.colorbar()
labels = np.arange(0,k,1)
loc = labels + .5
cb.set_ticks(loc)
cb.set_ticklabels(labels)
plt.show()
Related videos on Youtube
urschrei
Updated on July 09, 2022Comments
-
urschrei almost 2 years
I've placed a color bar alongside a choropleth map. Because the data being plotted are discrete rather than continuous values, I've used a LinearSegmentedColormap (using the recipe from the scipy cookbook), which I've initialised with my max counted value + 1, in order to show a colour for 0. However, I now have two problems:
-
The tick labels are incorrectly spaced (except for 5, more or less) – they should be located in the middle of the colour they identify; i.e. 0 - 4 should be moved up, and 6 - 10 should be moved down.
-
If I initialise the colorbar with
drawedges=True
, so that I can style itsdividers
properties, I get this:
I'm creating my colormap and colorbar like so:
cbmin, cbmax = min(counts), max(counts) # this normalises the counts to a 0,1 interval counts /= np.max(np.abs(counts), axis=0) # density is a discrete number, so we have to use a discrete color ramp/bar cm = cmap_discretize(plt.get_cmap('YlGnBu'), int(cbmax) + 1) mappable = plt.cm.ScalarMappable(cmap=cm) mappable.set_array(counts) # set min and max values for the colour bar ticks mappable.set_clim(cbmin, cbmax) pc = PatchCollection(patches, match_original=True) # impose our colour map onto the patch collection pc.set_facecolor(cm(counts)) ax.add_collection(pc,) cb = plt.colorbar(mappable, drawedges=True)
So I'm wondering whether my converting the counts to a 0,1 interval is one of the problems.
Update :
Having tried what Hooked suggested, the 0-value is correct, but subsequent values are set progressively higher, to the the point where 9 is where 10 should be:
Here's the code I used:
cb = plt.colorbar(mappable) labels = np.arange(0, int(cbmax) + 1, 1) loc = labels + .5 cb.set_ticks(loc) cb.set_ticklabels(labels)
And just to confirm,
labels
definitely has the correct values:In [3]: np.arange(0, int(cbmax) + 1, 1) Out[3]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
-
Hooked over 10 yearsIt seems like the problem must lie with the colorbar you essentially create "by hand" using a PatchCollection. Is there any reason not to use a linear discrete colormap like the scipy page you linked?
-
urschrei over 10 yearsYou mean create the colormap, pass it PatchCollection as a
cmap
argument, and thenpc.set_array
to the counts? -
Hooked over 10 yearsNo I mean use
cmap_discretize
as on the page wiki.scipy.org/Cookbook/Matplotlib/ColormapTransformations . This is how I generated my example which doesn't suffer from the strange placement.
-
-
urschrei over 10 yearsHmm, it's still not correct, but in a different way. See edit.