「乾貨」用 Vue + Echarts 打造你的專屬可視化界面(上)

timg.jpg

前言

在近期的項目中,有大量處理可視化數據的需求。說起這個,相信很多同學跟我一樣,都會想到用 Echarts 來實現。沒錯,Echarts 擁有高度可定製化的配置,以及非常詳盡的開發文檔,並且它的最新版已經更新到了 v4.3

不過,也是因爲 Echarts 的開發文檔過於龐雜,對於不熟悉 Echarts 的同學來說,在查找某個效果時,可能需要耗費大量的精力。雖然它也配備了一些官方實例,但或許往往只能借鑑其中的個別配置,還需要回到對應的文檔中做印證,另外還得自己做 demo,嘗試效果。。。踩坑的過程,很艱辛。

但同時也是收穫滿滿的。今天的這篇文章,主要總結近段時間結合 Echarts 實現數據可視化的一些心得體會。其實只需要一些小技巧,就能實現下面這樣 “美美噠” 的圖表了。

希望給有需要的同學一些啓發,相信你也可以定製出自己專屬的 Echarts 數據可視化風格。

版本說明 與 v-charts

在踩坑的期間,我查找了不少資料。發現一個不太友好的體驗:一些文章中僅僅敘述了某個效果的實現,卻沒提到它的當前版本。當我拿來嘗試的時候,卻發現怎麼也出不來效果,而且運氣不好的話,還會報錯,這讓我很無奈。

所以,爲了避免無謂的麻煩。首先需要申明,本篇文章中的各種配置以及它的效果,是基於 Echarts 目前的最新版 V4.3 實現的。

然後,因爲項目採用了 Vue 搭建,所以我搭配了 v-charts 來實現圖表效果。v-charts 是由 “餓了麼前端” 開發維護的基於 Vue2.0 和 Echarts 封裝的圖表組件。我用下來感覺挺不錯的,文檔很清晰,還有相配合的例子,非常容易上手。它目前的最新版是 v1.19.0

v-charts 一般常用的圖表有:ve-line(折線圖)、ve-histogram(柱狀圖)、ve-pie(餅圖)、ve-ring(環圖)等等。使用時,可以直接將單個圖表引入到項目中。

import VeLine from 'v-charts/lib/line.common';

Vue.component(VeLine.name, VeLine);

一種典型的 v-charts 的 data 屬性數據格式如下:

{
  columns: ['日期', '訪問用戶', '下單用戶'],
  rows: [
    { '日期': '2018-05-22', '訪問用戶': 32371, '下單用戶': 19810 },
    { '日期': '2018-05-23', '訪問用戶': 12328, '下單用戶': 4398 },
    { '日期': '2018-05-24', '訪問用戶': 92381, '下單用戶': 52910 }
  ]
}

其中,columns 中是維度和指標的集合,v-charts 中的大部分圖表都是單維度多指標,所以默認第一個值爲 維度,剩餘的值爲指標。另外,rows 中是數據的集合。

再舉一個柱狀圖與折線圖組合的典型例子。

它的代碼實現也十分簡單,只需額外配置 settings 參數即可,代碼如下:

<template>
  <ve-histogram :data="chartData" :settings="chartSettings"></ve-histogram>
</template>

<script>
  export default {
    data () {
      this.chartSettings = {
        axisSite: { right: ['下單率'] },
        yAxisType: ['KMB', 'percent'],
        yAxisName: ['數值', '比率'],
        showLine: ['下單率'],
      }
      return {
        chartData: {
          columns: ['日期', '訪問用戶', '下單用戶', '下單率'],
          rows: [
            { '日期': '1/1', '訪問用戶': 1393, '下單用戶': 1093, '下單率': 0.32 },
            { '日期': '1/2', '訪問用戶': 3530, '下單用戶': 3230, '下單率': 0.26 },
            { '日期': '1/3', '訪問用戶': 2923, '下單用戶': 2623, '下單率': 0.76 },
            { '日期': '1/4', '訪問用戶': 1723, '下單用戶': 1423, '下單率': 0.49 },
            { '日期': '1/5', '訪問用戶': 3792, '下單用戶': 3492, '下單率': 0.323 },
            { '日期': '1/6', '訪問用戶': 4593, '下單用戶': 4293, '下單率': 0.78 }
          ]
        }
      }
    }
  }
</script>

所以,如果沒有很強烈的定製化需求的話,v-charts 已經可以滿足大多數的基礎需求了。

