數據可視化 gojs 實踐關系圖 demo:節點分組


本文是關於如何使用可視化庫 gojs 完成節點分組關系展示的,從零基礎到實現最終效果。希望對使用 gojs 的小伙伴有幫助。

1. 節點分組需求及 demo 展示

demo

需求
  • 能正確展示組的層次,以及節點之間的關系。
  • 單選節點、多選節點,獲取到節點信息
  • 選中組,能選中組中的節點,能獲取到組中的節點信息
  • 選中節點,當前節點視為根節點,能選中根節點連線下的所有節點,並獲取到節點信息

2. 准備

  • 從后端獲取到的接口數據:
const data = {
  "properties": [
    { "key": "t-2272", "parentKey": "j-1051", "name": "哈哈" },
    { "key": "p-344", "parentKey": "g--1586357764", "name": "test" },
    { "key": "t-2271", "parentKey": "j-1051", "name": "查詢" },
    { "key": "t-2275", "parentKey": "j-1052", "name": "開開心心" },
    { "key": "j-1054", "parentKey": "p-344", "name": "嘻嘻" },
    { "key": "t-2274", "parentKey": "j-1052", "name": "查詢" },
    { "key": "j-1051", "parentKey": "p-444", "name": "hello" },
    { "key": "j-1052", "parentKey": "p-444", "name": "編輯" },
    { "key": "t-2281", "parentKey": "j-1054", "name": "嘻嘻" },
    { "key": "p-444", "parentKey": "g--1586357624", "name": "test" },
    { "key": "g--1586357624", "name": "數據組1" },
    { "key": "g--1586357764", "name": "數據組2" },
    { "key": "t-2273", "parentKey": "j-1051", "name": "新建" }
  ],
  "dependencies": [
    { "sourceKey": "t-2272", "targetKey": "t-2274" },
    { "sourceKey": "t-2274", "targetKey": "t-2275" },
    { "sourceKey": "t-2273", "targetKey": "t-2272" },
    { "sourceKey": "t-2271", "targetKey": "t-2272" },
    { "sourceKey": "t-2272", "targetKey": "t-2281" }
  ]
}

3. 實現步驟1:數據組建

  • gojs 圖表實例所需數據結構如下:

    diagram.model = new go.GraphLinksModel(
        [ // node data
            { key: "A"},
            { key: "F", group: "Omega"},
            { key: "G"},
            { key: "Chi", isGroup: true },
        ],
        [  // link data
            { from: "A", to: "A" },
            { from: "F", to: "G" },
            { from: "G", to: "Chi"}
        ]
    );
    
  • 根據接口數據構建出的最終數據如下:

    node

[
  { "key": "g--1586357624", "text": "數據組1", "type": "g", "isGroup": true },
  { "key": "p-444", "text": "test", "type": "p", "isGroup": true, "group": "g--1586357624" },
  { "key": "j-1051", "text": "hello", "type": "j", "isGroup": true, "group": "p-444" },
  { "key": "t-2272", "text": "哈哈", "type": "t", "group": "j-1051" },
  { "key": "t-2271", "text": "查詢", "type": "t", "group": "j-1051" },
  { "key": "t-2273", "text": "新建", "type": "t", "group": "j-1051" },
  { "key": "j-1052", "text": "編輯", "type": "j", "isGroup": true, "group": "p-444" },
  { "key": "t-2275", "text": "開開心心", "type": "t", "group": "j-1052" },
  { "key": "t-2274", "text": "查詢", "type": "t", "group": "j-1052" },
  { "key": "g--1586357764", "text": "數據組2", "type": "g", "isGroup": true },
  { "key": "p-344", "text": "test", "type": "p", "isGroup": true, "group": "g--1586357764" },
  { "key": "j-1054", "text": "嘻嘻", "type": "j", "isGroup": true, "group": "p-344" },
  { "key": "t-2281", "text": "嘻嘻", "type": "t", "group": "j-1054" }
]

link

