用d3绘制可折叠的缩进树 [英] Drawing a collapsible indented tree with d3

查看:117
本文介绍了用d3绘制可折叠的缩进树的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

正在绘制带有d3的缩进树.我从迈克·罗斯托克的代码开始,并进行了一些修改,以:1)显示一个正确的/向下箭头,但叶子上除外; 2)在每行中添加一个复选框; 3)隐藏根节点.

'm drawing an indented tree with d3. I started from Mike Rostock's code and made a few modifications in order to: 1) display a right/down arrow except on leaves; 2) add a check box to each line; 3) hide the root node.

下面的代码将接收任何数据,我使用两个参数调用drawIntentedTree函数:根节点和草绘树的div ID.

Code is below, it takes any data, I call the drawIntentedTree function with two arguments: the root node, and the div id in which the tree is sketched.

如图所示,代码中存在一些问题,需要您提供帮助: 1.在扩展树枝时重绘了根/起始节点,导致左箭头和下箭头重叠,请参见SCL行. 2.复选框也观察到类似的问题,该复选框基本上是x且在白色矩形上透明.我的第一个意图是用笔触颜色填充该框,但由于每行css的颜色会发生变化,因此必须弄清楚它的颜色.

As on might see on the picture, there are a few issues in the code, for which help would be appreciated: 1. the root/start node is redrawn while expending a tree branch, resulting in overlapping the left and down arrow, see SCL line. 2. a similar issue is observed with the check box, which is basically an x hidden with a transparent on white rect. My first intension was to fill the box with the stroke color, but would have to figure out what the css color is for each line since it changes.

在解决这两个问题的同时,我打算在节点之间绘制直线,但是原始代码改为绘制卷曲线,并允许在折叠和展开之间使用中间角度(部分折叠),并带有45°旋转的箭头,仅显示分支中的复选框.另外,我希望分支在展开另一个分支时可以折叠或部分折叠,以免向下滚动.

Along with addressing these two issues, I had the intension to draw straight lines between nodes, but the original codes draws curly lines instead and allow a intermediate sate (partly collapsed) between collapsed and expended, with a 45° rotated arrow, showing only checked boxes in a branch. Additionally, I'd like branches to be collapsed or partly collapsed when expending another branch to avoid far down scrolling.

迈克·波斯托克(Mike Bostock)使用技巧来显示/隐藏树的一部分,他备份_children中的子代,然后将子代分配为null以隐藏折叠的分支,但是重绘始终从根节点开始,而我却无法: 1)避免重绘根节点; 2)将预先存在的左三角形旋转90或90°.

Mike Bostock is using a trick to display/hide part of the tree, he backs up children in _children then assigns children to null to hide collapsed branches, but redrawing always starts at the root node and I didn't manage to: 1) avoid the root node redrawing; 2) rotate the preexisting left triangle by 90 or 90°.

在一个帖子中有很多问题,在任何方面的任何帮助,我将不胜感激. jsfiddle链接.

Many questions in one post, I'd appreciate any help on any part. jsfiddle link.

d3js代码:

function drawIndentedTree(root, wherein) {

var width = 300, minHeight = 800;
var barHeight = 20, barWidth = 50;

var margin = {
        top: -10,
        bottom: 10,
        left: 0,
        right: 10
    }

var i = 0, duration = 200;

var tree = d3.layout.tree()
    .nodeSize([0, 20]);

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.y, d.x]; });

var svg = d3.select("#"+wherein).append("svg")
    .attr("width", width + margin.left + margin.right)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// set initial coordinates
root.x0 = 0;
root.y0 = 0;

// collapse all nodes recusively, hence initiate the tree
function collapse(d) {
    d.Selected = false;
    if (d.children) {
        d.numOfChildren = d.children.length;
        d._children = d.children;
        d._children.forEach(collapse);
        d.children = null;
    }
    else {
        d.numOfChildren = 0;
    }
}
root.children.forEach(collapse);

update(root);

function update(source) {

    // Compute the flattened node list. TODO use d3.layout.hierarchy.
    var nodes = tree.nodes(root);

    height = Math.max(minHeight, nodes.length * barHeight + margin.top + margin.bottom);

    d3.select("svg").transition()
        .duration(duration)
        .attr("height", height);

    d3.select(self.frameElement).transition()
        .duration(duration)
        .style("height", height + "px");

    // Compute the "layout".
    nodes.forEach(function(n, i) {
          n.x = i * barHeight;
        });

    // Update the nodes…
    var node = svg.selectAll("g.node")
        .data(nodes, function(d) {
              return d.index || (d.index = ++i); });

    var nodeEnter = node.enter().append("g").filter(function(d) { return d.id != root.id })
        .attr("class", "node")
        .style("opacity", 0.001)
        .attr("transform", function(d) {
              return "translate(" + source.y0 + "," + source.x0 + ")";
        });

    // Enter any new nodes at the parent's previous position.
    nodeEnter.append("path").filter(function(d) { return d.numOfChildren > 0 && d.id != root.id })
        .attr("width", 9)
        .attr("height", 9)
        .attr("d", "M -3,-4, L -3,4, L 4,0 Z")
        .attr("class", function(d) { return "node "+d.type; } )
        .attr("transform", function(d) {
              if (d.children) {
                return "translate(-14, 0)rotate(90)";
              }
              else {
                return "translate(-14, 0)rotate(0)";
              }
            })
        .on("click", click);

    // Enter any new nodes at the parent's previous position.
    nodeEnter.append("rect").filter(function(d) { return d.id != root.id })
        .attr("width", 11)
        .attr("height", 11)
        .attr("y", -5)
        .attr("class", function(d) { return "node "+d.type; } );

// check box filled with 'x' or '+'
    nodeEnter.append("text")
        .attr("dy", 4)
        .attr("dx", 2)
        .attr("class", function(d) { return "node "+d.type+" text"; } )
        .text("x");

    nodeEnter.append("rect").filter(function(d) { return d.parent })
        .attr("width", 9)
        .attr("height", 9)
        .attr("x", 1)
        .attr("y", -4)
        .attr("class", "node select")
        .attr("style", function(d) { return "fill: "+boxStyle(d) })
        .on("click", check);

    nodeEnter.append("text")
        .attr("dy", 5)
        .attr("dx", 14)
        .attr("class", function(d) { return "node "+d.type+" text"; } )
        .text(function(d) { return d.Name; });

    // Transition nodes to their new position.
    nodeEnter.transition()
        .duration(duration)
        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
        .style("opacity", 1);

    node.transition()
        .duration(duration)
        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
        .style("opacity", 1)
        .select("rect");

    // Transition exiting nodes to the parent's new position.
    node.exit().transition()
        .duration(duration)
        .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
        .style("opacity", 1e-6)
        .remove();

    // Stash the old positions for transition.
    nodes.forEach(function(d) {
          d.x0 = d.x;
          d.y0 = d.y;
      });
}

// Toggle children on click.
function click(d) {
    if (d.children) {
        d3.select(this).attr("translate(-14, 0)rotate(90)");
        d._children = d.children;
        d.children = null;
    } else if (d._children) {
        d.children = d._children;
        d._children = null;
    }
    update(d);
}

// Toggle check box on click.
function check(d) {
    d.Selected = !d.Selected;
    d3.select(this).style("fill", boxStyle(d));
}

function boxStyle(d) {
    return d.Selected ? "transparent" : "white";
}
}

