接上一篇文章 《「乾貨」用 Vue + Echarts 打造你的專屬可視化界面(上)》,今天着重介紹 標記
的用法,來實現下圖中的效果。html
所用的 Echarts 的版本號爲: v4.3。v-charts 的版本號爲:v1.19.0。前端
標記的用法有不少,今天要介紹的場景有:折線圖、柱狀圖、折線圖 + 柱狀圖。數組
上圖中,折線的拐點處,一些 「小圓點」,被替換成了小圖標。bash
要實現這樣的效果,須要先理一下原始的需求:數據結構
要實現這樣的效果,須要思考如下幾點:函數
思路解析:佈局
首先,爲了作日期的定向匹配,須要設計的數據結構以下:post
data: [
{
id: '1, 1, 3, 2',
date: '2019-10-10',
name: 'test-name1, test-name2, test-name3, test-name4'
},
...
]
複製代碼
接下來的這個 核心 屬性:symbol
是關鍵,它其實就是折線上的 拐點
。ui
symbol 支持的標記類型有:circle、rect、roundRect、triangle、diamond、pin、arrow、none。默認狀況下爲 emptyCircle,也就是空心的圓。this
它還支持連接的格式:'image://http://xxx.xxx.xxx/a/b.png'
。此外,若是須要每一個數據的圖形不同,能夠設置爲以下格式的回調函數:
(value: Array|number, params: Object) => string
複製代碼
其中第一個參數 value 爲 data 中的數據值。第二個參數 params 是其它的數據項參數。
這些正是咱們須要的。踩坑親測:上述回調函數,只有在最新版的 V4.3
中才能正常使用,不然會報錯。這也是爲什麼,我在一開始就先強調了 Echarts 的版本問題。具體實現以下:
<ve-line ... :extend="chartExtend"></ve-line>
...
// 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。
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 }<br>`;
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 = `<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;
}
複製代碼
或許有同窗會問 getOriginalTooltipResult 方法返回的值,裏面只有一個 strResult,爲什麼要設計爲對象?
實際上是爲了方便擴展。例如,能夠在日期的後面跟上下方具體數據的值的總計。那就須要經過 getOriginalTooltipResult 方法裏的 params 循環,計算出 total,在配合上樣式,生成一個 strTotal。
getOriginalTooltipResult (params) {
...
return {
strTotal: strTotal,
strResult: result
};
}
複製代碼
此外,在實際的業務中,還可能會出現某些線的數值是百分比。那就須要再對 getOriginalTooltipResult 方法作擴展,好比傳入一個 options 對象:
getOriginalTooltipResult = (params, options = { isLinePercent: false, isShowTotal: false }) {
...
}
複製代碼
至此,折線圖標記的渲染,就能完美地呈現了。
很尷尬的一點是:柱狀圖沒有 symbol 屬性。也就意味着上面的折線圖的那一套,在柱狀圖中玩不轉了。
沒辦法,只能從頭查文檔,繼續找資料。通過一番「摸爬滾打」,終於發現了 markPoint 這個屬性。在 markPoint 這個對象裏面,能夠設置 symbol,這樣的話,那麼以前搞出來的那一套就沒有白費呀?!?
須要注意的是,markPoint 的默認 symbol 爲 'pin',就是一個氣泡的圖標。另外,想要讓 markPoint 的標記出現,就必須設置它的 data 屬性。咱們須要設置 data 裏的這樣幾個屬性:
data: [
{
symbol: '...', // 設置標記的圖標連接
symbolSize: 15, // 設置標記的大小
coord: [index, 0], // x 軸的第 index 個上,打標記
symbolOffset: [0, 0] // 將標記定位在 x 軸上
},
...
]
複製代碼
具體的實現代碼以下:
<ve-histogram :data="chartData" :extend="chartExtend"></ve-histogram>
...
// 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,渲染展示。效果以下:
tooltip 的實現方法,不受圖表的類型影響,是能夠通用的,故此處再也不贅述。
另外,有的時候,會遇到須要處理柱狀圖是否堆疊的效果。這會影響 symbolOffset 的定位,爲了美觀,能夠這樣處理:對於非堆疊的項,將之往右偏移 50%,將標記居中展現,即 symbolOffset : ['50%', 0]
。
最後來個 組合拳,折線圖與柱狀圖的複合型圖表結構,像下面這樣:
從代碼實現上,咱們固然能夠給每一根符合條件的柱子,和折線的拐點,都打上標記。但在界面設計上,爲了美觀,咱們選擇只給折線的拐點打上標記,而且忽略了虛線的拐點。
這樣的設計初衷是:標記,只是爲了給人提個醒,是爲了告訴查閱者,這一天由於發生了某些特殊事件,而致使數據發生了較爲明顯的變化。因此,由此獲得的結論是:天天只要出現一個標記就夠了。
具體的實現,其實很簡單,只須要在渲染時判斷 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 的自定義圖標不顯示了
。
我查了好久,仍是沒找到有用的信息。懷疑是個渲染的 bug,因此給 Echarts 提了 issue,但願能獲得解決吧。也歡迎你們在留言區中,共同探討相關的問題,感謝!
PS:歡迎關注個人公衆號 「超哥前端小棧」,交流更多的想法與技術。