在節點-鏈接圖中巧用坐標變換畫線

節點-鏈接圖是一種非常常見的可視化類型,比如樹圖、力學圖等。大多數情況下,我們會藉助可視化庫計算出數據集中節點的坐標 <x, y>,然後在根據兩個節點的坐標繪製邊。但是直接將兩個節點的中心點連接通常不會滿足我們的需求,考慮如下需求(以SVG為例):

連點兩端並非直接與節點中心點相連

如果直接將兩點坐標連接起來,結果顯然不是想要的:

連線穿過節點區域

對於這個問題,你可能首先想到的是分別計算節點 A 以及節點 B 的半徑,然後使用三角函數計算出連線兩端坐標,下面為這種演算法的關鍵示例代碼:

// 根據指定的source、target以及半徑計算調整後的連線兩端坐標getNewPos (source, target, r) { let angle = Math.atan(Math.abs(source.y - target.y) / Math.abs(source.x - target.x)) let ax let ay if (angle) { ax = r * Math.cos(angle) ay = r * Math.sin(angle) } else { ax = 0 ay = 0 } let x2 = source.x < target.x ? source.x + ax : source.x - ax let y2 = source.y < target.y ? source.y + ay : source.y - ay let x3 = source.x < target.x ? target.x - ax : target.x + ax let y3 = source.y < target.y ? target.y - ay : target.y + ay return { r: angle * (180 / 3.14), s: [x2, y2], d: [x3, y3] }}

此時可能計算量不是太複雜,那麼如果有這種需求:AB 之間的連線不是直線,而是一條樣條曲線,其中控制點的位置位於垂直於 AB 連線上的某一點,這一點距離 AB 連線的距離可設置,如下圖:

使用樣條曲線代替直線

計算控制點的坐標基本思路很簡單:首先計算 AB 的連線 L,然後取 L 上一點 p,計算通過 p 點的垂線 L,然後計算 L 上離 L 距離為指定值的兩個點 c1c2 最後選取一個點作為控制點。數學理論很簡單,但是用代碼實現起來會比較繁瑣,那麼有沒有一個好的解決辦法呢?下面講述本文重點:巧用坐標轉換避免繁瑣的數學計算

那麼請思考一個問題:如果連線水平橫著或者垂直豎著那麼連線的起點和終點、樣條曲線的控制點是不是可以通過簡單的加減乘除就可以計算?進而可以發現上述方法之所以繁瑣最重要的原因是兩個節點之間的連線 「不規則」。

上述假設也就是本文的基本思路:

1、假設源節點和目標節點中心坐標水平排列

2、在 1 的基礎上繪製好線之後使用坐標轉換移動到合適的位置

首先使用演示變換過程:

用平移旋轉變換代替直接基於節點坐標計算

使用坐標變換的方法只需要計算一次位移以及旋轉量即可,下面代碼根據源節點和目標節點位置計算出邊的變換量:

function getTransform(source, target) { let _dis = Math.sqrt((source.x - target.x) * (source.x - target.x) + (source.y - target.y) * (source.y - target.y)) let angle = 0 if (target.x > source.x) { if (target.y > source.y) { angle = Math.asin((target.y - source.y) / _dis) } else { angle = Math.asin((source.y - target.y) / _dis) angle = -angle; } } else { if (target.y > source.y) { angle = Math.asin((target.y - source.y) / _dis) angle = Math.PI - angle; } else { angle = Math.asin((source.y - target.y) / _dis) angle -= Math.PI; } } angle = angle * (180 / Math.PI); return "translate(" + source.x + "," + source.y + ")rotate(" + angle + ")"}

需要注意的是,考慮到在實際應用場景中每個節點可能同時與多個邊相連接,因此沒有對節點做坐標轉換,而僅僅對邊進行坐標轉換,如果需要豐富邊上的顯示元素,則可以將需要的元素包裹在 g 標籤中,然後對 g 進行坐標轉換。如果源節點和目標節點半徑不同,則只需要在進行轉換前進行相應的調整即可。

在線演示代碼請戳

RMLYWx?

codepen.io圖標
推薦閱讀:

一張圖告訴你全球葡萄酒都產自哪些國家
【R圖秀】情人節快樂!
AI演算法眼中的世界是什麼樣子?這些圖像或許能幫你更好地理解
Matplotlib畫三維曲線
畫給設計師看的營銷大法

TAG:數據可視化 | 可視化 | 前端數據可視化 |