在 Matplotlib 中创建颜色图#

Matplotlib 有许多内置的颜色图,可通过 matplotlib.colormaps. 还有一些外部库,如 palettable,它们有许多额外的颜色图。

然而,我们经常想在 Matplotlib 中创建或操作颜色图。这可以使用类ListedColormapLinearSegmentedColormap. 从外部看,两个颜色图类都将 0 到 1 之间的值映射到一堆颜色。但是,有一些细微的差别,其中一些如下所示。

在手动创建或操作颜色图之前,让我们先看看如何从现有的颜色图类中获取颜色图及其颜色。

获取颜色图并访问它们的值#

首先,获取一个命名的颜色图,其中大部分都列在 Matplotlib中的选择颜色图中,可以使用 来完成matplotlib.colormaps,它返回一个颜色图对象。内部用于定义颜色图的颜色列表的长度可以通过调整Colormap.resampled。下面我们使用适中的 8 值,因此没有太多值得关注的值。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib.colors import ListedColormap, LinearSegmentedColormap

viridis = mpl.colormaps['viridis'].resampled(8)

该对象viridis是一个可调用对象,当传递一个介于 0 和 1 之间的浮点数时,它会从颜色图中返回一个 RGBA 值:

print(viridis(0.56))
(0.122312, 0.633153, 0.530398, 1.0)

列出的颜色图#

ListedColormaps 将它们的颜色值存储在一个.colors属性中。可以使用该colors属性直接访问组成颜色图的颜色列表,也可以通过调用viridis与颜色图长度匹配的值数组来间接访问它。请注意,返回的列表是 RGBA Nx4 数组的形式,其中 N 是颜色图的长度。

