如何将matplotlib图像渲染到前端

温馨提示:点击页面下方以展开或折叠目录

摘要:matplotlib结合seaborn二维绘图功能强大,可以绘制eCharts画不出的图像,因此考虑直接将matplotlib图像渲染到前端。

文章说明
文章说明:前端绘图一般是用百度eCharts,但其样式不够丰富且可定制化程度不高,有时无法满足需求,因此直接将pythonmatplotlib数据可视化渲染到前端。
另请参考:pythonmatplotlib输出到web
另请参考:如何将Matplotlib图像展示在web页面上
另请参考:Mac使用matplotlib.pyplot画图出现中文乱码
文章作者:鴻塵
文章链接:https://hwame.top/20201008/render-matplotlib-figure-to-flaskweb.html

一、背景

前端页面的数据可视化一般是用百度eCharts,但其样式不够丰富且可定制化程度不高,有时无法满足需求。
最近需要在前端展示相关图(Correlogram),python中可以使用matplotlib结合seaborn轻松实现,但eCharts实例中没有类似的样例,因此考虑直接将matplotlib图像渲染到前端。

二、思路

1.保存图片到服务器static目录

将画好的图片保存在服务器的static目录下,前端再从该目录读取图片进行展示。这种思路很直接,是最容易想到的,但是存在以下问题:

  • 无法判断响应时间。因为图像生成是通过前端用户传递一系列参数,再根据这些参数计算相应的数据,利用计算的数据进行可视化,因此后端生成图片的时间未知,所以只能采用在前端延时展示。
  • 前端访问static目录不方便。前端VUE+后端Flask的前后端分离的项目中,前端访问static目录不方便。
  • 占用服务器内存和带宽。将图片保存在服务器,前端从服务器获取资源时会占用一部分网络带宽,影响到前端资源加载的速度,并且图片累积将会占用服务器内存。

2.使用请求的方式将图像传到前端

将图像以请求的方式传到前端,前端只需将<img>标签的src属性赋值为后端的请求路径即可。
该方法可以在后端生成完图像后再发送给前端,无需设置延时获取图像。拟采用该方法。

3.将图像以Base64格式发送给前端

这种思路和思路2类似,但是区别在于调用savefig方法时不存储为图像,而是存储为二进制格式,二进制格式再转化为Base64字符串格式,并将其发送给前端,前端只需要将<img>标签的src属性赋值为该字符串即可。

三、代码实现

对比三种方法,显然思路3为最优解:

  • 在后端生成完图像后再发送给前端,无需设置延时获取图像;
  • 只需要往前端发送一次请求,代码更加精简;
  • 调用了 savefig 方法,可以去除白边。

接口使用pythonflask框架,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import matplotlib
matplotlib.use('Agg') # 不出现画图的框
import matplotlib.pyplot as plt
from io import BytesIO
import base64
import seaborn as sns

@app.route('/correlation')
def correlation():
data = df.corr()
# 开始画图
plt.figure(figsize=(12, 10), dpi=80)
sns.heatmap(data, xticklabels=data.columns, yticklabels=data.columns, cmap='RdYlGn', center=0, annot=True)
plt.title('Correlogram', fontsize=22)
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)
# plt.show()
# 转成图片的步骤
sio = BytesIO()
plt.savefig(sio, format='png', bbox_inches='tight', pad_inches=0.0)
data = base64.encodebytes(sio.getvalue()).decode()
src = "data:image/png;base64,{}".format(data)
plt.close() # 记得关闭,不然画出来的图是重复的
return src

四、效果预览

前端通过请求获取该src后,只需将其赋值给<img>标签的src属性即可:

1
<img src={{src}}>

预览图如下:
效果预览图

五、部署问题

1.无GUI图形界面

服务器是不带GUI界面的CentOS 7,但是matplotlib.pyplot是需要tkinter库的支持的:_tkinter.TclError: no display name and no $DISPLAY environment variable
然而,我们并不需要安装tkintertcl-develtk-devel等,因为我们并不是需要在Linux上看到画出的图,只需要改一下后端agg即可【貌似Linux下默认后端已是Agg,从配置文件可以看到】,代码如下:

1
2
3
4
import matplotlib as mpl
mpl.use('Agg')
# 在from matplotlib import pylot之前添加mpl.use('Agg')
from matplotlib import pylot

2.中文字体乱码

原因分析

由于matplotlib没有中文字体,直接会报错:RuntimeWarning: Glyph xxxxx missing from current font.,网上有说需要添加两行代码(在缺少字体文件的情况下,这种方法无效),即:

1
2
3
4
5
plt.rcParams['font.sans-serif'] = ['SimHei']
#设置字体,设置了字体后,负号会变成乱码

plt.rcParams['axes.unicode_minus'] = False
#让负号的乱码正常显示

因为没有黑体这个字体,所以仍会报错,因此需要手动下载对应字体。并将其复制到对应位置:./python3.6/site-packages/matplotlib/mpl-data/fonts/ttf/

修改配置文件

解决字体缺失的问题后,还需要修改配置文件matplotlibrc,文件位置:./python3.6/site-packages/matplotlib/mpl-data/matplotlibrc。直接修改配置文件的好处是,不需要每次都在程序代码中添加诸如plt.rcParams['font.sans-serif'] = ['SimHei']的配置项。

  • #font.family : sans-serif修改为font.family : sans-serif(去掉注释)
  • #font.sans-serif : DejaVu Serif, ...修改为font.sans-serif : SimHei, DejaVu Serif, ...(去掉注释,添加SimHei
  • #axes.unicode_minus : True修改为axes.unicode_minus : False(去掉注释,属性设置为False

说明:黑体SimHei也可以改为其他字体,最接近宋体和Times New Roman的是STSong,参考如何使matplotlib同时使用宋体和Times New Roman

删除matplotlib缓存

Linux上的matplotlib缓存文件位于用户家目录下的”~/.cache/matplotlib/“,可以看到其下包含了缓存文件fontlist-v310.json及缓存文件夹tex.cache。若不删除缓存,可能看不到配置生效。

1
rm -rf ~/.cache/matplotlib/

3.其他

任何人都没法保证自己写的代码没有bug,而且测试时也不可能考虑到所有的情况。一旦程序部署上线作为服务运行,出现问题用户也只能看到50x的错误,因此要养成善于查看日志的习惯!从日志入手,才能分析问题出现的原因,从而能打补丁、修bug、改代码,使程序更健壮、服务更稳定。