黑白颜色图(带虚线、点等)

37 投票
4 回答
19301 浏览
提问于 2025-04-17 01:52

我正在使用matplotlib来制作二维线图。为了发表,我希望这些图是黑白的(不是灰度的),但我在寻找一个不太影响图形的解决方案时遇到了困难。

Gnuplot会自动为不同的线条改变虚线的样式,请问在matplotlib中也能做到类似的效果吗?

4 个回答

3

我之前很依赖Yann的代码,但今天我看到一个关于在matplotlib中能否循环使用线条样式的回答。所以现在我打算用这种方式来制作我的黑白图:

import pylab as plt
from itertools import cycle
lines = ["k-","k--","k-.","k:"]
linecycler = cycle(lines)
plt.figure()
for i in range(4):
    x = range(i,i+10)
    plt.plot(range(10),x,next(linecycler))
plt.show()

在这里输入图片描述

15

简而言之

在这里输入图片描述

import matplotlib.pyplot as plt
from cycler import cycler
monochrome = (cycler('color', ['k']) * cycler('marker', ['', '.']) *
              cycler('linestyle', ['-', '--', ':', '=.']))
plt.rc('axes', prop_cycle=monochrome)
... 

详细解答

最近的 matplotlib 更新引入了一个新的设置项 rcParams,叫做 axes.prop_cycle

In [1]: import matplotlib.pyplot as plt

In [2]: plt.rcParams['axes.prop_cycle']
Out[2]: cycler('color', ['b', 'g', 'r', 'c', 'm', 'y', 'k'])

对于通过 plt.style.use(...) 或者 with plt.style.context(...): 使用的预设样式,prop_cycle 相当于以前的、现在已经不推荐使用的 axes.color_cycle

In [3]: plt.rcParams['axes.color_cycle']
/.../__init__.py:892: UserWarning: axes.color_cycle is deprecated and replaced with axes.prop_cycle; please use the latter.
  warnings.warn(self.msg_depr % (key, alt_key))
Out[3]: ['b', 'g', 'r', 'c', 'm', 'y', 'k']

不过,cycler 对象有更多的可能性,特别是可以通过简单的 cycler 组合成复杂的 cycler,可以引用不同的属性,使用 +*,分别表示组合和笛卡尔积。

在这里我们导入了 cycler 辅助函数,定义了三个简单的 cycler,它们引用不同的属性,最后使用笛卡尔积将它们组合起来。

In [4]: from cycler import cycler
In [5]: color_c = cycler('color', ['k'])
In [6]: style_c = cycler('linestyle', ['-', '--', ':', '-.'])
In [7]: markr_c = cycler('marker', ['', '.', 'o'])
In [8]: c_cms = color_c * markr_c * style_c
In [9]: c_csm = color_c * style_c * markr_c

这里我们有两个不同的复杂 cycler,是的,它们是不同的,因为这个操作是不可交换的,看看就知道了。

In [10]: for d in c_csm: print('\t'.join(d[k] for k in d))
-               k
-       .       k
-       o       k
--              k
--      .       k
--      o       k
:               k
:       .       k
:       o       k
-.              k
-.      .       k
-.      o       k

In [11]: for d in c_cms: print('\t'.join(d[k] for k in d))
-               k
--              k
:               k
-.              k
-       .       k
--      .       k
:       .       k
-.      .       k
-       o       k
--      o       k
:       o       k
-.      o       k

在这个组合中,变化最快的元素循环是最后一个,这一点很重要,如果我们想要线条样式有特定的顺序。

如何使用 cycler 的组合呢?可以通过 plt.rc,或者用其他方法修改 matplotlibrcParams。例如:

In [12]: %matplotlib
Using matplotlib backend: Qt4Agg
In [13]: import numpy as np
In [14]: x = np.linspace(0, 8, 101)
In [15]: y = np.cos(np.arange(7)+x[:,None])
In [16]: plt.rc('axes', prop_cycle=c_cms)
In [17]: plt.plot(x, y);
In [18]: plt.grid();

在这里输入图片描述

当然,这只是一个例子,提问者可以混合不同的属性,以达到最满意的视觉效果。