print('viridis.colors', viridis.colors)
print('viridis(range(8))', viridis(range(8)))
print('viridis(np.linspace(0, 1, 8))', viridis(np.linspace(0, 1, 8)))
viridis.colors [[0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]
viridis(range(8)) [[0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]
viridis(np.linspace(0, 1, 8)) [[0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]

颜色图是一个查找表,因此“过采样”颜色图会返回最近邻插值(注意下面列表中的重复颜色)

print('viridis(np.linspace(0, 1, 12))', viridis(np.linspace(0, 1, 12)))
viridis(np.linspace(0, 1, 12)) [[0.267004 0.004874 0.329415 1.      ]
 [0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]

LinearSegmentedColormap #

LinearSegmentedColormaps 没有.colors属性。但是,仍然可以使用整数数组或 0 到 1 之间的浮点数组调用颜色图。

copper = mpl.colormaps['copper'].resampled(8)

print('copper(range(8))', copper(range(8)))
print('copper(np.linspace(0, 1, 8))', copper(np.linspace(0, 1, 8)))
copper(range(8)) [[0.         0.         0.         1.        ]
 [0.17647055 0.1116     0.07107143 1.        ]
 [0.35294109 0.2232     0.14214286 1.        ]
 [0.52941164 0.3348     0.21321429 1.        ]
 [0.70588219 0.4464     0.28428571 1.        ]
 [0.88235273 0.558      0.35535714 1.        ]
 [1.         0.6696     0.42642857 1.        ]
 [1.         0.7812     0.4975     1.        ]]
copper(np.linspace(0, 1, 8)) [[0.         0.         0.         1.        ]
 [0.17647055 0.1116     0.07107143 1.        ]
 [0.35294109 0.2232     0.14214286 1.        ]
 [0.52941164 0.3348     0.21321429 1.        ]
 [0.70588219 0.4464     0.28428571 1.        ]
 [0.88235273 0.558      0.35535714 1.        ]
 [1.         0.6696     0.42642857 1.        ]
 [1.         0.7812     0.4975     1.        ]]

创建列出的颜色图#

创建颜色图本质上是上述操作的逆操作,我们提供颜色规范的列表或数组ListedColormap来制作新的颜色图。

在继续本教程之前,让我们定义一个辅助函数,该函数将多个颜色图之一作为输入,创建一些随机数据并将颜色图应用于该数据集的图像图。

def plot_examples(colormaps):
    """
    Helper function to plot data with associated colormap.
    """
    np.random.seed(19680801)
    data = np.random.randn(30, 30)
    n = len(colormaps)
    fig, axs = plt.subplots(1, n, figsize=(n * 2 + 2, 3),
                            constrained_layout=True, squeeze=False)
    for [ax, cmap] in zip(axs.flat, colormaps):
        psm = ax.pcolormesh(data, cmap=cmap, rasterized=True, vmin=-4, vmax=4)
        fig.colorbar(psm, ax=ax)
    plt.show()

在最简单的情况下,我们可能会输入一个颜色名称列表来从中创建一个颜色图。

cmap = ListedColormap(["darkorange", "gold", "lawngreen", "lightseagreen"])
plot_examples([cmap])
颜色图操作

事实上,该列表可能包含任何有效 的 Matplotlib 颜色规范。Nx4 numpy 数组对创建自定义颜色图特别有用。因为我们可以在这样的数组上执行各种 numpy 操作,所以从现有颜色图制作新颜色图的木工变得非常简单。

例如,假设我们出于某种原因想要将 256 长度的“viridis”颜色图的前 25 个条目设为粉红色:

viridis = mpl.colormaps['viridis'].resampled(256)
newcolors = viridis(np.linspace(0, 1, 256))
pink = np.array([248/256, 24/256, 148/256, 1])
newcolors[:25, :] = pink
newcmp = ListedColormap(newcolors)

plot_examples([viridis, newcmp])
颜色图操作

我们可以减小颜色图的动态范围;这里我们选择颜色图的中间一半。但是请注意,由于 viridis 是列出的颜色图,我们最终将得到 128 个离散值,而不是原始颜色图中的 256 个值。此方法不会在颜色空间中插入以添加新颜色。

viridis_big = mpl.colormaps['viridis']
newcmp = ListedColormap(viridis_big(np.linspace(0.25, 0.75, 128)))
plot_examples([viridis, newcmp])
颜色图操作

我们可以轻松地连接两个颜色图:

top = mpl.colormaps['Oranges_r'].resampled(128)
bottom = mpl.colormaps['Blues'].resampled(128)

newcolors = np.vstack((top(np.linspace(0, 1, 128)),
                       bottom(np.linspace(0, 1, 128))))
newcmp = ListedColormap(newcolors, name='OrangeBlue')
plot_examples([viridis, newcmp])
颜色图操作

当然我们不需要从一个命名的颜色图开始,我们只需要创建一个 Nx4 数组来传递给ListedColormap. 在这里,我们创建了一个从棕色(RGB:90、40、40)到白色(RGB:255、255、255)的颜色图。

N = 256
vals = np.ones((N, 4))
vals[:, 0] = np.linspace(90/256, 1, N)
vals[:, 1] = np.linspace(40/256, 1, N)
vals[:, 2] = np.linspace(40/256, 1, N)
newcmp = ListedColormap(vals)
plot_examples([viridis, newcmp])
颜色图操作

创建线性分段颜色图#

该类LinearSegmentedColormap使用在其间插值 RGB(A) 值的锚点指定颜色图。

指定这些颜色图的格式允许在锚点处不连续。每个锚点被指定为形式矩阵中的一行,其中是锚点, 和 是锚点两侧的颜色值。[x[i] yleft[i] yright[i]]x[i]yleft[i]yright[i]

如果没有间断,则:yleft[i] == yright[i]

cdict = {'red':   [[0.0,  0.0, 0.0],
                   [0.5,  1.0, 1.0],
                   [1.0,  1.0, 1.0]],
         'green': [[0.0,  0.0, 0.0],
                   [0.25, 0.0, 0.0],
                   [0.75, 1.0, 1.0],
                   [1.0,  1.0, 1.0]],
         'blue':  [[0.0,  0.0, 0.0],
                   [0.5,  0.0, 0.0],
                   [1.0,  1.0, 1.0]]}


def plot_linearmap(cdict):
    newcmp = LinearSegmentedColormap('testCmap', segmentdata=cdict, N=256)
    rgba = newcmp(np.linspace(0, 1, 256))
    fig, ax = plt.subplots(figsize=(4, 3), constrained_layout=True)
    col = ['r', 'g', 'b']
    for xx in [0.25, 0.5, 0.75]:
        ax.axvline(xx, color='0.7', linestyle='--')
    for i in range(3):
        ax.plot(np.arange(256)/256, rgba[:, i], color=col[i])
    ax.set_xlabel('index')
    ax.set_ylabel('RGB')
    plt.show()

plot_linearmap(cdict)
颜色图操作

为了在锚点处产生不连续性,第三列与第二列不同。“red”、“green”、“blue”和可选的“alpha”中的每一个的矩阵设置为:

cdict['red'] = [...
                [x[i]      yleft[i]     yright[i]],
                [x[i+1]    yleft[i+1]   yright[i+1]],
               ...]

x[i]对于在和之间传递给颜色图的值x[i+1],插值在yright[i]和之间yleft[i+1]

在下面的示例中,在 0.5 处有一个红色的不连续性。0 和 0.5 之间的插值从 0.3 变为 1,而 0.5 和 1 之间的插值从 0.9 变为 1。请注意, 和对插值都是多余的,因为(即) 是 0 左侧的值,并且(即) 是 1 右边的值,它在颜色映射域之外。red[0, 1]red[2, 2]red[0, 1]yleft[0]red[2, 2]yright[2]

cdict['red'] = [[0.0,  0.0, 0.3],
                [0.5,  1.0, 0.9],
                [1.0,  1.0, 1.0]]
plot_linearmap(cdict)
颜色图操作

直接从列表创建分段颜色图#

上面描述的方法非常通用,但无可否认,实施起来有点麻烦。对于一些基本的情况,使用起来 LinearSegmentedColormap.from_list可能会更容易一些。这会从提供的颜色列表中创建一个具有相等间距的分段颜色图。

colors = ["darkorange", "gold", "lawngreen", "lightseagreen"]
cmap1 = LinearSegmentedColormap.from_list("mycmap", colors)

如果需要,可以将颜色图的节点指定为 0 到 1 之间的数字。例如,可以让偏红色的部分在颜色图中占据更多空间。

nodes = [0.0, 0.4, 0.8, 1.0]
cmap2 = LinearSegmentedColormap.from_list("mycmap", list(zip(nodes, colors)))

plot_examples([cmap1, cmap2])
颜色图操作

脚本总运行时间:(0分4.802秒)

由 Sphinx-Gallery 生成的画廊