實現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參數,傳遞給瀏覽器,在瀏覽器端拿到對應的參數進行渲染,所以基本實現步驟如下:
傳遞參數到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)暫時不支持
實現node端渲染圖表的簡單方案