家譜(特殊的層級人物關係)數據結構與自動排版算法的一種實現

出處:http://www.fengchang.cc/post/24前端

家譜的數據結構並不複雜,邏輯上能夠抽象成一種圖,節點爲人物,邊爲人物關係,關係粗略分爲兩類,一類是跨層級的親子關係(如父子,父女,母子,母女),另外一類爲同層級的夫妻關係(其實若是要加上更多的也能夠)。有了這兩類關係,就能夠徹底地描述一個家譜人物關係。那麼在數據庫中表示只須要兩張表就夠了,一個person表,一個relation表算法

person表的形式能夠爲(id, name, sex ...), relation表的形式能夠爲(id, from_person_id, to_person_id, relation_name)數據庫

這種存儲方式能夠很方便地查詢到一個完整地家譜,固然也有關係型數據庫固有的缺點,就是很差作單人的連續的層級遍歷,例如找一我的祖上十八代,那必定是對應大量的table join, 不過我這裏不考慮這個問題,只專一於如何表徵一個完整的家譜,並能自動排版在前端展現,最終要達到的一個效果如圖數據結構

 

這張圖在數據庫層面就是按照如上描述存儲的,然而前端要繪製成這樣的樹形結構則須要花一點點小功夫。post

我這裏使用d3的force directed graph進行繪製。d3的example圖是這樣的:網站

看起來是否是亂得一塌糊塗?若是你只按照上面的表關係創建好數據,而後直接用d3畫圖,結果也必然是這個樣子。那如何把它變成看起來比較乾淨整潔的相似樹形圖呢?d3是沒辦法按照咱們的要求自動排版的,緣由很簡單,咱們的要求有三個,第一要分層級(父母在上子女在下),第二,線條交叉要儘可能少,不要太雜亂無章,第三,樹看起來比較平衡(例如不要很右邊的父節點連到圖最左邊的子節點,難看的很),很顯然,這種要求屬於高度定製的要求,d3是不可能自動給你排的,那怎麼作呢?個人思路是經過某種算法,肯定圖中每個人物的座標(x,y),使得知足上面的3個要求,則天然結果圖可以整潔。是否是廢話?待我細細說來。。。.net

第一步,計算層級。blog

思路以下,先定一個記錄標準:最上層爲1層,其子所在層爲2,再往下一層爲3,以此類推。那麼在給定一個圖以後,只要這個圖是連通圖,那麼從一個節點沿着關係必定能走到任意其餘節點,基於這個前提,我用一種想象中的染色法,想象圖中全部節點一開始都是白色,而後選定任意一個節點開始,隨意標記一個層級,例如10,染成紅色,而後從該紅色節點出發,沿着其全部關係遞歸遍歷其餘節點,遍歷時,若是是向上走,則走到的節點層級減1,向下走,則走到的節點層級加1,同層走,則走到的節點層級相同,直到全部節點都變成紅色。這樣遞歸完成後,全部的層級都定下來了,可是因爲初始節點的層級是隨便取的,最終獲得的結果多是10,11,12,13。。這樣的層次,只要再作個「歸一」,即把最小的層級變成1(例如若是層級列表爲(10,11,12),那麼只要統一減去9,便可"歸一"爲(1,2,3))。遞歸

第二部:減小線條交織,自動調整層高get

第一步作完之後,全部的節點都被分到了對應的層級,但僅僅這樣畫出來的圖必定仍是很差看,例如一對夫妻在同層級,可是若是一個放在圖最左,一個放在圖最右,中間還放了不少其餘兄弟節點,那麼這就很難看,亦或是A放在B的左邊,而後A的後代卻放在B的後代的右邊,那麼可想而知這裏又會有不少沒必要要的線條交織,影響美觀,因此要作到幾件事,包括:一、把夫妻要並在一塊兒放置,若是A和B是夫妻,C和D是夫妻,那麼應該是ABCD這樣的排布ok,但若是是ACDB這樣就不行。二、若是甲和乙是親兄弟,甲在乙的左邊,那麼甲的後代必須也在乙的後代的左邊,遞歸傳下去。3,每層的層間距也不該固定,例如古代皇帝,有些有一百多個兒子,有些就一兩個兒子,那麼稍微想象,也能知道,畫前者的層間距應該大於後者的層間距纔好看,不然兒女多的人發散出去的線條會畫得很是扁平。

 

第三步,樹的平衡

這一步是基於前兩步來作的,最終可以肯定每一個節點的列位置,若是說第一步肯定了層位置,第二步粗粒度肯定了列位置(排好了層級內每一個節點的位序),那麼這一步則是細粒度最終肯定了列位置。根據實測,最終定了3個原則來惟一肯定一個節點的列位置,第一,位序靠後的節點列座標必定要大於位序靠前的列座標,例如某層內根據第二步肯定好的位序爲(A,B,C,D,E)則,B的列座標必定大於A的列座標,C的必定大於B的,以此類推。第二,一個節點的後代葉子節點數越多,它佔領的該層的列空間就要越大,同時與下層的距離也要越大,每層和下一層的最終距離爲該層全部節點的這種距離中的最大者。第三,一個節點的位置還受到其父節點的影響,父節點如有n個後代葉子節點,則本節點的列座標不該該小於父節點的列座標-n/2。根據這三個原則,就能肯定惟一的節點位置。

具體的算法代碼用到了大量的記帳式遞歸(recursion+memoization),例如計算層級,計算某節點的後代葉子節點數,拆開來看都不算複雜,拼在一塊兒會有點繞

所得結果的演示,已有網站成品:http://www.familytreesea.com/public-tree-detail/11

歡迎你們體驗和使用

--------------------- 本文來自 無產光輝指 的CSDN 博客 ,全文地址請點擊:https://blog.csdn.net/xunileida/article/details/80250608?utm_source=copy 

相關文章
相關標籤/搜索