D3.js学习笔记十五:D3.js树图(Tree)展开和折叠

构图(d3.layout)——树图(Tree)的展开和折叠

树的基本操作就是展开和折叠,在D3里面,树的展开和折叠是通过设置树的子节点属性来实现的,tree.nodes(root)返回树的当前节点集合,通过操作这个集合,可以实现节点的动态变化。

  • children - the array of child nodes, or null for leaf nodes.
  • x - the computed x-coordinate of the node position.
  • y - the computed y-coordinate of the node position.

取得节点集合可用如下代码:

var nodes = tree.nodes(root).reverse(); var links = tree.links(nodes);

为了能够区分节点是新加入的还是冗余的,我们为节点数据集指定id属性,然后我们就可以通过这个属性,使用.exit()来获取冗余节点:

var node = svg.selectAll("g.node") .data(nodes,function(d){return d.id|| (d.id = ++index);});

为了能够保证动画的流畅,我们需要记录节点的当前位置,当节点数目变化,产生新的坐标以后,我们使用当前位置过渡到新的位置:

nodes.forEach(function(d) { d.x0 = d.x; d.y0 = d.y; });

树的展开和折叠代码如下:

< script type = "text/javascript" >//图像区域大小
var R = 600;
//动画持续时间
var duration = 1000;
//节点编号
var index = 0;
//动画时长
var duration = 1000;//定义一个Tree对象,定义旋转角度和最大半径
var tree = d3.layout.tree()
.size([360, R / 2 - 120])
.separation(function(a, b) {
return (a.parent == b.parent ? 1 : 2) / a.depth;
});

//定义布局方向
var diagonal = d3.svg.diagonal()
.projection(function(d) {
var r = d.y,
a = (d.x - 90) / 180 * Math.PI;
return [r * Math.cos(a), r * Math.sin(a)];
});

//新建画布,移动到圆心位置
var svg = d3.select("body").append("svg")
.attr("width", R)
.attr("height", R)
.append("g")
.attr("transform", function(d) {
return "translate(" + R / 2 + "," + R / 2 + ")";
});

//根据JSON数据生成树
d3.json("treeDataC.php", function(error, data) {

var root = data;

//根据数据生成nodes集合
var nodes = tree.nodes(data);

//记录现在的位置
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});

//获取node集合的关系集合
var links = tree.links(nodes);

//根据node集合生成节点,添加id是为了区分是否冗余的节点
var node = svg.selectAll(".node")
.data(nodes, function(d) {
return d.id || (d.id = ++index);
});

//为关系集合设置贝塞尔曲线连接
var link = svg.selectAll(".link")
.data(links, function(d) {
return d.target.id;
})
.enter()
.append("path")
.attr("class", "link")
.attr("d", diagonal);

node.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
})
.on("click", nodeClick);

//为节点添加圆形标记,如果有子节点为红色,否则绿色
node.append("circle")
.attr("fill", function(d) {
return d.children == null ? "#0F0" : "#F00";
})
.attr("r", 5);

//为节点添加说明文字
node.append("text")
.attr("dy", ".4em")
.text(function(d) {
return d.level;
})
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)";
});

//点击的话,隐藏或者显示子节点
function nodeClick(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}

//更新显示
function update(source) {
//取得现有的节点数据,因为设置了Children属性,没有Children的节点将被删除
var nodes = tree.nodes(root).reverse();
var links = tree.links(nodes);

//为节点更新数据
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++index);
});

//为链接更新数据
var link = svg.selectAll("path.link").data(links, function(d) {
return d.target.id;
});

//更新链接
link.enter()
.append("path")
.attr("class", "link")
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
});

link.transition()
.duration(duration)
.attr("d", diagonal);

//移除无用的链接
link.exit()
.transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();

//更新节点集合
var nodeEnter = node.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "rotate(" + (source.x0 - 90) + ")translate(" + source.y0 + ")";
})
.on("click", nodeClick);

//为节点添加圆形标记,如果有子节点为红色,否则绿色
node.append("circle")
.attr("fill", function(d) {
return d.children == null && d._children == null ? "#0F0" : "#F00";
})
.attr("r", 5);

//为节点添加说明文字
node.append("text")
.attr("dy", ".4em")
.text(function(d) {
return d.level;
})
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)";
});

