淺談圖的層次佈局

簡介

圖是一種常見的數據結構和表示形式,可視化場景也經常會用到圖來展現有關聯關係的數據。進行圖的可視化時,往往需要將其自動佈局,而針對不同的問題和場景,需要不同的佈局方法。本文主要介紹圖的層次佈局的思路。

 一些常用的圖的佈局方法。圖片截自 G6[1]

圖的層次佈局

在數據有一定層級結構或先後順序時,經常會用到層次佈局來展現,一般對應的數據結構是 DAG(Directed Acyclic Graph,即有向無環圖)。常用的場景包括:流程圖、組織架構圖、狀態轉移圖等。

層次佈局 [2] 的方法是 Kozo Sugiyama 首先於 1981 年詳細闡明的,因此也常被稱爲 Sugiyama 佈局。這裏我們講一下 Sugiyama 佈局的思路。

目的

根據圖的數據,自動畫出一個易於理解的有層次 (hierarchy) 的圖。

畫圖的基本規則

這樣,給節點分層後,把每層的節點放到合理的水平位置,就可以把圖畫出來。

佈局思路

根據以上原則,Sugiyama 把圖的佈局問題分成了多個步驟,每個步驟解決不同的子問題。Sugiyama 算法總共分爲以下 4 步。

步驟 1:節點分層

步驟 2:減少交叉

步驟 3:計算節點座標

步驟 4:畫圖

每一步的具體算法

看起來是很簡單的 4 步,但每一步的實現都並不簡單,也有很多種不同的算法。

節點分層

怎麼把節點分到不同的層呢?這裏介紹幾種算法。

首先最容易想到的可能就是最長路徑算法。即一個節點的層級等於要到達它需要走過的最長路徑。最長路徑算法的優點是速度很快,遍歷圖即可完成分層。然而它有比較明顯的問題:節點被分到儘可能低的層級,給圖的下方留出很多空白,並且可能會出現很多長邊。不過,由於它速度快,經常被用於分層的預處理,粗分層後再交給其他算法來進行優化。

一個最長路徑算法得到的結果,截自 d3-dag[3]

緊緻樹算法就是一種優化方法,目的是減少長邊的數量。從名稱可以看出,「緊緻」,即調整節點的分層,使更多邊變成 “緊緻邊”。

主要思路:

一個 Network Simplex 算法得到的結果,截自 d3-dag[4]

減少交叉

怎樣畫一個圖纔是美觀、可理解的是比較主觀的,但儘可能減少邊的交叉這個標準得到了廣泛的認可。

由於節點已經分層,縱座標已確定,且對於每個長邊也已經通過增加僞節點的方式被分成了相鄰層之間的短邊,減少邊的交叉問題就變成了如何對層內節點排序的問題。

然而已經有研究證明,即使是最簡單的情況,即只處理兩層之間的交叉,這個問題仍是一個 NP 困難 [5] 問題。因此,很多啓發式算法被提出來解決這個問題,比較經典的是重心算法和中心算法。

計算座標

根據此前所述原則,計算每層節點的座標主要的目的是:邊儘量垂直、節點佈局平衡、長邊儘量直。

Sugiyama 提出了一種二次規劃算法 (Quadratic Programming) 和一種基於優先級的啓發式算法(我都沒看懂)。之後也有很多研究者提出了多種不同的算法。

這裏介紹一下 dagre 中使用的 Brandes & Kopf 提出的一種簡單快速的啓發式算法,在線性時間內即可完成座標計算。(不過這個算法似乎會導致一些異常情況,見 issue[6])

其它考慮

實際佈局的過程中,可能還會有更多需要考慮的點。這裏列舉一些供大家參考,就不詳細展開了。

一個有嵌套的圖,截自 ELK[7]

實現自己的佈局

Sugiyama 佈局提供了圖的層次佈局的基本框架,而實際業務場景中的需求可能各有側重,已有的佈局方法可能無法滿足要求,就需要實現自己的佈局方式。

因爲 Sugiyama 佈局已經把佈局分成了不同的步驟和子問題,所以比較靈活。可以對其進行調整,在不同的步驟使用不同的算法達到目的。舉個例子,下圖這種流程圖,邊的關係不復雜,沒有長邊,即使邊有交叉也影響不大,展示的重點可能是整個圖的居中的效果,那麼在分層、去交叉之後,第三步計算座標時,可以直接用簡單的居中對齊的方式排布節點。

簡單看一個 js 實現 - dagre

dagre[8] 是一個實現了層次佈局的 js 庫,G6 就是直接引用的 dagre 來實現的層次佈局。

佈局流程很清晰地寫在了這個 runLayout 方法裏,雖然看起來有 27 步之多,整體思路還是我們上面說的:分層、減少交叉、計算座標。而 dagre 也增加了一些比如去自環、去環等一些預處理,以及支持了我們上面提到一些其它能力比如:邊的 label、嵌套佈局等。

