前言
在近期的項目中,有大量處理可視化數據的需求。說起這個,相信很多同學跟我一樣,都會想到用 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:歡迎關注我的公衆號 “超哥前端小棧”,交流更多的想法與技術。