但如果想實現本文開頭的那張圖中的效果,還需要在 v-charts 的 extends 屬性上花一番功夫。extends 在本質上,對應的就是 Echarts 文檔的 配置項 中的所有屬性。

定製化的踩坑之旅

接下來,會在 v-charts 的基礎配置之上,具體地介紹一些很實用的小技巧,來幫助你實現 Echarts 的深度定製化需求。

1、配色

Echarts 提供了 colors 來自定義顏色,它接收一個數組作爲結果。這裏有一份來自產品小姐姐的 UI 配色方案,已經可以適用於絕大多數場景了。

代碼如下:

colors: ['#60ACFC', '#35C5EB', '#4DBECF', '#65D5B2', '#5BC4A0', '#9DDD81', '#D4ED58', '#FFDB43', '#FEB54E', '#FF9D68']

2、虛線

v-charts 中的 x 軸實線看上去有點生硬,可以將它變爲虛線,看上去就會清爽很多了,具體可以配置 setOption 下的 yAxis 屬性。

yAxis (item) {
    item[0].splitLine = Object.assign({}, {
        lineStyle: {
            color: '#e1e2e2',
            type: 'dashed'
        }
    });

    return item;
}

這裏用到了 es6 中 function 的簡寫模式。其中的 item 指的是 y 軸,爲什麼是個數組呢?那是因爲一般情況下單個直角座標系 grid 組件最多隻能放左右兩個 y 軸。那麼 item[0] 是指左側的最常用的 y 軸,item[1] 是指當有雙軸情況下的右側的 y 軸。

這裏核心是要設置 splitLine 下的 lineStyle,效果就如開篇的第一張圖那樣:

當然,也可以定製 tooltip的虛線,效果如下:

只需要配置 tooltip 下 axisPointer 的 lineStyle 即可,代碼很簡單:

tooltip : {
    axisPointer: {
        lineStyle: {
            color: '#e1e2e2',
            type: 'dashed',
            shadowColor: 'rgba(0, 0, 0, 0.3)',
            shadowBlur: 5
        }
    }
}

3、堆疊與面積

堆疊圖與面積圖在剛接觸時容易混淆,它們實際上是兩種不同的效果。

堆疊圖中的後一項數據會疊在前一項數據之上,像下面這樣:

可以通過設置 v-charts 提供的 extend 屬性進行配置。之前也說過,它本質上就是配置 Echarts 中的 setOption。

我們分別對柱狀圖和折線圖設置了堆疊效果,可以發現 e.stack 的值是分開設置的,不然的話在數值上就會再次疊加。

