首页 / 资讯中心 / 文章详情

Matplotlib中文乱码解决方案:字体配置与坐标轴调整全攻略

Matplotlib中文乱码解决方案:字体配置与坐标轴调整全攻略 ★ FEATURED ARTICLE
第一次在matplotlib里碰到中文变方块应该很多人都有印象数据算得好好的标签也写得规规矩矩结果图片一出来横轴纵轴上的中文全变成一个个小方框。这个现象在数据可视化里太常见了尤其是用matplotlib画中文图表时问题基本都集中在字体设置上。这篇文章就专门聊聊matplotlib的中文坐标轴、标题字体和字体大小该怎么调从全局配置到局部控制再给一份可以直接抄作业的完整示例。不管你是刚开始用Python画图的新手还是做了几年数据报表想彻底摆脱乱码的老手都可以在里边找到对应的解法。我最早被这个问题卡住是在做一个模拟项目X的月度趋势图时图表里的标题、坐标轴标签全部需要中文。当时百度了一圈试了好几种写法最后才搞明白matplotlib本身完全支持中文只是默认字体不含中文字库。只要把字体配置做对了剩下的事情就顺理成章。1. 中文显示问题的根源不是matplotlib不会中文是默认字体不支持1.1 为什么中文会变成“豆腐块”matplotlib在不做任何设置时默认字体通常是DejaVu Sans或者系统里的基础英文字体。这套字体的字库覆盖范围主要是拉丁字母、希腊字母、西里尔字母和常用数学符号并没有包含CJK中日韩统一表意文字字符。当绘图代码里出现中文时渲染引擎在当前字体里找不到对应的字形只能用一个空心方块占位这就是俗称的豆腐块。你可以理解为matplotlib拿到一串中文字符好比让一个只认识英文的人朗读一本中文书他读不出来只能在每个不认识的位置画个圈。解决思路就两条要么告诉matplotlib改用某个支持中文的字体要么把这个字体提前注册到matplotlib的字体管理器里。1.2 字体回退机制决定了配置方式matplotlib的文本渲染并不是简单地把字符串直接画上去它会经历一个字体匹配过程先看你指定的字体族名称是否存在于系统字体列表里如果存在就使用它如果不存在就回退到默认字体。所以很多时候代码里明明写了font.family: SimHei图表还是方块原因很可能是当前系统根本没有叫这个名字的字体或者字体名写错了一个字母。判断系统里是否有目标字体可以用下面这段代码from matplotlib import font_manager fonts [f.name for f in font_manager.fontManager.ttflist] print([f for f in fonts if Hei in f or YaHei in f or CJK in f])如果输出列表为空说明你的环境里确实没有常见中文字体需要先安装字体或者通过addfont()注册自定义字体文件。这个细节后面排查部分会展开讲。2. 全局设置让整个图表默认中文字体一次到位2.1 核心配置三行代码最常用的全局配置方式是修改matplotlib的rcParams参数在绘图脚本开头加上这三行import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Noto Sans CJK SC, PingFang SC] plt.rcParams[axes.unicode_minus] False第一行是告诉matplotlib在无衬线字体族里优先使用列表中的这些字体渲染中文。第二行是为了修正坐标轴刻度上的负号显示问题后面单独说。这里有个关键点font.sans-serif不是随便填一个字体名就完事它是一个候选列表。matplotlib会按顺序查找第一个在系统里实际存在的字体。所以我把最常用的黑体SimHei放在最前面Windows环境下大概率能直接命中Linux服务器上没装黑体时会自动跳到思源黑体macOS则会落到苹方。这种配置方式在不同操作系统之间迁移时不用改代码也能尽量保持中文可显示。2.2 为什么是sans-serif而不是serif很多新手会困惑为什么设置中文字体要改font.sans-serif而不是font.family或者font.serif其实matplotlib默认的font.family值就是sans-serif也就是说大部分图表的默认字体属于无衬线类。如果你在font.family里没有单独指定那么实际生效的就是font.sans-serif这个列表。如果你希望整体使用宋体这类衬线字体需要这样写plt.rcParams[font.family] serif plt.rcParams[font.serif] [SimSun, Noto Serif CJK SC]不过在数据可视化场景里黑体、苹方这类无衬线字体在屏幕上更清晰也更符合图表信息传达的需求。我个人的习惯是优先用无衬线中文字体需要正式报告风格时再切换衬线体。2.3 别忘了负号这个“潜伏者”设置完中文字体后很多人发现中文正常了但坐标轴上的负号又变成了方块。这是因为matplotlib默认用ASCII字符里的-来表示负号而某些中文字体并不包含这个特殊符号的Unicode字形。解决办法就是前面提到的那行配置plt.rcParams[axes.unicode_minus] False设成False之后matplotlib会用普通连字符代替Unicode减号让负号正常显示。这个选项和中文乱码是两回事但经常同时出现所以我习惯把它和字体设置写在相邻位置形成一套固定的“中文字体三板斧”。2.4 修改字体后的缓存清理与验证还有一个容易被忽略的坑如果你是在系统里新安装了中文字体或者把下载的ttf字体文件放到了用户字体目录matplotlib并不会立刻感知到。默认情况下matplotlib在启动时会扫描系统字体并建立缓存新字体第一次使用前可能需要刷新缓存。清理方式很简单删除font cache目录下的缓存文件即可。在代码里能拿到缓存路径import matplotlib print(matplotlib.get_cachedir())在Windows上通常是一个用户目录下的matplotlib文件夹在Linux和macOS上一般是~/.cache/matplotlib。删掉这个目录下以.cache结尾的文件重启Python进程matplotlib就会重新扫描字体。验证字体是否生效最直接的方法是画一个只包含中文标题的空图或者用font_manager打印匹配结果import matplotlib.font_manager as fm font_path fm.findfont(SimHei) print(font_path)如果输出的是字体文件路径说明字体已经被正确识别如果输出的是默认DejaVu字体路径那就说明中文字体配置没有生效需要回头检查字体名和缓存。3. 局部控制给每个文本对象单独设定字体和字号全局配置适合解决80%的场景但真实绘图时经常需要对标题、坐标轴标签、刻度、图例分别设置不同的大小和样式。比如报告封面图标题要醒目正文小图标签可以略小。这部分就是纯粹的“指哪打哪”。3.1 标题、坐标轴标签、刻度标签分别怎么设最常见的局部设置方式是直接在绘图函数里传参数ax.set_title(产品销量趋势, fontsize18, fontweightbold, fontfamilySimHei) ax.set_xlabel(日期, fontsize14) ax.set_ylabel(销量件, fontsize14) ax.tick_params(axisx, labelsize10, rotation15) ax.tick_params(axisy, labelsize10)fontsize控制字号单位是磅值point。fontweight可以设置为bold但要注意有些中文字体本身没有对应的粗体变体设置了之后可能不会真正变粗甚至会有渲染异常。稳妥的做法是单独准备一个粗体字体文件或者使用本身就包含多种字重的字体例如思源黑体。tick_params用来调刻度标签labelsize控制刻度数字的大小rotation控制旋转角度。日期类刻度经常重叠旋转15度到45度是很实用的操作。3.2 用fontdict统一管理参数当多个文本对象需要相同样式时可以先用字典把字体参数打包再传入title_font { family: SimHei, size: 18, weight: bold, color: #333333 } label_font { family: SimHei, size: 13, color: #444444 } ax.set_title(近六个月各渠道销售额趋势, fontdicttitle_font) ax.set_xlabel(月份, fontdictlabel_font) ax.set_ylabel(销售额万元, fontdictlabel_font)使用fontdict的好处是参数集中管理后续要统一调整样式时只改一处就行。这种方法在绘制多个子图时尤其好用避免每个子图都重复写一长串参数。3.3 图例、文本标注和色标怎么调图例默认会继承全局字体配置但字体大小经常需要单独控制ax.legend(fontsize11, locbest, framealpha0.8)如果图例里的中文仍然乱码说明全局字体配置没生效要回到第2部分检查。图例字体一般比正文字号小一号看起来比较协调。在图上直接标注中文文本时用text方法ax.text(3, 25, 峰值出现在6月, fontsize12, colordarkred, hacenter)如果是colorbar需要获取colorbar对象后单独设置刻度字体cbar plt.colorbar(scatter_plot) cbar.ax.tick_params(labelsize10)3.4 数学公式与中英文混排的处理matplotlib支持通过$...$插入数学公式但公式环境默认使用的字体并不包含中文。所以下面这种写法会出问题ax.set_title(r$x_{1}$ 和中文混排) # 中文部分容易被渲染成方块规避方案是数学公式部分保留在$...$中中文放在外面并且配合全局中文字体设置。就像这样ax.set_title(r$x_1$ 与销量变化关系)如果你的标签主体是中文只是偶尔需要上标下标可以用Unicode字符代替部分公式例如x₁普通文本模式下也能正常显示还省去公式环境的字体冲突。4. 实操案例做一个中文销售趋势折线图理论讲多了容易飘来一个完整的实操案例。前阵子某公司需要给渠道会议准备一张趋势图要求标题、坐标轴、图例全部中文并且图片导出后要能直接放进PPT。这类需求很典型正好可以把上面所有知识点串起来。4.1 准备数据与基础图形模拟一组月度渠道销售数据横轴是月份纵轴是销售额包含线上和线下两条曲线。先做全局字体配置再创建画布import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Noto Sans CJK SC, PingFang SC] plt.rcParams[axes.unicode_minus] False months [1月, 2月, 3月, 4月, 5月, 6月] online [12.3, 18.6, 22.1, 19.4, 28.9, 35.2] offline [10.8, 11.2, 12.5, 13.0, 11.8, 12.4] fig, ax plt.subplots(figsize(9, 5.5), dpi120)figsize和dpi是我做汇报图时的常用组合。figsize(9, 5.5)保证横纵比例适合PPT版面dpi120让屏幕预览时不会发虚。4.2 调整坐标轴范围、刻度旋转与网格基础绘图代码加上各类设置ax.plot(months, online, markero, linewidth2, label线上渠道) ax.plot(months, offline, markers, linewidth2, label线下渠道) ax.set_title(近六个月各渠道销售额趋势, fontsize16, fontweightbold) ax.set_xlabel(月份, fontsize12) ax.set_ylabel(销售额万元, fontsize12) ax.tick_params(axisx, labelsize10, rotation15) ax.tick_params(axisy, labelsize10) ax.grid(True, linestyle--, alpha0.5) ax.set_ylim(0, 40) ax.set_xlim(-0.5, 5.5) ax.legend(fontsize10, locupper left)设置ylim时我特意留了顶部空间方便后面逐个数据点做文本标注。xlim设置为-0.5到5.5避免第一个点和最后一个点被画布边缘裁剪。网格使用虚线并降低透明度既不干扰数据阅读又方便看图上的具体数值位置。4.3 完整代码与效果解读给每个线上数据点加一个数值标签并导出图片for x, y in zip(months, online): ax.text(x, y 1, f{y:.1f}, hacenter, fontsize9) plt.tight_layout() plt.savefig(sales_trend.png, dpi300, bbox_inchestight) plt.show()这段代码最后的效果标题是16磅粗体黑体坐标轴标签12磅坐标刻度10磅并做了15度旋转图例在左上角线上方有每个月的数值标注。整张图的层级很清楚标题信息层级最高坐标轴标签其次刻度数字最弱。plt.tight_layout()用来自动调整子图之间的间距避免标签被裁掉。savefig时dpi300保证印刷清晰bbox_inchestight会自动裁剪多余白边导出图片的大小刚好贴合内容不用再手动二次裁剪。4.4 为什么输出图片要强调dpi和bbox_inches我见过不少同事代码写得没问题但导出的图片放到PPT里模糊不清基本都出在保存参数上。屏幕显示用dpi100~120就够了但打印或投影需要至少dpi200以上。bbox_inchestight的作用是让保存图片时重新计算边界把标签、标题全部包含进来避免四周留白过大或者边缘内容被切断。如果你需要把图片粘贴到网页上可以用dpi150加bbox_inchestight的组合清晰度和文件大小都比较均衡。不需要为了文件小强行降低dpi最终效果会差很多。5. 常见问题排查与避坑笔记做了这么多图表中文字体踩过的坑基本都集中在几个固定位置。整理成速查表遇到问题直接对号入座。5.1 排查速查表现象可能原因解决方案中文全是方块全局字体未设置或字体名不存在配置font.sans-serif确认字体名正确标题能显示刻度仍乱码只设置了title字体没有配置全局使用plt.rcParams全局配置或用tick_params设置负号变成方块axes.unicode_minus未设为False添加plt.rcParams[axes.unicode_minus] False设置了fontsize但字号没变参数名写错或配置顺序不对检查是否需要通过fontdict或tick_params传递字体文件已安装但仍乱码matplotlib字体缓存未更新删除matplotlib.get_cachedir()下的缓存文件图片导出后中文变细字体没有粗体变体使用思源黑体等多字重字体或单独注册粗体文件服务器上中文字体缺失系统没有安装中文字体使用fc-list :langzh检查安装字体或用addfont注册5.2 字体名不对的坑matplotlib的字体名不是文件路径而是字体族名称。比如Windows里的黑体注册名是SimHei如果写成SimHei.ttf或者黑体有可能匹配不到。不同的字体在不同系统上的注册名还不一样思源黑体在Linux上可能是Noto Sans CJK SCmacOS上的苹方是PingFang SC。最安全的做法是先打印字体列表再确认from matplotlib import font_manager for f in font_manager.fontManager.ttflist: if CJK in f.name or Hei in f.name or PingFang in f.name: print(f.name)把打印出来的准确名称填进配置就不会因为字体名拼写问题白折腾。5.3 fontsize设置了还是不变局部设置不生效最常见的原因是把参数传给了错误的对象。ax.set_title用的是fontsizeax.tick_params用的是labelsizeax.legend用的是fontsizeax.text用的是fontsize。这几个参数名不完全一致特别是刻度标签的labelsize容易被忽略。另外注意设置顺序先创建figure和axes再调用set_*方法最后绘制数据。如果先绘制再设置正常情况下也能生效但一旦涉及子图布局就可能出现样式不一致。我习惯把所有样式设置统一放在绘图语句之后、保存图片之前这样逻辑最清晰。5.4 全局配置被局部覆盖的顺序问题有时候全局配置已经生效但某个子图又乱码了原因是你在某个地方手动传了fontfamily参数覆盖了全局设置。比如ax.set_title(某标题, fontfamilyTimes New Roman)Times New Roman没有中文字符中文部分就会变成方块。这种情况下不是matplotlib坏了而是你的局部设置把全局中文字体顶掉了。处理方法是局部字体要选择包含中文字符的字体或者去掉这个fontfamily参数让全局配置接管。5.5 服务器环境无中文字体的解决方法生产环境常在Linux服务器上跑绘图脚本服务器不一定会默认安装中文字体。先检查fc-list :langzh如果输出为空说明没有可用的中文字体。这时候可以把一个ttf字体文件放到项目的fonts/目录下在绘图脚本开头显式注册import matplotlib.pyplot as plt import matplotlib.font_manager as fm fm.fontManager.addfont(/path/to/NotoSansCJKsc-Regular.otf) plt.rcParams[font.sans-serif] [Noto Sans CJK SC]addfont是较新版本matplotlib提供的方法它能把任意路径下的字体文件注册进当前进程的字体管理器不需要往系统目录写文件非常适合部署在容器或者无GUI服务器上的场景。6. 写在最后把字体配置固化成样式文件我自己的经验是中文字体配置这种基础事项不应该在每个脚本里重复写而是固化成样式文件让所有绘图脚本统一加载。matplotlib支持.mplstyle后缀的样式文件内容非常直观font.family: sans-serif font.sans-serif: SimHei, Noto Sans CJK SC, PingFang SC axes.unicode_minus: False font.size: 14 axes.titlesize: 16 axes.labelsize: 12 xtick.labelsize: 10 ytick.labelsize: 10 legend.fontsize: 10把这段内容保存成chinese_style.mplstyle放在项目根目录。绘图代码只需要两行import matplotlib.pyplot as plt plt.style.use(chinese_style.mplstyle)后续无论谁在公司内部写图表脚本只要加载这个样式文件中文字体、标题大小、标签大小、刻度大小全部保持一致。团队协作时这种做法的收益很明显不用在每份代码里解释三行字体配置是什么意思大家看到的默认出图效果就是统一的风格。做数据可视化很多人会把注意力放在配色、图表类型、交互效果上却忽略了最基础的字体渲染。但恰恰是中文字体这个看起来不起眼的细节决定了最终成品是专业报表还是半成品。我在实际项目里的体会是先把字体配置做成默认再谈图表设计顺序不要反。这样每张图出来都是中文正常、层级清晰、细节统一的状态后续调整样式也只是锦上添花。
阅读完成 · 觉得有帮助?
咨询建站