这个题目有点小, 本篇博客真正谈论的应该是服务端生成图表的简单方案, 这里面有两个关键字: 服务端 & 简单, 我们知道基于 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 不太了解的同学 点击这里 https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md
代码完善
上面的伪代码中, 主要有两个变化点, 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