通常情况下百度的Echarts会在浏览器中动态渲染图表,但有些情况下需要在服务端渲染并且输出到浏览器中,笔者前端时间就遇到这么个需求:在服务器端定时生成含有Echarts图表的运营报告(pdf格式),现将大致的实现方法分享给小伙伴们,以供参考。
整体实现示意图
Nodejs http server
这里使用puppeteer代替浏览器,以实现前端代码的渲染执行,然后使用puppeteer的快照方法将渲染后的网页截图。
Puppeteer https://github.com/puppeteer/puppeteer 是 Chrome 开发团队在 2017 年发布的一个 Node.js 包,用来模拟 Chrome 浏览器的运行。
nodejs 主要代码
import express, {raw} from 'express'
import fs from 'fs'
import puppeteer from 'puppeteer';
const app = express()
app.use(express.json()) // for parsing application/json
app.use(express.urlencoded({extended: true})) // for parsing application/x-www-form-urlencoded
const readFileAsync = promisify(fs.readFile);
app.post('/echart/create', async function (req, res) {
let echartOption = req.body.echartOption;
let width = 800;
let height = 500;
if (req.body.width) {
width = req.body.width
}
if (req.body.height) {
height = req.body.height
}
let jsFileContent = "let mainDom = document.getElementById('main');\n mainDom.style.width='" + width + "px';\n mainDom.style.height='" + height + "px';\n" + echartOption;
await writeFile('public/js/echart-option.js', jsFileContent);
// 用puppeteer打开浏览器,并快照拍下echart图片
const browser = await puppeteer.launch({
args: ['--no-sandbox'],
headless: true
});
const page = await browser.newPage();
let viewport = {
width: Number(width),
height: Number(height)
};
await page.setViewport(viewport);
await page.goto('http://127.0.0.1:3000/index.html');
const echartImgPath = 'resource/echart.png';
await page.screenshot({path: echartImgPath});
await browser.close();
const data = await readFileAsync(echartImgPath);
res.setHeader('Content-Type', 'image/jpeg')
res.end(data)
})
function writeFile(filePath, content) {
return new Promise((resolve, reject) => {
fs.writeFile(filePath, content, (err, data) => {
if (err) {
reject(err);
}
console.log('write ok');
resolve(data);
});
})
}
app.use(express.static('public'))
console.log('已启动http')
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log("http启动成功,地址为 http://%s:%s", host, port);
});
echartOption参数例子
let mainDom = document.getElementById('main');
mainDom.style.width='475px';
mainDom.style.height='250px';
let option = {
animation: false,
title: {
text: '**小时分布(㎡)',
left: 'center',
top: 5
},
grid: {
bottom: 50,
right: 50,
left: 50
},
toolbox: {
feature: {}
},
legend: {
data: ['面积'],
bottom: 10
},
xAxis: {
type: 'category',
data: [
'00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'
],
name: '小时'
},
yAxis: {
type: 'value'
, name: '㎡'
},
tooltip: {
trigger: 'axis'
},
series: [
{
name: '面积',
color: '#9a60b4',
data: [0,0,0,0,0,0,0,0,0,0,0,0,0,100,125,0,0,0,0,0,0,0,0,0,],
type: 'line',
label: {
show: false,
position: 'top'
},
smooth: true
}
]
};
index.html主要代码
echart server
服务器语言调用方
这里贴出java语言的实现代码
//下一步中的nodejs api
private String echartServerUrl = "http://19.3.14.16:3000/echart/create"";
public void drawEChartImage(String option,int imgWidht,int imgHeight, String imgPath) {
HashMap paramMap = new HashMap<>();
paramMap.put("echartOption", option);
paramMap.put("width", imgWidht);
paramMap.put("height", imgHeight);
InputStream inputStream = HttpRequest.post(echartServerUrl).form(paramMap).execute().bodyStream();
try {
OutputStream out = new FileOutputStream(imgPath);
byte[] b = new byte[1024];
int length;
while ((length = inputStream.read(b)) != -1) {
out.write(b, 0, length);
}
inputStream.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
总结
此方法实现的主要原理为使用浏览器模拟器 puppeteer代替浏览器执行前端代码 , 其它的就堆砌业务代码了。