查看内容

网络拓扑树呈现

  • 2020-03-02 15:58
  • 澳门太阳集团2007网站
  • Views

HT for Web中2D和3D应用都辅助树状构造数据的呈现,表现效果分歧,2D上的树状布局在表现层级关系鲜明,但是假使数据量大的话,看起来就没那么直观,找到内定的节点比较困难,而3D上的树状构造在显示上出色HT for Web的弹力布局组件会来得相比直观,一眼望去能够把全部树状构造数据看个概略,然则在弹力布局的效果与利益下,其等级次序结构看得就不是那么清楚了。所以那时结构清晰的3D树的供给就来了,那么那些3D树具体长成啥样呢,大家来一块目击下~

图片 1

要兑现那样的作用,该从何出手呢?接下去大家就将这些主题材料拆解成多少个小标题来消除。

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下的树状结构体最大的标题就在于,每种节点的档次及每层节点围绕其老爹节点的半径计算。以往树状布局数据现原来就有了,那么接下去就该起来思索半径了,大家从两层树状构造初阶推算:

图片 2

本人前几天先创建了两层的树状布局,全数的子节点是一字排开,并从未环绕其阿爸节点,那么我们该怎么去明显这一个子女节点的任务吗?

第一大家得理解,每一个末端节点都有一圈归于自身的世界,不然节点与节点之间将会设有重叠的情事,所以在这里地,我们假使末端节点的领域半径为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(卡塔尔国方法就足以对组织树举行结构了,其布局功用如下:

图片 3

从功用图能够看得出,末端节点的暗中同意半径实际不是极好看,布局出来的功用连线都快看不到了,因而我们得以增添末端节点的暗中同意半径来消逝布局太密的标题,如将暗中同意半径设置成40的法力图如下:

图片 4

明日两层的树状分布消除了,那么大家来看看三层的树状布满该如什么地点理。

将第二层和第三层用作三个安然无事,那么实际上三层的树状构造跟两层是同一的,区别的是在管理第二层节点时,应该将其看作三个两层的树状构造来管理,那么像这种规律的拍卖用递归最棒可是了,因而我们将代码微微该着下,在探问效果如何:

图片 5

丰硕,节点都重叠在联合了,看来轻松的递归是那个的,那么具体的标题出在何地吧?

留意深入分析了下,开掘老爹节点的天地半径是由其孩子节点的园地半径决定的,因此在结构时索要知道自身节点的世界半径,况兼节点的岗位决议于老爸节点的小圈子半径及职责音信,这样一来就不可能边计算半径边布局节点地方了。

那么今后只能将半径的精兵简政和布局分开来,做两步操作了,我们先来剖判下节点半径的计算:

先是须求明显最入眼的尺码,老爸节点的半径决计于其儿女节点的半径,这几个法则告诉大家,只好从下往上总括节点半径,因而大家设计的递归函数必需是先递归后总括,废话相当少说,我们来看下具体的代码完结:

/**
 * 就按节点领域半径
 * @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);
    });
}

代码写完了,接下去正是亲眼见到神蹟的任何时候了,大家来探视效果图吧:

图片 6

不对呀,代码应该是没难点的呀,为何来得出来的效劳依然会重叠呢?不过用心考察大家可以开掘比较上个版本的结构会好过多,最少这一次只是背后节点重叠了,那么难点出在哪儿呢?

不明了大家有未有觉察,排除节点自个儿的高低,尾数第二层节点与节点之间的小圈子是相切的,那么也便是说节点的半径不仅仅和其子女节点的半径有关,还与其外孙子节点的半径有关,那大家把总计节点半径的章程退换下,将孙子节点的半径也思忖进来再看看效果如何,更动后的代码如下:

/**
 * 就按节点领域半径
 * @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');

   ……
}

下边就来看看效果啊~

图片 7

哈哈,看来大家深入分析对了,果然就不再重叠了,那大家来拜谒再多一层节点会是什么的壮观光象吧?

图片 8

哦,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);
}

在代码中我们将节点的世界半径缓存起来,从下往上一层一层地叠合上去。接下来大家协作验证其科学:

图片 9

化解,正是那样子了,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上结构的功力:

图片 10

恩,三思而行的了,在文章的起来,我们能够看见每一层的节点皆有两样的水彩及大小,那些都以比较简单,在这里地笔者就不做浓重的上课,具体的代码完结如下:

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);
});

在这里地引进了一个自由生成颜色值的秘诀,对每一层随机生成一种颜色,并将节点的模样改成了球形,让页面看起来美观些(其实超丑)。

图片 11

提个外话,节点上得以贴上航海用教室片,还足以设置文字的通向,能够根据客户的意见动态调治岗位,等等一文山会海的扩充,这么些我们都能够去品味,相信都能够做出三个极漂亮貌的3D树出来。

到此,整个德姆o的制作就终止了,前几天的篇幅有个别长,多谢大家的意志力阅读,在统筹上或则是发挥上有啥提议或意见应接大家提出,点击这里能够访问HT for Web官互连网的手册