日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

基于HT for Web的3D拓扑树的实现

發布時間:2024/1/17 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于HT for Web的3D拓扑树的实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

HT for Web中2D和3D應用都支持樹狀結構數據的展示,展現效果各異,2D上的樹狀結構在展現層級關系明顯,但是如果數據量大的話,看起來就沒那么直觀,找到指定的節點比較困難,而3D上的樹狀結構在展現上配合HT for Web的彈力布局組件會顯得比較直觀,一眼望去可以把整個樹狀結構數據看個大概,但是在彈力布局的作用下,其層次結構看得就不是那么清晰了。所以這時候結構清晰的3D樹的需求就來了,那么這個3D樹具體長成啥樣呢,我們來一起目睹下~

?

要實現這樣的效果,該從何下手呢?接下來我們就將這個問題拆解成若干個小問題來解決。

1. 創建一個樹狀結構

有了解過HT for Web的朋友,對樹狀結構數據的創建應該都不陌生,在這里我就不做深入的探討了。樹狀結構數據的創建很簡單,在這里為了讓代碼更簡潔,我封裝了三個方法來創建樹狀結構數據,具體代碼如下:

/*** 創建連線* @param {ht.DataModel} dataModel - 數據容器* @param {ht.Node} source - 起點* @param {ht.Node} target - 終點*/ function createEdge(dataModel, source, target) {// 創建連線,鏈接父親節點及孩子節點var edge = new ht.Edge();edge.setSource(source);edge.setTarget(target);dataModel.add(edge); }/*** 創建節點對象* @param {ht.DataModel} dataModel - 數據容器* @param {ht.Node} [parent] - 父親節點* @returns {ht.Node} 節點對象*/ function createNode(dataModel, parent) {var node = new ht.Node();if (parent) {// 設置父親節點node.setParent(parent);createEdge(dataModel, parent, node);}// 添加到數據容器中dataModel.add(node);return node; }/*** 創建結構樹* @param {ht.DataModel} dataModel - 數據容器* @param {ht.Node} parent - 父親節點* @param {Number} level - 深度* @param {Array} count - 每層節點個數* @param {function(ht.Node, Number, Number)} callback - 回調函數(節點對象,節點對應的層級,節點在層級中的編號)*/ function createTreeNodes(dataModel, parent, level, count, callback) {level--;var num = (typeof count === 'number' ? count : count[level]);while (num--) {var node = createNode(dataModel, parent);// 調用回調函數,用戶可以在回調里面設置節點相關屬性callback(node, level, num);if (level === 0) continue;// 遞歸調用創建孩子節點createTreeNodes(dataModel, node, level, count, callback);} }

嘿嘿,代碼寫得可能有些復雜了,簡單的做法就是嵌套幾個for循環來創建樹狀結構數據,在這里我就不多說了,接下來我們來探究第二個問題。

2. 在2D拓撲下模擬3D樹狀結構每層的半徑計算

在3D下的樹狀結構體最大的問題就在于,每個節點的層次及每層節點圍繞其父親節點的半徑計算。現在樹狀結構數據已經有了,那么接下來就該開始計算半徑了,我們從兩層樹狀結構開始推算:

?

我現在先創建了兩層的樹狀結構,所有的子節點是一字排開,并沒有環繞其父親節點,那么我們該如何去確定這些孩子節點的位置呢?

首先我們得知道,每個末端節點都有一圈屬于自己的領域,不然節點與節點之間將會存在重疊的情況,所以在這里,我們假定末端節點的領域半徑為25,那么兩個相鄰節點之間的最短距離將是兩倍的節點領域半徑,也就是50,而這些末端節點將均勻地圍繞在其父親節點四周,那么相鄰兩個節點的張角就可以確認出來,有了張角,有了兩點間的距離,那么節點繞其父親節點的最短半徑也就能計算出來了,假設張角為a,兩點間最小距離為b,那么最小半徑r的計算公式為:

r = b / 2 / sin(a / 2);?

那么接下來我么就來布局下這個樹,代碼是這樣寫的:

/*** 布局樹* @param {ht.Node} root - 根節點* @param {Number} [minR] - 末端節點的最小半徑*/ function layout(root, minR) {// 設置默認半徑minR = (minR == null ? 25 : minR);// 獲取到所有的孩子節點對象數組var children = root.getChildren().toArray();// 獲取孩子節點個數var len = children.length;// 計算張角var degree = Math.PI * 2 / len;// 根據三角函數計算繞父親節點的半徑var sin = Math.sin(degree / 2),r = minR / sin;// 獲取父親節點的位置坐標var rootPosition = root.p();children.forEach(function(child, index) {// 根據三角函數計算每個節點相對于父親節點的偏移量var s = Math.sin(degree * index),c = Math.cos(degree * index),x = s * r,y = c * r;// 設置孩子節點的位置坐標child.p(x + rootPosition.x, y + rootPosition.y);}); }

在代碼中,你會發現我將末端半徑默認設置為25了,如此,我們通過調用layout()方法就可以對結構樹進行布局了,其布局效果如下:

從效果圖可以看得出,末端節點的默認半徑并不是很理想,布局出來的效果連線都快看不到了,因此我們可以增加末端節點的默認半徑來解決布局太密的問題,如將默認半徑設置成40的效果圖如下:

現在兩層的樹狀分布解決了,那么我們來看看三層的樹狀分布該如何處理。

將第二層和第三層看成一個整體,那么其實三層的樹狀結構跟兩層是一樣的,不同的是在處理第二層節點時,應該將其看做一個兩層的樹狀結構來處理,那么像這種規律的處理用遞歸最好不過了,因此我們將代碼稍微該著下,在看看效果如何:

不行,節點都重疊在一起了,看來簡單的遞歸是不行的,那么具體的問題出在哪里呢?

仔細分析了下,發現父親節點的領域半徑是由其孩子節點的領域半徑決定的,因此在布局時需要知道自身節點的領域半徑,而且節點的位置取決于父親節點的領域半徑及位置信息,這樣一來就無法邊計算半徑邊布局節點位置了。

那么現在只能將半徑的計算和布局分開來,做兩步操作了,我們先來分析下節點半徑的計算:

首先需要明確最關鍵的條件,父親節點的半徑取決于其孩子節點的半徑,這個條件告訴我們,只能從下往上計算節點半徑,因此我們設計的遞歸函數必須是先遞歸后計算,廢話不多說,我們來看下具體的代碼實現:

/*** 就按節點領域半徑* @param {ht.Node} root - 根節點對象* @param {Number} minR - 最小半徑*/ function countRadius(root, minR) {minR = (minR == null ? 25 : minR);// 若果是末端節點,則設置其半徑為最小半徑if (!root.hasChildren()) {root.a('radius', minR);return;}// 遍歷孩子節點遞歸計算半徑var children = root.getChildren();children.each(function(child) {countRadius(child, minR);});var child0 = root.getChildAt(0);// 獲取孩子節點半徑var radius = child0.a('radius');// 計算子節點的1/2張角var degree = Math.PI / children.size();// 計算父親節點的半徑var pRadius = radius / Math.sin(degree);// 設置父親節點的半徑及其孩子節點的布局張角root.a('radius', pRadius);root.a('degree', degree * 2); }

OK,半徑的計算解決了,那么接下來就該解決布局問題了,布局樹狀結構數據需要明確:孩子節點的坐標位置取決于其父親節點的坐標位置,因此布局的遞歸方式和計算半徑的遞歸方式不同,我們需要先布局父親節點再遞歸布局孩子節點,具體看看代碼吧:

/*** 布局樹* @param {ht.Node} root - 根節點*/ function layout(root) {// 獲取到所有的孩子節點對象數組var children = root.getChildren().toArray();// 獲取孩子節點個數var len = children.length;// 計算張角var degree = root.a('degree');// 根據三角函數計算繞父親節點的半徑var r = root.a('radius');// 獲取父親節點的位置坐標var rootPosition = root.p();children.forEach(function(child, index) {// 根據三角函數計算每個節點相對于父親節點的偏移量var s = Math.sin(degree * index),c = Math.cos(degree * index),x = s * r,y = c * r;// 設置孩子節點的位置坐標child.p(x + rootPosition.x, y + rootPosition.y);// 遞歸調用布局孩子節點layout(child);}); }

代碼寫完了,接下來就是見證奇跡的時刻了,我們來看看效果圖吧:

不對呀,代碼應該是沒問題的呀,為什么顯示出來的效果還是會重疊呢?不過仔細觀察我們可以發現相比上個版本的布局會好很多,至少這次只是末端節點重疊了,那么問題出在哪里呢?

不知道大家有沒有發現,排除節點自身的大小,倒數第二層節點與節點之間的領域是相切的,那么也就是說節點的半徑不僅和其孩子節點的半徑有關,還與其孫子節點的半徑有關,那我們把計算節點半徑的方法改造下,將孫子節點的半徑也考慮進去再看看效果如何,改造后的代碼如下:

/*** 就按節點領域半徑* @param {ht.Node} root - 根節點對象* @param {Number} minR - 最小半徑*/ function countRadius(root, minR) {……var child0 = root.getChildAt(0);// 獲取孩子節點半徑var radius = child0.a('radius');var child00 = child0.getChildAt(0);// 半徑加上孫子節點半徑,避免節點重疊if (child00) radius += child00.a('radius');…… }

下面就來看看效果吧~

哈哈,看來我們分析對了,果然就不再重疊了,那我們來看看再多一層節點會是怎么樣的壯觀場景呢?

哦,NO!這不是我想看到的效果,又重疊了,好討厭。

不要著急,我們再來仔細分析分析下,在前面,我們提到過一個名詞——領域半徑,什么是領域半徑呢?很簡單,就是可以容納下自身及其所有孩子節點的最小半徑,那么問題就來了,末端節點的領域半徑為我們指定的最小半徑,那么倒數第二層的領域半徑是多少呢?并不是我們前面計算出來的半徑,而應該加上末端節點自身的領域半徑,因為它們之間存在著包含關系,子節點的領域必須包含于其父親節點的領域中,那我們在看看上圖,是不是感覺末端節點的領域被侵占了。那么我們前面計算出來的半徑代表著什么呢?前面計算出來的半徑其實代表著孩子節點的布局半徑,在布局的時候是通過該半徑來布局的。

OK,那我們來總結下,節點的領域半徑是其下每層節點的布局半徑之和,而布局半徑需要根據其孩子節點個數及其領域半徑共同決定。

好了,我們現在知道問題的所在了,那么我們的代碼該如何去實現呢?接著往下看:

/*** 就按節點領域半徑及布局半徑* @param {ht.Node} root - 根節點對象* @param {Number} minR - 最小半徑*/ function countRadius(root, minR) {minR = (minR == null ? 25 : minR);// 若果是末端節點,則設置其布局半徑及領域半徑為最小半徑if (!root.hasChildren()) {root.a('radius', minR);root.a('totalRadius', minR);return;}// 遍歷孩子節點遞歸計算半徑var children = root.getChildren();children.each(function(child) {countRadius(child, minR);});var child0 = root.getChildAt(0);// 獲取孩子節點半徑var radius = child0.a('radius'),totalRadius = child0.a('totalRadius');// 計算子節點的1/2張角var degree = Math.PI / children.size();// 計算父親節點的布局半徑var pRadius = totalRadius / Math.sin(degree);// 緩存父親節點的布局半徑root.a('radius', pRadius);// 緩存父親節點的領域半徑root.a('totalRadius', pRadius + totalRadius);// 緩存其孩子節點的布局張角root.a('degree', degree * 2); }

在代碼中我們將節點的領域半徑緩存起來,從下往上一層一層地疊加上去。接下來我們一起驗證其正確性:

搞定,就是這樣子了,2D拓撲上面的布局搞定了,那么接下來該出動3D拓撲啦~

?

3. 加入z軸坐標,呈現3D下的樹狀結構

3D拓撲上面布局無非就是多加了一個坐標系,而且這個坐標系只是控制節點的高度而已,并不會影響到節點之間的重疊,所以接下來我們來改造下我們的程序,讓其能夠在3D上正常布局。

也不需要太大的改造,我們只需要修改下布局器并且將2D拓撲組件改成3D拓撲組件就可以了。

/*** 布局樹* @param {ht.Node} root - 根節點*/ function layout(root) {// 獲取到所有的孩子節點對象數組var children = root.getChildren().toArray();// 獲取孩子節點個數var len = children.length;// 計算張角var degree = root.a('degree');// 根據三角函數計算繞父親節點的半徑var r = root.a('radius');// 獲取父親節點的位置坐標var rootPosition = root.p3();children.forEach(function(child, index) {// 根據三角函數計算每個節點相對于父親節點的偏移量var s = Math.sin(degree * index),c = Math.cos(degree * index),x = s * r,z = c * r;// 設置孩子節點的位置坐標child.p3(x + rootPosition[0], rootPosition[1] - 100, z + rootPosition[2]);// 遞歸調用布局孩子節點layout(child);}); }

上面是改造成3D布局后的布局器代碼,你會發現和2D的布局器代碼就差一個坐標系的的計算,其他的都一樣,看下在3D上布局的效果:

恩,有模有樣的了,在文章的開頭,我們可以看到每一層的節點都有不同的顏色及大小,這些都是比較簡單,在這里我就不做深入的講解,具體的代碼實現如下:

var level = 4,size = (level + 1) * 20;var root = createNode(dataModel); root.setName('root'); root.p(100, 100);root.s('shape3d', 'sphere'); root.s('shape3d.color', randomColor()); root.s3(size, size, size);var colors = {},sizes = {}; createTreeNodes(dataModel, root, level - 1, 5, function(data, level, num) {if (!colors[level]) {colors[level] = randomColor();sizes[level] = (level + 1) * 20;}size = sizes[level];data.setName('item-' + level + '-' + num);// 設置節點形狀為球形data.s('shape3d', 'sphere');data.s('shape3d.color', colors[level]);data.s3(size, size, size); });

在這里引入了一個隨機生成顏色值的方法,對每一層隨機生成一種顏色,并將節點的形狀改成了球形,讓頁面看起來美觀些(其實很丑)。

提個外話,節點上可以貼上圖片,還可以設置文字的朝向,可以根據用戶的視角動態調整位置,等等一系列的拓展,這些大家都可以去嘗試,相信都可以做出一個很漂亮的3D樹出來。

到此,整個Demo的制作就結束了,今天的篇幅有些長,感謝大家的耐心閱讀,在設計上或則是表達上有什么建議或意見歡迎大家提出,點擊這里可以訪問HT for Web官網上的手冊

?

總結

以上是生活随笔為你收集整理的基于HT for Web的3D拓扑树的实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 激情综合区 | 手机电影在线观看 | 色婷av| 成人激情站| 一区国产精品 | 日韩精品一区二区三区中文字幕 | 国产视频欧美视频 | 国产九九久久 | 久久人人做 | 精品综合网| 91 久久 | 性欧美一区二区 | 一区二区三区视频免费看 | 人妻精品久久久久中文字幕 | www.国产在线观看 | 国产精品成人无码 | 九草在线观看 | 亚洲综合日韩在线 | 国产专区在线视频 | 久久中文字幕网 | 国产精品视频在线观看免费 | 九九热免费视频 | 美女性生活视频 | 我要看一级黄色片 | 久久久男女 | 一级福利视频 | 男欢女爱久石 | 亚洲精品 欧美 | 久久久久久少妇 | 免费看黄色片网站 | 亚久久 | 麻豆午夜 | av手机在线看 | 一级片一级片 | 嫩草影院久久 | 欧美日韩高清一区二区 | 国产成人一区二区三区电影 | 成年人免费观看网站 | 一区国产视频 | 免费一级欧美 | 亚洲视频一区在线观看 | 成年人的视频网站 | 亚洲综合久久网 | 日本欧美另类 | 99re伊人| 欧美日韩一区二区区 | 99国产精品国产免费观看 | 亚洲AV综合色区无码国产播放 | 国产精品一区二区av白丝下载 | 国产做受69| 日韩电影一区二区三区 | 人妻无码一区二区三区 | 在线播放毛片 | 黄色一级大片免费版 | 亚洲视频色图 | 精品亚洲一区二区三区四区五区 | 91视频在线免费观看 | av看片在线 | 操屁股视频 | 亚洲第一视频网站 | 欧美日韩综合一区 | 国产一区午夜 | 丁香六月啪啪 | jlzzjlzzjlzz亚洲人| 波多野吉衣伦理片 | 久久久久极品 | 手机看片99 | 美国一级大黄一片免费中文 | 久久三级网站 | 一级二级av| 天堂网免费视频 | 国产一区二区视频在线免费观看 | 一区二区三区四区精品 | 国产美女自慰在线观看 | 青青视频免费在线观看 | 成年网站在线观看 | 中文字幕狠狠干 | 欧美69精品久久久久久不卡 | 韩国一区二区在线观看 | 超碰丝袜| 久久久国产成人一区二区三区 | 日韩系列在线 | 三上悠亚 在线观看 | 冈本视频在线观看 | 看片久久 | 狠狠操影视 | 日韩电影福利 | 亚洲妇女体内精汇编 | 91极品尤物 | 女人色极品影院 | 尤物视频在线免费观看 | 一区二区三区四区不卡 | www日日日| 人人插人人干 | 裸体女视频 | 九草视频在线 | 91香蕉视频在线观看免费 | 中文字幕狠狠干 | aaa特级毛片 |