干貨」用 Vue + Echarts 打造你的專(zhuān)屬可視化界面(下)

作者簡(jiǎn)介:
Jaked
8年前端工作經(jīng)驗(yàn),
主要分享:職業(yè)發(fā)展方面、前端技術(shù)、面試技巧等。
公眾號(hào):超哥前端小棧
掘金:https://juejin.im/user/5a5d4522518825732b19d364/posts

接上一篇文章 《「干貨」用 Vue + Echarts 打造你的專(zhuān)屬可視化界面(上)》,今天著重介紹 標(biāo)記 的用法,來(lái)實(shí)現(xiàn)下圖中的效果。

image

所用的 Echarts 的版本號(hào)為:v4.3。v-charts 的版本號(hào)為:v1.19.0。

標(biāo)記的用法有很多,今天要介紹的場(chǎng)景有:折線(xiàn)圖、柱狀圖、折線(xiàn)圖 + 柱狀圖。

折線(xiàn)圖標(biāo)記 —— symbol

上圖中,折線(xiàn)的拐點(diǎn)處,一些 “小圓點(diǎn)”,被替換成了小圖標(biāo)。

要實(shí)現(xiàn)這樣的效果,需要先理一下原始的需求:

  • 每種標(biāo)記,代表一種活動(dòng)類(lèi)型。

  • 有一些活動(dòng)會(huì)發(fā)生在某些時(shí)間點(diǎn),或時(shí)間段內(nèi),需要在活動(dòng)發(fā)生的日期上標(biāo)注出該活動(dòng)的類(lèi)型。

  • 當(dāng)同一天有多個(gè)活動(dòng)發(fā)生時(shí),采用復(fù)合圖標(biāo),并當(dāng)展示 tooltip 時(shí),顯示當(dāng)日的每一個(gè)活動(dòng)的信息。

  • tooltip的布局為:首先顯示當(dāng)前日期,中段展示各個(gè)活動(dòng)的圖標(biāo)以及活動(dòng)名稱(chēng),最后展示指標(biāo)名稱(chēng)和對(duì)應(yīng)的數(shù)值。

  • 沒(méi)有活動(dòng)的日期,拐點(diǎn)處與 tooltip 照常顯示原先的樣式。

所以,它的完整效果,應(yīng)該是這樣的:

image

要實(shí)現(xiàn)這樣的效果,需要思考以下幾點(diǎn):

  • 如何通過(guò)日期的定向匹配,將活動(dòng)的圖標(biāo)以“打點(diǎn)”的形式,定在折線(xiàn)拐點(diǎn)處。

  • 圖標(biāo)的大小,要怎么設(shè)置?它需要區(qū)別于正常的拐點(diǎn)標(biāo)識(shí)。

  • tooltip 的樣式要如何改寫(xiě)?還需要兼容沒(méi)有活動(dòng)的日期樣式。

思路解析:

首先,為了做日期的定向匹配,需要設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu)如下:

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

接下來(lái)的這個(gè) 核心 屬性:symbol 是關(guān)鍵,它其實(shí)就是折線(xiàn)上的 拐點(diǎn)

symbol 支持的標(biāo)記類(lèi)型有:circle、rect、roundRect、triangle、diamond、pin、arrow、none。默認(rèn)情況下為 emptyCircle,也就是空心的圓。

它還支持鏈接的格式:'image://http://xxx.xxx.xxx/a/b.png'。此外,如果需要每個(gè)數(shù)據(jù)的圖形不一樣,可以設(shè)置為如下格式的回調(diào)函數(shù):

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

其中第一個(gè)參數(shù) value 為 data 中的數(shù)據(jù)值。第二個(gè)參數(shù) params 是其它的數(shù)據(jù)項(xiàng)參數(shù)。

這些正是我們需要的。踩坑親測(cè):上述回調(diào)函數(shù),只有在最新版的 V4.3 中才能正常使用,否則會(huì)報(bào)錯(cuò)。這也是為何,我在一開(kāi)始就先強(qiáng)調(diào)了 Echarts 的版本問(wèn)題。具體實(shí)現(xiàn)如下:

