Implementation of a 3D topology tree based on HT for Web, ht Topology

Source: Internet
Author: User

Implementation of a 3D topology tree based on HT for Web, ht Topology

In HT for Web, 2D and 3D Applications Support displaying tree-structured data with different display effects. The tree-structured 2D data has obvious hierarchical relationships. However, if the data volume is large, it looks less intuitive, and it is difficult to find a specified node, while the 3D tree structure is intuitive in combination with the HT for Web elastic layout component, at a glance, we can look at the entire tree structure data, but with the help of the elastic layout, its hierarchy is not so clear. So now the demand for a 3D tree with clear structure comes. What exactly does this 3D tree look like? Let's see it together ~

 

Where can I start to achieve this? Next, we will split the problem into several small problems to solve.

1. Create a Tree Structure

If you have some knowledge of HT for Web, you should be familiar with the creation of tree structure data. I will not discuss it in depth here. Tree Structure Data is easy to create. Here, to make the code more concise, I encapsulate three methods to create tree structure data. The specific code is as follows:

/*** Create a line ** @ param {ht. dataModel} dataModel-data container * @ param {ht. node} source-start point * @ param {ht. node} target-endpoint */function createEdge (dataModel, source, target) {// create a link to the Father's Day Node and the child Node var edge = new ht. edge (); edge. setSource (source); edge. setTarget (target); dataModel. add (edge);}/*** create a Node object * @ param {ht. dataModel} dataModel-data container * @ param {ht. node} [parent]-Father's Day * @ returns {ht. node} Node object */func Tion createNode (dataModel, parent) {var node = new ht. node (); if (parent) {// set node for Father's Day. setParent (parent); createEdge (dataModel, parent, node);} // Add it to the data container dataModel. add (node); return node;}/*** create structure tree * @ param {ht. dataModel} dataModel-data container * @ param {ht. node} parent-Father's Day point * @ param {Number} level-depth * @ param {Array} count-Number of nodes on each layer * @ param {function (ht. node, Number, Number)} callback-back Call a function (Node object, hierarchy corresponding to the node, number of the node in the hierarchy) */function createTreeNodes (dataModel, parent, level, count, callback) {level --; var num = (typeof count = 'number '? Count: count [level]); while (num --) {var node = createNode (dataModel, parent); // call the callback function, you can set node-related attributes callback (node, level, num) in the callback; if (level = 0) continue; // recursively call createTreeNodes (dataModel, node, level, count, callback );}}

Hey, the Code may be a bit complicated. The simple method is to nest several for loops to create tree-structured data. I will not talk about it here. Next we will explore the second question.

2. Simulate the radius calculation of each layer of the 3D tree structure in a 2D Topology

The biggest problem with tree structures in 3D is that the layers of each node and the radius of each layer node around its father's day point are calculated. Now that the tree structure data is available, we should start to calculate the radius. We will start from the two-layer tree structure:

 

Now I have created a two-layer tree structure. All the child nodes are lined up and do not surround their father's day points. How can we determine the locations of these child nodes?

First, we need to know that each end node has a circle of its own domain, otherwise there will be overlapping between nodes, so here, assuming that the radius of the End Node is 25, the shortest distance between two adjacent nodes will be twice the radius of the node, that is, 50, these end nodes will be evenly around the Father's Day, so the angle of the two adjacent nodes can be confirmed. With the angle of the angle, there is a distance between the two points, the shortest radius of a node around its father's day point can be calculated. If the angle is a and the minimum distance between two points is B, the formula for calculating the shortest radius r is as follows:

R = B/2/sin (a/2 );

Then I will deploy this tree. The code is written as follows:

/* ** Layout tree * @ param {ht. node} root-root Node * @ param {Number} [minR]-minimum radius of the End Node */function layout (root, minR) {// set the default radius minR = (minR = null? 25: minR); // obtain the array of all child node objects var children = root. getChildren (). toArray (); // obtain the number of child nodes var len = children. length; // calculate the Zhang Jiao var degree = Math. PI * 2/len; // calculate the radius of the circle around Father's Day Based on the trigonometric function var sin = Math. sin (degree/2), r = minR/sin; // obtain the coordinates of the Father's Day vertex var rootPosition = root. p (); children. forEach (function (child, index) {// calculate the offset of each node relative to the Father's Day point based on the trigonometric function var s = Math. sin (degree * index), c = Math. cos (degree * index), x = s * r, y = c * r; // you can specify the coordinates of the child node, child. p (x + rootPosition. x, y + rootPosition. y );});}

In the code, you will find that I set the end radius to 25 by default. In this way, we can layout the structure tree by calling the layout () method. The layout effect is as follows:

We can see that the default radius of the end node is not very ideal, and the layout results are almost invisible. Therefore, we can increase the default radius of the End Node to solve the problem of too close layout, for example, set the default radius to 40 as follows:

Now the two-layer tree distribution is solved, let's take a look at how to handle the three-layer tree distribution.

Think of the second and third layers as a whole. In fact, the tree structure of the third layer is the same as that of the second layer. The difference is that when processing the second layer node, we should regard it as a two-layer tree structure for processing, so it is better to use recursion to process such a rule, so we should take the Code a little bit and look at the effect:

No, the nodes overlap. It seems that simple recursion is not feasible. What is the specific problem?

After careful analysis, we found that the domain radius of the Father's Day node is determined by the domain radius of the child node. Therefore, you need to know the domain radius of your node during the layout, the node location depends on the domain radius and location information of the Father's Day node. As a result, the node location cannot be calculated simultaneously.

Now we can only separate the calculation and layout of the radius and perform two steps. Let's first analyze the calculation of the node radius:

First, we need to clarify the most critical conditions. The radius of the Father's Day point depends on the radius of the child node. This condition tells us that we can only calculate the node radius from the bottom up, therefore, the recursive function we designed must be first recursive and then computed. Let's talk a little bit about it. Let's look at the specific code implementation:

/*** Click the node domain radius * @ param {ht. node} root-root Node object * @ param {Number} minR-Minimum radius */function countRadius (root, minR) {minR = (minR = null? 25: minR); // if it is an end node, set its radius to the minimum radius if (! Root. hasChildren () {root. a ('radius ', minR); return;} // traverse the child node and recursively calculate the radius var children = root. getChildren (); children. each (function (child) {countRadius (child, minR) ;}); var child0 = root. getChildAt (0); // obtain the radius of the child node var radius = child0.a ('radius '); // calculate the 1/2 corner of the child node var degree = Math. PI/children. size (); // calculate the radius of the Father's Day vertex var pRadius = radius/Math. sin (degree); // set the radius of the Father's Day and the layout of the child node. a ('radius ', pRadius); root. a ('degree', degree * 2 );}

OK, the radius calculation is done, so the layout problem should be solved. The layout tree structure data should be clear: the coordinates of the child node depend on the coordinates of the Father's Day node, therefore, the layout recursion method is different from the calculation radius recursion method. We need to layout the Father's Day point first and then the child node recursively. Let's take a look at the Code:

/* ** Layout tree * @ param {ht. node} root-root Node */function layout (root) {// obtain the array of all child Node objects var children = root. getChildren (). toArray (); // obtain the number of child nodes var len = children. length; // calculate the Zhang Jiao var degree = root. a ('degree'); // calculate the radius of the circle around Father's Day Based on the trigonometric function. var r = root. a ('radius '); // obtain the coordinates of the Father's Day vertex var rootPosition = root. p (); children. forEach (function (child, index) {// calculate the offset of each node relative to the Father's Day point based on the trigonometric function var s = Math. sin (degree * index), c = Math. cos (degree * index), x = s * r, y = c * r; // you can specify the coordinates of the child node, child. p (x + rootPosition. x, y + rootPosition. y); // recursively call the layout of the child node layout (child );});}

The Code has been written, and the next step is to witness the miracle. Let's take a look:

No, the code should be okay. Why does the display effect overlap? However, we can find that the layout is much better than that of the previous version. At least this time, the end nodes overlap. What is the problem?

I don't know if you have found that excluding the size of the node itself, the second-to-last node is tangent to the field between the node. That is to say, the radius of the node is not only related to the radius of the child node, it is also related to the radius of the Sun Tzu node. Let's transform the method of calculating the node radius, take the radius of the Sun Tzu node into consideration, and then look at the effect. The transformed code is as follows:

/*** Click the node domain radius * @ param {ht. node} root-root Node object * @ param {Number} minR-Minimum radius */function countRadius (root, minR ){...... Var child0 = root. getChildAt (0); // obtain the radius of the child node var radius = child0.a ('radius '); var child00 = child0.getChildAt (0); // Add the radius of the child node with the radius of the child node, avoid overlapping nodes if (child00) radius + = child00.a ('radius ');......}

Let's take a look at the effect ~

Haha, it seems that we have analyzed it right, and it does not overlap anymore. Let's take a look at what a spectacular scenario would be like with another layer of nodes?

Oh, NO! This is not what I want to see. it overlaps with each other, so annoying.

Don't worry. Let's analyze it carefully. We mentioned a term called domain radius. What is domain radius? It is very simple, that is, it can accommodate the minimum radius of itself and all its child nodes. Then the problem arises. The domain radius of the end node is the minimum radius we have specified, what is the radius of the second-to-last domain? Instead of the radius we calculated above, we should add the domain radius of the End Node, because there is an inclusion relationship between them, the sub-node field must be included in the father's day field. Let's see if we feel that the field of the End Node is encroached. What is the radius calculated above? The radius calculated above actually represents the layout radius of the child node, which is used for layout.

OK. In summary, the radius of the node field is the sum of the radius of the nodes on each layer. The radius of the node is determined based on the number of nodes and the radius of the node field.

Now that we know the problem, how can we implement our code? Next, let's look:

/*** Click the node domain radius and layout radius * @ param {ht. node} root-root Node object * @ param {Number} minR-Minimum radius */function countRadius (root, minR) {minR = (minR = null? 25: minR); // if it is an end node, set its layout radius and domain radius to the minimum radius if (! Root. hasChildren () {root. a ('radius ', minR); root. a ('totalradius ', minR); return;} // recursively calculates the radius of a child node by traversing var children = root. getChildren (); children. each (function (child) {countRadius (child, minR) ;}); var child0 = root. getChildAt (0); // obtain the radius of the child node var radius = child0.a ('radius '), totalRadius = child0.a ('totalradius '); // calculate the second corner of the subnode var degree = Math. PI/children. size (); // calculate the layout radius of the Father's Day point var pRadius = totalRadius/Math. sin (degree); // cache the layout radius of Father's Day. root. a ('radius ', pRadius); // cache the domain radius root of Father's Day. a ('totalradius ', pRadius + totalRadius); // cache the layout of the child node. a ('degree', degree * 2 );}

In the code, We cache the domain radius of the node and overlay it layer by layer from the bottom up. Next, let's verify the correctness:

That's it. The layout above the 2D topology is done. Now we need to dispatch the 3D topology ~

 

3. Add the Z axis coordinates to display the tree structure in 3D.

The layout above the 3D topology is nothing more than adding a coordinate system, and this coordinate system only controls the height of nodes and does not affect the overlap between nodes. So next we will transform our program, make it work properly on 3D.

There is no need for too much transformation. We only need to modify the layout and change the 2D topology component to the 3D topology component.

/* ** Layout tree * @ param {ht. node} root-root Node */function layout (root) {// obtain the array of all child Node objects var children = root. getChildren (). toArray (); // obtain the number of child nodes var len = children. length; // calculate the Zhang Jiao var degree = root. a ('degree'); // calculate the radius of the circle around Father's Day Based on the trigonometric function. var r = root. a ('radius '); // obtain the coordinates of the Father's Day vertex var rootPosition = root. p3 (); children. forEach (function (child, index) {// calculate the offset of each node relative to the Father's Day point based on the trigonometric function var s = Math. sin (degree * index), c = Math. cos (degree * index), x = s * r, z = c * r; // set the coordinates of the child node child. p3 (x + rootPosition [0], rootPosition [1]-100, z + rootPosition [2]); // recursive call layout child node layout (child );});}

The above is the layout code after being transformed into a 3D layout. You will find that the layout code of the 2D layout is different from the calculation of a coordinate system, and the rest are the same, let's see the effect of 3D layout:

Well, there is a pattern. At the beginning of the article, we can see that each layer of nodes has different colors and sizes. These are relatively simple. Here I will not explain them in depth, the Code is as follows:

Var level = 4, size = (level + 1) * 20; var root = createNode (dataModel); root. setName ('root'); root. p (1, 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); // set the node shape to spherical data. s ('shape3d ', 'sphere'); data. s ('shape3d. color ', colors [level]); data. s3 (size, size, size );});

A random color value generation method is introduced here to randomly generate a color for each layer and change the shape of the node to a spherical shape, make the page look more beautiful (in fact, ugly ).

In other words, the node can be patched, the orientation of the text can be set, the location can be dynamically adjusted based on the user's perspective, and a series of extensions, all of which can be tried, I believe that we can make a beautiful 3D tree.

At this point, the entire Demo is over. Today's space is a little long. Thank you for your patience and patience. In terms of design or expression, please give us some suggestions or comments, click here to visit the manual on the HT for Web website.

 

Copyright Disclaimer: This article is an original article by the blogger and cannot be reproduced without the permission of the blogger.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.