顺便提一下,这种方法会自动处理图例框中的线条样本,在这里输入图片描述

50

下面我提供了一些函数,可以把有颜色的线条转换成黑色的线条,并且每条线都有独特的样式。经过我简单的测试,发现经过7条线后,颜色就会重复。如果不是这样(也可能是我搞错了),那么就需要对提供的程序中的“常量”COLORMAP做一些小调整。

这里是程序和示例:

import matplotlib.pyplot as plt
import numpy as np

def setAxLinesBW(ax):
    """
    Take each Line2D in the axes, ax, and convert the line style to be 
    suitable for black and white viewing.
    """
    MARKERSIZE = 3

    COLORMAP = {
        'b': {'marker': None, 'dash': (None,None)},
        'g': {'marker': None, 'dash': [5,5]},
        'r': {'marker': None, 'dash': [5,3,1,3]},
        'c': {'marker': None, 'dash': [1,3]},
        'm': {'marker': None, 'dash': [5,2,5,2,5,10]},
        'y': {'marker': None, 'dash': [5,3,1,2,1,10]},
        'k': {'marker': 'o', 'dash': (None,None)} #[1,2,1,10]}
        }


    lines_to_adjust = ax.get_lines()
    try:
        lines_to_adjust += ax.get_legend().get_lines()
    except AttributeError:
        pass

    for line in lines_to_adjust:
        origColor = line.get_color()
        line.set_color('black')
        line.set_dashes(COLORMAP[origColor]['dash'])
        line.set_marker(COLORMAP[origColor]['marker'])
        line.set_markersize(MARKERSIZE)

def setFigLinesBW(fig):
    """
    Take each axes in the figure, and for each line in the axes, make the
    line viewable in black and white.
    """
    for ax in fig.get_axes():
        setAxLinesBW(ax)

xval = np.arange(100)*.01

fig = plt.figure()
ax = fig.add_subplot(211)

ax.plot(xval,np.cos(2*np.pi*xval))
ax.plot(xval,np.cos(3*np.pi*xval))
ax.plot(xval,np.cos(4*np.pi*xval))
ax.plot(xval,np.cos(5*np.pi*xval))
ax.plot(xval,np.cos(6*np.pi*xval))
ax.plot(xval,np.cos(7*np.pi*xval))
ax.plot(xval,np.cos(8*np.pi*xval))

ax = fig.add_subplot(212)
ax.plot(xval,np.cos(2*np.pi*xval))
ax.plot(xval,np.cos(3*np.pi*xval))
ax.plot(xval,np.cos(4*np.pi*xval))
ax.plot(xval,np.cos(5*np.pi*xval))
ax.plot(xval,np.cos(6*np.pi*xval))
ax.plot(xval,np.cos(7*np.pi*xval))
ax.plot(xval,np.cos(8*np.pi*xval))

fig.savefig("colorDemo.png")
setFigLinesBW(fig)
fig.savefig("bwDemo.png")

这个程序会生成以下两个图表:首先是彩色的:

enter image description here

然后是黑白的:

enter image description here

你可以调整每种颜色是如何转换成样式的。如果你只想玩一下虚线的样式(比如 -.、-- 或者你想要的任何图案),可以把COLORMAP中对应的“标记”值设置为None,然后调整“虚线”图案,反之亦然。

举个例子,字典中的最后一种颜色是 'k'(代表黑色);最开始我只用了一个虚线图案[1,2,1,10],这个图案的意思是:显示一个像素,不显示两个,显示一个,不显示十个,形成点点空格的样式。然后我把这个注释掉,把虚线设置为(None, None),这是一种很正式的说法,表示实线,并且添加了标记 'o',表示圆圈。

我还设置了一个“常量” MARKERSIZE,这个会决定每个标记的大小,因为我发现默认的大小有点大。

显然,这个方法并不能处理你的线条本身已经有虚线或标记图案的情况,但你可以把这些程序当作一个起点,去构建一个更复杂的转换器。例如,如果你原来的图表有一条红色实线和一条红色虚线,使用这些程序后,它们都会变成黑色的虚点线。在使用这些程序时,记得这一点。

撰写回答