Echarts作为一款优秀的可视化js库,其应用场景覆盖新闻传媒、证券金融、电子商务、旅游酒店、天气地理、视频游戏、电力、监控等众多领域。为了满足不同的场景,Echarts暴露出非常丰富配置项,能想到的想不到都有。针对细分领域,如本文监控、分析、APM类等用到的图表也就那几种,接下来就项目中为什么要定制化封装、怎么定制化、封装后带来便利性及多年使用echarts各种坑的在封装过程中一并修复。
代码地址:https://github.com/wlc534/react-r3-saga
dataset之前
ECharts 4 开始支持了 dataset 组件用于单独的数据集声明,从而数据可以单独管理,被多个组件复用,并且可以基于数据指定数据到视觉的映射。这在不少场景下能带来使用上的方便。
ECharts 4 以前,数据只能声明在各个“系列(series)”中;
option:{
title: {
text: '折线图堆叠'
},
tooltip: {
trigger: 'axis'
},
legend: {
bottom:'3%',
data:[
{name:'邮件营销',icon:'circle'},
{name:'联盟广告',icon:'circle'},
{name:'视频广告',icon:'circle'},
{name:'直接访问',icon:'circle'},
{name:'搜索引擎',icon:'circle'},
]
},
grid: {
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true
},
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: {readOnly: false},
magicType: {type: ['line', 'bar']},
restore: {},
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一','周二','周三','周四','周五','周六','周日']
},
yAxis: {
type: 'value'
},
series: [
{
name:'邮件营销',
type:'line',
data:[120, 132, 101, 134, 90, 230, 210]
},
{
name:'联盟广告',
type:'line',
data:[220, 182, 191, 234, 290, 330, 310]
},
{
name:'视频广告',
type:'line',
data:[150, 232, 201, 154, 190, 330, 410]
},
{
name:'直接访问',
type:'line',
data:[320, 332, 301, 334, 390, 330, 320]
},
{
name:'搜索引擎',
type:'line',
data:[820, 932, 901, 934, 1290, 1330, 1320]
}
],
color: [
'#FF9C6E', '#FFC069', '#95DE64', '#5CDBD3', '#69C0FF', '#85A5FF', '#B37FEB', '#FF85C0'
]
这种方式的优点是,直观易理解,以及适于对一些特殊图表类型进行一定的数据类型定制。但是缺点是,为匹配这种数据输入形式,常需要有数据处理的过程,把数据分割设置到各个系列(和类目轴)中。此外,不利于多个系列共享一份数据,也不利于基于数据原始进行图表类型、系列的映射安排。在没dataset之前,拿到数据第一件事就是数据各种convert。这里有几个关键option属性,如果转换不合理echarts图是不会出来的。
图例:
legend: {
// data:['邮件营销','联盟广告','视频广告','直接访问','搜索引擎']
data:[
{name:'邮件营销',icon:'circle'},
{name:'联盟广告',icon:'circle'},
{name:'视频广告',icon:'circle'},
{name:'直接访问',icon:'circle'},
{name:'搜索引擎',icon:'circle'},
]
},
X轴:
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一','周二','周三','周四','周五','周六','周日']
},
系列:
series: [
{
name:'邮件营销',
type:'line',
data:[120, 132, 101, 134, 90, 230, 210]
},
{
name:'联盟广告',
type:'line',
data:[220, 182, 191, 234, 290, 330, 310]
},
{
name:'视频广告',
type:'line',
data:[150, 232, 201, 154, 190, 330, 410]
},
{
name:'直接访问',
type:'line',
data:[320, 332, 301, 334, 390, 330, 320]
},
{
name:'搜索引擎',
type:'line',
data:[820, 932, 901, 934, 1290, 1330, 1320]
}
],
其中legend.data要跟series一一对应,而xAxis中data又要和series data对应。如果不这么做,渲染出来就是缺胳膊少腿,是不是头都大了。有没有方便做法呢,接住往下看。
dataset入门
在dataset之前数据和其他配置是融在一起的。应该echarts团队意识到这个问题。所以ECharts 4 新提供了 数据集(dataset)组件来单独声明数据,他带来了这些效果:
|
先来一个简单dataset例子:
option={
title: {
text: '多折线图'
},
tooltip: {
trigger: 'axis'
},
legend: {
bottom: '3%',
data:[
{name:'邮件营销',icon:'circle'},
{name:'联盟广告',icon:'circle'},
{name:'视频广告',icon:'circle'},
{name:'直接访问',icon:'circle'},
{name:'搜索引擎',icon:'circle'},
]
},
dataset:{
source:{
// 提供一份数据。
week:['周一','周二','周三','周四','周五','周六','周日'],
'邮件营销':[120, 132, 101, 134, 90, 230, 210],
'联盟广告':[220, 182, 191, 234, 290, 330, 310],
'视频广告':[150, 232, 201, 154, 190, 330, 410],
'直接访问':[320, 332, 301, 334, 390, 330, 320],
'搜索引擎':[820, 932, 901, 934, 1290, 1330, 1320],
}
},
grid: {
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true
},
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: {readOnly: false},
magicType: {type: ['line', 'bar']},
restore: {},
saveAsImage: {}
}
},
xAxis: {
// 声明一个 X 轴,类目轴(category)。默认情况下,类目轴对应到 dataset 第一列。
type: 'category',
boundaryGap: false,
},
yAxis: {
// 声明一个 Y 轴,数值轴。
type: 'value'
},
// 声明多个 line 系列,默认情况下,每个系列会自动对应到 dataset 的每一列。
series: [
{
type:'line'
},
{
type:'line'
},
{
type:'line'
},
{
type:'line'
},
{
type:'line'
},
],
color: [
'#FF9C6E', '#FFC069', '#95DE64', '#5CDBD3', '#69C0FF', '#85A5FF', '#B37FEB', '#FF85C0'
]
};
关于数据的各种格式,前面示例是按列的 key-value 形式,dataset 也支持例如按行的 key-value 形式(对象数组)及二维数组等。更多参考
dataset使用场景
目前并非所有图表都支持 dataset。支持 dataset 的图表有: line、bar、pie、scatter、effectScatter、parallel、candlestick、map、funnel、custom。 后续会有更多的图表进行支持。不支持就用ECharts 4 之前一直以来的数据声明方式仍然被正常支持,如果系列已经声明了 series.data, 那么就会使用 series.data 而非 dataset。其实,series.data 也是种会一直存在的重要设置方式。一些特殊的非 table 格式的图表,如 treemap、graph、lines 等,现在仍不支持在 dataset 中设置,仍然需要使用 series.data。另外,对于巨大数据量的渲染(如百万以上的数据量),需要使用 appendData 进行增量加载,这种情况不支持使用 dataset。
Echars定制化封装
现在说说监控、分析、APM类图表大概分成两大类:
普通数据图表展示
下面截取APM监控领域比较知名的几款应用图表展示,不难发现X轴都时间,Y轴是相应的数据,不同的图例(线条)就代表不同的指标,如CPU使用率、平均响应时间、成功率等。有一张图一条线的也有多条线的。抽象出细分领域模型,对接下来封装有至关重要作用。
关系数据展示
如何把有关联的单个指标串联起来、或者程序的调用链路展示、某个具体业务流程及业务功能点之间的呈现、展现整个集群的拓扑关系,调用依赖度,告警,实例等关键信息、从单个应用的角度,展现应用的上下游关系,TopN的服务和服务器。就会用到关系数据展示。
关于关系型可视化展示,有兴趣的可以看之前写的文章,antV G6中以html为节点的自动分层布局数据可视化详解,后续还有更多关系类数据可视化内容及实践总结。
回到正题,今天普通数据图表展示是主角。如上述所说结合业界产品及现在项目,Line类型图表占大多数。在没dataset之前,转换数据、按设计规范定制样式、数据样式混编等。对于同一个项目Line类型的图表样式已经是固定的,真正需要render的内容包括:图表的title、X轴时间、Y轴数值、图例legend。Dataset出现减轻转换的负担,但没有解放。细心的读者应该注意到开篇的dataset入门中,legend中data及series中type是要自己手动处理的。
在此基于React进行组件化封装。直接上代码,其中有一些ES6语法,不熟悉自行学习es6(阮一峰的es6入门) - ECMAScript 6入门。在WiseCharts中,数据及关键信息通过props从父组件传进来,不变的配置参数被封装在React组件里,开发的同事只要按照事先定义好的接口传入dataset,再也不需要手动去转legend中data及series中type了。
import React, {Component} from 'react';
import ReactEcharts from 'echarts-for-react';
const getKeys = data => Object.keys(data);
export default class WiseCharts extends Component {
static defaultProps = {
height: 400,
title: '默认名称',
type: 'line'
};
constructor(props) {
super(props);
this.getOption = this.getOption.bind(this);
}
getOption() {
const [first, ...legendData] = getKeys(this.props.data);
const legendDataArr = [];
legendData.forEach(item => {
legendDataArr.push({
name: item,
icon: 'circle'
});
});
return {
title: {
text: this.props.title,
},
tooltip: {
trigger: 'axis'
},
legend: {
bottom: '3%',
data: legendDataArr
},
dataset: {
source: this.props.data
},
grid: {
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true
},
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: {
readOnly: false
},
magicType: {
type: ['line', 'bar']
},
restore: {},
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
},
yAxis: {
type: 'value'
},
series: [...legendData].fill({
type: this.props.type
}),
color: [
'#FF9C6E', '#FFC069', '#95DE64', '#5CDBD3', '#69C0FF', '#85A5FF', '#B37FEB', '#FF85C0'
]
};
}
render() {
return (
<ReactEcharts option = {
this.getOption()
} style = {
{
height: '350px',
width: '60%'
}
}
className = 'react_for_echarts' / >
)}
}
父组件书写方式:
const data={
week: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
'邮件营销': [120, 132, 101, 134, 90, 230, 210],
'联盟广告': [220, 182, 191, 234, 290, 330, 310],
'视频广告': [150, 232, 201, 154, 190, 330, 410],
'直接访问': [320, 332, 301, 334, 390, 330, 320],
'搜索引擎': [820, 932, 901, 934, 1290, 1330, 1320],
}
//line类型图表
<WiseCharts type='line' title='多曲线图' data={data}/>
//bar类型图表
<WiseCharts type='bar' title='多柱状图' data={data}/>
这种封装方式受G2影响、简约了很多。之前手动转legend中data及series中type在组件内部已经处理掉了。
问题一 用dataset默认dataview异常
以为现在就万事大吉了,NO、NO、NO,其实在现在的项目有使用dataview的场景,那就把toolbox中数据视图开启来。
非dataset方式:
dataset方式:
数据都是一样,有没有发现异常。这个应该是echarts的一个bug。为此还提了一个issue
用dataset提供渲染数据并开启默认数据视图toolbox.feature.dataView,dataView显示异常 #9035
有问题就要解决啊,在配置项深处找到toolbox.feature.dataView.optionToContent,自定义 dataView 展现函数,用以取代默认的 textarea 使用更丰富的数据编辑。可以返回 dom 对象或者 html 字符串。
修改默认optionToContent渲染方式后
optionToContent: function otc(opt) {
const timeArr = Object.values(opt.dataset[0].source);
let table = `<table style="width:100%;text-align:center;background-color: #f5f5f5" class='reference'><tbody><tr>
<td>时间</td>${legendTrStr}
</tr>`;
for (let i = 0, l = timeArr[0].length; i < l; i += 1) {
let tdElm = ``;
for (let j = 1; j < timeArr.length; j += 1) {
const element = timeArr[j][i];
tdElm += `<td>${element}</td>`;
}
table += `<tr><td>${timeArr[0][i]}</td>${tdElm}
</tr>`;
}
table += '</tbody></table>';
return table;
}
问题二 图例区域太大导致遮挡住图表
方法一:legend.type: 'scroll':可滚动翻页的图例。当图例数量较多时可以使用。
方法二:可以设置 grid 控制图表区域位置。如,将 grid.top 设置得大一些,可以将绘图区域下移。
问题三 提示框 tooltip被其他图层遮罩
方法:额外附加到浮层的 css 样式 tooltip.extraCssText中z-index,合理设置echarts容器的position为包括relative,absolute,fixed样式等。
总结
在监控、分析领域,是有查看同一时间各项指标的数据需求的,比如某台主机CPU的使用率、内存展示情况等“黄金指标”。如果把这些指标放在同一个图中问题很简单,如果放在不同图(组件)中,想查看同一时间怎么处理呢。对,你猜到了,就是不同图表之间的联动。
敬请期待,下一篇。
最后还是要打一波广告的,平安科技集团运营管理部监控平台团队持续招人【前端岗位、运营岗位、算法岗位……】,如果想和我们一起打造极致的企业级全链路端到端监控平台,欢迎把简历投递到 WULIANGCHEN511@pingan.com.cn 微信:wlc5720 备注:开源中国
更新 :增加可拖拽,可大可小,自动布局GridCharts
项目代码地址:https://github.com/wlc534/react-r3-saga