前端 | 使用 ECharts 繪制關系圖


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 (圖例)里面的那個類別數組對應,否則會缺少對應的圖例
  • 每個節點的數據里增加類別的索引,對應類別數組的下標。注意這里必須是一個 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節點之后,使用setOptionAPI更新新的配置項:

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再寫一篇(跑

參考資料

ECharts官方文檔


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM