本文的前半部分来自书《科研论文配图绘制指南–基于Python》,后半部分来自matplotlib官网(v3.7)
基础知识
科研论文绘图规范
- 科研论文绘图分为线性图、灰度图、照片彩图和综合配图 ,现在主要学习线形图( 折线图、散点图、柱形图等 )
- 论文配图包括:X, Y轴; X, Y轴标签; 主次刻度; 图例
- 像素图放大会失真,而矢量图不会失真。
- 矢量图使用点、直线或多边形等基于数学方程的几何图元表示的图像。矢量图的图像文件包含独立的分离图像,可以自由、无限制地进行重新组合。
- 常见的矢量图格式包括 EPS(体积小质量高,常用)、PDF、AI(可修改体积大)、SVG
- 像素图:一般要求大于300dpi( dpi 是表示空间分辨率的计量单位,即每英寸可分辨的点数 )
- 多子图:单栏排版(竖向排列)、双栏排版(考虑每行可排列的子图,子图各组件都要对齐)
- 字体字号一致, 字号不大于正文字体的字号
- 中文期刊:宋体,黑体
- 英文期刊: Arial、Helvetica 或 Times New Roman
- 先文后图: 配图应出现在引用文字的下方或右侧
- 避免使用过亮或过暗的 颜色,相邻的图层元素不宜采用相近的颜色
绘制三原则
必要性原则:
- 如果配图可以起到补充说明文字、直观展示结果、引出下文内容等作用,那么它 就是必要的 。
- 避免堆砌: 无须将原始数据和中间处理 过程涉及的插图全部展示在论文中,而应在有复杂和多维数据的情况下,提高精选插图的能力。
易读性原则:
- 完整、 准确的标题、标签和图例等可以有效地增强科研论文配图的易读性
一致性原则:
- 配图所表达出的内容( 物理量缩写、符号 )与上下文或者指定内容描述一致
- 配图数据与上下文保持一致
- 插图比例尺和缩放比例大小保持一致
- 类似配图各图层要素( 大小、字体、颜色 、符号)保持一致
配色基础
RGB
- 利用三个颜色通道变化,叠加。
- 黑色:0,0,0 白色:255,255,255
- 品红没有绿:255,0,255
- 青色没有红:0,255,255
- 黄色没有蓝:255,255,0
CMYK
- RGB的子集,主要用于彩色印刷, 依靠反光的色彩模式
- C——青色 Cyan
- M——品红 Magenta
- Y——黄色 Yellow
- K——黑色 Key Plate(Black)
- 黄色、品红色和青色分别位 于立方体在坐标轴上的 3 个顶点
- 白色在原点处,黑色位于离原点最远的顶点上
- 红色、绿色 和蓝色则位于其余 3 个顶点
HEX
- 十六进制色彩模式,原理同RGB
- 写前端CSS经常用😁
四种色轮(色环)配色方案
单色配色方案
- 单色配色方案的饱和度和明暗 层次明显
- 容易上手,只考虑一个色相
- 单色配色方案常被用于表示有直接关系、关系较为密切或同系列的数据
- 单色配色方案中颜色的选择,其种类不宜过多,3 ~ 5 种较为合适
互补色配色方案
- 当只能选择两种颜色时,参考互补色配色方案
- 色轮上间隔 180°(相对) 的两种颜色为互补色
- 强烈的对比效果 , 可用于科研论文配图中观察组数 据和对照组数据的可视化表达
等距三角配色方案
- 色轮上彼此间隔 120°的 3 种颜色
- 可以将其中一种颜色选为主色,将另外两种颜色作为辅色
四角配色方案
- 两种,见下图d
- 优点是能够使配图的颜色更加丰富,缺点是使用时具有很大的挑战性,容易造成色彩杂乱
- 尽量避免
颜色主题
- 修改成期刊要求的主题
- 单色系: 主要维度是颜色亮度(lightness); 次要维度是色调(hue) ,较暖的颜色出现在较亮的一端, 较冷的颜色则会出现在较暗的一端 。
- 双色渐变色系: 主要用在有一个关键中心值(midpoint)的数值变量中,其本质是 两个连续单色系的组合,把关键的中心值作为中间点,一般使用白色表示
- 多色系: 类别型数值(类别变量)
- 在使用 Matplotlib 库时,用户可直 接通过绘图函数的 cmap 参数来设置绘图的颜色主题
配色工具
Color Scheme Designer
太复杂了,实操有难度😕
Adobe Color
试了试,不太好用😕
ColorBrewer 2.0
https://colorbrewer2.org/#type=sequential&scheme=BuGn&n=3
在线的配色方案辅助工具,尝试了一下,确实非常好用
① 表示可选的数据类别数。
② 表示可选择的颜色主题。ColorBrewer 2.0 提供了单色系、双色渐变色系和多色系。
③ 表示选定颜色主题后的配色方案。
④ 表示配色方案输出时的注意事项,即用户 是否需要考虑色盲情形、是否友好打印等。
⑤ 表示具体搭配色系的输出模式及对应的颜 色码,可选择的格式HEX、RGB 和 CMYK。
⑥ 用于控制不同颜色搭配方案的一些属性,
⑦ 表示背景设置区域。
⑧ 展示不同颜色搭配方案的预览效果
推荐一个网站
https://colorsinspo.com/
比较简单,点击复制颜色
实操部分
环境配置
因为总是需要特定的包版本,所以在anaconda新开一个环境,环境全部按照书中的来。conda create -n plotspec python=3.8.13
conda activate plotspec
pip install matplotlib==3.4.3 seaborn==0.11.2 ProPlot==0.9.5 Geopandas==0.11.0 Numpy==1.23.0 pandas==1.4.3 scipy==1.8.1
运行1-1-1,说我没有science主题,根据提示安装了对应包,还是包同样的错误。干脆注释了设置主题这一行代码。
重新运行,报错没有openpyxl,继续pip安装。安装后就画出来了。
后面的代码基本上都可以运行出来,报错的地方是用不上的包和保存图片的部分,注释即可。
官网color教程
plt中常用单一颜色格式
RGB RGBA, [0,1] 之间的浮点数tuple 其中透明度A:0是完全透明 | - (0.1, 0.2, 0.5) - (0.1, 0.2, 0.5, 0.3) |
---|---|
十六进制RGB/RGBA字符串,支持缩写 | - ‘#0f0f0f’ - ‘#0f0f0f80’ - ‘#abc’ as ‘#aabbcc’ - ‘#fb1’ as ‘#ffbb11’ |
表示灰度 度:[0,1]浮点数字符串 | - ‘0’ as black - ‘1’ as white - ‘0.8’ as light gray |
单字符表示特定颜色 | - ‘b’ as blue - ‘g’ as green - ‘r’ as red - ‘c’ as cyan - ‘m’ as magenta - ‘y’ as yellow - ‘k’ as black - ‘w’ as white |
来自T10的tableau colors | - ‘tab:blue’ - ‘tab:orange’ - ‘tab:green’ - ‘tab:red’ - ‘tab:purple’ - ‘tab:brown’ - ‘tab:pink’ - ‘tab:gray’ - ‘tab:olive’ - ‘tab:cyan’ |
CN格式 | - ‘C0’ - ‘C1’ |
不同主题下CN不同:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
th = np.linspace(0, 2*np.pi, 128)
def demo(sty):
mpl.style.use(sty) # 设置style的方法
fig, ax = plt.subplots(figsize=(3, 3))
ax.set_title('style: {!r}'.format(sty), color='C0')
ax.plot(th, np.cos(th), 'C1', label='C1')
ax.plot(th, np.sin(th), 'C2', label='C2')
ax.legend()
demo('default')
demo('seaborn-v0_8')
定制Colorbar
colorbar 需要一个"mappable" (matplotlib.cm.ScalarMappable) 对象(典例是一张图片)。想要不用图片创建,可以使用ScalarMappable。
创建连续colorbar
使用colorbar创建。调用 colorbar 的参数包括ScalarMappable对象(ScalarMappable由norm
, cmap
构建),应绘制 colorbar 的坐标轴以及 colorbar 的方向。
import matplotlib.pyplot as plt
import matplotlib as mpl
fig, ax = plt.subplots(figsize=(6, 1))
fig.subplots_adjust(bottom=0.5)
cmap = mpl.cm.cool
norm = mpl.colors.Normalize(vmin=5, vmax=10)
fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),
cax=ax, orientation='horizontal', label='Some Units')
基于连续colorbar创建离散colorbar
fig, ax = plt.subplots(figsize=(6, 1))
fig.subplots_adjust(bottom=0.5)
cmap = mpl.cm.viridis
bounds = [-1, 2, 5, 7, 12, 15]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both')
fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),
cax=ax, orientation='horizontal',
label="Discrete intervals with extend='both' keyword")
使用ListedColormap创建离散colorbar
fig, ax = plt.subplots(figsize=(6, 1))
fig.subplots_adjust(bottom=0.5)
cmap = (mpl.colors.ListedColormap(['red', 'green', 'blue', 'cyan'])
.with_extremes(over='0.25', under='0.75'))
bounds = [1, 2, 4, 7, 8]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
fig.colorbar(
mpl.cm.ScalarMappable(cmap=cmap, norm=norm),
cax=ax,
extend='both',
ticks=bounds,
spacing='proportional',
orientation='horizontal',
label='Discrete intervals, some other units',
)
如何使用colorbar
color一般作为图片的一部分放在图中特定位置。
colormap
Matplotlib 有许多内置的colormap,可通过 matplotlib.colormaps 访问。还有一些外部库(如 palettable)也有许多额外的颜色图。
不过,我们经常希望在 Matplotlib 中创建或操作colormap。这可以使用 ListedColormap或 LinearSegmentedColormap 类来实现。从外观上看,这两个颜色图类都是将 0 和 1 之间的值映射到一系列颜色。不过,它们之间还是有细微差别的,部分差异将在下文中展示。
在手动创建或操作colormap之前,让我们先看看如何从现有的colormap类中获取colormap及其颜色。
获取colormap及其颜色(其他版本的matplotlib中类名有不同,每个版本都有文档可查)
首先,从Choosing Colormaps in Matplotlib获取已有名字的colormap, 可以使用 matplotlib.colormaps, 这将返回一个colormap对象。
通过 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))
# out:(0.122312, 0.633153, 0.530398, 1.0)
ListedColormap 将颜色值存储在 .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. ]]
'''
LinearSegmentedColormaps 没有 .colors 属性。不过,我们仍然可以使用整数数组或介于 0 和 1 之间的浮点数组调用colormap。
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)))
创建与登记colormap
这部分暂时用不到,用到再学。链接
数据非线性映射到colormap
这部分暂时也用不到,时间关系先不看。链接
选择colormap
选择一个好的色彩映射图背后的理念是在三维色彩空间中为数据集找到一个好的表示方法。对于任何给定的数据集来说,最佳的色彩映射取决于很多方面,包括
- 是表示形式数据还是度量数据([器])。
- 对数据集的了解(例如,是否存在一个临界值,其他值会偏离该值?)
- 绘制的参数是否有直观的颜色方案
- 受众可能期待的领域是否有标准
对于许多应用而言,感知统一的色彩图是最佳选择;也就是说,在色彩图中,数据中的等阶被感知为色彩空间中的等阶。研究人员发现,人脑将明度参数的变化视为数据的变化要比色调的变化好得多。因此,通过色图单调增加明度的色图更容易被观众理解。
要了解人类对颜色图的感知,[IBM]是一个很好的入门资源。
可选的colormap
动手实操
实操1
用nipy_spectral给官网实例换色:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
nipy_spectral = cm.get_cmap('nipy_spectral', 16)
# define a list of markevery cases to plot
cases = [
None,
8,
(30, 8),
[16, 24, 32],
[0, -1],
slice(100, 200, 3),
0.1,
0.4,
(0.2, 0.4)
]
# data points
delta = 0.11
x = np.linspace(0, 10 - 2 * delta, 200) + delta
y = np.sin(x) + 1.0 + delta
fig, axs = plt.subplots(3, 3, figsize=(10, 6))
fig.subplots_adjust(hspace=0.465)
i = 0
for ax, markevery in zip(axs.flat, cases):
ax.set_title(f'markevery={markevery}')
ax.plot(x, y, 'o', ls='-', ms=4, markevery=markevery, c=nipy_spectral(x[i]))
i+=1
plt.show()
实操2
tab20的效果
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm
x = np.linspace(0, 3 * np.pi, 500)
y = np.sin(x)
dydx = np.cos(0.6 * (x[:-1] + x[1:])) # first derivative
# Create a set of line segments so that we can color them individually
# This creates the points as an N x 1 x 2 array so that we can stack points
# together easily to get the segments. The segments array for line collection
# needs to be (numlines) x (points per line) x 2 (for x and y)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
fig, axs = plt.subplots(1, 1, sharex=True, sharey=True)
fig.subplots_adjust(top=0.6, bottom=0.2)
# Create a continuous norm to map from data points to colors
norm = plt.Normalize(dydx.min(), dydx.max())
lc = LineCollection(segments, cmap='tab20', norm=norm)
# Set the values used for colormapping
lc.set_array(dydx)
lc.set_linewidth(2)
line = axs.add_collection(lc)
fig.colorbar(line, ax=axs)
axs.set_xlim(x.min(), x.max())
axs.set_ylim(-1.1, 1.1)
plt.show()