93

I have the following problem, I want to create my own colormap (red-mix-violet-mix-blue) that maps to values between -2 and +2 and want to use it to color points in my plot. The plot should then have the colorscale to the right.

That is how I create the map so far. But I am not really sure if it mixes the colors.

cmap = matplotlib.colors.ListedColormap(["red","violet","blue"], name='from_list', N=None)
m = cm.ScalarMappable(norm=norm, cmap=cmap)


That way I map the colors to the values.

colors = itertools.cycle([m.to_rgba(1.22), ..])


Then I plot it:

for i in range(0, len(array_dg)):
  plt.plot(array_dg[i], markers.next(),alpha=alpha[i], c=colors.next())


My problems are:
1. I can't plot the color scale.
2. I am not completely sure if my scale is creating a continues (smooth) colorscale.

Trollbrot
  • 1,291
  • 2
  • 12
  • 16
  • Could you clarify your question a bit? For example, `c=` specifies the line color, while you are talking about points. You can only specify one `markerfacecolor`, scatter might be a better option if you really want points. And indeed `ListedColormap` is listed, not continuous, see `LinearSegmentedColormap`. – Rutger Kassies May 30 '13 at 11:44
  • That is strange, it is supposed to be points and it looks like points. – Trollbrot May 30 '13 at 11:47
  • You can off course, but thats what you should clarify. We cant see what plot style you are using. If you use `plt.plot(values, 'o')`, you will plot only markers and no line, but the markers will have one fixed color which doesnt (and cant) vary by the value. – Rutger Kassies May 30 '13 at 12:25

4 Answers4

122

Since the methods used in other answers seems quite complicated for such easy task, here is a new answer:

Instead of a ListedColormap, which produces a discrete colormap, you may use a LinearSegmentedColormap. This can easily be created from a list using the from_list method.

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

x,y,c = zip(*np.random.rand(30,3)*4-2)

norm=plt.Normalize(-2,2)
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["red","violet","blue"])

plt.scatter(x,y,c=c, cmap=cmap, norm=norm)
plt.colorbar()
plt.show()

enter image description here


More generally, if you have a list of values (e.g. [-2., -1, 2]) and corresponding colors, (e.g. ["red","violet","blue"]), such that the nth value should correspond to the nth color, you can normalize the values and supply them as tuples to the from_list method.

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

x,y,c = zip(*np.random.rand(30,3)*4-2)

cvals  = [-2., -1, 2]
colors = ["red","violet","blue"]

norm=plt.Normalize(min(cvals),max(cvals))
tuples = list(zip(map(norm,cvals), colors))
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", tuples)

