1

I am trying to port some plotting code from gnuplot to matplotlib and am struggling with porting a discontinuous color map that is specified by color names. Any suggestions on how to do this in matplotlib?

# Establish a 3-section color palette with lower 1/4 in the blues, 
# and middle 1/2 light green to yellow, and top 1/4 reds
set palette defined (0 'dark-blue', 0.5 'light-blue', \\
                     0.5 'light-green', 1 'green', 1.5 'yellow', \\
                     1.5 'red', 2 'dark-red')
# Establish that the palette range, such that the middle green range corresponds
# to 0.95 to 1.05
set cbrange [0.9:1.1]

Desired colorbar

1

2 Answers 2

4

I've used this script for years, can't really remember how or where I got it (edit: after some searching, this seems to be the source, but it requires some minor changes for Python3), but it has helped me a lot in quickly creating custom color maps. It allows you to simply specify a dictionary with locations (0..1) and colors, and creates a linear color map out of that; e.g. make_colormap({0:'w',1:'k'}) creates a linear color map going from white to black.

import numpy as np
import matplotlib.pylab as pl

def make_colormap(colors):
    from matplotlib.colors import LinearSegmentedColormap, ColorConverter
    from numpy import sort

    z  = np.array(sorted(colors.keys()))
    n  = len(z)
    z1 = min(z)
    zn = max(z)
    x0 = (z - z1) / (zn - z1)

    CC = ColorConverter()
    R = []
    G = []
    B = []
    for i in range(n):
        Ci = colors[z[i]]      
        if type(Ci) == str:
            RGB = CC.to_rgb(Ci)
        else:
            RGB = Ci
        R.append(RGB[0])
        G.append(RGB[1])
        B.append(RGB[2])

    cmap_dict = {}
    cmap_dict['red']   = [(x0[i],R[i],R[i]) for i in range(len(R))]
    cmap_dict['green'] = [(x0[i],G[i],G[i]) for i in range(len(G))]
    cmap_dict['blue']  = [(x0[i],B[i],B[i]) for i in range(len(B))]
    mymap = LinearSegmentedColormap('mymap',cmap_dict)
    return mymap

test1 = make_colormap({0.:'#40004b',0.5:'#ffffff',1.:'#00441b'})
test2 = make_colormap({0.:'b',0.25:'w',0.251:'g',0.75:'y',0.751:'r',1:'k'})

data = np.random.random((10,10))

pl.figure()
pl.subplot(121)
pl.imshow(data, interpolation='nearest', cmap=test1) 
pl.colorbar()

pl.subplot(122)
pl.imshow(data, interpolation='nearest', cmap=test2) 
pl.colorbar()

enter image description here

Sign up to request clarification or add additional context in comments.

3 Comments

Very nice. The function helps hide the intricacies of specifying the color channels independently. Is it possible to scale the active range to apply from 0.9 to 1.1 rather than 0 to 1? And would it work to specify duplicate values as in make_colormap({0:'w',0.5:'k', 0.5:'r', 1:'b'})?
For the range 0.9--1.1, see Toms answer (the same applies here). Duplicate values don't work at the moment (I just tested); I would use the pragmatic approach and simply use ({0:'w',0.5:'k', 0.500000000001:'r', 1:'b'}), but you could change the function and do this automatically for duplicate values.
Ah, makes sense. Too bad nothing like this is built-in to matplotlib.The native per channel color map syntax is non-intuitive.
2

Bart's function is very nice. However, if you want to make the colormap yourself, you can define a colormap like this using a dictionary in the way it is done in the custom_cmap example from the mpl website.

Here's an example that's pretty close to your colormap:

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

cdict = {'red':   ((0.0,  0.0, 0.0),   # From 0 to 0.25, we fade the red and green channels
                   (0.25, 0.5, 0.5),   # up a little, to make the blue a bit more grey

                   (0.25, 0.0, 0.0),   # From 0.25 to 0.75, we fade red from 0.5 to 1
                   (0.75, 1.0, 1.0),   # to fade from green to yellow

                   (1.0,  0.5, 0.5)),  # From 0.75 to 1.0, we bring the red down from 1
                                       # to 0.5, to go from bright to dark red

         'green': ((0.0,  0.0, 0.0),   # From 0 to 0.25, we fade the red and green channels
                   (0.25, 0.6, 0.6),   # up a little, to make the blue a bit more grey

                   (0.25, 1.0, 1.0),   # Green is 1 from 0.25 to 0.75 (we add red 
                   (0.75, 1.0, 1.0),   # to turn it from green to yellow)

                   (0.75, 0.0, 0.0),   # No green needed in the red upper quarter
                   (1.0,  0.0, 0.0)),

         'blue':  ((0.0,  0.9, 0.9),   # Keep blue at 0.9 from 0 to 0.25, and adjust its
                   (0.25, 0.9, 0.9),   # tone using the green and red channels

                   (0.25, 0.0, 0.0),   # No blue needed above 0.25
                   (1.0,  0.0, 0.0))

             }

cmap = colors.LinearSegmentedColormap('BuGnYlRd',cdict)

data = 0.9 + (np.random.rand(8,8) * 0.2)  # Data in range 0.9 to 1.1

p=plt.imshow(data,interpolation='nearest',cmap=cmap,vmin=0.9,vmax=1.1)

plt.colorbar(p)

plt.show()

enter image description here

3 Comments

This is indeed very close. Is it possible to scale this active range to apply from 0.9 to 1.1 rather than 0 to 1?
Yep, that's easy enough, the colormap doesn't need to change. You can just change the range of the data and the vmin and vmax for the imshow. I'll edit the answer to show you
Perfect, and nice job explaining the rather tricky per color channel color map syntax.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.