<ve-histogram :extend="chartExtend"></ve-histogram>
...
this.chartExtend = {
    series: (v) => {
        Array.from(v).forEach((e, idx) => {
            if (e.type === 'bar') {
                e.stack = 'bar';
            }

            if (e.type === 'line') {
                e.stack = 'line';
            }
        };
    },
    ...
};

而面積圖,往往在折線圖中是爲了突出某一塊區域的對比。

同樣地,在 chartExend 中進行配置:

<ve-line :extend="chartExtend"></ve-line>
...
this.chartExtend = {
    series: (v) => {
        Array.from(v).forEach((e, idx) => {
            // 將指定條件下的折線設置爲面積圖
            if (...) {
                e.areaStyle = 'line';
            }

        };
    },
    ...
};

4、自定義圖例

圖例,在 Echarts 的配置項中,也叫 legend。通過它可以直觀地看到不同顏色對應的數據分佈情況。同時,它也是包含點擊交互的,當你暫時不想關心某個數據項是,可以點擊對應的圖例,讓它暫時隱藏,再次點擊,又會重新渲染出來。

我遇到過更進一步地需求:在柱狀圖的圖例中,要默認展示當天的數據,當點擊某一天時柱子時,圖例中的數據要隨之變化。需求的目的是,讓用戶能更直觀地瞭解每一天的數據,當他需要看某兩天的數據對比時,只需要點擊柱子切換數據即可。效果如下:

當想看 9月16日的數據時,點擊對應的柱子,則圖例中的數據會刷新,同時左上角的時間也會隨之變化:

實現的思路基本是這樣的:

  • 通過點擊事件的回調方法,獲取當日的具體時間。
  • 對左上角的時間做格式化設置。
  • 再次設置 extend 的 legend 配置。
  • 在 legend 中,通過 formatter 函數返回自定義模板,實現文字與數字的展現。
  • 具體的做法是,根據左上角的時間,過濾 chartData.rows 中的數據,找到對應的那一天的數據。
  • 配置自定義模板,輸出帶樣式的結果。

匹配當日數據的具體方法,因爲業務邏輯的不同,數據結構也會不同,這裏就不細說了。我把事件回調的寫法,還有自定義模板的輸出的代碼貼出來,給大家參考一下:

<ve-histogram :data="chartData" :extend="chartExtend" :events="{ click: handleChartEvents }"></ve-histogram>
...

handleChartEvents (e) {
    const date = e.name;
    // 設置左上角的時間(帶格式化),這裏不再展開
    this.setCurrentDate(date);
    this.setChartExtend();
},
...
setChartExtend () {
    this.chartExtend = {
        legend: {
            formatter: (name) => {
                // currentNumber 是已經匹配到的當日數據
                const result = [
                    `{a|${name}}`,
                    `{b|${currentNumber}}`
                ];
                return result.join('\n');
            },
            textStyle: {
                height: 42,
                rich: {
                    a: {
                        fontSize: 12,
                        align: 'left',
                        padding: [0, 10, 15, 0]
                    },
                    b: {
                        fontSize: 24,
                        align: 'left',
                        padding: [0, 10, 0, 0],
                        lineHeight: 40,
                        width: 60
                    }
                }
            },
            // 圖例標記的圖形寬度
            itemWidth: 14,
            // grid 距離整個容器頂部的高度
            grid: {
                top: 120
            }
        },
        ...
    };
},
...

點擊事件的回調裏,可以拿到具體的時間 name,然後交給 setCurrentDate 方法做時間格式化的設置,並渲染到界面上,同時它也是後續匹配當日數據的依據。

在 legend 的 formatter 方法中組裝的自定義模板,分別有兩個樣式 a 和 b,你也可以取別的樣式名字,但要記得對應地在 textStyle 的 rich 對象裏設置樣式。

rich 是富文本樣式的自定義寫法,有點類似於平時常見的 css,但像 padding 之類的樣式寫法,又略有不同,而且調試起來並不容易。上述代碼中的數字,是經過多次微調之後的結果。

最後的 itemWidth 是爲了設置顏色塊的寬度,使之變爲方形,更加美觀。因爲圖例中添加的數字的樣式,所以整體的高度變得大了。grid 的 top,可以調整主繪製區域到整個容器頂部的距離,從而使之距離圖例,也能留出更合理的空白高度。

除此之外,對於數據較多的圖表,如果剛加載就展示全部數據的話,會比較亂。

這時,我們就想,是否可以只展示其中某一類的數據呢?像下面這樣:

這裏,就需要用到 legend 的 selected 屬性。它是一個對象,意思是圖例選中狀態表。用法很簡單:

<ve-line :extend="chartExtend"></ve-line>
...

setChartExtend () {
    this.chartExtend = {
        legend: {
            formatter: (name) => {
                return `{a|${name}}`;
            },
            textStyle: {
                // 這裏的 rich 樣式,是爲了四等分的佈局
                rich: {
                    a: {
                        width: 240,
                        height: 20,
                        lineHeight: 22,
                        align: 'left'
                    }
                }
            },
            width: 1090,
            // 圖例標記的圖形寬度
            itemWidth: 14,
            selected: {
                'T1-第7日': false,
                'T1-第14日': false,
                'T1-第30日': false,
                'T2-第7日': false,
                'T2-第14日': false,
                'T2-第30日': false,
                'T3-第7日': false,
                'T3-第14日': false,
                'T3-第30日': false,
            }
        },
        ...
    };
}

其中的 formatter 函數返回一個帶樣式的文本,通過樣式將佈局四等分。然後設置 selected,將默認不需要展示的數據隱藏起來。

注意,selected 對象中的 key 均爲指標的具體名稱,不能用別名代替,否則是出不來效果的。

5、mini圖

剛接到需求要實現 mini 圖的時候,第一時間想到的是下面這張圖的模樣:

這是一個不同緩動函數效果的官方示例,每一個緩動效果圖都帶有一個標題組件。

研究了 示例代碼 後,發現它的實現原理是:模擬了輸入 x 軸的不斷變大的值,通過不同的效果函數,來輸出對應的 y 軸的值,達到呈現出不同的效果。

很遺憾,這沒法不適用於我們的需求。我們希望實現一個迷你的單一數據趨勢圖,沒有任何座標軸,但數據的趨勢並不一定是線性增長的。然而,值得慶幸的是,可以借鑑其中的隱藏座標軸的做法。具體的實現,還是要設置 extend,代碼如下:

<ve-line :data="chartData" :extend="chartExtend" width="120px" height="40px">
...

this.chartExtend = {
    xAxis: {
        show: false,
    },
    yAxis: {
        show: false,
    },
    legend: {
        show: false
    },
    grid: {
        show: true,
        left: 0,
        top: 5,
        width: '100%',
        height: '100%',
        borderWidth: 0,
        backgroundColor: '#fff',
    },
    series: (v) => {
        Array.from(v).forEach((e, idx) => {
            e.symbol = 'circle';
            e.symbolSize = 2;
            e.areaStyle = { opacity: 0.3 };
        });
        return v;
    },
    tooltip: {
        formatter: (params) => {
            let result = '';
            params.forEach(param => {
                let str = `${param.marker}${param.value[0]} ${param.value[1]}<br>`;
                result += str;
            });

            return result;
        }
    }
};

因爲是迷你的圖表,所以需要在佈局中,將它調整爲可控的大小。相應的,grid 屬性的 top 和 left,也可以幫我們做微調。x 軸,y 軸,圖例,這些都隱藏掉。最後,爲了支持 tooltip,對其結構進行了 formatter。最終的呈現效果如下:

正因爲 mini 圖的尺寸足夠的小,所以比較適合於對某些重要數據,做週期性的趨勢解讀,並可以位於重要數據的附近。這也能使得界面的佈局,看上去更爲的協調。

6、雙軸單線

當一張圖表中,存在雙軸時,很大概率下都會出現 2 個 y 軸的刻度不一致的情況。

想想也是啊,設置爲雙軸的原因,本來就是因爲它們的數值差異過大。如果不設置雙軸,某些數據就看不到了,並不是沒有渲染,而是因爲太小,導致其被數值大的數據 “淹沒” 了。

一種解決方案是,需要自己去定義 y 軸的 max,min,interval。因爲系統不會再自動計算適配了。

yAxis:[
    {......},
    {
      type: 'value',
      name: '銷售額(元)',
      min: 0,
      max: max,        // 計算最大值
      interval: Math.ceil(max / 5),   //  平均分爲5份
      axisLabel: {
        formatter: '{value}'
      }
]

這個方案可以將雙軸的刻度重合在一起,實現 “單線” 的效果。但是,因爲要自己算,所以略微有些麻煩,均分的份數可能會因爲界面的美觀,需要進行調整。如果雙軸的單位不一致,比如左側爲數值,右側爲百分比,也挺麻煩的。

所以,就想了一個 “偷懶” 的辦法。既然只要展示 “單線”,那就把第二根線隱藏起來唄,其實儘管是 “雙線”,但其實它們之間離得還是挺近的,從效果上來看,並沒有太大的影響。

實現方案也很簡單,還記得之前介紹 “虛線” 是如何設置的麼?

yAxis (item) {
    item[0].splitLine = Object.assign({}, {
        lineStyle: {
            color: '#e1e2e2',
            type: 'dashed'
        }
    });

    return item;
}

其中的 item[0] 代表的是左側最常用的 y 軸。那麼右側的自然就是 item[1] 了,把它隱藏起來就行了:

yAxis (item) {
    item[0].splitLine = Object.assign({}, {
        ...
    });

    item[1].splitLine = {
        show: false
    };

    return item;
},

是不是很簡單?其實知道了對的方法,實現起來還是簡直就是 easy 模式。但往往探究 “對的方法” 需要耗費大量的精力。

總結

今天介紹了幾種 Echarts 定製化的方案,多是由實際工作中提煉總結出來的小技巧,希望能幫到有需要的同學。如果你覺得本文還不錯,還請點個 “贊” 或者 “關注”,以便讓更多有需要的同學看到,感謝支持!

可能細心的同學會問:本文開頭的那張圖中的“小圖標”是如何實現的呢?文章中貌似沒有涉及到。

是的。由於篇幅的關係,我會單獨再開一篇,專門來聊一聊關於 “標記” 的定製化方案,歡迎大家屆時捧場,感謝!

PS:歡迎關注我的公衆號 “超哥前端小棧”,交流更多的想法與技術。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章