plt.scatter(x,y,c=c, cmap=cmap, norm=norm)
plt.colorbar()
plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • 1
    How would you now pass an own defined range to this, e.g. that `red` corresponds to `-5`, `violet` to `1` and `blue` to `100`? I would very much appreciate if you could look at the question I asked [here](https://stackoverflow.com/questions/47697782/how-to-create-a-customized-colormap-and-use-it-for-different-ranges-of-data). – Cleb Dec 07 '17 at 15:10
  • 2
    Using the `vmin` and `vmax` or the `norm` argument of the respective plotting method. – ImportanceOfBeingErnest Dec 07 '17 at 16:15
  • 3
    This might not be as flexible as the complete custom map in the accepted answer, but that is crazily complicated and this answer is exactly what most people need when they want a custom colormap I think. – BjornW Apr 04 '18 at 09:43
  • I don't think there is any drawback concerning flexibility. In fact, I would go as far as if someone finds a case of a colormap which cannot be created via `.from_list` instead of from a dictionary, please notify me and I will prove that not to be true. – ImportanceOfBeingErnest Apr 04 '18 at 09:54
  • How about a colormap that goes from dark to light red from greater to lesser negatives, white for exactly equal to zero, increasing to yellow at 0.5, then light blue starting from 0.5 getting deep blue for greater positive numbers? How can that be done with the `.from_list` method? – Aaron Bramson Jun 08 '18 at 08:23
  • @AaronBramson Yes sure, I don't see the issue with that. If it doesn't work for you you may ask a question about it where you show your failed attempts. – ImportanceOfBeingErnest Jun 08 '18 at 08:34
  • Well, half of my point (setting where the colors change) is answered [here](https://stackoverflow.com/questions/38147997/how-to-change-a-linearsegmentedcolormap-to-a-different-distribution-of-color), but it seems you have to specify them on a normalized [0,1] range. The OP wants to specify the color changes on specific values in the range [-2,2] (or whatever). How is that rescaling part done with `.from_list`? Does it have to be done manually, or is there an option to use non-normalized values. – Aaron Bramson Jun 08 '18 at 10:50
  • Good point, I did not answer the [-2,2] part here; so I updated the answer. Apart, every colormap in matplotlib ranges between 0 and 1. The normalization is not part of the colormap, but the `Normalize` or `*Norm` instance in use. – ImportanceOfBeingErnest Jun 08 '18 at 10:59
  • @ImportanceOfBeingErnest it's not at all clear to me how one would create a colourmap that does is not divided equally using your method. For example, 0.0-0.1 being red-violet, 0.1-0.5 being violet-blue, and 0.5-1.0 being blue-green. – Notso Feb 05 '19 at 13:15
  • 1
    @Notso `LinearSegmentedColormap.from_list("", [(0,"red"), (.1,"violet"), (.5, "blue"), (1.0, "green")])`. I updated the answer to hopefully make this clearer. – ImportanceOfBeingErnest Feb 05 '19 at 13:37
  • @ImportanceOfBeingErnest: Can you add alpha value for transparency with "from_list"? – aerijman Apr 01 '19 at 20:43
  • @aerijman Yes, but you will need to supply the colors as RGBA tuples, like `(1.0, 0, 0, 0.5)` for half transparent red. – ImportanceOfBeingErnest Apr 01 '19 at 20:49
  • Is there a way to specify the ranges of the individual colors in your colormap more directly than passing in the colorvalues. For example, if I used: cvals = [-12., 0, 12] colors = ["red", "white", "blue"] This produces an evenly spaced distribution of the 3 colors going from red, white to blue. Is there a way to decrease the amount of the distribution that the white takes up, or increase red or blue specifically? – Shaun Lowis Dec 05 '19 at 23:21
  • 1
    @ShaunLowis In this specific case you can just interpolate the RGB values yourself. More generally, you may create a colormap first and take the colors of the map, at non-equidistant steps. E.g. if `cmap` is the original colormap, then `cvals=[-12, -2,0,2,12]; colors = cmap(plt.Normalize(-12,12)(np.array(cvals)))` will give you the new colors for the squeezed map. – ImportanceOfBeingErnest Dec 05 '19 at 23:58
95

There is an illustrative example of how to create custom colormaps here. The docstring is essential for understanding the meaning of cdict. Once you get that under your belt, you might use a cdict like this:

cdict = {'red':   ((0.0, 1.0, 1.0), 
                   (0.1, 1.0, 1.0),  # red 
                   (0.4, 1.0, 1.0),  # violet
                   (1.0, 0.0, 0.0)), # blue

         'green': ((0.0, 0.0, 0.0),
                   (1.0, 0.0, 0.0)),

         'blue':  ((0.0, 0.0, 0.0),
                   (0.1, 0.0, 0.0),  # red
                   (0.4, 1.0, 1.0),  # violet
                   (1.0, 1.0, 0.0))  # blue
          }

Although the cdict format gives you a lot of flexibility, I find for simple gradients its format is rather unintuitive. Here is a utility function to help generate simple LinearSegmentedColormaps:

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


def make_colormap(seq):
    """Return a LinearSegmentedColormap
    seq: a sequence of floats and RGB-tuples. The floats should be increasing
    and in the interval (0,1).
    """
    seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
    cdict = {'red': [], 'green': [], 'blue': []}
    for i, item in enumerate(seq):
        if isinstance(item, float):
            r1, g1, b1 = seq[i - 1]
            r2, g2, b2 = seq[i + 1]
            cdict['red'].append([item, r1, r2])
            cdict['green'].append([item, g1, g2])
            cdict['blue'].append([item, b1, b2])
    return mcolors.LinearSegmentedColormap('CustomMap', cdict)


c = mcolors.ColorConverter().to_rgb
rvb = make_colormap(
    [c('red'), c('violet'), 0.33, c('violet'), c('blue'), 0.66, c('blue')])
N = 1000
array_dg = np.random.uniform(0, 10, size=(N, 2))
colors = np.random.uniform(-2, 2, size=(N,))
plt.scatter(array_dg[:, 0], array_dg[:, 1], c=colors, cmap=rvb)
plt.colorbar()
plt.show()

enter image description here


By the way, the for-loop

for i in range(0, len(array_dg)):
  plt.plot(array_dg[i], markers.next(),alpha=alpha[i], c=colors.next())

plots one point for every call to plt.plot. This will work for a small number of points, but will become extremely slow for many points. plt.plot can only draw in one color, but plt.scatter can assign a different color to each dot. Thus, plt.scatter is the way to go.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Now I got a problem. I also would like to get a different marker symbol according to the color (I have 13 different colors). But the scatter plot allows only one marker per plot, or do I miss something? – Trollbrot Jun 03 '13 at 14:41
  • In that case you will need to call `plt.scatter` (or `plt.plot`) once for each color/marker combination. – unutbu Jun 03 '13 at 16:34
  • Why can't I use a color map created with this awesome function in plt.set_cmap()? The error is very long, the last line is ValueError: Colormap CustomMap is not recognized. – Phlya Jan 25 '14 at 10:47
  • 3
    @Ilya: First register the colormap: `plt.register_cmap(name=rvb.name, cmap=rvb)` and then call `plt.set_cmap(rvb)`. – unutbu Jan 25 '14 at 11:00
  • any suggestions on how to use this with plt.cm? I was hoping to color my scatterplot by the ratio of `x` to `y`. I was previously using `color = plt.cm.cool(x/y`) and `ax.scatter(x,y,c=colors)` – As3adTintin Oct 03 '14 at 16:25
  • 1
    @As3adTintin: `rvb` above is a full-fledge Colormap, just like `plt.cm.cool`. So they are fungible: `color = rvb(x/y)`. – unutbu Oct 03 '14 at 17:51
  • @unutbu oh that was right there... anywho, thanks so much for answering! – As3adTintin Oct 03 '14 at 18:31
14

If you want to automate the creating of a custom divergent colormap commonly used for surface plots, this module combined with @unutbu method worked well for me.

def diverge_map(high=(0.565, 0.392, 0.173), low=(0.094, 0.310, 0.635)):
    '''
    low and high are colors that will be used for the two
    ends of the spectrum. they can be either color strings
    or rgb color tuples
    '''
    c = mcolors.ColorConverter().to_rgb
    if isinstance(low, basestring): low = c(low)
    if isinstance(high, basestring): high = c(high)
    return make_colormap([low, c('white'), 0.5, c('white'), high])

The high and low values can be either string color names or rgb tuples. This is the result using the surface plot demo: enter image description here

hitzg
  • 12,133
  • 52
  • 54
Steven C. Howell
  • 16,902
  • 15
  • 72
  • 97
4

This seems to work for me.

def make_Ramp( ramp_colors ): 
    from colour import Color
    from matplotlib.colors import LinearSegmentedColormap

    color_ramp = LinearSegmentedColormap.from_list( 'my_list', [ Color( c1 ).rgb for c1 in ramp_colors ] )
    plt.figure( figsize = (15,3))
    plt.imshow( [list(np.arange(0, len( ramp_colors ) , 0.1)) ] , interpolation='nearest', origin='lower', cmap= color_ramp )
    plt.xticks([])
    plt.yticks([])
    return color_ramp

custom_ramp = make_Ramp( ['#754a28','#893584','#68ad45','#0080a5' ] ) 

custom color ramp

Can Sucuoglu
  • 185
  • 1
  • 8