Echarts 定制化封装之dataset

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 组件来提供数据),指定数据到视觉的映射(由 encode 属性来指定映射),形成图表。
  • 数据和其他配置可以被分离开来,使用者相对便于进行单独管理,也省去了一些数据处理的步骤。
  • 数据可以被多个系列或者组件复用,对于大数据,不必为每个系列创建一份。
  • 支持更多的数据的常用格式,例如二维数组、对象数组等,一定程度上避免使用者为了数据格式而进行转换。

 

先来一个简单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的服务和服务器。就会用到关系数据展示。

113140_CjM3_3298482.png

 

关于关系型可视化展示,有兴趣的可以看之前写的文章,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

转载于:https://my.oschina.net/u/3298482/blog/2032375

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值