干货」用 Vue + Echarts 打造你的专属可视化界面(下)

作者简介:
Jaked
8年前端工作经验,
主要分享:职业发展方面、前端技术、面试技巧等。
公众号:超哥前端小栈
掘金:https://juejin.im/user/5a5d4522518825732b19d364/posts

接上一篇文章 《「干货」用 Vue + Echarts 打造你的专属可视化界面(上)》,今天着重介绍 标记 的用法,来实现下图中的效果。

image

所用的 Echarts 的版本号为:v4.3。v-charts 的版本号为:v1.19.0。

标记的用法有很多,今天要介绍的场景有:折线图、柱状图、折线图 + 柱状图。

折线图标记 —— symbol

上图中,折线的拐点处,一些 “小圆点”,被替换成了小图标。

要实现这样的效果,需要先理一下原始的需求:

  • 每种标记,代表一种活动类型。

  • 有一些活动会发生在某些时间点,或时间段内,需要在活动发生的日期上标注出该活动的类型。

  • 当同一天有多个活动发生时,采用复合图标,并当展示 tooltip 时,显示当日的每一个活动的信息。

  • tooltip的布局为:首先显示当前日期,中段展示各个活动的图标以及活动名称,最后展示指标名称和对应的数值。

  • 没有活动的日期,拐点处与 tooltip 照常显示原先的样式。

所以,它的完整效果,应该是这样的:

image

要实现这样的效果,需要思考以下几点:

  • 如何通过日期的定向匹配,将活动的图标以“打点”的形式,定在折线拐点处。

  • 图标的大小,要怎么设置?它需要区别于正常的拐点标识。

  • tooltip 的样式要如何改写?还需要兼容没有活动的日期样式。

思路解析:

首先,为了做日期的定向匹配,需要设计的数据结构如下:

data: [    {        id: '1, 1, 3, 2',        date: '2019-10-10',        name: 'test-name1, test-name2, test-name3, test-name4'    },    ...]

接下来的这个 核心 属性:symbol 是关键,它其实就是折线上的 拐点

symbol 支持的标记类型有:circle、rect、roundRect、triangle、diamond、pin、arrow、none。默认情况下为 emptyCircle,也就是空心的圆。

它还支持链接的格式:'image://http://xxx.xxx.xxx/a/b.png'。此外,如果需要每个数据的图形不一样,可以设置为如下格式的回调函数:

(value: Array|number, params: Object) => string

其中第一个参数 value 为 data 中的数据值。第二个参数 params 是其它的数据项参数。

这些正是我们需要的。踩坑亲测:上述回调函数,只有在最新版的 V4.3 中才能正常使用,否则会报错。这也是为何,我在一开始就先强调了 Echarts 的版本问题。具体实现如下:

...// mock 包含标注的数据结构dataList: [    {        id: '1, 1, 3, 2',        date: '2019-10-10',        name: 'test-name1, test-name2, test-name3, test-name4'    },    ...    {        id: '1',        date: '2019-10-17',        name: 'test-name1'    }],...setChartExtend () {    this.chartExtend = {        series: (v) => {            Array.from(v).forEach((e, idx) => {                e.symbol = (value, params) => {                    return getSymbolIcon(params.name, dataList);                };                e.symbolSize = (value, params) => {                    return getSymbolSize(params.name, dataList);                };            });            return v;        },        ...    };},getSymbolIcon (date, dataList) {    const defaultSymbol = 'circle';    if (!dataList || dataList.length === 0) {        return defaultSymbol;    }    // 通过日期匹配,找到对应的标注对象    const dataItem = dataList.find(item => item.date === date);    const iconUrl = getSymbolUrl(dataItem.id);    return iconUrl ? iconUrl : defaultSymbol;},getSymbolSize (date, dataList) {    if (!dataList || dataList.length === 0) {        return 4;    }    // 通过日期匹配,找到对应的标注对象    const dataItem = dataList.find(item => item.date === date);    return dataItem ? 15 : 4;},getSymbolUrl (id) {    // 这里需要额外先做一层准备工作:将图标按 id 对应图标进行命名,然后传到自家的cdn上    // 命名可以像这样:symbol-icon-1.jpg、symbol-icon-2.jpg 等等    // 这里拿到标注的id,拼上链接返回即可    // 形如:image://http://xxx.xxx.com/symbol-icon-1.jpg    // 遇到多个 id 的情况,可以多加一个复合图标来处理,id 可以定为 0}