<ve-line ... :extend="chartExtend"></ve-line>...// mock 包含標(biāo)注的數(shù)據(jù)結(jié)構(gòu)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;    }    // 通過(guò)日期匹配,找到對(duì)應(yīng)的標(biāo)注對(duì)象    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;    }    // 通過(guò)日期匹配,找到對(duì)應(yīng)的標(biāo)注對(duì)象    const dataItem = dataList.find(item => item.date === date);    return dataItem ? 15 : 4;},getSymbolUrl (id) {    // 這里需要額外先做一層準(zhǔn)備工作:將圖標(biāo)按 id 對(duì)應(yīng)圖標(biāo)進(jìn)行命名,然后傳到自家的cdn上    // 命名可以像這樣:symbol-icon-1.jpg、symbol-icon-2.jpg 等等    // 這里拿到標(biāo)注的id,拼上鏈接返回即可    // 形如:image://http://xxx.xxx.com/symbol-icon-1.jpg    // 遇到多個(gè) id 的情況,可以多加一個(gè)復(fù)合圖標(biāo)來(lái)處理,id 可以定為 0}

最后的一個(gè)問(wèn)題,如何改寫(xiě) tooltip 的樣式問(wèn)題,以做好兼容呢?

之前說(shuō)到,tooltip 的布局分為三塊:日期、標(biāo)注信息、具體數(shù)值。那么我們就以此,來(lái)重新繪制 tooltip。

image

tooltip 支持 formatter 回調(diào)函數(shù),它的返回值類(lèi)型是 Sting。

// 回調(diào)函數(shù)格式(params: Object|Array, ticket: string, callback: (ticket: string, html: string)) => string

日期的信息,可以通過(guò) params[0].axisValue 來(lái)獲取。

獲取標(biāo)注信息的方法,與上述獲取圖標(biāo)的思路類(lèi)似,只是這里需要展示具體的標(biāo)注類(lèi)型和名稱(chēng)。

具體的數(shù)值,可以通過(guò) params 中的 marker,seriesName,value 等屬性獲得。具體實(shí)現(xiàn)如下:

setChartExtend () {    this.chartExtend = {        series: (v) => {            ...        },        tooltip: {            formatter: (params) => {                return getTooltipResult(params, dataList);            }        }    };},getTooltipResult (params, dataList) {    const dateResult = params[0].axisValue;    // 獲取原版 tooltip 的渲染結(jié)構(gòu)    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 會(huì)因?yàn)?seriesType 的不同,類(lèi)型也會(huì)有不同        let value = Object.prototype.toString.call(param.value) === '[object Array]' ? param.value[1] : param.value;        const str = `${param.marker}${param.seriesName}: ${ value }<br>`;        result += str;    });    return {        strResult: result    };},getSymbolResult (dataItem, originalResult) {    // 將 dataItem 的 id 轉(zhuǎn)為數(shù)組的形式,循環(huán)渲染輸出圖標(biāo)與名稱(chēng)的組合    const dataIds = dataItem.id.split(',');    const dataNames = dataItem.name.split(',');    dataIds.forEach ((id, idx) => {        // 通過(guò) id 換取 圖標(biāo)的鏈接        const iconUrl = ...;        // 仿照 param.marker 的 style 寫(xiě)法,渲染圖標(biāo)樣式        const str = `<img src="${iconUrl}" width="11" height="11" style="display: inline-block; margin-right: 4px; margin-left: -1px;">${dataNames[idx]}<br>`;        result += str;    });    return result + originalResult;}

或許有同學(xué)會(huì)問(wèn) getOriginalTooltipResult 方法返回的值,里面只有一個(gè) strResult,為何要設(shè)計(jì)為對(duì)象?

其實(shí)是為了方便擴(kuò)展。例如,可以在日期的后面跟上下方具體數(shù)據(jù)的值的總計(jì)。那就需要通過(guò) getOriginalTooltipResult 方法里的 params 循環(huán),計(jì)算出 total,在配合上樣式,生成一個(gè) strTotal。

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

此外,在實(shí)際的業(yè)務(wù)中,還可能會(huì)出現(xiàn)某些線(xiàn)的數(shù)值是百分比。那就需要再對(duì) getOriginalTooltipResult 方法做擴(kuò)展,比如傳入一個(gè) options 對(duì)象:

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

至此,折線(xiàn)圖標(biāo)記的渲染,就能完美地呈現(xiàn)了。

柱狀圖標(biāo)記 —— markPoint

很尷尬的一點(diǎn)是:柱狀圖沒(méi)有 symbol 屬性。也就意味著上面的折線(xiàn)圖的那一套,在柱狀圖中玩不轉(zhuǎn)了。

image

沒(méi)辦法,只能從頭查文檔,繼續(xù)找資料。經(jīng)過(guò)一番“摸爬滾打”,終于發(fā)現(xiàn)了 markPoint 這個(gè)屬性。在 markPoint 這個(gè)對(duì)象里面,可以設(shè)置 symbol,這樣的話(huà),那么之前搞出來(lái)的那一套就沒(méi)有白費(fèi)呀?!?

需要注意的是,markPoint 的默認(rèn) symbol 為 'pin',就是一個(gè)氣泡的圖標(biāo)。另外,想要讓 markPoint 的標(biāo)記出現(xiàn),就必須設(shè)置它的 data 屬性。我們需要設(shè)置 data 里的這樣幾個(gè)屬性:

data: [    {        symbol: '...', // 設(shè)置標(biāo)記的圖標(biāo)鏈接        symbolSize: 15, // 設(shè)置標(biāo)記的大小        coord: [index, 0], // x 軸的第 index 個(gè)上,打標(biāo)記        symbolOffset: [0, 0] // 將標(biāo)記定位在 x 軸上    },    ...]

具體的實(shí)現(xiàn)代碼如下:

<ve-histogram :data="chartData" :extend="chartExtend"></ve-histogram>...// mock 包含標(biāo)注的數(shù)據(jù)結(jié)構(gòu)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) => {        // 通過(guò)日期匹配,找到對(duì)應(yīng)的標(biāo)注對(duì)象        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) {    // 這里需要額外先做一層準(zhǔn)備工作:將圖標(biāo)按 id 對(duì)應(yīng)圖標(biāo)進(jìn)行命名,然后傳到自家的cdn上    // 命名可以像這樣:symbol-icon-1.jpg、symbol-icon-2.jpg 等等    // 這里拿到標(biāo)注的id,拼上鏈接返回即可    // 形如:image://http://xxx.xxx.com/symbol-icon-1.jpg    // 遇到多個(gè) id 的情況,可以多加一個(gè)復(fù)合圖標(biāo)來(lái)處理,id 可以定為 0}

因?yàn)?markPoint 在設(shè)置 data 時(shí),取不到日期的數(shù)據(jù),所以就需要用到 chartData 中的 rows 了。

在 rows 的循環(huán)中,如果匹配到當(dāng)天需要打標(biāo)記,則往結(jié)果數(shù)組中存入剛才預(yù)設(shè)的數(shù)據(jù)結(jié)構(gòu),最終返回給 markPoint 的 data,渲染展現(xiàn)。效果如下:

image

tooltip 的實(shí)現(xiàn)方法,不受圖表的類(lèi)型影響,是可以通用的,故此處不再贅述。

另外,有的時(shí)候,會(huì)遇到需要處理柱狀圖是否堆疊的效果。這會(huì)影響 symbolOffset 的定位,為了美觀,可以這樣處理:對(duì)于非堆疊的項(xiàng),將之往右偏移 50%,將標(biāo)記居中展示,即 symbolOffset : ['50%', 0]

折線(xiàn)圖 + 柱狀圖

最后來(lái)個(gè) 組合拳,折線(xiàn)圖與柱狀圖的復(fù)合型圖表結(jié)構(gòu),像下面這樣:

image

從代碼實(shí)現(xiàn)上,我們當(dāng)然可以給每一根符合條件的柱子,和折線(xiàn)的拐點(diǎn),都打上標(biāo)記。但在界面設(shè)計(jì)上,為了美觀,我們選擇只給折線(xiàn)的拐點(diǎn)打上標(biāo)記,并且忽略了虛線(xiàn)的拐點(diǎn)。

這樣的設(shè)計(jì)初衷是:標(biāo)記,只是為了給人提個(gè)醒,是為了告訴查閱者,這一天因?yàn)榘l(fā)生了某些特殊事件,而導(dǎo)致數(shù)據(jù)發(fā)生了較為明顯的變化。所以,由此得到的結(jié)論是:每天只要出現(xiàn)一個(gè)標(biāo)記就夠了。

具體的實(shí)現(xiàn),其實(shí)很簡(jiǎn)單,只需要在渲染時(shí)判斷 series 的 type 即可:

setChartExtend () {    this.chartExtend = {        series: (v) => {            Array.from(v).forEach((e, idx) => {                if (e.type === 'bar') {                    // 設(shè)置柱狀圖 markPoint 的方法                    ...                }                if (e.type === 'line') {                    // 設(shè)置折線(xiàn)圖 symbol、symbolSize 的方法                    ...                }            });        },        ...    };}

總結(jié)

Echarts 可以實(shí)現(xiàn)的效果有很多,本篇涉及其中 “標(biāo)記” 的渲染。在折線(xiàn)圖的拐點(diǎn)處,用 symbol 做了匹配化的處理。在柱狀圖中,因?yàn)闆](méi)有直接的 symbol,轉(zhuǎn)而使用 markPoint 來(lái)實(shí)現(xiàn),采用將標(biāo)記定位在某個(gè)維度上的做法。效果都挺不錯(cuò)的。

不過(guò),在后續(xù)的使用中,發(fā)現(xiàn)了另一個(gè)尷尬的情況:在折線(xiàn)圖中,當(dāng)點(diǎn)擊圖例中的某一項(xiàng),使其數(shù)據(jù)隱藏,然后再次點(diǎn)擊重新渲染后,發(fā)現(xiàn) symbol 的自定義圖標(biāo)不顯示了。

作者簡(jiǎn)介:
Jaked
8年前端工作經(jīng)驗(yàn),
主要分享:職業(yè)發(fā)展方面、前端技術(shù)、面試技巧等。
公眾號(hào):超哥前端小棧
掘金:https://juejin.im/user/5a5d4522518825732b19d364/posts

本文已經(jīng)獲得Jaked老師授權(quán)轉(zhuǎn)發(fā),其他人若有興趣轉(zhuǎn)載,請(qǐng)直接聯(lián)系作者授權(quán)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 最近在實(shí)習(xí)期間,接手了一個(gè)數(shù)據(jù)搜索項(xiàng)目,需要我從后臺(tái)調(diào)取數(shù)據(jù),并將之用折線(xiàn)圖直方圖顯示出來(lái),并且可以將多組數(shù)據(jù)繪制...
    南客nk閱讀 8,280評(píng)論 3 21
  • Echarts總結(jié) 簡(jiǎn)介:js圖標(biāo)庫(kù),可以兼容在pc和移動(dòng)端。Echarts底層使用canvas實(shí)現(xiàn),支持多圖表、...
    窩頭咸菜閱讀 8,407評(píng)論 0 4
  • 一、簡(jiǎn)單介紹 Echart是百度研發(fā)團(tuán)隊(duì)開(kāi)發(fā)的一款報(bào)表視圖JS插件,功能十分強(qiáng)大,使用內(nèi)容做簡(jiǎn)單記錄;(EChar...
    Marin_chen閱讀 21,999評(píng)論 9 5
  • ECharts,一個(gè)使用 JavaScript 實(shí)現(xiàn)的開(kāi)源可視化庫(kù),可以流暢的運(yùn)行在 PC 和移動(dòng)設(shè)備上,兼容當(dāng)前...
    YOYO做設(shè)計(jì)閱讀 9,627評(píng)論 2 7
  • 苦澀的它濃郁,卻張顯它的魅力如咖啡。
    蒼野的仰望閱讀 433評(píng)論 0 0

友情鏈接更多精彩內(nèi)容