[
  { "from": "t-2272", "to": "t-2274", "nextLinks": [ "t-2274", "t-2275" ] },
  { "from": "t-2274", "to": "t-2275", "nextLinks": [ "t-2275" ] },
  { "from": "t-2273", "to": "t-2272", "nextLinks": [ "t-2272", "t-2274", "t-2275" ] },
  { "from": "t-2271", "to": "t-2272", "nextLinks": [ "t-2272", "t-2274", "t-2275" ] },
  { "from": "t-2272", "to": "t-2281", "nextLinks": [ "t-2281" ] }
]

如何根據接口數據組裝出所需數據就不介紹了。text字段用於顯示組及節點的標題,nextLinks是為后面做選中當前節點,能選中節點連線下的所有節點做數據准備。

大家如果感興趣,可以先不讀后面的,自己根據組裝出的數據自己實現下后面的交互。

4. 實現步驟2:構建圖表容器、實例,自定義布局、節點、連線、組的樣式等屬性。

  • 容器
<div class="diagram" id="diagram"></div>  
  • 去除水印、畫布藍色邊框,參考前篇
  • 構建圖表實例
import * as go from './go-module.js';

const $ = go.GraphObject.make;
    const diagram = $(go.Diagram,
        'diagram', // diagram 繪圖容器的 id
        {
          layout: $(go.TreeLayout, // 布局方式
            {
              angle: 90, // 自上而下,0 從左到右
              arrangement: go.TreeLayout.ArrangementHorizontal
            }
          )
        }
    );
  • 自定義節點、連線、組
const config = {
  borderColor: '#d1d9e2',
  groupTextColor: '#444',
  nodeTextColor: '#585858',
  linkColor: '#666',
  selectedLinkColor: '#2090ff',
}

圖表顏色值統一管理。

定義節點

    diagram.nodeTemplate = $(go.Node,
        "Auto",
        $(go.Shape, "Rectangle", // 節點形狀:矩形
          { stroke: config.borderColor, // 邊框顏色
            strokeWidth: 1, // 邊框寬度
            fill: "white", // 形狀填充顏色
          },
        ),
        $(go.TextBlock, // 節點文本
          { margin: 4,
            stroke: config.nodeTextColor // 文本顏色
          },
          new go.Binding("text", "text"), // 將 model 中的 text 屬性進行綁定,用於節點顯示文本
        ),
        { doubleClick: nodeDblClick, // 節點雙擊事件,選中節點下的所有節點
        },
    );

定義邊

    diagram.linkTemplate = $(go.Link,
        {
          curve: go.Link.Bezier // 貝塞爾曲線
        },
        // 連線
        $(go.Shape, { name: 'link', strokeWidth: 1, stroke: config.linkColor }),
        // 連線的箭頭
        $(go.Shape, { name: 'linkArrow', toArrow: "OpenTriangle", stroke: config.linkColor })
    );

定義組

    diagram.groupTemplate = $(go.Group,
        "Auto",
        { // 定義分組的內部布局
          layout: $(go.TreeLayout,
            { angle: 90, arrangement: go.TreeLayout.ArrangementHorizontal }),
          isSubGraphExpanded: false, // 默認展開true、折疊false
          // 分組單擊事件
          click: (e, group) => {
            // todo 實現組選中,選中組中所有節點
          }
        },
        $(go.Shape, // 定義分組形狀及描述
          "Rectangle",
          {
            parameter1: 14,
            fill: "rgba(2, 153, 255, .2)", // 填充色
            stroke: config.borderColor, // 邊框色
            strokeWidth: 1,
          },
        ),
        $(go.Panel, "Vertical",
          { defaultAlignment: go.Spot.Left, margin: 4 },
          $(go.Panel, "Horizontal",
            { defaultAlignment: go.Spot.Top, margin: 4 },
            $("SubGraphExpanderButton"), // 設置收縮按鈕,用於展開折疊子圖
            $(go.TextBlock, // 定義文本
              {
                alignment: go.Spot.TopLeft,
                font: "Bold 12px Sans-Serif",
                stroke: config.groupTextColor,
              },
              new go.Binding("text"), // 將 model 中的 text 屬性進行綁定,用於節點顯示文本
            )
          ),
          // 創建占位符來表示組內容所在的區域
          $(go.Placeholder, { padding: new go.Margin(5, 10) })
        )
    )
  • 綁定數據