function runLayout(g, time) { // 給邊label留出空間
time("makeSpaceForEdgeLabels"function() { makeSpaceForEdgeLabels(g); }); // 去除自環 
time("removeSelfEdges",        function() { removeSelfEdges(g); }); // 如果有環,去環,方法是反轉一些成環的邊
time("acyclic",                function() { acyclic.run(g); }); // 嵌套圖的佈局
time("nestingGraph.run",       function() { nestingGraph.run(g); }); // Step 1. 分層
time("rank",                   function() { rank(util.asNonCompoundGraph(g)); }); // 在邊的中間位置生成虛擬節點,代表label的位置
time("injectEdgeLabelProxies"function() { injectEdgeLabelProxies(g); }); // 移除空層 
time("removeEmptyRanks",       function() { removeEmptyRanks(g); }); // 移除虛擬根節點和虛擬邊 
time("nestingGraph.cleanup",   function() { nestingGraph.cleanup(g); }); // 標準化rank的值 
time("normalizeRanks",         function() { normalizeRanks(g); }); // 找到節點組的最大和最小層級 
time("assignRankMinMax",       function() { assignRankMinMax(g); }); // 移除邊的label虛擬節點 
time("removeEdgeLabelProxies"function() { removeEdgeLabelProxies(g); }); // 把長邊拆分爲長度1的邊,並插入虛擬節點
time("normalize.run",          function() { normalize.run(g); });
time("parentDummyChains",      function() { parentDummyChains(g); });
time("addBorderSegments",      function() { addBorderSegments(g); }); // Step 2. 節點排序
time("order",                  function() { order(g); }); // 回填自環
time("insertSelfEdges",        function() { insertSelfEdges(g); }); // 調整座標系 上下/左右佈局 
time("adjustCoordinateSystem"function() { coordinateSystem.adjust(g); }); // Step 3. 節點定位 
time("position",               function() { position(g); });
time("positionSelfEdges",      function() { positionSelfEdges(g); });
time("removeBorderNodes",      function() { removeBorderNodes(g); }); // 移除長邊插入的虛擬節點 
time("normalize.undo",         function() { normalize.undo(g); });
time("fixupEdgeLabelCoords",   function() { fixupEdgeLabelCoords(g); });
time("undoCoordinateSystem",   function() { coordinateSystem.undo(g); });
time("translateGraph",         function() { translateGraph(g); }); // 計算邊和節點的交點
time("assignNodeIntersects",   function() { assignNodeIntersects(g); }); // 反轉在去環階段被反轉的邊
time("reversePoints",          function() { reversePointsForReversedEdges(g); });
time("acyclic.undo",           function() { acyclic.undo(g); }); }

總結

References

參考資料

[1]

G6: https://g6.antv.vision/

[2]

層次佈局: https://en.wikipedia.org/wiki/Layered_graph_drawing

[3]

d3-dag: https://observablehq.com/@erikbrinkman/d3-dag-sugiyama

[4]

d3-dag: https://observablehq.com/@erikbrinkman/d3-dag-sugiyama

[5]

NP 困難: https://en.wikipedia.org/wiki/NP-hardness

[6]

issue: https://github.com/dagrejs/dagre/issues/239

[7]

ELK: https://www.eclipse.org/elk/documentation/tooldevelopers/graphdatastructure.html

[8]

dagre: https://github.com/dagrejs/dagre

[9]

Github - dagre/dagre: https://github.com/dagrejs/dagre

[10]

Github - d3-dag: https://github.com/erikbrinkman/d3-dag

[11]

G6: https://g6.antv.vision/

[12]

Eclipse Layout Kernel: https://www.eclipse.org/elk/

[13]

Wiki - Layered Graph Drawing: https://en.wikipedia.org/wiki/Layered_graph_drawing

[14]

圖可視化之圖佈局: https://zhuanlan.zhihu.com/p/346059370

[15]

深入解讀 Dagre 佈局算法: https://www.yuque.com/antv/g6-blog/xxp5nl

[16]

Methods for Visual Understanding of Hierarchical System Structures: https://ieeexplore.ieee.org/document/4308636/

[17]

A Technique for Drawing Directed Graphs: https://www.researchgate.net/profile/Emden-Gansner/publication/3187542_A_Technique_for_Drawing_Directed_Graphs/links/5c0abd024585157ac1b04523/A-Technique-for-Drawing-Directed-Graphs.pdf

[18]

Fast and Simple Horizontal Coordinate Assignment: https://link.springer.com/content/pdf/10.1007%2F3-540-45848-4_3.pdf

[19]

Layout of Compound Directed Graphs: https://publikationen.sulb.uni-saarland.de/bitstream/20.500.11880/25862/1/tr-A03-96.pdf

[20]

An Efficient Implementation of Sugiyama’s Algorithm for Layered Graph Drawing: https://link.springer.com/content/pdf/10.1007%2F978-3-540-31843-9_17.pdf

[21]

Port Constraints in Hierarchical Layout of Data Flow Diagrams: https://link.springer.com/content/pdf/10.1007%2F978-3-642-11805-0_14.pdf

[22]

Improved Vertical Segment Routing for Sugiyama Layouts: https://rtsys.informatik.uni-kiel.de/~biblio/downloads/theses/thw-bt.pdf

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/68I6SXlrdMS84AqLm8daKQ