最后的一个问题,如何改写 tooltip 的样式问题,以做好兼容呢?

之前说到,tooltip 的布局分为三块:日期、标注信息、具体数值。那么我们就以此,来重新绘制 tooltip。

image

tooltip 支持 formatter 回调函数,它的返回值类型是 Sting。

// 回调函数格式(params: Object|Array, ticket: string, callback: (ticket: string, html: string)) => string

日期的信息,可以通过 params[0].axisValue 来获取。

获取标注信息的方法,与上述获取图标的思路类似,只是这里需要展示具体的标注类型和名称。

具体的数值,可以通过 params 中的 marker,seriesName,value 等属性获得。具体实现如下:

setChartExtend () {    this.chartExtend = {        series: (v) => {            ...        },        tooltip: {            formatter: (params) => {                return getTooltipResult(params, dataList);            }        }    };},getTooltipResult (params, dataList) {    const dateResult = params[0].axisValue;    // 获取原版 tooltip 的渲染结构    const originalResultObj = getOriginalTooltipResult(params);    if (!dataList || dataList.length === 0) {        return dateResult + originalResultObj.strResult;    }    const dataItem = dataList.find(item => item.date === date);    if (dataItem) {        return dateResult +  getSymbolResult(dataItem, originalResultObj.strResult);    }    return dateResult + originalResultObj.strResult;},getOriginalTooltipResult (params) {    let result = '';    params.forEach((param, idx) => {        // value 会因为 seriesType 的不同,类型也会有不同        let value = Object.prototype.toString.call(param.value) === '[object Array]' ? param.value[1] : param.value;        const str = `${param.marker}${param.seriesName}: ${ value }
`; result += str; }); return { strResult: result };},getSymbolResult (dataItem, originalResult) { // 将 dataItem 的 id 转为数组的形式,循环渲染输出图标与名称的组合 const dataIds = dataItem.id.split(','); const dataNames = dataItem.name.split(','); dataIds.forEach ((id, idx) => { // 通过 id 换取 图标的链接 const iconUrl = ...; // 仿照 param.marker 的 style 写法,渲染图标样式 const str = `${dataNames[idx]}
`; result += str; }); return result + originalResult;}

或许有同学会问 getOriginalTooltipResult 方法返回的值,里面只有一个 strResult,为何要设计为对象?

其实是为了方便扩展。例如,可以在日期的后面跟上下方具体数据的值的总计。那就需要通过 getOriginalTooltipResult 方法里的 params 循环,计算出 total,在配合上样式,生成一个 strTotal。

getOriginalTooltipResult (params) {    ...    return {        strTotal: strTotal,        strResult: result    };}

此外,在实际的业务中,还可能会出现某些线的数值是百分比。那就需要再对 getOriginalTooltipResult 方法做扩展,比如传入一个 options 对象:

getOriginalTooltipResult = (params, options = { isLinePercent: false, isShowTotal: false }) {    ...}

至此,折线图标记的渲染,就能完美地呈现了。

柱状图标记 —— markPoint

很尴尬的一点是:柱状图没有 symbol 属性。也就意味着上面的折线图的那一套,在柱状图中玩不转了。

image

没办法,只能从头查文档,继续找资料。经过一番“摸爬滚打”,终于发现了 markPoint 这个属性。在 markPoint 这个对象里面,可以设置 symbol,这样的话,那么之前搞出来的那一套就没有白费呀?!?

需要注意的是,markPoint 的默认 symbol 为 'pin',就是一个气泡的图标。另外,想要让 markPoint 的标记出现,就必须设置它的 data 属性。我们需要设置 data 里的这样几个属性:

data: [    {        symbol: '...', // 设置标记的图标链接        symbolSize: 15, // 设置标记的大小        coord: [index, 0], // x 轴的第 index 个上,打标记        symbolOffset: [0, 0] // 将标记定位在 x 轴上    },    ...]

具体的实现代码如下:

...// mock 包含标注的数据结构dataList: [    {        id: '1, 1, 3, 2',        date: '2019-10-10',        name: 'test-name1, test-name2, test-name3, test-name4'    },    ...    {        id: '1',        date: '2019-10-17',        name: 'test-name1'    }],...setChartExtend () {    this.chartExtend = {        series: (v) => {            Array.from(v).forEach((e, idx) => {                e.markPoint = {                    data: getMarkPointData(this.chartData.rows, dataList)                };            });        },        ...    };},getMarkPointData (rows, dataList) {    const results = [];    rows.forEach((row, index) => {        // 通过日期匹配,找到对应的标注对象        const dataItem = dataList.find(item => item.date === row.date);        if (dataItem) {            results.push({                symbol: getSymbolUrl(dataItem.id),                symbolSize: 15,                coord: [index, 0],                symbolOffset: [0, 0]            });        }    });    return results;},getSymbolUrl (id) {    // 这里需要额外先做一层准备工作:将图标按 id 对应图标进行命名,然后传到自家的cdn上    // 命名可以像这样:symbol-icon-1.jpg、symbol-icon-2.jpg 等等    // 这里拿到标注的id,拼上链接返回即可    // 形如:image://http://xxx.xxx.com/symbol-icon-1.jpg    // 遇到多个 id 的情况,可以多加一个复合图标来处理,id 可以定为 0}

因为 markPoint 在设置 data 时,取不到日期的数据,所以就需要用到 chartData 中的 rows 了。

在 rows 的循环中,如果匹配到当天需要打标记,则往结果数组中存入刚才预设的数据结构,最终返回给 markPoint 的 data,渲染展现。效果如下:

image

tooltip 的实现方法,不受图表的类型影响,是可以通用的,故此处不再赘述。

另外,有的时候,会遇到需要处理柱状图是否堆叠的效果。这会影响 symbolOffset 的定位,为了美观,可以这样处理:对于非堆叠的项,将之往右偏移 50%,将标记居中展示,即 symbolOffset : ['50%', 0]

折线图 + 柱状图

最后来个 组合拳,折线图与柱状图的复合型图表结构,像下面这样:

image

从代码实现上,我们当然可以给每一根符合条件的柱子,和折线的拐点,都打上标记。但在界面设计上,为了美观,我们选择只给折线的拐点打上标记,并且忽略了虚线的拐点。

这样的设计初衷是:标记,只是为了给人提个醒,是为了告诉查阅者,这一天因为发生了某些特殊事件,而导致数据发生了较为明显的变化。所以,由此得到的结论是:每天只要出现一个标记就够了。

具体的实现,其实很简单,只需要在渲染时判断 series 的 type 即可:

setChartExtend () {    this.chartExtend = {        series: (v) => {            Array.from(v).forEach((e, idx) => {                if (e.type === 'bar') {                    // 设置柱状图 markPoint 的方法                    ...                }                if (e.type === 'line') {                    // 设置折线图 symbol、symbolSize 的方法                    ...                }            });        },        ...    };}

总结

Echarts 可以实现的效果有很多,本篇涉及其中 “标记” 的渲染。在折线图的拐点处,用 symbol 做了匹配化的处理。在柱状图中,因为没有直接的 symbol,转而使用 markPoint 来实现,采用将标记定位在某个维度上的做法。效果都挺不错的。

不过,在后续的使用中,发现了另一个尴尬的情况:在折线图中,当点击图例中的某一项,使其数据隐藏,然后再次点击重新渲染后,发现 symbol 的自定义图标不显示了。

作者简介:
Jaked
8年前端工作经验,
主要分享:职业发展方面、前端技术、面试技巧等。
公众号:超哥前端小栈
掘金:https://juejin.im/user/5a5d4522518825732b19d364/posts

本文已经获得Jaked老师授权转发,其他人若有兴趣转载,请直接联系作者授权。

你可能感兴趣的:(干货」用 Vue + Echarts 打造你的专属可视化界面(下))