//节点动画
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
});

//将无用的子节点删除
var nodeExit = node.exit()
.transition()
.duration(duration)
.attr("transform", function(d) {
return "rotate(" + (source.x - 90) + ")translate(" + source.y + ")";
})
.remove();

//记录下当前位置,为下次动画记录初始值
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
}); < /script>

  <!DOCTYPE html>
  <html>
  <head>
  <meta charset="utf-8">
  <title>画一个树图(Tree)</title>
  <script type="text/javascriptsrc="js/d3.js"></script>
  </head>
  <style>
   
  .node circle {
  stroke: steelblue;
  stroke-width: 1px;
  cursor:pointer;
  }
   
  .node {
  font: 12px sans-serif;
  display:block;
  }
   
  .hidenode {
  font: 12px sans-serif;
  display:none;
  }
   
  .link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1px;
  }
   
  </style>
  <body>
  <script type="text/javascript">
   
  //图像区域大小
  var R = 600;
  //动画持续时间
  var duration=1000;
  //节点编号
  var index=0;
   
  //定义一个Tree对象,定义旋转角度和最大半径
  var tree = d3.layout.tree()
  .size([360,R/2-120])
  .separation(function(a,b) { return (a.parent == b.parent ? 1 : 2)/a.depth;});
   
  //定义布局方向
  var diagonal = d3.svg.diagonal()
  .projection(function(d) {
  var r = d.y, a = (d.x-90) / 180 * Math.PI;
  return [r * Math.cos(a), r * Math.sin(a)];
  });
   
  //新建画布,移动到圆心位置
  var svg = d3.select("body").append("svg")
  .attr("width", R)
  .attr("height", R)
  .append("g")
  .attr("transform", function(d){ return "translate("+R/2+"," + R/2 + ")";});
   
  //根据JSON数据生成树
  d3.json("treeDataC.php", function(error, data) {
   
  var root=data;
   
  //根据数据生成nodes集合
  var nodes = tree.nodes(data);
   
  //记录现在的位置
  nodes.forEach(function(d){
  d.x0 = d.x;
  d.y0 = d.y;
  });
   
  //获取node集合的关系集合
  var links = tree.links(nodes);
   
  //根据node集合生成节点,添加id是为了区分是否冗余的节点
  var node = svg.selectAll(".node")
  .data(nodes,function(d){return d.id|| (d.id = ++index);});
   
  //为关系集合设置贝塞尔曲线连接
  var link=svg.selectAll(".link")
  .data(links, function(d) { return d.target.id;})
  .enter()
  .append("path")
  .attr("class", "link")
  .attr("d",diagonal);
   
  node.enter()
  .append("g")
  .attr("class", "node")
  .attr("transform",function(d){return "rotate(" + (d.x-90) + ")translate(" + d.y + ")"; })
  .on("click",nodeClick);
   
  //为节点添加圆形标记,如果有子节点为红色,否则绿色
  node.append("circle")
  .attr("fill",function(d){return d.children==null?"#0F0":"#F00";})
  .attr("r", 5);
   
  //为节点添加说明文字
  node.append("text")
  .attr("dy", ".4em")
  .text(function(d){return d.level;})
  .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
  .attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; });
   
  //点击的话,隐藏或者显示子节点
  function nodeClick(d)
  {
  if (d.children)
  {
  d._children = d.children;
  d.children = null;
  }
  else
  {
  d.children = d._children;
  d._children = null;
  }
  update(d);
  }
   
  //更新显示
  function update(source)
  {
  //取得现有的节点数据,因为设置了Children属性,没有Children的节点将被删除
  var nodes = tree.nodes(root).reverse();
  var links = tree.links(nodes);
   
  //为节点更新数据
  var node = svg.selectAll("g.node")
  .data(nodes,function(d){return d.id|| (d.id = ++index);});
   
  //为链接更新数据
  var link = svg.selectAll("path.link").data(links, function(d) {return d.target.id;});
   
  //更新链接
  link.enter()
  .append("path")
  .attr("class", "link")
  .attr("d", function(d) {
  var o = {x: source.x, y: source.y};
  return diagonal({source: o, target: o});
  });
   
  link.transition()
  .duration(duration)
  .attr("d",diagonal);
   
  //移除无用的链接
  link.exit()
  .transition()
  .duration(duration)
  .attr("d", function(d) {
  var o = {x: source.x, y: source.y};
  return diagonal({source: o, target: o});
  })
  .remove();
   
  //更新节点集合
  var nodeEnter = node.enter()
  .append("g")
  .attr("class", "node")
  .attr("transform",function(d){return "rotate(" + (source.x0-90) + ")translate(" + source.y0 + ")"; })
  .on("click",nodeClick);
   
  //为节点添加圆形标记,如果有子节点为红色,否则绿色
  node.append("circle")
  .attr("fill",function(d){return d.children==null && d._children==null?"#0F0":"#F00";})
  .attr("r", 5);
   
  //为节点添加说明文字
  node.append("text")
  .attr("dy", ".4em")
  .text(function(d){return d.level;})
  .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
  .attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; });
   
  //节点动画
  var nodeUpdate = node.transition()
  .duration(duration)
  .attr("transform", function(d) { return "rotate(" + (d.x-90) + ")translate(" + d.y + ")"; });
   
  //将无用的子节点删除
  var nodeExit =node.exit()
  .transition()
  .duration(duration)
  .attr("transform", function(d){return "rotate(" + (source.x-90) + ")translate(" + source.y + ")"; })
  .remove();
   
  //记录下当前位置,为下次动画记录初始值
  nodes.forEach(function(d) {
  d.x0 = d.x;
  d.y0 = d.y;
  });
  }
  });
  </script>
  </body>
  </html>

 

 

