摘要:matplotlib
结合seaborn
二维绘图功能强大,可以绘制eCharts
画不出的图像,因此考虑直接将matplotlib
图像渲染到前端。
文章说明
文章说明:前端绘图一般是用百度eCharts
,但其样式不够丰富且可定制化程度不高,有时无法满足需求,因此直接将python
的matplotlib
数据可视化渲染到前端。
另请参考:python
将matplotlib
输出到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 方法,可以去除白边。
接口使用python
的flask
框架,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import matplotlib
matplotlib.use('Agg') # 不出现画图的框
import matplotlib.pyplot as plt
from io import BytesIO
import base64
import seaborn as sns
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
。
然而,我们并不需要安装tkinter
、tcl-devel
和tk-devel
等,因为我们并不是需要在Linux
上看到画出的图,只需要改一下后端agg
即可【貌似Linux
下默认后端已是Agg
,从配置文件可以看到】,代码如下:1
2
3
4import 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
5plt.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、改代码,使程序更健壮、服务更稳定。