实现node端渲染图表的简单方案

实现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参数,传递给浏览器,在浏览器端拿到对应的参数进行渲染,所以基本实现步骤如下:

  1. 传递参数到node层render函数中
  2. 接收到render中option参数传递给浏览器的window对象
  3. 浏览器运行时从window对象中获取options渲染对应的结果
  4. 执行截图操作,保存渲染结果

可以用如下伪代码表示:

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)暂时不支持

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏云计算教程系列

如何在Ubuntu 14.04上设置uWSGI和Nginx以服务Python应用程序

在本教程中,我们将设置一个由uWSGI提供服务的简单WSGI应用程序。我们将使用Nginx Web服务器作为应用程序服务器的反向代理,以提供更强大的连接处理。我...

25100
来自专栏Java学习123

fiddler2抓包工具使用图文教程

1.6K60
来自专栏大前端开发

使用mpvue开发小程序教程(一)

前段时间,美团开源了mpvue这个项目,使得我们又多了一种用来开发小程序的框架选项。由于mpvue框架是完全基于Vue框架的(重写了其runtime和compi...

24650
来自专栏bboysoul

matomo的安装使用和体验

matomo的前身是piwik,是一个强大的网站流量分析网站,我使用它的原因就是因为我的网站原先使用的是百度统计,但是百度统计最近貌似不稳定了,所以我想自己搭建...

34620
来自专栏云计算教程系列

CentOS 7如何设置uWSGI和Nginx提供Python应用服务

在本指南中,我们将设置一个由uWSGI提供服务的简单WSGI应用程序。我们将使用Nginx Web服务器作为应用程序服务器的反向代理,以提供强大的连接处理。我们...

23840
来自专栏贾鹏辉的技术专栏@CrazyCodeBoy

Windows平台搭建React Native开发环境

尊重版权,未经授权不得转载 本文出自:贾鹏辉的技术博客(http://www.devio.org) 告诉大家一个好消息,为大家精心准备的React N...

30640
来自专栏张善友的专栏

使用密码记录工具keepass来保存密码

在第一章,曾经给过您建议,密码不要保存在文档中,那样不安全,如果密码很多而且又很复杂,人的大脑是不可能很容易记住的,只能记录下来,如果不能记在文档中那记在哪里呢...

46290
来自专栏老马寒门IT

08Vue.js快速入门-Vue综合实战项目

8.1. 前置知识学习 npm 学习 官方文档 推荐资料 npm入门 npm介绍 需要了解的知识点 package.json 文件相关配置选项 ...

44870
来自专栏搞前端的李蚊子

Vue.js实现一个SPA登录页面的过程

技术栈 vue.js 主框架 vuex 状态管理 vue-router 路由管理 一般过程 在一般的登录过程中,一种前端方案是: 检查状态:进入页面时或者路...

1.2K120
来自专栏技术墨客

构建用于生产的React静态化单页面服务 原

React 作为一项热门的前端开发技术,现在使用它的团队越来越多。之前也介绍了react 的所有的特性,但是仅仅了解怎么开发 react 只走了万里长征的第一步...

73540

扫码关注云+社区

领取腾讯云代金券