
要實作這樣的效果,該從何下手呢?接下來我們就将這個問題拆解成若幹個小問題來解決。
1. 建立一個樹狀結構
嘿嘿,代碼寫得可能有些複雜了,簡單的做法就是嵌套幾個for循環來建立樹狀結構資料,在這裡我就不多說了,接下來我們來探究第二個問題。
在3D下的樹狀結構體最大的問題就在于,每個節點的層次及每層節點圍繞其父親節點的半徑計算。現在樹狀結構資料已經有了,那麼接下來就該開始計算半徑了,我們從兩層樹狀結構開始推算:
我現在先建立了兩層的樹狀結構,所有的子節點是一字排開,并沒有環繞其父親節點,那麼我們該如何去确定這些孩子節點的位置呢?
首先我們得知道,每個末端節點都有一圈屬于自己的領域,不然節點與節點之間将會存在重疊的情況,是以在這裡,我們假定末端節點的領域半徑為25,那麼兩個相鄰節點之間的最短距離将是兩倍的節點領域半徑,也就是50,而這些末端節點将均勻地圍繞在其父親節點四周,那麼相鄰兩個節點的張角就可以确認出來,有了張角,有了兩點間的距離,那麼節點繞其父親節點的最短半徑也就能計算出來了,假設張角為a,兩點間最小距離為b,那麼最小半徑r的計算公式為:
r = b / 2 / sin(a / 2);
那麼接下來我麼就來布局下這個樹,代碼是這樣寫的:
在代碼中,你會發現我将末端半徑預設設定為25了,如此,我們通過調用layout()方法就可以對結構樹進行布局了,其布局效果如下:
從效果圖可以看得出,末端節點的預設半徑并不是很理想,布局出來的效果連線都快看不到了,是以我們可以增加末端節點的預設半徑來解決布局太密的問題,如将預設半徑設定成40的效果圖如下:
現在兩層的樹狀分布解決了,那麼我們來看看三層的樹狀分布該如何處理。
将第二層和第三層看成一個整體,那麼其實三層的樹狀結構跟兩層是一樣的,不同的是在處理第二層節點時,應該将其看做一個兩層的樹狀結構來處理,那麼像這種規律的處理用遞歸最好不過了,是以我們将代碼稍微該着下,在看看效果如何:
不行,節點都重疊在一起了,看來簡單的遞歸是不行的,那麼具體的問題出在哪裡呢?
仔細分析了下,發現父親節點的領域半徑是由其孩子節點的領域半徑決定的,是以在布局時需要知道自身節點的領域半徑,而且節點的位置取決于父親節點的領域半徑及位置資訊,這樣一來就無法邊計算半徑邊布局節點位置了。
那麼現在隻能将半徑的計算和布局分開來,做兩步操作了,我們先來分析下節點半徑的計算:
首先需要明确最關鍵的條件,父親節點的半徑取決于其孩子節點的半徑,這個條件告訴我們,隻能從下往上計算節點半徑,是以我們設計的遞歸函數必須是先遞歸後計算,廢話不多說,我們來看下具體的代碼實作:
OK,半徑的計算解決了,那麼接下來就該解決布局問題了,布局樹狀結構資料需要明确:孩子節點的坐标位置取決于其父親節點的坐标位置,是以布局的遞歸方式和計算半徑的遞歸方式不同,我們需要先布局父親節點再遞歸布局孩子節點,具體看看代碼吧:
代碼寫完了,接下來就是見證奇迹的時刻了,我們來看看效果圖吧:
不對呀,代碼應該是沒問題的呀,為什麼顯示出來的效果還是會重疊呢?不過仔細觀察我們可以發現相比上個版本的布局會好很多,至少這次隻是末端節點重疊了,那麼問題出在哪裡呢?
不知道大家有沒有發現,排除節點自身的大小,倒數第二層節點與節點之間的領域是相切的,那麼也就是說節點的半徑不僅和其孩子節點的半徑有關,還與其孫子節點的半徑有關,那我們把計算節點半徑的方法改造下,将孫子節點的半徑也考慮進去再看看效果如何,改造後的代碼如下:
下面就來看看效果吧~
哈哈,看來我們分析對了,果然就不再重疊了,那我們來看看再多一層節點會是怎麼樣的壯觀場景呢?
哦,NO!這不是我想看到的效果,又重疊了,好讨厭。
不要着急,我們再來仔細分析分析下,在前面,我們提到過一個名詞——領域半徑,什麼是領域半徑呢?很簡單,就是可以容納下自身及其所有孩子節點的最小半徑,那麼問題就來了,末端節點的領域半徑為我們指定的最小半徑,那麼倒數第二層的領域半徑是多少呢?并不是我們前面計算出來的半徑,而應該加上末端節點自身的領域半徑,因為它們之間存在着包含關系,子節點的領域必須包含于其父親節點的領域中,那我們在看看上圖,是不是感覺末端節點的領域被侵占了。那麼我們前面計算出來的半徑代表着什麼呢?前面計算出來的半徑其實代表着孩子節點的布局半徑,在布局的時候是通過該半徑來布局的。
OK,那我們來總結下,節點的領域半徑是其下每層節點的布局半徑之和,而布局半徑需要根據其孩子節點個數及其領域半徑共同決定。
好了,我們現在知道問題的所在了,那麼我們的代碼該如何去實作呢?接着往下看:
在代碼中我們将節點的領域半徑緩存起來,從下往上一層一層地疊加上去。接下來我們一起驗證其正确性:
3. 加入z軸坐标,呈現3D下的樹狀結構
上面是改造成3D布局後的布局器代碼,你會發現和2D的布局器代碼就差一個坐标系的的計算,其他的都一樣,看下在3D上布局的效果:
恩,有模有樣的了,在文章的開頭,我們可以看到每一層的節點都有不同的顔色及大小,這些都是比較簡單,在這裡我就不做深入的講解,具體的代碼實作如下:
在這裡引入了一個随機生成顔色值的方法,對每一層随機生成一種顔色,并将節點的形狀改成了球形,讓頁面看起來美觀些(其實很醜)。