diagram.model = new go.GraphLinksModel(
    [], // nodes
    [] // links
)

5. 實現步驟3:交互處理

  • 選中分組交互相對簡單,就不附上代碼了。

  • 選中節點,選中節點連線下的所有節點

    function nodeDblClick (e, node) {
      // 遍歷每一條邊進行設置
      let goneNodes = []; // 記錄遍歷過的,避免再次遍歷它
      const forEdges = (edges, isSelected) => {
        edges.forEach(edge => {
          if (edge && edge.nextLinks) { // 當前節點下面有多個節點
            edge.nextLinks.forEach((id, i) => {
              if (!goneNodes.includes(id)) { // 避免遍歷過的
                goneNodes.push(id);
                const node = diagram.findNodeForKey(id);
                node.isSelected = isSelected;
                highlightLink(node, node.isSelected);
                // 遞歸設置節點連線上下游的每一個節點選中及連線高亮,linkArr 為前面組裝出的圖的邊數據
                forEdges(linkArr.filter(e => e.from === id), isSelected)
              }
            })
          }
        })
      }
      
      const {key: nodeId} = node.data;
      // 存在多條邊,linkArr 為前面組裝出的圖的邊數據
      const edges = linkArr.filter(e => e.from === nodeId);
      // 先清除上次高亮的連線
      clearHightLink();
      // 高亮當前節點的連線
      highlightLink(node, node.isSelected);
      
      // 循環設置當前節點連線上下游的每一個節
      點選中及連線高亮
      forEdges(edges, node.isSelected);
    }

優化:思路:先統計數據,再對統計數據進行UI處理。職責分明,增強可讀性。

function nodeDblClick (e, node) {
  // 遍歷每一條邊
  let allNodes = []; // 統計連線上的所有節點
  const forEdges = (edges) => {
    edges.forEach(edge => {
      if (edge && edge.nextLinks) { // 當前節點下面有多個節點
        edge.nextLinks.forEach((id, i) => {
          allNodes.push(id);
          // 遞歸節點連線上下游的每一個節點,linkArr 為前面組裝出的圖的邊數據
          forEdges(linkArr.filter(e => e.from === id))
        })
      }
    })
  }
 
  const {key: nodeId} = node.data;
  // 存在多條邊,linkArr 為前面組裝出的圖的邊數據
  const edges = linkArr.filter(e => e.from === nodeId);
  // 循環統計當前節點連線上下游的每一個節點
  forEdges(edges);

  // 先清除上次高亮的連線
  clearHightLink();
  
  // 先統計出所有的,再去重,再對節點進行處理
  // 設置統計出的連線上的所有節點及邊高亮
  allNodes.push(nodeId);
  allNodes = [...new Set(allNodes)]; // 去重
  allNodes.forEach(id => {
    const node = diagram.findNodeForKey(id);
    node.isSelected = true;
    highlightLink(node, true);
  })
}

clearHightLink 方法:清除連線高亮

highlightLink 方法:根據node獲取到對應的id找出node的出去的線設置顏色等高亮。

最后

完成這個效果難點在哪,我自己的感受是:

  • 組裝數據:如何組裝出圖表所需的數據,特別是選中節點要選中節點連線下的所有節點,怎么組裝數據才方便后面的處理。
  • 在自定義布局、節點、連線、組屬性及樣式上,特別是細節處理,需要大量翻看文檔指南或api,查看案例是等,確定哪個屬性的哪個值改了才是需要的。
  • 在做交互時,需要理清思路,看文檔事件相關的部分。特別難的是,節點信息打印出來查看時,看不到具體的,只能看出來是 迭代器,可以遍歷,但看不出具體的數據,只能通過相應 api 才能得到。

關於本文的代碼,只放了核心部分的。

最后的最后,有不到位的地方或者錯誤的地方,亦或是更好的意見,歡迎指出。

非常感謝!!!


免責聲明!

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



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