Working with colours in matplotlib

2013

June

3

Tags: matplotlib (4), Python (10)

When dealing with colours in scientific visualisations some people like to have a colourmap which can be indexed into to pick specific colours. Whilst this isn't necessarily the best way of handling colours in matplotlib, it certainly adds a degree of familiarity to users who have come over from other visualisation tools, such as IDL.

In this article I'll cover one approach to using the colour-by-index paradigm in matplotlib.

I've created a text file containing byte values for each of the RGB channels. I actually already had this as a numpy array, so wrote it out with:

np.savetxt('custom_colors.txt', colors_array, fmt='%03i', delimiter=', ')

In [1]:
with open('custom_colors.txt') as fh:
    print ''.join(fh.readlines())
# R,   G,   B
255, 255, 255
000, 000, 000
250, 060, 060
000, 220, 000
030, 060, 255
000, 200, 200
240, 000, 130
230, 220, 050
240, 130, 040
160, 000, 200
160, 230, 050
000, 160, 255
230, 175, 045
# This is a comment...
000, 173, 208
135, 136, 000
185, 219, 014
215, 169, 000
119, 074, 057
156, 162, 153
237, 041, 057
255, 255, 255

In [2]:
import numpy as np

colors = np.loadtxt('custom_colors.txt', delimiter=', ')

# convert to RGB values in the [0, 1] interval
colors = colors / 255.

print colors.shape
(21, 3)
In [3]:
import matplotlib.colors as mcolors

my_cmap = mcolors.ListedColormap(colors)

plt.figure(figsize=(20, 0.5))
plt.title('My own color map*')
plt.pcolormesh(np.arange(my_cmap.N).reshape(1, -1), cmap=my_cmap)
plt.gca().yaxis.set_visible(False)
plt.gca().set_xlim(0, my_cmap.N)
plt.show()

* Actually, I borrowed this colormap from Bernd Becker (https://groups.google.com/forum/?fromgroups#!topic/scitools-iris/hsb6ExNgSss).

Now that I have a cmap instance I can access specific colors by their index, using the call notation on the colormap:

In [4]:
plt.fill([0, 0, 0.5, 0.5], [0, 1, 1, 0], color=my_cmap(2))
plt.fill([0.5, 0.5, 1, 1], [0, 1, 1, 0], color=my_cmap(3))
plt.show()

It is possible to get all of the RGB values themselves fairly easily:

In [5]:
my_cmap(np.arange(my_cmap.N))
Out[5]:
array([[ 1.        ,  1.        ,  1.        ,  1.        ],
       [ 0.        ,  0.        ,  0.        ,  1.        ],
       [ 0.98039216,  0.23529412,  0.23529412,  1.        ],
       [ 0.        ,  0.8627451 ,  0.        ,  1.        ],
       [ 0.11764706,  0.23529412,  1.        ,  1.        ],
       [ 0.        ,  0.78431373,  0.78431373,  1.        ],
       [ 0.94117647,  0.        ,  0.50980392,  1.        ],
       [ 0.90196078,  0.8627451 ,  0.19607843,  1.        ],
       [ 0.94117647,  0.50980392,  0.15686275,  1.        ],
       [ 0.62745098,  0.        ,  0.78431373,  1.        ],
       [ 0.62745098,  0.90196078,  0.19607843,  1.        ],
       [ 0.        ,  0.62745098,  1.        ,  1.        ],
       [ 0.90196078,  0.68627451,  0.17647059,  1.        ],
       [ 0.        ,  0.67843137,  0.81568627,  1.        ],
       [ 0.52941176,  0.53333333,  0.        ,  1.        ],
       [ 0.7254902 ,  0.85882353,  0.05490196,  1.        ],
       [ 0.84313725,  0.6627451 ,  0.        ,  1.        ],
       [ 0.46666667,  0.29019608,  0.22352941,  1.        ],
       [ 0.61176471,  0.63529412,  0.6       ,  1.        ],
       [ 0.92941176,  0.16078431,  0.22352941,  1.        ],
       [ 1.        ,  1.        ,  1.        ,  1.        ]])

To visualize specific colors using pcolormesh, we need to avoid using a normalization of the data by using matplotlib.colors.NoNorm() and need a 2d array of colors

In [6]:
colors = [4, 11, 0, 2, 17]
plt.pcolormesh(np.array(colors, ndmin=2), norm=mcolors.NoNorm(), cmap=my_cmap)
plt.show()

Using a recent change applied to matplotlib (available in matplotlib v1.3 after https://github.com/matplotlib/matplotlib/pull/2002) it is possible to pick specific colours to go with levels when contouring with an extend argument:

In [7]:
import cartopy.crs as ccrs
import iris
import iris.quickplot as qplt


fname = iris.sample_data_path('air_temp.pp')
temperature = iris.load_cube(fname)

colors = [4, 11, 0, 2, 17]
levels = [263, 273, 288, 298]

plt.figure(figsize=(15, 10))
ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=-180))

cs = qplt.contourf(temperature, colors=my_cmap(colors), levels=levels, extend='both')

# Get hold of the colorbar instance and update its extendfrac length
cbar = cs.colorbar[0]
cbar.extendfrac = 0.15
cs.changed()

ax.coastlines()
plt.show()

This article certainly shows a way of handling the colour-by-index paradigm, though it must be said that handling colours like this in matplotlib is not necessarily the best approach - I'll leave that to a future article.

Find this useful? How do you handle colours in your matplotlib figures? Is there a killer feature you think matplotlib is missing out on? Let me know via the comments section.