0 需求
做的項目需要畫一個關系圖,主要需求如下:
- 需要展示6種對象之間的關系:數據機構 數據 合約 模型 計算機構 應用
- 支持突出顯示6種對象中的某一種的所有對象
- 支持Top x子圖功能。top x子圖的定義:在6種對象中的每一種對象,取關系數最多的x個,將這至多6*x個對象繪制在一張圖中
- 任務僅為原型展示,無后端,可以面向數據編程
眾所周知 ECharts 是一個功能強大的 JS 圖表庫,這次也使用 echarts 進行圖表繪制。
最終效果大致如下圖;感興趣的話可以在 Codepen 預覽動畫效果,也可以親自嘗試各種參數的修改。
1 使用 ECharts
使用echarts想必大家都比較熟悉了,官網上也有很詳細的教程,這里就簡單介紹一下。
charts 庫支持的圖表類型有二三十種,基本涵蓋了能想象到和完全沒想過的各種圖表繪制。同時echarts也支持各種常用的引入方式:從源碼構建、npm安裝、CDN引入等。
引入 JS 庫后在網頁中使用也很容易:
<!-- 為 ECharts 准備一個具備大小的 DOM -->
<div id="model-kg-graph" style="height: 800px"></div>
var graph_chart = echarts.init(document.getElementById("model-kg-graph"));
let option = {
// 相應的配置項
}
graph_chart.setOption(option);
不管繪制什么圖表,需要的代碼都是如上幾行,區別只在 option 配置項的不同。主要的配置項有這些:
let option = {
// 圖例,因為一張圖中可能存在多個圖表(例如柱狀圖+折線圖),因此參數是數組
// 數組中每一項對應一個系列,每項是一個包含類別名稱數組的對象;
// categories: ["數據機構", "數據", "合約", "模型", "計算機構", "應用"],
legend: [
{
data: graph_data.categories,
},
],
// 圖表的配置;如果數組有多項幾個圖表將繪制在一起
// 每種不同的圖表都有大量專屬的配置內容,以下是比較常見通用的幾種
series: {[
type: "graph", // 指定繪制的是什么圖
roam: true, // 使用鼠標滾輪縮放、點擊移動
animation: false, // 是否開啟動畫;默認是開啟
emphasis: {}, // 可以設置被選中的元素突出顯示的特殊樣式
]},
};
除此以外還有標題、坐標系(柱狀圖/折線圖中很常用)等等常見配置,這次沒用到就忽略了。使用時可以查閱echarts事無巨細的配置項文檔。
2 關系圖 Graph
本節中提到的所有配置項均是在 option.series 內,下文不再特殊說明
圖的基本構成
說回關系圖。關系圖其實就是平時常說的圖,基本構成是節點和邊。在配置項中對應的就是:
// 也可以叫 nodes
data: [
{ name: "數據機構1", symbolSize: 3.0 },
],
// 也可以叫 edges
links: [
{ source: "數據1", target:"數據機構3" },
],
- data 中的每個對象對應一個節點,name 為唯一表示,不能重復,否則會報錯
Cannot set property 'dataIndex' of undefined
,直接渲染失敗;symbolSize 表示節點的大小,不設置會有默認值,根據連接數設置不同大小也是常見的做法。 - links 中的每個對象對應一條邊,對應 data 的 name 屬性;如果找不到
name == source || name == target
的節點,那這條邊就不會渲染。
分類
需求中提到有6類對象需要分別表示,也就是節點對應不同的顏色。相應的配置項:
// categories: ["數據機構", "數據", "合約", "模型", "計算機構", "應用"],
categories: graph_data.categories.map((c) => ({ name: c })),
data: [
{ name: "數據機構1", category: 0, symbolSize: 3.0 },
],
- categories 雖然就是類別的數組,但數組的每項是一個對象,對象只有一個叫 name 的屬性。
- 如果不熟悉map語句,簡單來說這個值就是
[ {name: "aaa"}, {name: "bbb"}, ... ]
- 這里的名稱需要和 legend (圖例)里面的那個類別數組對應,否則會缺少對應的圖例
- 如果不熟悉map語句,簡單來說這個值就是
- 每個節點的數據里增加類別的索引,對應類別數組的下標。注意這里必須是一個 number,不能用字符串,否則整張圖無法渲染(親自踩過的抗)
圖的布局
有了節點和邊就可以構造出一張圖了。echarts 提供了三種布局方式:
layout: "none" | "circular" | "force"
-
none:布局完全由每個節點中指定的 (x, y) 坐標決定;顯然對於數據很多並且不確定的圖來說並不現實
-
circular:環形布局;很有特點的布局方式,見下圖
-
force:力引導布局;這種其實就是最常見的圖的樣子,可以根據參數自動渲染。文中使用的就是這種方式。
使用 force 布局之后還有一些細節設置可以選擇:
force: {
initLayout: "circular",
repulsion: 1000,
layoutAnimation: false,
},
- initLayout:初始布局,之后會根據設置的力引導屬性繼續變化直到穩定。說實話指不指定好像樣子區別也不大
- repulsion:斥力大小,簡而言之數值越大節點之間距離越遠,反之節點距離越近
- layoutAnimation:渲染動畫,就是從初始位置直到穩定的動畫過程。官方文檔中的說法是”節點數據較多(>100)的時候不建議關閉,布局過程會造成瀏覽器假死。“但那個動畫真的十分魔性,建議去文章開頭的鏈接里親自體驗一下。比起看這個視覺污染動畫我寧願他假死。
強調和樣式
圖有一些可以自定義的樣式配置,基本節點、邊、標簽等等的顏色形狀位置都可以自定義。這里介紹幾個我用到的:
focusNodeAdjacency: true,
legendHoverLink: true,
lineStyle: {
color: "source",
opacity: 0.2,
curveness: 0.3,
},
- focusNodeAdjacency:聚焦鄰接節點,就是下圖所示這種喜聞樂見的樣式,很不戳。文檔中說這個選項的默認值是true,但我用v4.9.0版本的庫手動添加這一句之后才有效果,可能是v5中改了。
- legendHoverLink:文檔的說法是鼠標懸停在圖例上節點高亮,但實際上至少v5.0.2還並不是這個效果;可能是庫的bug,可以期待一下之后會不會改進。
- lineStyle:顧名思義,線的樣式:
- curveness :線的曲率,不設置將為直線
- opacity/color:透明度/顏色,可以用 source/target 指定為源/目標節點的顏色
其他配置項設置的是靜態狀態下圖的樣式,對於高亮狀態的元素(例如鼠標懸停在節點/邊上),還可以單獨設置強調樣式 emphasis。同樣,基本所有元素的各種樣式都能設置,以下是幾個例子:
emphasis: {
itemStyle: {
shadowColor: "rgba(0, 0, 0, 0.4)",
shadowBlur: 15,
},
lineStyle: {
width: 3,
},
label: {
textBorderColor: "rgba(255, 255, 255, 0.8)",
textBorderWidth: 2,
},
},
3 突出顯示指定節點
有一項需求是:突出顯示某個類別的節點。(按理說鼠標懸停圖例應該是這個效果,但他並不能用)
不過echarts提供了API,可以對圖執行動作(action)。調用方式如下:
graph_chart.dispatchAction({
type: action,
seriesIndex: 0,
name: names,
});
- type:指定動作,這里要用到的是 "highlight"/"downplay",高亮/取消高亮
- seriesIndex/seriesName:用下標/名字指定操作的系列,數組指定多個
- dataIndex/name:用data[]中的下標/名字指定要操作的數據,數組指定多個
據此可以寫出函數:
nodesAction(action, category) {
if (category !== "") {
// 取到指定圖option中的data[]
let nodes = this.graph_chart.getOption().series[0].data;
// 根據category下標,獲取到對應類別所有name的數組
let names = nodes
.filter((node) => node.category == category)
.map((node) => node.name);
// 對指定的name[]執行指定操作
this.graph_chart.dispatchAction({
type: action,
seriesIndex: 0,
name: names,
});
}
}
項目使用的Vue框架,下拉選擇框綁定了graphFocus
屬性,對這個數據添加一個偵聽器:
watch: {
graphFocus(n, o) {
// n:新的值;o: 舊的值
this.nodesAction("highlight", n);
this.nodesAction("downplay", o);
},
},
就能實現切換選項的同時高亮對應的節點:
如果你使用的echarts版本在5.0.0以上,還可以通過以下配置實現類似於focusNodeAdjacency的效果:
emphasis: {
focus: "adjacency",
}
4 替換數據
最后一個需求是顯示不同的子圖。根據之前說的“如果兩端節點不同時存在則邊不會渲染”,我們只需要將data[]
替換成子圖的節點,就可以實現對應的子圖。
按理說top節點應該有后端直接返回,所以如何獲取節點的過程這里省略了。有了top節點之后,使用setOption
API更新新的配置項:
switchSubgraph(value) {
let data = graph_data.nodes;
switch (value) {
case 1:
data = this.top1Sub;
break;
case 3:
data = this.top3Sub;
break;
case 5:
data = this.top5Sub;
break;
}
this.graph_chart.setOption({
series: { data: data, zoom: 1 }, // zoom=1 重置縮放
});
},
setOption
的默認更新方式是合並更新,意思是只更改傳入的新option與原來不同的地方,其他部分保持不變;所以我們只需要將更換的data傳進去,不需要復制整個原先的option。
如果不希望使用合並更新,可以手動傳入第二個參數notMerge=true
,就會將整個option替換為傳入的新選項
如果打開了動畫,還可以自動根據更新前后的差異展示適當的動畫。
- 但是動畫這東西是個坑。用v4.9.0的時候,替換data后邊的顯示是亂的,需要手動縮放一下;換成v5.0.0之后,替換完data顯示沒問題,一縮放又壞了。設置了
animation: false
之后啥毛病都沒了。 - 可能這功能還不夠完善,對於常用的柱狀圖折線圖表現比較好,關系圖這種不那么常用又復雜的東西就有各種神秘bug。期望日后的版本中可以改善。
替換節點后,得到了期望的Top子圖,並且之前的高亮功能也可以正常使用。
結語
以上就是我這次使用ECharts關系圖的過程中遇到的問題以及最終的解決方式,希望也可以幫到你。如果有相關的問題、或是文章中存在疏漏,歡迎在評論區留言討論!
PS:第一次用CodePen,真的夠嗆,研究這玩意的時間快和寫文差不多長了。眼看可以就如何使用CodePen再寫一篇(跑