亚洲最大看欧美片,亚洲图揄拍自拍另类图片,欧美精品v国产精品v呦,日本在线精品视频免费

  • 站長資訊網(wǎng)
    最全最豐富的資訊網(wǎng)站

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

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

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

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

    1. 創(chuàng)建一個樹狀結(jié)構(gòu)

    有了解過HT for Web的朋友,對樹狀結(jié)構(gòu)數(shù)據(jù)的創(chuàng)建應(yīng)該都不陌生,在這里我就不做深入的探討了。樹狀結(jié)構(gòu)數(shù)據(jù)的創(chuàng)建很簡單,在這里為了讓代碼更簡潔,我封裝了三個方法來創(chuàng)建樹狀結(jié)構(gòu)數(shù)據(jù),具體代碼如下:

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

    嘿嘿,代碼寫得可能有些復(fù)雜了,簡單的做法就是嵌套幾個for循環(huán)來創(chuàng)建樹狀結(jié)構(gòu)數(shù)據(jù),在這里我就不多說了,接下來我們來探究第二個問題。

    2. 在2D拓?fù)湎履M3D樹狀結(jié)構(gòu)每層的半徑計算

    在3D下的樹狀結(jié)構(gòu)體最大的問題就在于,每個節(jié)點的層次及每層節(jié)點圍繞其父親節(jié)點的半徑計算?,F(xiàn)在樹狀結(jié)構(gòu)數(shù)據(jù)已經(jīng)有了,那么接下來就該開始計算半徑了,我們從兩層樹狀結(jié)構(gòu)開始推算:

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

    我現(xiàn)在先創(chuàng)建了兩層的樹狀結(jié)構(gòu),所有的子節(jié)點是一字排開,并沒有環(huán)繞其父親節(jié)點,那么我們該如何去確定這些孩子節(jié)點的位置呢?

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

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

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

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

    在代碼中,你會發(fā)現(xiàn)我將末端半徑默認(rèn)設(shè)置為25了,如此,我們通過調(diào)用layout()方法就可以對結(jié)構(gòu)樹進(jìn)行布局了,其布局效果如下:

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

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

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

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

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

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

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

    仔細(xì)分析了下,發(fā)現(xiàn)父親節(jié)點的領(lǐng)域半徑是由其孩子節(jié)點的領(lǐng)域半徑?jīng)Q定的,因此在布局時需要知道自身節(jié)點的領(lǐng)域半徑,而且節(jié)點的位置取決于父親節(jié)點的領(lǐng)域半徑及位置信息,這樣一來就無法邊計算半徑邊布局節(jié)點位置了。

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

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

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

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

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

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

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

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

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

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

    下面就來看看效果吧~

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

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

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

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

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

    OK,那我們來總結(jié)下,節(jié)點的領(lǐng)域半徑是其下每層節(jié)點的布局半徑之和,而布局半徑需要根據(jù)其孩子節(jié)點個數(shù)及其領(lǐng)域半徑共同決定。

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

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

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

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

    搞定,就是這樣子了,2D拓?fù)渖厦娴牟季指愣耍敲唇酉聛碓摮鰟?D拓?fù)淅瞺

    3. 加入z軸坐標(biāo),呈現(xiàn)3D下的樹狀結(jié)構(gòu)

    3D拓?fù)渖厦娌季譄o非就是多加了一個坐標(biāo)系,而且這個坐標(biāo)系只是控制節(jié)點的高度而已,并不會影響到節(jié)點之間的重疊,所以接下來我們來改造下我們的程序,讓其能夠在3D上正常布局。

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

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

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

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

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

    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);      // 設(shè)置節(jié)點形狀為球形      data.s('shape3d', 'sphere');      data.s('shape3d.color', colors[level]);      data.s3(size, size, size);  });

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

    利用HTML5 實現(xiàn)3D 網(wǎng)絡(luò)拓?fù)錁洌▓D文詳解)

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

    到此,整個Demo的制作就結(jié)束了,今天的篇幅有些長,感謝大家的耐心閱讀,在設(shè)計上或則是表達(dá)上有什么建議或意見歡迎大家提出

    贊(0)
    分享到: 更多 (0)
    網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號