Matplotlib
是 python 中非常常用的绘图模块。 matplotlib 支持使用类似于 matlab 的指令式语言绘图,也支持使用面向对象的调用方法绘图。其绘图方法简单,且与 numpy, pandas 等科学计算库配合良好,因此被广泛使用。但是,在 matplotlib 的使用过程中,插入中文并控制字体是一件比较麻烦的事,需要特殊配置才能正常显示。本文详细介绍在 matplotlib 中使用中文的方法。
首先,我们来绘制一个简单的图,包含一条正弦曲线和一条余弦曲线:
1 | import numpy as np |
运行程序,会发现生成了一个正弦曲线图,如下图所示。
上面的代码使用的是面向对象的方法绘制的。当然,也可以使用 matlab 的命令流风格绘制。这里不再赘述。
现在代码里没有出现中文,一切正常。如果直接使用中文,我们来看一下效果
1 | import numpy as np |
生成的图片如下图所示。
里面的文字无法显示,使用一个方框代表一个文字。同时,在控制台中会输出以下信息
1 | RuntimeWarning: Glyph 20313 missing from current font. |
这个信息的意思是,在调用 font.set_text
时,发现在当前的字体集中,找不到相应字的字体。
那么当前使用的字体集是什么呢?可以在 ipython 中,使用以下命令,打印 matplotlib 的设置参数
1 | from matplotlib import rcParams |
从控制台的输出中可以看到,有几项以 font
开头的与字体有关。
font.family
是指使用的字体组。每一个字体组对应一系列字体,这些字体有共同的特征。常用的字体组有三个,分别是 serif
,即衬线字体,另一个是 sans-serif
,即无衬线字体,最后一个是 monospace
,即等宽字体。三种字体的适用场景这里不再赘述。程序默认的为无衬线字体,适合屏幕输入。对于打印论文,一般使用的是衬线字体。
对于每一个字体组,都有一个对应的字体列表。比如,在我的 MacBook 中,衬线字体的字体列表是这样定义的
1 | font.serif: ['DejaVu Serif', 'Bitstream Vera Serif', 'Computer Modern Roman', |
熟悉 HTML 编程的读者看到这一列表一定会感到使熟悉。在 HTML 中,有一个字体 fallback 机制,浏览器会先尝试使用字体列表中的第一个字体渲染文字。如果第一个字体文件中没有对应的文字,则会 fallback 到下一个字体中再次查找。这也是为什么在浏览器中,使用特殊字体时,对于一些生辟字依然可以渲染,但是字体不同。但是,需要特别注意的是,matplotlib 中没有类似的 fallback 机制 。尽管也使用了一个字体列表,但是 matplotlib 使用的机制是,从列表的第一项开始在系统中查找字体,如果存在这一字体,则使用这一字体渲染,而不会再使用任何后续字体。只有系统中不存在这一字体时,才会沿列表向后寻找可用字体。因此,无法使用类似于 Word 的中文和西文字体分别渲染的功能,也无法使用 HTML 中查找多种字体功能。
上述讨论表明,无法通过在字体列表后面加入字体的方法来添加中文支持。那么如何解决这一问题呢?
首先,我们尝试直接使用中文字体。先要确定使用什么字体。如果使用的是 MacBook ,可以在 Launchpad 的“其它” -> “字体列表”中,找到可以使用的中文字体对应的英文名。在我的电脑中,宋体的字体名是 “Simsun (founder extended)”。在 Windows 中,也可以从“字体设置”中找到宋体,点击它,从元数据中可以看到其字体文件名为 “SIMSUN.TTC” ,即字体名为 “SIMSUN”。那么就可以把字体名传入 matplotlib 。建立以下文件:
1 | import numpy as np |
绘制的结果如下图所示:
可以看出,汉字都可以正常显示了。但是有一个问题,就是图中的英文和数字字体也使用了宋体。宋体的英文和数字都比较丑陋,在科技文献中,一般不会使用,而是使用 “Times New Roman” 字体代替。这里就遇到了一个麻烦, matplotlib 中目前无法同时使用两个字体,这时就只能使有一种比较麻烦的方式“曲线救国”了。
一种解决的思路是,使用英文更好看的中文字体,比如华文宋体 “STSONG” ,Adobe宋体 “ADOBESONGSTD” 等。但是这些字体与传统的宋体和 Times New Roman 还是有一定区别的。如果对字体要求比较严格,则行不通。
另一种解决思路是,在全局使用英文字体 “Times New Roman”, 而对于出现中文的地方,使用临时的中文字体。但是对于中英文混排,使用临时中文字体也无济于事。这时需要借助 LaTeX 渲染器,使用数学字体中的 “stix” 字体来完成混排。它与 “Times New Roman” 的相似度非常高,可以起到“以假乱真”的效果。
下面是进行中英文混排的代码
1 | import numpy as np |
上面的代码中,先将全局字体设为 “Times New Roman”,将数学字体设为 “stix” 。然后指定使用的中文字体 ch_font
为 “Simsun (founder extended)” 。这一字体是 MacBook 用户使用的。对于 Windows 用户,将其变为 “SIMSUN”。然后在绘制字体时,对有中文的部分设置临时字体。在调用 set_xlabel
设置坐标轴时,传入 fontname=
参数,设置其字体。对于“相位角(rad)”这一中英混排部分,使用 LaTeX 表达式把英文部分包裹住(即前后各加一个 $
符号)。由于 LaTeX 中默认渲染字体为斜体,因此需要使用 \mathrm{}
命令,使字符变成正体。
在添加图例时,legend()
方法返回的不是文字,而是一个图例对象。我们使用其 get_texts
方法,获得图例中所用的所有文字组成的列表。然后遍历这一列表,使用 set_fontproperties
方法设置其字体。
对于其它文字操作,大多都有与 fontname
和 fontproperties
有关的操作,使用类似的方式基本都可以解决。可以查阅 matplotlib 的文档。这里举了一个使用 set_title
添加标题和一个使用 annotate
添加注释的例子。
下面来看这一段代码生成的效果。
值得指出的是,在 MacBook 中的宋体,与 Windows 中的宋体也有不同。因此此图中的宋体看起来也有一些奇怪。如果使用 Windows 设备,则与常见的宋体完全相同。
以上,就是在 matplotlib 中使用中英文混排的方法。希望在未来版本的 matplotlib 中,也可以引入字体的 fallback 机制,设置字体就不会这么麻烦了。