85

I am plotting 20 different lines on a single plot using matplotlib. I use a for loop for plotting and label every line with its key and then use the legend function

for key in dict.keys():
    plot(x,dict[key], label = key)
graph.legend()

But using this way, the graph repeats a lot of colors in the legend. Is there any way to ensure a unique color is assigned to each line using matplotlib and over 20 lines?

thanks

Don Kirkby
  • 53,582
  • 27
  • 205
  • 286
msohail
  • 1,085
  • 2
  • 13
  • 14
  • It happens that the legend has nothing to do with the colors. There would be repeats in the color regardless of whether you had a legend or not. – Yann Dec 05 '11 at 20:30
  • 12
    It's pretty mad to me that `matplotlib` by default re-uses colors so easily – Chris_Rands Nov 02 '18 at 15:54

4 Answers4

144

The answer to your question is related to two other SO questions.

The answer to How to pick a new color for each plotted line within a figure in matplotlib? explains how to define the default list of colors that is cycled through to pick the next color to plot. This is done with the Axes.set_color_cycle method.

You want to get the correct list of colors though, and this is most easily done using a color map, as is explained in the answer to this question: Create a color generator from given colormap in matplotlib. There a color map takes a value from 0 to 1 and returns a color.

So for your 20 lines, you want to cycle from 0 to 1 in steps of 1/20. Specifically you want to cycle form 0 to 19/20, because 1 maps back to 0.

This is done in this example:

import matplotlib.pyplot as plt
import numpy as np

NUM_COLORS = 20

cm = plt.get_cmap('gist_rainbow')
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_prop_cycle(color=[cm(1.*i/NUM_COLORS) for i in range(NUM_COLORS)])
for i in range(NUM_COLORS):
    ax.plot(np.arange(10)*(i+1))

fig.savefig('moreColors.png')
plt.show()

This is the resulting figure:

Yosemitebear Mountain Giant Double Rainbow 1-8-10

Alternative, better (debatable) solution

There is an alternative way that uses a ScalarMappable object to convert a range of values to colors. The advantage of this method is that you can use a non-linear Normalization to convert from line index to actual color. The following code produces the same exact result:

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

NUM_COLORS = 20

cm = plt.get_cmap('gist_rainbow')
cNorm  = colors.Normalize(vmin=0, vmax=NUM_COLORS-1)
scalarMap = mplcm.ScalarMappable(norm=cNorm, cmap=cm)
fig = plt.figure()
ax = fig.add_subplot(111)
# old way:
#ax.set_prop_cycle(color=[cm(1.*i/NUM_COLORS) for i in range(NUM_COLORS)])
# new way:
ax.set_prop_cycle(color=[scalarMap.to_rgba(i) for i in range(NUM_COLORS)])
for i in range(NUM_COLORS):
    ax.plot(np.arange(10)*(i+1))

fig.savefig('moreColors.png')
plt.show()
beyarkay
  • 513
  • 3
  • 16
Yann
  • 33,811
  • 9
  • 79
  • 70
  • nice. btw, what does 'color' do in your for loop? i deleted its declaration in the loop and the code seemed to run fine... – Quetzalcoatl May 18 '15 at 21:42
  • 17
    `ax.set_color_map()` is deprecated in matplotlib v1.5. Use `ax.set_prop_cycle(color=[cm...])` instead. – blokeley Mar 21 '16 at 12:21
  • 3
    A list of the available color maps is here: http://matplotlib.org/examples/color/colormaps_reference.html – blokeley Mar 21 '16 at 12:22
  • Im having a lot of trouble applying this color scheme to my own code that I posted here: https://stackoverflow.com/questions/47775914/need-more-colors-from-matplotlib. It was deemed a duplicate of this post, rightly so, but I am unable to make the answers here work for me. –  Dec 12 '17 at 16:24
  • This is one of the most appealing answers I've ever seen on Stackoverflow – ColinMac Mar 29 '19 at 20:14
  • UPDATE: the above answer is dated. See https://stackoverflow.com/a/57421483/1167475 – mortonjt May 13 '20 at 22:13
29

I had a plot with 12 lines, and I found it hard to distinguish lines with similar colours when I tried Yann's technique. My lines also appeared in pairs, so I used the same colour for the two lines in each pair, and used two different line widths. You could also vary the line style to get more combinations.

You could use set_prop_cycle(), but I just modified the line objects after calling plot().

Here is Yann's example with three different line widths:

import matplotlib.pyplot as plt
import numpy as np

NUM_COLORS = 20

