Implementation of 3D tree based on HT for web

Source: Internet
Author: User
Tags cos sin

In the HT for Web , both 3D applications support the display of tree-like structure data, showing different effects, the tree structure on 2D is obvious in the presentation hierarchy, but if the amount of data is large, it seems to be less intuitive, it is difficult to find the specified node, The 3D tree structure on the display with the HT for Web Elastic layout components will be more intuitive, a glance can be the whole tree structure data to see a general, but in the role of elastic layout, its hierarchical structure is not so clear. So this time the structure of a clear 3D tree needs to come, then this 3D tree specifically grow what kind of, we come together to witness the next ~

To achieve such an effect, where do we begin? Next we will solve the problem by breaking it down into a few small problems.

1. Create a tree structure

Have known HT for Web friends, the tree structure of data creation should not be unfamiliar, here I do not do in-depth discussion. The creation of tree structure data is simple, here in order to make the code more concise, I encapsulated three methods to create tree structure data, the code is as follows:

/** * Create connection * @param {ht. Datamodel} datamodel-Data container * @param {ht. Node} Source-Start * @param {ht. Node} target-Endpoint */function Createedge (Datamodel, source, target) {//Create connection, link Father node and child node var edge = new ht.    Edge ();    Edge.setsource (source);    Edge.settarget (target); Datamodel.add (edge);} /** * Create Node Object * @param {ht. Datamodel} datamodel-Data container * @param {ht. node} [parent]-Father nodes * @returns {ht. Node} nodes object */function CreateNode (Datamodel, parent) {var node = new ht.    Node ();        if (parent) {//Set Father node Node.setparent (parent);    Createedge (Datamodel, parent, node);    }//Added to the Data container Datamodel.add (node); return node;} /** * Create structure Tree * @param {ht. Datamodel} datamodel-Data container * @param {ht. Node} parent-Father nodes * @param {number} level-depth * @param {Array} count-nodes per layer * @param {function (HT. node, number, #)} Callback-Callback function (node object, node-corresponding hierarchy, node-numbered in 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, the user can set the node-related properties in the callback callback (node, level, num);        if (level = = = 0) continue;    Recursive call to create a child node Createtreenodes (Datamodel, node, level, count, callback); }}

Hey, the code may be a little bit more complicated, the simple way is to nest a few for loops to create tree structure data, here I will not say, then we will explore the second question.

2. Calculating the radius of each layer of 3D tree structure under 2D topology

The biggest problem with the tree structure at 3D is that the hierarchy of each node and the radius of each node around its parent node are computed. Now that the tree structure data is available, it's time to start calculating the radius, and we'll start with a two-tiered tree structure:

Now that I've created a two-tier tree, all of the child nodes are lined up, not around their fathers, so how do we determine where these kids ' nodes are?

First we need to know that each end node has a circle belongs to their own domain, otherwise there will be overlapping between the nodes and nodes, so here, we assume that the end node has a domain radius of 25, then the shortest distance between two adjacent nodes will be twice times the node domain radius, that is, 50, And these end nodes will be evenly spaced around their father's nodes, then the angle of the adjacent two nodes can be confirmed, with a corner, with two points between the distance, then the node around its father node's shortest radius can be calculated, assuming that the angle is a, the minimum distance between two points is B, then the minimum radius r is calculated as:

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

So then I'll come to the layout of this tree, the code is written like this:

/** * Layout Tree * @param {ht. node} root-root node * @param {number} [MinR]-The minimum radius of the end node */function layout (root, MinR) {    //Set default radius    MinR = (MinR = = Nu ll? 25:MINR);    Gets the array of all child node objects    var children = Root.getchildren (). ToArray ();    Gets the number of child nodes    var len = children.length;    Calculate the angle    var degree = Math.PI * 2/len;    Calculates the radius around the Father node according to trigonometric functions    var sin = Math.sin (DEGREE/2),        r = minr/sin;    Gets the position coordinates of the Father node    var rootposition = ROOT.P ();    Children.foreach (function (Child, index) {        //) calculates the offset of each node relative to the Father node        var s = Math.sin (degree * index) according to the trigonometric functions,            c = Math.Cos (degree * index),            x = S * r,            y = c * r;        Set the location coordinates of the child node        child.p (x + rootposition.x, y + rootposition.y);    });

In the code, you'll find that I set the end radius to 25 by default, so we can lay out the tree by calling the layout () method, which has the following layout effect:

From the point of view, the default radius of the end node is not ideal, the layout of the effect line is not visible, so we can increase the default radius of the end node to solve the problem of too dense layout, such as the default radius of 40 is set as follows:

Now that the two-tier tree distribution is resolved, let's look at how the three-tier tree distribution is handled.

The second layer and the third layer as a whole, then in fact, the three-layer tree structure is the same as the two layers, the difference is that when dealing with the second layer of nodes, it should be treated as a two-layer tree structure to deal with, then like this rule of recursion is best, so we will be slightly rational the code, look at how the effect

No, the nodes overlap together, it seems that the simple recursion is not possible, then the specific problem is where?

After careful analysis, it is found that the domain radius of the Father node is determined by the domain radius of the child node, so it is necessary to know the domain radius of the node in the layout, and the position of the node depends on the domain radius and position information of the Father node, so that the radius Edge layout node position cannot be computed.

So now we can only separate the calculation and layout of the radius, do two steps, we first analyze the next node radius calculation:

First of all, we need to clarify the most critical conditions, the radius of the Father node depends on the radius of their children's nodes, this condition tells us that only from the bottom to calculate the node radius, so we design the recursive function must be recursive after the calculation, not much nonsense, we look at the specific code implementation:

/** * by 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 child node Recursive compute radius    var children = Root.getchildren ();    Children.each (function (child) {        Countradius (child, MinR);    });    var child0 = root.getchildat (0);    Get child node radius    var radius = child0.a (' radius ');    Calculates the 1/2-angle    var degree = math.pi/children.size () of a child node;    Calculates the radius of the Father node    var Pradius = radius/math.sin (degree);    Set the radius of the Father node and the layout of the child's node    root.a (' radius ', Pradius);    ROOT.A (' degree ', degree * 2);}

OK, the radius of the calculation solved, then the layout of the problem, the layout of the tree structure data need to be clear: the child node coordinate position depends on the coordinates of their father node, so the layout of recursion and computing radius of the recursive way, we need to first layout the Father node and then recursively layout the child node, Let's look at the code:

/** * Layout Tree * @param {ht. node} root-root node */function layout (root) {    //Gets all the child node object array    var children = Root.getchildren (). ToArray ();    Gets the number of child nodes    var len = children.length;    Calculate the angle    var degree = root.a (' degree ');    Calculates the radius around the Father node based on the trigonometric functions    var r = root.a (' radius ');    Gets the position coordinates of the Father node    var rootposition = ROOT.P ();    Children.foreach (function (Child, index) {        //) calculates the offset of each node relative to the Father node        var s = Math.sin (degree * index) according to the trigonometric functions,            c = Math.Cos (degree * index),            x = S * r,            y = c * r;        Set the location coordinates of the child node        child.p (x + rootposition.x, y + rootposition.y);        Recursive call layout children node layouts        (child);}    );

The code is finished, and then there is the time to witness the miracle, let's take a look:

No, the code should be no problem, why does the effect of the display still overlap? But looking closely we can see that the layout is much better than the previous version, at least this time the end nodes overlap, so where is the problem?

Do not know whether we have found that the size of the exclusion node itself, the second-to-last node and the node between the domain is tangent, so that the radius of the node is not only related to the radius of its children's nodes, but also the radius of its grandson node, then we transform the method of calculating the node radius, The radius of the grandson node is also taken into account to see how the effect, the modified code is as follows:

/** * by node domain radius * @param {ht. node} root-Root node object * @param {number} MinR-Minimum radius */function Countradius (root, MinR) {   ...    var child0 = root.getchildat (0);    Get child node radius    var radius = child0.a (' radius ');    var child00 = child0.getchildat (0);    Radius plus grandson node radius, avoid node overlap    if (child00) Radius + = child00.a (' radius ');   ......}

Let's take a look at the effect here ~

Haha, it seems that we analyzed the right, and really no longer overlap, then we have to see how many more nodes will be what kind of spectacular scene?

Oh, no!. This is not what I want to see the effect, but also overlapping, so annoying.

Don't worry, let's take a closer look at the analysis, in front, we mentioned a noun-the domain radius, what is the domain radius? It is simply that the smallest radius of the node that can hold itself and all of its children, then the problem comes, and the radius of the end node is the smallest radius we specify, so what is the domain radius of the second-to-last level? Instead of the radius we calculated earlier, we should add the domain radius of the end node itself, because there is a containment relationship between them, the domain of the child nodes must be included in the domain of their father's node, and we are looking at whether the domain of the end node is perceived as being encroached upon. So what does the radius we've calculated above represent? The previously calculated Radius actually represents the child's node's layout radius, which is laid out by that radius at the time of layout.

OK, let's conclude that the domain radius of a node is the sum of the layout radius of each node beneath it, and the layout radius needs to be determined by the number of child nodes and their domain radius.

Well, now that we know what the problem is, how can our code be implemented? Then look down:

/** * by 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    }//Traverse child node recursive compute radius var children = Root.getchildren ();    Children.each (function (child) {Countradius (Child, MinR);    });    var child0 = root.getchildat (0);    Get child node radius var radius = child0.a (' radius '), Totalradius = child0.a (' Totalradius ');    Calculates the 1/2-angle var degree = math.pi/children.size () of a child node;    Calculates the parent node's layout radius var Pradius = totalradius/math.sin (degree);    Cache the parent node's layout radius root.a (' radius ', Pradius);    Cache the domain radius of the Father node Root.a (' Totalradius ', Pradius + Totalradius); Caches the layout of their child nodes root.a (' degree ', degree * 2);} 

In the code, we cache the domain radius of the nodes, stacking them from the bottom up to the top layer. Next we verify its correctness:

OK, that's it,2D topology above the layout is done, then the next to deploy a 3D topology ~

3. Add z-axis coordinates to render the tree structure under 3D

3D topology The above layout is nothing more than a coordinate system, and this coordinate system is only the height of the control node, and does not affect the overlap between the nodes, so we will then transform our program, so that it can be normal layout on 3D.

It does not require much modification, we just need to modify the next layout and change the 2D topology component to a three-dimensional topology component.

/** * Layout Tree * @param {ht. node} root-root node */function layout (root) {    //Gets all the child node object array    var children = Root.getchildren (). ToArray ();    Gets the number of child nodes    var len = children.length;    Calculate the angle    var degree = root.a (' degree ');    Calculates the radius around the Father node based on the trigonometric functions    var r = root.a (' radius ');    Gets the position coordinates of the Father node    var rootposition = Root.p3 ();    Children.foreach (function (Child, index) {        //) calculates the offset of each node relative to the Father node        var s = Math.sin (degree * index) according to the trigonometric functions,            c = Math.Cos (degree * index),            x = S * r,            z = c * r;        Set the location coordinates of the child's node        child.p3 (x + rootposition[0], rootposition[1]-+, z + rootposition[2]);        Recursive call layout children node layouts        (child);}    );

The above is the layout after the layout of the code, you will find and 2D layout code for a coordinate system of the calculation, the other is the same, look at the effect of the layout on 3D:

Well, there is a kind of mold, at the beginning of the article, we can see each layer of nodes have different color and size, these are relatively simple, here I do not do in-depth explanation, the specific code implementation is as follows:

var level = 4,    size = (level + 1) * 20;var root = CreateNode (Datamodel); Root.setname (' root '); ROOT.P (+); 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) *;    }    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);});

Here, a randomly generated color value is introduced, randomly generating a color for each layer, and changing the shape of the node to a sphere, making the page look beautiful (actually ugly).

A digression, the node can be SMD, you can also set the direction of the text, you can dynamically adjust the position according to the user's perspective, and so on a series of expansion, these people can go to try, I believe can make a very beautiful 3D tree out.

To this, the entire production of the demo is over, today's length is a bit long, thank you for your patience to read, in the design or expression have any suggestions or comments welcome, Click here to access the HT for Web official online manual .

Implementation of 3D tree based on HT for web

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.