实现node端渲染图表的简单方案
这个题目有点小,本篇博客真正谈论的应该是服务端生成图表的简单方案,这里面有两个关键字:服务端 & 简单,我们知道基于js有很多的图表库,知名的如D3、echarts 、highcharts等等,对于做数据可视化方向的同学可能自己都做过此类chart的研发,无论从零构建还是使用已有的轮子,基本上都是基于js在做,因为大部分数据可视化产品都是to B的产品。
但是有些场景下,我们还是会需要服务端的渲染结果的,比如,需要给用户发送订阅邮件,邮件中包含了图表类展示,我们知道邮件内容可以支持html,但是只能支持最基本的html,图表类内容只能以图片资源的方式嵌入进去,由于图表是动态内容,所以需要我们在发送邮件之前根据用户特性内容去动态生成,这种情况下就会有对应的需要了;另外如果你的产品需要和类似slack这样的app 集成,做dashboard展示,也同样需要在服务端生成图表。
请注意服务端生成图表和编写服务端代码生成图表的细微区别,服务端编写代码生成图表并不一定是在服务端渲染图表,有可能只是是对客户端js的一种封装而已.
常规思路
- 图表渲染的结果当前主要有两种(canvas绘制和svg渲染),以svg渲染为例来说明
svg本质上是xml,可以看到基于svg生成的图表其实就是生成一大坨的xml,如果服务端熟悉生成svg(xml)的规则,其实在服务端完全可以生成对应的xml(即svg图片),这种思路虽然没有问题,但是实现起来有些复杂,尤其在使用第三方chart 库的情况下,每种chart 对应的svg规则可能不同,如果官方没有提供对应服务端渲染方案,那么写起来还是比较费劲的。
- 借用浏览器渲染
在highcharts的官网可以看到不同平台的服务端导出实现,highcharts渲染后支持导出图片(svg、png、jpeg)以及pdf;默认情况下,点击导出的时候客户端会向highcharts服务器发送请求,然后服务器生成图片,响应到前端下载下来,但是这种并非是服务端渲染,而是前端发送渲染好的svg(xml)到服务器,服务端转换svg内容成图片文件,但是这种方式的前提是在浏览器端渲染完毕,服务端根据渲染结果做一些转换工作而已。
常规思路微调整
借用常规思路,我们了解到,在我们不熟悉chart库生成图表规则的前提下,我们并没有特别简单的方式来构建svg或者canvas图表,但是如果我们能在服务端直接把渲染的结果截图保存下来也基本实现了我们的方案,但是渲染chart最方便的方式是通过浏览器,此时我们便可以借用headless浏览器来实现,puppeteer正是google headless浏览器的上层node api,通过node 可以操控浏览器,node和浏览器能在同一个编程环境中,让我们在服务端借用浏览器成为一个很好思路。
要实现这么一个库,并且简单好用,那么就要保持和原chart库同样的配置,对于实现的消费者来说,最简单的调用应该就是render(options) ,options为所用第三方chart库的配置项,render方法是node端方法,图表需要浏览器渲染,我们需要一种机制在调用render方法是传递的options参数,传递给浏览器,在浏览器端拿到对应的参数进行渲染,所以基本实现步骤如下:
- 传递参数到node层render函数中
- 接收到render中option参数传递给浏览器的window对象
- 浏览器运行时从window对象中获取options渲染对应的结果
- 执行截图操作,保存渲染结果
可以用如下伪代码表示:
const puppeteer =require('puppeteer');
const render= async (options)=>{
//创建浏览器实例
const browser = await puppeteer.launch({
args:['--no-sandbox']
});
//创建page对象
const page = await browser.newPage();
//设置page内容
await page.setContent(`
//省略部分代码
<div id="container" style="width:600px;hight=400px"></div>
...
`);
//传递options对象到evaluate函数中,挂载到window对象的全局属性中
await page.evaluate((options)={
window.chart={
options
}
},options);
//这里以百度echarts为例说明 ,注入echarts库到页面
await page.addScriptTag({
url:'https://cdnxxx.echarts库'
})
//echarts 初始化脚本注入页面
await page.addScriptTag({
content:`
(function (window) {
let option =window.chart.options; //浏览器环境下获取window对象中chart的配置项进行初始化
var myChart = window.echarts.init(document.getElementById('container'), null, {
renderer: 'svg'
});
myChart.setOption(option);
})(this);
`
});
let $el = await page.$('#container');
let buffer = await $el.screenshot({
type: 'png',
path:'xxx.png'
});
await page.close();
await browser.close();
}
//使用方法
let options = {
...// echarts 各种配置
}
render(options);
上述代码可能没办法正常运行(毕竟只是伪代码),但是基本上把文字描述的步骤完整的表达了出来。对上面api不太了解的同学 点击这里
代码完善
上面的伪代码中,主要有两个变化点,1、第三方库 2、初始化脚本。
如果把上述两个变化点能封装起来,其实我们是理论上可以兼容所有charts的node端渲染的,只要提供了第三方库脚本和自定义的初始化脚本,不仅仅是chart,其它的任何内容都可以做到,只是需要写得初始化脚本是否复杂而已,这个需要根据具体需要均衡,毕竟没有银弹。
在上面思路的基础上,我抽象了一个node模块node-charts,内置了echart和highcharts的初始化脚本并支持外部扩展,使用方式如下:
npm install --save node-charts
const fs = require('fs');
const NodeCharts = require('node-charts');
let nc = new NodeCharts();
let option = {
//第三方chart 配置项
}
//监听全局异常事件
nc.on('error',(err)=>{
console.log(err);
});
nc.render(option,(err,data)=>{
fs.writeFileSync('test.png',data);
},{
type:'echarts' //所用的第三方库标识,内置highcharts 和echarts两种默认为echarts,可通过根目录创建node.config.js文件配置 外部chart
})
源码见 https://github.com/JerrZhang/node-charts 欢迎issue & star.
总结
这种思路写起来较为简单,但是也有一定的不足,首先限于puppeteer的限制,截图只支持两种png 、jpeg,其它格式当前版本(1.4.0)暂时不支持
原文地址:https://www.cnblogs.com/Johnzhang/p/9119711.html