cm = plt.get_cmap('gist_rainbow')
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(NUM_COLORS):
    lines = ax.plot(np.arange(10)*(i+1))
    lines[0].set_color(cm(i//3*3.0/NUM_COLORS))
    lines[0].set_linewidth(i%3 + 1)

fig.savefig('moreColors.png')
plt.show()

Example plot with line widths

Here's the same example with different line styles. Of course you could combine the two if you wanted.

import matplotlib.pyplot as plt
import numpy as np

NUM_COLORS = 20
LINE_STYLES = ['solid', 'dashed', 'dashdot', 'dotted']
NUM_STYLES = len(LINE_STYLES)

cm = plt.get_cmap('gist_rainbow')
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(NUM_COLORS):
    lines = ax.plot(np.arange(10)*(i+1))
    lines[0].set_color(cm(i//NUM_STYLES*float(NUM_STYLES)/NUM_COLORS))
    lines[0].set_linestyle(LINE_STYLES[i%NUM_STYLES])

fig.savefig('moreColors.png')
plt.show()

Example plot with line styles

Don Kirkby
  • 53,582
  • 27
  • 205
  • 286
  • Wouldn't it be nicer to cycle through different line styles (dashed, dotted, double dashed, dash-dotted, ...) for each color? In case you'd need to reference the lines in a caption, you'd have a hard time with the line widths ("the medium thick orange line"?). But, OTOH, so would the a solution with 20 different colors as was asked for. – NichtJens Jul 06 '17 at 03:04
  • Sure, @NichtJens, that's why I mentioned line style as an alternative. Line width just occurred to me first, that's all. – Don Kirkby Jul 06 '17 at 06:43
  • Understood. I mainly meant that you might want to add it as second example to your answer to make it more complete :) – NichtJens Jul 06 '17 at 18:36
  • 1
    I have added a second example, @NichtJens, as you suggested. – Don Kirkby Jul 07 '17 at 19:21
  • Very helpful answer. Also helps me with the issue of adressing colors by name I had a while ago (https://graphicdesign.stackexchange.com/questions/84320/how-do-i-refer-to-a-color-in-a-text-what-naming-system-do-i-use). Much easier to refer to the red dash-dot vs the red solid line, instead of the salmon red line vs the lava red line (ignoring the whole color-blindness issue of course…) – JC_CL Dec 04 '17 at 14:13
23

To build off of Don Kirkby's answer, if you're willing to install/use seaborn, then you can have colors computed for you:

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

NUM_COLORS = 20
LINE_STYLES = ['solid', 'dashed', 'dashdot', 'dotted']
NUM_STYLES = len(LINE_STYLES)

sns.reset_orig()  # get default matplotlib styles back
clrs = sns.color_palette('husl', n_colors=NUM_COLORS)  # a list of RGB tuples
fig, ax = plt.subplots(1)
for i in range(NUM_COLORS):
    lines = ax.plot(np.arange(10)*(i+1))
    lines[0].set_color(clrs[i])
    lines[0].set_linestyle(LINE_STYLES[i%NUM_STYLES])

fig.savefig('moreColors.png')
plt.show()

Aside from being able to use seaborn's various color palettes, you can get a list of RGB tuples that can be used/manipulated later on if need be. Obviously, you could compute something similar using matplotlib's colormaps, but I find this to be handy. seaborn husl color map with 20 colors

  • Thanks! For everyone who wants to sample colors and linestyles uniquely: `clrs = sns.color_palette('muted', n_colors=num_colors) product(['solid', 'dashed', 'dashdot', 'dotted'], clrs)` – Jonas Dec 04 '20 at 09:42
5

These answers seemed more complicated than needed. If you are looping through a list to plot lines, then just enumerate on the list and assig color to some point on the colormap. Say you are looping through all the columns from a pandas dataframe:

fig, ax = plt.subplots()
cm = plt.get_cmap('gist_rainbow')
 for count, col in enumerate(df.columns):
    ax.plot(df[col], label = col, linewidth = 2, color = cm(count*20))

This works because cm is just an iterable dictionary of color numerics. Multiplying those by some factor gets you further along in the colormap (more difference in color).

GoPackGo
  • 341
  • 5
  • 9
  • What is ColList? Also why not use snail_case in Python? – AturSams Jun 01 '21 at 05:59
  • I edited my comment - ColList was meant to be a list of columns in a pandas dataframe. df.columns would be more clear. I'm using pandas but you can iterate over whatever data you want. I'm not familiar with snail_case. – GoPackGo Jun 01 '21 at 17:18
  • 1
    very straightforward, thank you – rictuar Jan 17 '22 at 23:29