var wherein = "chart";
var root = {
"name": "AUT-1",
"children": [
    {
        "name": "PUB-1","children": [
            {"name": "AUT-11","children": [
                {"name": "AFF-111"},
                {"name": "AFF-112"}
            ]},
            {"name": "AUT-12","children": [
                {"name": "AFF-121"}
            ]},
            {"name": "AUT-13","children": [
                {"name": "AFF-131"},
                {"name": "AFF-132"}
            ]},
            {"name": "AUT-14","children": [
                {"name": "AFF-141"}
            ]}
        ]
    },
    {
        "name": "PUB-2","children": [
            {"name": "AUT-21"},
            {"name": "AUT-22"},
            {"name": "AUT-23"},
            {"name": "AUT-24"},
            {"name": "AUT-25"},
            {"name": "AUT-26"},
            {"name": "AUT-27"},
            {"name": "AUT-28","children":[
                {"name": "AFF-281"},
                {"name": "AFF-282"},
                {"name": "AFF-283"},
                {"name": "AFF-284"},
                {"name": "AFF-285"},
                {"name": "AFF-286"}
            ]}
        ]
    },
    {"name": "PUB-3"},
    {
        "name": "PUB-4","children": [
            {"name": "AUT-41"},
            {"name": "AUT-42"},
            {"name": "AUT-43","children": [
                {"name": "AFF-431"},
                {"name": "AFF-432"},
                {"name": "AFF-433"},
                {"name": "AFF-434","children":[
                    {"name": "ADD-4341"},
                    {"name": "ADD-4342"},
                ]}
            ]},
            {"name": "AUT-44"}
        ]
    }
]
};

CSS:

.node {
font: 12px sans-serif;
fill: #ccebc5;
stroke: #7c9b75;
stroke-width: 1px;
}

.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
cursor: pointer;
}

.node rect {
width: 11px;
height: 11px;
cursor: pointer;
}

.node.select {
width: 9px;
height: 9px;
cursor: pointer;
fill: red;
stroke-width: 0px;
}

.node path {
width: 11px;
height: 11px;
cursor: pointer;
}

.node text Panel {
stroke: #08519c;
stroke-width: 0.5px;
}

.node text Cell {
stroke: #a50f15;
stroke-width: 0.5px;
}

.node.Root {
fill: #f7f7f7;
stroke: #505050;
stroke-width: 1.0px;
}

.node.Root.text {
fill: #505050;
stroke-width: 0px;
font-size: 10px;
font-family: sans-serif;
}

.node.Panel {
fill: #eff3ff;
stroke: #08519c;
stroke-width: 1.0px;
}

.node.Panel.text {
fill: #08519c;
stroke-width: 0px;
font-size: 12px;
font-family: sans-serif;
}

.node.Cell {
fill: #fee5d9;
stroke: #a50f15;
stroke-width: 1.0px;
}

.node.Cell.text {
fill: #a50f15;
stroke-width: 0px;
font-size: 12px;
font-family: sans-serif;
}

推荐答案

在解决您的问题时,我会更新答案.

I'll update my answer as I work through your questions.

  1. 在扩展树枝时重绘了根节点/起始节点,导致左箭头和下箭头重叠,请参见SCL行.

这是d3的输入/更新/退出的经典示例.您有nodeEnter变量-输入数据时要绘制的内容-这是最初绘制的元素.然后,您有了node变量-这是所有已经绘制的东西.切换箭头时,您正在操作nodeEnter,因此您要重新添加新的path,从而导致重叠.相反,只需更新已经存在的path并更改转换:

This is a classic example of d3's enter/update/exit. You have nodeEnter variable - what to draw on entering your data - this is the initially drawn elements. You then have node variable - this is all the already drawn stuff. When you toggle the arrow, you are acting on the nodeEnter hence you are re-appending a new path resulting in an overlap. Instead, just update the already existing path and change the transform:

node.select("path").attr("transform", function(d) {
    if (d.children) {
      return "translate(-14, 0) rotate(90)";
    } else {
      return "translate(-14, 0) rotate(0)";
    }
});

示例此处.

这篇关于用d3绘制可折叠的缩进树的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