zoukankan      html  css  js  c++  java
  • d3 使用记录: 树形图

    先上效果图:

     对应源码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="../d3.v5.js"></script>
        <script src="./data.js"></script>
        <style>
            svg{
                display: block;
                margin: 100px auto;
                border: 3px solid #ccc;
                background: darkgray;
            }
            path.link{
                stroke: #333;
                stroke-width: 1.5px;
                fill:transparent;
            }
            /* path.line{
                fill: none;
            } */
            .node circle{
                fill:#fff;
                stroke:steelblue;
                stroke-width: 1.5px;
            }
            .grid-line {
                stroke: #fcfcfc;
                stroke-dasharray: 3 3;
                stroke-opacity: 0.5;
            }
            .x-axis .tick:nth-child(2) .grid-line {
                /*stroke- 4px;*/
                stroke: none;
            }
            .y-axis .tick:last-child .grid-line {
                /* stroke: none; */
            }
        </style>
    </head>
    <body>
    
        <script>
    
            var globalId = 1;
            function tree() {
                var _chart = {};
                var _width = 1000,
                    _height = 600,
                    _margin = { left: 25, right: 15, top: 20, bottom: 20 },
                    _colors = d3.scaleOrdinal(d3.schemeCategory10),
                    _svg,
                    _nodes,
                    _i = 0,
                    _duration = 300,
                    _bodyG,
                    _root,
                    _curMaxHeight,
                    _tree,
                    _nodeRectWidth = 100,
                    hierarchyData = {},
                    textLineHeightCache = {},
                    textPadding = [10, 5, 10, 5],
                    textLineHeight = 18;
    
                
    
                _chart.render = function() {
                    if (!_svg) {
                        _svg = d3.select("body").append("svg")
                                    .attr("width", _width)
                                    .attr("height", _height);
                    }
    
                    renderBody(_svg)
                }
    
                
                function renderBody(svg) {
                    if (!_bodyG) {
                        _bodyG = svg.append("g")
                                    .attr("class", "body")
                                    .attr("transform", function(d) {
                                        return 'translate(' + _margin.left + ',' + _margin.top +')'
                                    })
                        addScroll();
                    }
    
                    _tree = d3.tree().nodeSize([120, 80]);
    
                    for (let position in _nodes) {
    
                        // 递归计算节点的偏移, 节点高度; 并缓存;
                        initDistance(_nodes[position], calcTextLineght(_nodes[position].name, false));
    
                        // 获取树结构数据
                        hierarchyData[position] = d3.hierarchy(_nodes[position])
    
                        // 设置树的折叠;
                        collapseChildren(hierarchyData[position], position > 'left' ? 1 : 2, position);
                        
                        // 渲染节点树
                        render(hierarchyData[position], _tree, position);
                    }
                }
    
                // 折叠节点:
                function collapseChildren (treeNode, collapseDepth, direction) {
                    var preOffset = 0;
                    var walk = function(node, depth, direction) {
                        node.direction = direction;
                        node.offset = (node.offset || 0) + preOffset;
                        if ( node.children ) {
                            if (Array.isArray(node.children)) {
                                if (node.depth >= depth) {
                                    node._children = node.children;
                                    node.children = null
                                    node.collapseAble = true;
    
                                    node._children.forEach(function(v) {
                                        walk(v, depth, direction)
                                    })
                                } else {
                                    node.children.forEach(function(v) {
                                        walk(v, depth, direction)
                                    })
                                }
                            } else {
                                if (node.depth >= depth) {
                                    node._children = node.children;
                                    node.children = null
                                    node.collapseAble = true;
    
                                    walk(node._children, depth, direction)
                                } else {
                                    walk(node.children, depth, direction)
                                }
                            }
                        }
                    }
                    walk(treeNode, collapseDepth, direction)
                }
    
                // 应用缩放
                function addScroll() {
                    var zoomHandler = d3.zoom().scaleExtent([0.2, 20]).on('zoom', function () {
                        var transform = d3.event.transform;
                        d3.select('svg g.body').attr('transform', transform) // .transition().duration(100)
                    })
                    _svg.call(zoomHandler)
                }
    
                function render(root, tree, position) {
                    // 计算树的布局
                    tree(root);
    
                    // 将对称的两个树进行偏移, 让其居中, 对称
                    setNodePosition(root, position)
                    
                    renderNodes(root, position);
                    renderLinks(root, position);
                }
    
                // 设置节点偏移, 计算节点高度
                function initDistance(list, preOffSize) {
                    preOffSize = preOffSize || 0;
                    var iterator = [].forEach;
                    iterator.call(list.children || list._children, item => {
                        // item._children = item.children ? (item.children.length ? item.children : null) : null
                        // item.children = null
    
                        let offSize = 0
                        let height = calcTextLineght(item.name, !!(item._children || item.children))
    
                        // console.log('高度', height, item);
                        offSize = height + preOffSize
                        item.offSize = preOffSize
                        item.height = height
                        
                        if (item.children || item._children) {
    
                            initDistance(item, offSize)
                        }
                    })
                }
    
                var canvas2dContext = document.createElement("canvas").getContext("2d");
                function setNodePosition(root, position) {
                    var offsetX = _width / 2,
                        offsetY = _height / 2,
                        oneWidth = 120,
                        oneHeight = 80;
    
                    // root.each()
                    // console.log('root', root);
                    root.each((node,index) => {
                        var text = node.data.name;
                        var textLineHeights = null;
                        var height = null;
    
                        if (textLineHeightCache[text]) {
                            textLineHeights = textLineHeightCache[text]
                        } else {
                            textLineHeights = calcBreakWord(text, _nodeRectWidth - textPadding[1] - textPadding[3] - (node.collapseAble ? 16 + 10 : 0), 14, canvas2dContext)
                            textLineHeightCache[text] = textLineHeights
                        }
    
                        // 如果是根节点, 将位置设置到中心
                        if (node.depth === 0) {
                            height = textLineHeights.length * textLineHeight + textPadding[0] + textPadding[2]
    
                            // 横向的上下图
                            node.x = offsetX;
                            node.y = offsetY;
    
                            node._width = _nodeRectWidth;
                            node._height = height;
    
                        } else {
                            // console.log('计算高度', textLineHeights);
                            height = textLineHeights.length * textLineHeight + textPadding[0] + textPadding[2]
    
                            // cur = root
                            if (position === 'left') {
                                node.x += offsetX;
                                node.y += offsetY;
    
                                node._width = _nodeRectWidth;
                                node._height = height;
                            } else if (position === 'right') {
                                node.x += offsetX;
                                // node.y -= offsetY;
                                
                                node.y = -(node.y - offsetY)
    
                                node._width = _nodeRectWidth;
                                node._height = height;
                            } else if (position === 'top') {
                                // 
                                // node.x = -(node.x - gridX / 2);
                                // console.log('Y周偏移', gridX / 2, node.x, offsetX, node.y, offsetY);
                                // // node.y += offsetY * Math.cos(Math.PI / 2)
                                // node.y = gridY / 2 - (node.x - gridX / 2);
                            } else {
                                node.x -= offsetX;
                                node.y += offsetY;
    
                                node._width = _nodeRectWidth;
                                node._height = height;
                            }
                        }
                    })
                }
    
    
                // 计算文本高度; 并缓存起来
                function calcTextLineght(text, collapseAble) {
                    var textLineHeights = null;
                    var height = null;
    
                    if (textLineHeightCache[text]) {
                        textLineHeights = textLineHeightCache[text]
                    } else {
                        textLineHeights = calcBreakWord(text, _nodeRectWidth - textPadding[1] - textPadding[3] - (collapseAble ? 16 + 10 : 0), 14, canvas2dContext)
                        textLineHeightCache[text] = textLineHeights
                    }
                    height = textLineHeights.length * textLineHeight + textPadding[0] + textPadding[2]
    
                    return height
                }
    
                
    
                function renderNodes(root, baseId) {
                    var nodes = root.descendants(); // .slice(1)
                    // console.log('重新计算坐标位置', nodes, root);
                    var _offsetX = _width / 2,
                        _offsetY = _height / 2,
                        oneWidth = 120,
                        oneHeight = 80;
                    
                    var nodeELements = _bodyG.selectAll("g.node._" + baseId)
                                            .data(nodes, function(d){
                                                return d.id || (d.id = ++_i)
                                            });
                    var nodeEnter = nodeELements.enter().append("g")
                                        .attr("class", "node _" + baseId);
    
                    nodeEnter.append("rect")
                            .attr("x", function(d) { return -d._width / 2 })
                            .attr("y", function(d) { return 0 })
                            .attr("width", function(d) { return d._width })
                            .attr("height", function(d) { return d._height })
                            .attr("class", "node-background")
                            .attr("fill", function(d) { return d.backgournd || '#fff' })
                    nodeEnter.append("circle")
                            .attr("r", function(d) {
                                return d.children || d._children ? 8 : 1e-8
                            })
                            .attr("class", "indicator-circle")
                            .attr("stroke", "#333")
                            .attr("fill", "#fff")
                            .style("cursor", "pointer")
                            .attr("cx", function(d) {
                                return d._width / 2 - textPadding[1] - 8
                            })
                            .attr("cy", function(d) {
                                return d._height / 2
                            })
                            .style("display", function(d) {
                                return d.children || d._children ? "block" : "none"
                            });
                    nodeEnter.append("text")
                            .attr("class", "indicator-text")
                            .attr("x", function(d) {
                                return d._width / 2 - textPadding[1] - 8
                            })
                            .attr("y", function(d) {
                                return d._height / 2
                            })
                            .attr("text-anchor", "middle")
                            .attr("dominant-baseline", "middle")
                            .style("cursor", "pointer")
                            .text(function(d) {
                                return d.children ? "-" : d._children ? "+" : ""
                            });
                    nodeEnter.attr("transform", function(d) {
                                            
                                if (d.parent) {
                                    var offsetY = d.parent.data.offSize || 0;
    
                                    if (baseId === 'right') {
                                        return 'translate(' + d.parent.x + ',' + (d.parent.y - offsetY) + ')'
                                    } else {
                                        return 'translate(' + d.parent.x + ',' + (d.parent.y + offsetY) + ')'
                                    }
                                } else {
                                    var offsetY = d.data.offSize || 0;
    
                                    if (baseId === 'right') {
                                        return 'translate(' + d.x + ',' + (d.y - offsetY) + ')'
                                    } else {
                                        return 'translate(' + d.x + ',' + (d.y + offsetY) + ')'
                                    }
                                }
                            })
                            .on("click", function(d) {
                                toggle(d);
                                render(hierarchyData[d.direction], _tree, d.direction);
                            })
    
                    // console.log('缓存的行高信息', textLineHeightCache, nodeEnter);
                    nodeEnter.call(function (nodeGroup, i) {
                                nodeGroup.selectAll("text.node-text")
                                    .data(function(d) {
                                        var cache = textLineHeightCache[d.data.name]
                                        var list = []
                                        if (cache) {
                                            for (var i = 0; i < cache.length; i++) {
                                                list.push({
                                                    text: cache[i],
                                                    _ d._width,
                                                    _height: d._height
                                                })
                                            }
                                        }
                                        return list
                                    })
                                    .enter()
                                    .append("text")
                                    .attr("class", "node-text")
                                    .attr("x", function(d) {
                                        return -d._width / 2 + textPadding[3] 
                                    })
                                    .attr("y", function(d, i) {
                                        // var totalHeight = d._height - textPadding[0] - textPadding[3];
                                        // var Total -d._height / 2 + 
                                        return textPadding[0] + textLineHeight/2 + i * textLineHeight
                                    })
                                    .text(function(d) { 
                                        return d.text 
                                    })
                                    .attr("dominant-baseline", "middle")
                            })
                            
                    
    
                    var nodeUpdate = nodeEnter.merge(nodeELements)
                                                // 从父节点伸展到节点位置
                                                .attr("transform", function(d) {
                                                    // if (d.parent) {
                                                    //     var offsetY = d.parent.data.offSize || 0;
    
                                                    //     if (baseId === 'right') {
                                                    //         return 'translate(' + d.parent.x + ',' + (d.parent.y - offsetY) + ')'
                                                    //     } else {
                                                    //         return 'translate(' + d.parent.x + ',' + (d.parent.y + offsetY) + ')'
                                                    //     }
                                                    // } else {
                                                        var offsetY = d.data.offSize || 0;
    
                                                        if (baseId === 'right') {
                                                            return 'translate(' + d.x + ',' + (d.y - offsetY) + ')'
                                                        } else {
                                                            return 'translate(' + d.x + ',' + (d.y + offsetY) + ')'
                                                        }
                                                    // }
                                                    
                                                })
                                                .transition().duration(_duration)
                                                .attr("transform", function(d) {
                                                    var offsetY = d.data.offSize || 0;
    
                                                    if (baseId === 'right') {
                                                        return 'translate(' + d.x + ',' + (d.y - offsetY) + ')'
                                                    } else {
                                                        return 'translate(' + d.x + ',' + (d.y + offsetY) + ')'
                                                    }
                                                });
    
                    nodeUpdate.select("text.indicator-text")
                                .text(function(d) {
                                    return d.children ? "-" : d._children ? "+" : ""
                                });
                    nodeUpdate.select("circle.indicator-circle")
                                .attr("r", function(d) {
                                    return d.children || d._children ? 8 : 1e-8
                                })
                                .style("display", function(d) {
                                    return d.children || d._children ? "block" : "none"
                                })
    
                    var nodeExit = nodeELements.exit()
                                                .transition().duration(_duration)
                                                .attr("transform", function(d) {
                                                    // if (baseId === 'right') {
                                                    //     return 'translate(' + d.x + ',' + d.y + ')'
                                                    // } else {
                                                    //     return 'translate(' + d.x + ',' + d.y + ')'
                                                    // }
                                                    // 收缩回父节点
                                                    if (d.parent) {
                                                        var offsetY = d.parent.data.offSize || 0;
    
                                                        if (baseId === 'right') {
                                                            return 'translate(' + d.parent.x + ',' + (d.parent.y - offsetY) + ')'
                                                        } else {
                                                            return 'translate(' + d.parent.x + ',' + (d.parent.y + offsetY) + ')'
                                                        }
                                                    } else {
                                                        var offsetY = d.data.offSize || 0;
    
                                                        if (baseId === 'right') {
                                                            return 'translate(' + d.x + ',' + (d.y - offsetY) + ')'
                                                        } else {
                                                            return 'translate(' + d.x + ',' + (d.y + offsetY) + ')'
                                                        }
                                                    }
                                                })
                                                .call(function(node) {
                                                    node.select("rect.node-text")
                                                            .attr("r", 1e-6)    // 0.000001
                                                            .remove();
                                                    node.select("text.indicator-text")
                                                        .text(function(d) {
                                                            return ""
                                                        })
                                                        .remove();
                                                    node.select("circle.indicator-circle")
                                                        .attr("r", function(d) {
                                                            return 1e-8
                                                        })
                                                        .remove();
                                                    node.select("rect.node-background")
                                                        .remove()
                                                    node.selectAll("text.node-text")
                                                        .remove()
                                                })
                                                .remove();
                    
                    // reCalcMaxHeight(nodes);
                    // renderLabels(nodeEnter, nodeUpdate, nodeExit, root);
                }
    
                function toggle(d) {
                    if(d.children) {
                        d._children = d.children;
                        d.children = null
                    } else {
                        d.children = d._children;
                        d._children = null
                    }
                }
    
    
                function renderLinks(root, baseId) {
                    var nodes = root.descendants().slice(1);
                    var links = _bodyG.selectAll("path.link._" + baseId)
                                    .data(nodes, function(d) {
                                        return d.id || (d.id = ++_i);
                                    })
    
                    links.enter()
                            .insert("path")
                        .merge(links)
                            .attr("class", "link _" + baseId)
                            .transition().duration(_duration)
                            .attr("d", function(d) {
                                return generateLinkPath(d, d.parent, baseId)
                            })
                    links.exit().remove();
                }
    
                function generateLinkPath(target, source, baseId) {
                    // console.log('顶点位置', target, source);
                    // (d.y + offsetY)
                    // 直线链接
                    // var path = d3.path();
                    // if (baseId === 'left') {
                    //     path.moveTo(source.x, source.y + (source.data.offSize || 0) + (source._height || 0));
                    //     path.lineTo(target.x, target.y + (target.data.offSize || 0));
                    // } else if (baseId === 'right') {
                    //     path.moveTo(source.x, source.y - (source.data.offSize || 0));
                    //     path.lineTo(target.x, target.y - (target.data.offSize || 0) + (target._height || 0));
                    // }
                    // return path.toString()
                    
                    // 曲线链接
                    // var path = d3.path();
                    // // // bezierCurveTo 三阶贝塞尔曲线: 
                    // // // 控制点 1 的 x坐标
                    // // // 控制点 1 的 y坐标
                    // // // 控制点 2 的 x坐标
                    // // // 控制点 2 的 y坐标
                    // // // 结束点 的 x坐标
                    // // // 结束点 的 y坐标
                    // // 折线链接方式
                    // var centerPointX = source.x - target.x;
                    // var sourceY, targetY;
                    // if (baseId === 'left') {
                    //     sourceY = source.y + (source.data.offSize || 0) + (source._height || 0);
                    //     targetY = target.y + (target.data.offSize || 0);
                    // } else if (baseId === 'right') {
                    //     sourceY = source.y - (source.data.offSize || 0);
                    //     targetY = target.y - (target.data.offSize || 0) + (target._height || 0);
                    // }
                    // var centerPointY = sourceY - targetY;
                    // path.moveTo(target.x, targetY);
                    // path.bezierCurveTo(
                    //     target.x, 
                    //     (targetY + sourceY) / 2,
                    //     source.x, 
                    //     (targetY + sourceY) / 2, 
                    //     source.x,
                    //     sourceY
                    // );
                    // return path.toString()
                    
                    
                    // 折线链接方式
                    var centerPointX = source.x - target.x;
                    var sourceY, targetY;
                    if (baseId === 'left') {
                        sourceY = source.y + (source.data.offSize || 0) + (source._height || 0);
                        targetY = target.y + (target.data.offSize || 0);
                    } else if (baseId === 'right') {
                        sourceY = source.y - (source.data.offSize || 0);
                        targetY = target.y - (target.data.offSize || 0) + (target._height || 0);
                    }
                    var centerPointY = sourceY - targetY;
                    var d = `M${target.x},${targetY}v${centerPointY / 3}h${centerPointX}v${centerPointY * 2 / 3}`;
                    return d
                }
    
                _chart.nodes = function(node) {
                    if(!arguments.length) return _nodes;
                    _nodes = node;
                    return _chart
                }
    
                _chart.valueAccessor = function(accessor) {
                    if(!arguments.length) return _valueAccessor;
                    _valueAccessor = accessor;
                    return _chart
                }
    
                
                _chart.width = function(width) {
                    if(!arguments.length) return _width;
                    _width = width;
                    return _chart
                }
                _chart.height = function(height) {
                    if(!arguments.length) return _height;
                    _height = height;
                    return _chart
                }
    
                _chart.margins = function(margin) {
                    if(!arguments.length) return _margin;
                    _margin = margin;
                    return _chart
                }
    
                _chart.colors = function(color) {
                    if(!arguments.length) return _colors;
                    _colors = color;
                    return _chart
                }
    
                return _chart
            }
    
            function size(d) {
                return d.size
            }
    
            function count() {
                return 1
            }
    
            function clip(d) {
                d.valueAccessor(chart.valueAccessor() == size ? count : size)
            }
    
            function randomData(base) {
                base = base || 10
                return Math.random() * base
            }
    
            // 获取系统默认字体
            function getSysFont(text) {
                var span = document.createElement('span');
                var fontFamily = '';
                span.innerHTML = text;
                span.style.display = 'none';
                document.body.appendChild(span);
                fontFamily = getComputedStyle(span).fontFamily;
                span.parentNode.removeChild(span);
                return fontFamily;
            }
            // 计算文本宽度
            function calcTextWidth(text, font, canvas2dContext) {
                
                if (canvas2dContext) {
                    canvas2dContext.font = font;
                    return canvas2dContext.measureText(text).width
                } else {
                    var canvas = document.createElement("canvas");
                    var context = canvas.getContext("2d");
                    context.font = font;
                    var metrics = context.measureText(text);
                    canvas = null;
                    return metrics.width
                }
            }
            // 文本断行
            function calcBreakWord (text, limitWidth, fontSize, canvas2dContext) {
                var defaultSysFont = getSysFont(text),
                    textWidth = calcTextWidth(text, fontSize + 'px ' + defaultSysFont, canvas2dContext), // 计算字符串总宽度;
                    res = {length: 0},
                    len = text.length,
                    i = 0,
                    char = '',
                    lastCharUnit = '',
                    lastIndex;
    
                if (textWidth > limitWidth) {
                    while(i < len) {
                        char += text[i];
                        if (calcTextWidth(char, fontSize + 'px ' + defaultSysFont, canvas2dContext) > limitWidth) {
                            lastIndex = i;
                            lastCharUnit = text[i];
    
                            res[res.length] = char.slice(0, -1);
                            res.length += 1;
                            char = lastCharUnit;
                        }
                        i++;
                    }
                    // 添加末尾
                    if (lastIndex < len) {
                        res[res.length] = text.slice(lastIndex);
                        res.length += 1;
                    }
                } else {
                    res[res.length] = text;
                    res.length += 1;
                }
    
                return res;
            }
    
            var chart = tree();
            chart.nodes(data).valueAccessor(size).render()
    
        </script>
    </body>
    </html>
    var data = {
        left: {
          name: 'Boss',
          bgColor: '#F6AA65',
          id: 1,
          children: [
            {
              name: 'left-1',
              bgColor: '#D0E1F1',
              total: 10,
              children: [
                { name: 'left-1-1' },
                { name: 'left-1-2' },
                { name: 'left-1-3' },
                { name: 'left-1-4' },
                { name: 'left-1-5',
                  children: [
                    { name: 'left-1-5-1 来一个我们不一样' }
                  ]
                },
                { name: 'left-1-6' },
                { name: 'left-1-7' },
                { name: 'left-1-8' },
                { name: 'left-1-9' },
                { name: 'left-1-10' },
              ]
            },
            {
              name: 'left-2',
              bgColor: '#C3CFEF',
              total: 22,
              children: [
                { name: 'left-2-1 文字来长一点断个行, 计算好叠加的坐标', 
                  children: [ 
                    { name: 'left-2-1-1 再来一个; 其中之一而已, 要在一个分支上的不断叠加', 
                      children: [
                        {name: 'left-2-1-1-1'},
                        {name: 'left-2-1-1-2'}
                      ] 
                    },
                    { name: 'left-2-1-2' }
                  ] 
                },
                { name: 'left-2-2' },
                { name: 'left-2-3' },
                { name: 'left-2-4' },
                { name: 'left-2-5' },
                { name: 'left-2-6' },
                { name: 'left-2-7' },
                { name: 'left-2-8' },
                { name: 'left-2-9' },
                { name: 'left-2-10' },
                { name: 'left-2-11' },
                { name: 'left-2-12' }
              ]
            },
            { name: 'left-3', bgColor: '#D7D7D7', total: 0, children: [] }
          ]
        },
        right: {
          name: 'Boss',
          bgColor: '#F6AA65',
          id: 1,
          children: [
            {
              name: 'right-1',
              bgColor: '#BFCDE3',
              total: 63,
              children: [
                { name: 'right-1-1' },
                { name: 'right-1-2' },
                { name: 'right-1-3' },
                { name: 'right-1-4' },
                { name: 'right-1-5' },
                { name: 'right-1-6' },
                { name: 'right-1-7' }
              ]
            },
            {
              name: 'right-2',
              bgColor: '#B9D4F3',
              total: 18326,
              children: [
                { name: 'right-2-1' },
                { name: 'right-2-2' },
                { name: 'right-2-3' },
                { name: 'right-2-4' },
                { name: 'right-2-5' }
              ]
            },
            { name: 'right-3', bgColor: '#D7D7D7', total: 0, children: [] }
          ]
        }
    }

    实现上比较粗糙, 但也应该能够应对一般的业务场景了. 像天眼查企业图谱的树形结构, 对展开事件变换处理下即可。

    实现上关键点在于

    d3.tree().nodeSize([120, 80]) 这一行代码, d3.tree() 会提供我们一个树形布局算法, 支持 [nodeSize | size]两种方式
    nodeSize: 为每一个节点设定固定大小的布局
    size: 为整个树设定固定大小的布局
    separation 额外支持设置相邻节点之间的间隔
    设想一下:
    在一块画布上, 针对未知层级未知数量的节点进行布局的方案, 如果是需要固定树大小, 那么就需要找出最边界的节点(上 | 下 | 左 | 右),
    上下(深度)确定每个节点所占的高度, 左右(跨度)确定每个节点所占宽度;
    而如果是固定节点大小, 通过计算当前(父)节点的最大子节点数量和子节点数量, 则可以确定(父)节点的宽度和横向位置, 高度累加;

    d3 帮我们完成了从: 树结构数据 -> 包含坐标信息的数据 的一个运算;
    得到了坐标信息, 再对坐标进行一个平移, 以达到居中;
    如果要把节点内容所占宽高也算进去, 则还需要计算出节点的偏移距离(因为上层节点的位置是会影响的), 在具体绘制时把节点坐标和偏移都进行计算, 这样便达到了最终的布局效果;

    以下是 d3.tree() 的实现;

    // Node-link tree diagram using the Reingold-Tilford "tidy" algorithm
    function tree() {
      var separation = defaultSeparation$1,
          dx = 1,
          dy = 1,
          nodeSize = null;
    
      function tree(root) {
        var t = treeRoot(root);
    
        // Compute the layout using Buchheim et al.’s algorithm.
        t.eachAfter(firstWalk), t.parent.m = -t.z;
        t.eachBefore(secondWalk);
    
        // If a fixed node size is specified, scale x and y.
        if (nodeSize) root.eachBefore(sizeNode);
    
        // If a fixed tree size is specified, scale x and y based on the extent.
        // Compute the left-most, right-most, and depth-most nodes for extents.
        else {
          var left = root,
              right = root,
              bottom = root;
          root.eachBefore(function(node) {
            if (node.x < left.x) left = node;
            if (node.x > right.x) right = node;
            if (node.depth > bottom.depth) bottom = node;
          });
          var s = left === right ? 1 : separation(left, right) / 2,
              tx = s - left.x,
              kx = dx / (right.x + s + tx),
              ky = dy / (bottom.depth || 1);
          root.eachBefore(function(node) {
            node.x = (node.x + tx) * kx;
            node.y = node.depth * ky;
          });
        }
    
        return root;
      }
    
      // Computes a preliminary x-coordinate for v. Before that, FIRST WALK is
      // applied recursively to the children of v, as well as the function
      // APPORTION. After spacing out the children by calling EXECUTE SHIFTS, the
      // node v is placed to the midpoint of its outermost children.
      function firstWalk(v) {
        var children = v.children,
            siblings = v.parent.children,
            w = v.i ? siblings[v.i - 1] : null;
        if (children) {
          executeShifts(v);
          var midpoint = (children[0].z + children[children.length - 1].z) / 2;
          if (w) {
            v.z = w.z + separation(v._, w._);
            v.m = v.z - midpoint;
          } else {
            v.z = midpoint;
          }
        } else if (w) {
          v.z = w.z + separation(v._, w._);
        }
        v.parent.A = apportion(v, w, v.parent.A || siblings[0]);
      }
    
      // Computes all real x-coordinates by summing up the modifiers recursively.
      function secondWalk(v) {
        v._.x = v.z + v.parent.m;
        v.m += v.parent.m;
      }
    
      // The core of the algorithm. Here, a new subtree is combined with the
      // previous subtrees. Threads are used to traverse the inside and outside
      // contours of the left and right subtree up to the highest common level. The
      // vertices used for the traversals are vi+, vi-, vo-, and vo+, where the
      // superscript o means outside and i means inside, the subscript - means left
      // subtree and + means right subtree. For summing up the modifiers along the
      // contour, we use respective variables si+, si-, so-, and so+. Whenever two
      // nodes of the inside contours conflict, we compute the left one of the
      // greatest uncommon ancestors using the function ANCESTOR and call MOVE
      // SUBTREE to shift the subtree and prepare the shifts of smaller subtrees.
      // Finally, we add a new thread (if necessary).
      function apportion(v, w, ancestor) {
        if (w) {
          var vip = v,
              vop = v,
              vim = w,
              vom = vip.parent.children[0],
              sip = vip.m,
              sop = vop.m,
              sim = vim.m,
              som = vom.m,
              shift;
          while (vim = nextRight(vim), vip = nextLeft(vip), vim && vip) {
            vom = nextLeft(vom);
            vop = nextRight(vop);
            vop.a = v;
            shift = vim.z + sim - vip.z - sip + separation(vim._, vip._);
            if (shift > 0) {
              moveSubtree(nextAncestor(vim, v, ancestor), v, shift);
              sip += shift;
              sop += shift;
            }
            sim += vim.m;
            sip += vip.m;
            som += vom.m;
            sop += vop.m;
          }
          if (vim && !nextRight(vop)) {
            vop.t = vim;
            vop.m += sim - sop;
          }
          if (vip && !nextLeft(vom)) {
            vom.t = vip;
            vom.m += sip - som;
            ancestor = v;
          }
        }
        return ancestor;
      }
    
      function sizeNode(node) {
        node.x *= dx;
        node.y = node.depth * dy;
      }
    
      tree.separation = function(x) {
        return arguments.length ? (separation = x, tree) : separation;
      };
    
      tree.size = function(x) {
        return arguments.length ? (nodeSize = false, dx = +x[0], dy = +x[1], tree) : (nodeSize ? null : [dx, dy]);
      };
    
      tree.nodeSize = function(x) {
        return arguments.length ? (nodeSize = true, dx = +x[0], dy = +x[1], tree) : (nodeSize ? [dx, dy] : null);
      };
    
      return tree;
    }
  • 相关阅读:
    安装VS 2015完成后,VS2012 打开报错
    ASP.NET MVC 项目中 一般处理程序ashx 获取Session
    windows平台 查看 dll 程序集 PublicKeyToken
    MySQL 表与字段编码格式报错
    Linux系统下安装MongoDB 指南
    ASP.NET 访问路径 错误提示 HTTP 错误 404.8 原来路径中包含bin目录被拒绝
    ASP.NET 大文件上传
    将类型(int,string,…)转换为 T 类型
    直接插入排序
    MySQL 优化之索引合并(index_merge)
  • 原文地址:https://www.cnblogs.com/liuyingde/p/13791190.html
Copyright © 2011-2022 走看看