{"name":"\u6839\u8282\u70b9","children":[{"name":"\u8282\u70b9(3)","children":[{"name":"\u8282\u70b9(4)","children":[{"name":"\u53f6\u5b501","size":863},{"name":"\u53f6\u5b502","size":80},{"name":"\u53f6\u5b503","size":740},{"name":"\u53f6\u5b504","size":688}]},{"name":"\u8282\u70b9(2)","children":[{"name":"\u53f6\u5b505","size":110},{"name":"\u53f6\u5b506","size":102}]},{"name":"\u8282\u70b9(2)","children":[{"name":"\u53f6\u5b507","size":249},{"name":"\u53f6\u5b508","size":540}]}]},{"name":"\u8282\u70b9(5)","children":[{"name":"\u53f6\u5b509","size":451},{"name":"\u53f6\u5b5010","size":453},{"name":"\u53f6\u5b5011","size":256},{"name":"\u8282\u70b9(3)","children":[{"name":"\u53f6\u5b5012","size":476},{"name":"\u53f6\u5b5013","size":678},{"name":"\u53f6\u5b5014","size":954}]},{"name":"\u8282\u70b9(1)","children":[{"name":"\u53f6\u5b5015","size":608}]}]},{"name":"\u8282\u70b9(5)","children":[{"name":"\u8282\u70b9(4)","children":[{"name":"\u53f6\u5b5016","size":770},{"name":"\u53f6\u5b5017","size":985},{"name":"\u53f6\u5b5018","size":106},{"name":"\u53f6\u5b5019","size":89}]},{"name":"\u8282\u70b9(3)","children":[{"name":"\u53f6\u5b5020","size":917},{"name":"\u53f6\u5b5021","size":754},{"name":"\u53f6\u5b5022","size":271}]},{"name":"\u8282\u70b9(1)","children":[{"name":"\u53f6\u5b5023","size":226}]},{"name":"\u8282\u70b9(3)","children":[{"name":"\u53f6\u5b5024","size":195},{"name":"\u53f6\u5b5025","size":862},{"name":"\u53f6\u5b5026","size":730}]},{"name":"\u8282\u70b9(5)","children":[{"name":"\u53f6\u5b5027","size":969},{"name":"\u53f6\u5b5028","size":871},{"name":"\u53f6\u5b5029","size":225},{"name":"\u53f6\u5b5030","size":721},{"name":"\u53f6\u5b5031","size":97}]}]},{"name":"\u8282\u70b9(6)","children":[{"name":"\u8282\u70b9(3)","children":[{"name":"\u53f6\u5b5032","size":434},{"name":"\u53f6\u5b5033","size":646},{"name":"\u53f6\u5b5034","size":614}]},{"name":"\u8282\u70b9(5)","children":[{"name":"\u53f6\u5b5035","size":330},{"name":"\u53f6\u5b5036","size":155},{"name":"\u53f6\u5b5037","size":782},{"name":"\u53f6\u5b5038","size":736},{"name":"\u53f6\u5b5039","size":177}]},{"name":"\u53f6\u5b5040","size":662},{"name":"\u8282\u70b9(3)","children":[{"name":"\u53f6\u5b5041","size":110},{"name":"\u53f6\u5b5042","size":540},{"name":"\u53f6\u5b5043","size":124}]},{"name":"\u8282\u70b9(5)","children":[{"name":"\u53f6\u5b5044","size":284},{"name":"\u53f6\u5b5045","size":487},{"name":"\u53f6\u5b5046","size":385},{"name":"\u53f6\u5b5047","size":346},{"name":"\u53f6\u5b5048","size":945}]},{"name":"\u8282\u70b9(2)","children":[{"name":"\u53f6\u5b5049","size":270},{"name":"\u53f6\u5b5050","size":88}]}]},{"name":"\u8282\u70b9(6)","children":[{"name":"\u8282\u70b9(5)","children":[{"name":"\u53f6\u5b5051","size":502},{"name":"\u53f6\u5b5052","size":466},{"name":"\u53f6\u5b5053","size":550},{"name":"\u53f6\u5b5054","size":661},{"name":"\u53f6\u5b5055","size":558}]},{"name":"\u8282\u70b9(1)","children":[{"name":"\u53f6\u5b5056","size":314}]},{"name":"\u8282\u70b9(2)","children":[{"name":"\u53f6\u5b5057","size":742},{"name":"\u53f6\u5b5058","size":340}]},{"name":"\u8282\u70b9(5)","children":[{"name":"\u53f6\u5b5059","size":949},{"name":"\u53f6\u5b5060","size":99},{"name":"\u53f6\u5b5061","size":305},{"name":"\u53f6\u5b5062","size":684},{"name":"\u53f6\u5b5063","size":90}]},{"name":"\u8282\u70b9(6)","children":[{"name":"\u53f6\u5b5064","size":732},{"name":"\u53f6\u5b5065","size":980},{"name":"\u53f6\u5b5066","size":758},{"name":"\u53f6\u5b5067","size":359},{"name":"\u53f6\u5b5068","size":166},{"name":"\u53f6\u5b5069","size":413}]},{"name":"\u53f6\u5b5070","size":946}]}]}
  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要用到d3.js库中的布局功能,具体来说是树状布局(tree layout)。具体的代码实现可以参考以下示例: ``` var margin = {top: 20, right: 120, bottom: 20, left: 120}, width = 960 - margin.right - margin.left, height = 800 - margin.top - margin.bottom; var tree = d3.layout.tree() .size([height, width]); var diagonal = d3.svg.diagonal() .projection(function(d) { return [d.y, d.x]; }); var svg = d3.select("body").append("svg") .attr("width", width + margin.right + margin.left) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var root = { "name": "A", "children": [ { "name": "B", "children": [ { "name": "C" }, { "name": "D" } ] }, { "name": "E", "children": [ { "name": "F" }, { "name": "G" } ] } ] }; var nodes = tree.nodes(root), links = tree.links(nodes); var link = svg.selectAll(".link") .data(links) .enter().append("path") .attr("class", "link") .attr("d", diagonal); var node = svg.selectAll(".node") .data(nodes) .enter().append("g") .attr("class", "node") .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) node.append("circle") .attr("r", 4.5); node.append("text") .attr("dx", function(d) { return d.children ? -8 : 8; }) .attr("dy", 3) .style("text-anchor", function(d) { return d.children ? "end" : "start"; }) .text(function(d) { return d.name; }); ``` 这个示例其实就是一个非常简单的树状结构,但是可以看到节点和节点之间是通过直线相连接的。具体来说,需要用到d3.layout.tree()这个函数进行树状布局,再用到d3.svg.diagonal()这个函数来定义连接线的路径,最后通过d3.selectAll()以及.data()等函数将节点和线条加入进去即可。需要注意的是,在节点连接线这一部分的代码当中,attr函数中的参数d以及diagonal函数中的参数d都表示一个节点在树状结构中的位置,需要根据实际情况加以理解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值