使用matplotlib创建超过20种独特的图例颜色

86

我正在使用matplotlib在单个图中绘制20条不同的线。我使用for循环进行绘图,并使用每条线的键标记每条线,然后使用legend函数。

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

但是使用这种方式,图例中会重复很多颜色。有没有办法使用matplotlib确保每条线都分配了唯一的颜色,即使有20多条线?

谢谢


传统上,标签和颜色之间并没有必然的联系。无论是否有标签,同样的颜色也可能出现多次。 - Yann
13
默认情况下,matplotlib 如此轻松地重复使用颜色对我来说相当疯狂。 - Chris_Rands
4个回答

146

回答你的问题需要涉及到另外两个SO问题。

回答如何在matplotlib中为每条线选择新颜色?的问题解释了如何定义默认颜色列表并循环选择下一个要绘制的颜色。这可以使用Axes.set_color_cycle方法完成。

但是,您希望得到正确的颜色列表,最简单的方法是使用颜色映射,正如回答如何从给定的matplotlib颜色映射创建颜色生成器中所解释的那样。在这里,颜色映射将值从0到1映射为一个颜色。

因此,对于您的20条线,您希望在每20分之一的步长内从0到1进行循环。具体而言,您想循环从0到19/20,因为1映射回0。

这在这个例子中完成:

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()

这是生成的图片:

Yosemitebear Mountain Giant Double Rainbow 1-8-10

另一种(更好的,有争议的)解决方案

还有一种使用ScalarMappable对象将一系列值转换为颜色的替代方法。该方法的优点在于您可以使用非线性Normalization将行索引转换为实际颜色。以下代码产生完全相同的结果:

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()

很好。顺便问一下,在你的for循环中,'color'是做什么用的?我删除了它在循环中的声明,代码似乎也能正常运行... - Quetzalcoatl
17
在Matplotlib v1.5中,ax.set_color_map()已被弃用。请改用ax.set_prop_cycle(color=[cm...])。注意不要更改原意。 - blokeley
3
可用颜色映射列表在此处:http://matplotlib.org/examples/color/colormaps_reference.html - blokeley
我在将这个颜色方案应用到我自己发布在这里的代码时遇到了很多麻烦:https://stackoverflow.com/questions/47775914/need-more-colors-from-matplotlib。它被认为是一个重复的帖子,这是正确的,但我无法让这里的答案对我起作用。 - user3044431
这是我在Stackoverflow上看过的最吸引人的答案之一。 - ColinMac
更新:上面的答案已经过时。请参见https://dev59.com/2FoU5IYBdhLWcg3wIkeI#57421483 - mortonjt

29

我有一个包含12条线的图表,使用Yann的技术时,很难区分颜色相似的线条。我的线条呈成对出现,因此我在每对线条中使用相同的颜色,并使用两种不同的线宽。您还可以通过改变线条样式来获得更多组合。

您可以使用set_prop_cycle(),但我只是在调用plot()后修改了线条对象。

这是Yann的示例,包含三种不同的线宽:

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()

具有线宽的示例图

这是相同的示例,使用不同的线条样式。当然,如果您想要,可以将两者组合在一起。

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()

使用线条样式的示例图


循环使用不同的线条样式(虚线、点线、双虚线、点划线等)来配合每种颜色,这样不是更好吗?如果您需要在标题中引用这些线条,使用线宽描述会很困难(“中等粗细的橙色线”?)。但是,另一方面,使用20种不同的颜色也会有同样的问题。 - NichtJens
当然,@NichtJens,这就是我提到线条样式作为替代方案的原因。线宽只是我首先想到的,仅此而已。 - Don Kirkby
明白了。我的意思是你可能想把它作为第二个例子添加到你的答案中,使其更完整 :) - NichtJens
1
我已经按照你的建议添加了第二个示例,@NichtJens。 - Don Kirkby
非常有帮助的答案。它还帮助我解决了一段时间以前遇到的通过名称寻址颜色的问题(https://graphicdesign.stackexchange.com/questions/84320/how-do-i-refer-to-a-color-in-a-text-what-naming-system-do-i-use)。现在更容易引用红色虚线与红色实线,而不是鲑鱼红线与熔岩红线(当然忽略整个色盲问题...) - JC_CL

24

Don Kirkby的答案基础上,如果您愿意安装/使用Seaborn,那么您可以让颜色为您计算:

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()

除了能够使用seaborn的各种配色方案外,您还可以获得一个RGB元组列表,以备将来需要使用/操作。显然,您可以使用matplotlib的颜色映射算出类似的结果,但我认为这很方便。 带有20种颜色的seaborn husl色图


谢谢!对于想要独特地取样颜色和线条样式的每个人: clrs = sns.color_palette('muted', n_colors=num_colors) product(['solid', 'dashed', 'dashdot', 'dotted'], clrs) - Jonas

5
这些答案看起来比必要的更为复杂。如果您正在循环遍历一个列表以绘制线条,则只需在列表上枚举并将颜色分配给色图上的某些点即可。假设您正在遍历 pandas 数据框的所有列:

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))

这是因为cm只是一个包含颜色数字的可迭代字典。将这些数字乘以某个因子可以在颜色映射中获得更大的范围(即更多的颜色差异)。

什么是ColList?另外,为什么Python中不使用蜗牛命名法? - AturSams
我编辑了我的评论 - ColList 指的是 pandas dataframe 中列的列表。使用 df.columns 会更清晰。我正在使用 pandas,但您可以迭代任何您想要的数据。我不熟悉 snail_case。 - GoPackGo
1
非常直接,谢谢。 - rictuar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接