web

d3力学图(force layout)更新

Posted by Codeboy on December 16, 2015

d3是javascript的一个可视化库,可以制作很漂亮的表格等,其中有一个非常好玩的力学图,可以方便的进行拖拽等,但是关于更新的操作网上介绍的很少,下面介绍下关于力学图的更新,先看一个例子(http://example.codeboy.me/d3/force-layout.html,点击一次下图后可以直接按回车键添加):

绘制图形的方式基本上可以分为svg与canvas两种,两者各有优缺点,不对于以后屏幕越来越大,svg更加的有优势,并且svg可以很方便的进行事件交互。

下面看一下这个力学图具体实现:

整体代码

  • <html>
  • <head>
  • <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  • <meta name="viewport" content="width=device-width, initial-scale=1">
  • <link href="css/bootstrap.min.css" rel="stylesheet">
  • <script src="js/jquery.min.js"></script>
  • <script src="js/bootstrap.min.js"></script>
  • <script src="js/layer/layer.min.js"></script>
  • <script src="js/d3.min.js" charset="utf-8"></script>
  • <title>d3 layout update</title>
  • </head>
  • <body style="height: 100%;">
  • <div class="container" style="padding-left: 0; width: 100%;">
  • <div id="svg">
  • </div>
  • </div>
  • <script>
  • $(document).ready(function () {
  • var height = document.body.clientHeight;
  • var width = document.body.clientWidth;
  • var nodes_data = [{'name': '0'},
  • {'name': '1'},
  • {'name': '2'},
  • {'name': '3'},
  • {'name': '4'},
  • {'name': '5'},
  • {'name': '6'}];
  • var edges_data = [{'source': 0, 'target': 1},
  • {'source': 0, 'target': 2},
  • {'source': 0, 'target': 3},
  • {'source': 0, 'target': 4},
  • {'source': 0, 'target': 5},
  • {'source': 0, 'target': 6}];
  • var color = d3.scale.category20();
  • var edgeWidth = 2;
  • var r = 40;
  • var svg = d3.select("#svg").append("svg")
  • .attr("width", width)
  • .attr("height", height);
  • var force = d3.layout.force()
  • .nodes(nodes_data)
  • .links(edges_data)
  • .size([width, height])
  • .linkDistance(200)
  • .friction(0.8)
  • .charge(-500)
  • .start();
  • //边
  • var links = svg.selectAll("line")
  • .data(edges_data)
  • .enter()
  • .append("line")
  • .attr("marker-end", "url(#arrow)")
  • .style("stroke", "#ccc")
  • .style("stroke-width", edgeWidth);
  • //节点
  • var nodes = svg.selectAll("circle")
  • .data(nodes_data)
  • .enter()
  • .append("circle")
  • .attr("r", r)
  • .style("fill", function (d, i) {
  • return color(i);
  • })
  • .on("click", function (d, i) {
  • if (i == 0) {
  • update();
  • }
  • })
  • .call(force.drag);
  • //标签
  • var nodes_labels = svg.selectAll("text")
  • .data(nodes_data)
  • .enter()
  • .append("text")
  • .attr("dx", function (d, i) {
  • return -16 * (nodes_data[i].name.length);
  • })
  • .attr("dy", 5)
  • .attr("fill", "#fff")
  • .style("font-size", 16)
  • .text(function (d, i) {
  • if (i == 0) {
  • return "点我";
  • }
  • return "";
  • });
  • //运动刷新
  • force.on("tick", function (d) {
  • links.attr("x1", function (d) {
  • var distance = Math.sqrt((d.target.y - d.source.y) * (d.target.y - d.source.y) +
  • (d.target.x - d.source.x) * (d.target.x - d.source.x));
  • var x_distance = (d.target.x - d.source.x) / distance * r;
  • return d.source.x + x_distance;
  • }).attr("y1", function (d) {
  • var distance = Math.sqrt((d.target.y - d.source.y) * (d.target.y - d.source.y) +
  • (d.target.x - d.source.x) * (d.target.x - d.source.x));
  • var y_distance = (d.target.y - d.source.y) / distance * r;
  • return d.source.y + y_distance;
  • }).attr("x2", function (d) {
  • var distance = Math.sqrt((d.target.y - d.source.y) * (d.target.y - d.source.y) +
  • (d.target.x - d.source.x) * (d.target.x - d.source.x));
  • var x_distance = (d.target.x - d.source.x) / distance * r;
  • return d.target.x - x_distance;
  • }).attr("y2", function (d) {
  • var distance = Math.sqrt((d.target.y - d.source.y) * (d.target.y - d.source.y) +
  • (d.target.x - d.source.x) * (d.target.x - d.source.x));
  • var y_distance = (d.target.y - d.source.y) / distance * r;
  • return d.target.y - y_distance;
  • });
  • nodes.attr("cx", function (d) {
  • return d.x;
  • }).attr("cy", function (d) {
  • return d.y;
  • });
  • nodes_labels.attr("x", function (d) {
  • return d.x;
  • });
  • nodes_labels.attr("y", function (d) {
  • return d.y;
  • });
  • });
  • //用于产生不同颜色的节点
  • var colorIndex = 8;
  • //添加节点更新
  • function update() {
  • nodes_data.push({'name': 'xxx'});
  • edges_data.push({'source': 0, 'target': nodes_data.length - 1});
  • links = links.data(force.links());
  • links.enter()
  • .append("line")
  • .style("stroke", "#ccc")
  • .style("stroke-width", 2);
  • links.exit().remove();
  • nodes = nodes.data(force.nodes());
  • nodes.enter().append("circle")
  • .attr("r", 40)
  • .style("fill", color(colorIndex++))
  • .call(force.drag);
  • nodes.exit().remove();
  • force.start();
  • }
  • //回车事件
  • $(document).keydown(function(e) {
  • if(e.which == 13) {
  • update();
  • }
  • });
  • });
  • </script>
  • </body>
  • </html>

更新讲解

更新部分只有 update()函数,更新的操作分为以下

  1. 数据更新
  2. 力学图重新加载数据
  3. 追加对应的边与节点(enter)
  4. 去除无用的元素(remove)
  5. 执行 force.start() 操作

只要按照顺序进行,新增的节点等就可以很好的添加。

例子说明

例子中有几点需要说明一下

  1. force.on("tick", function (d){...}) 中的计算用户绘制线条的时候只绘制两个圆之间的部分,当然这么做也便于添加箭头,也能较少一些其他的重叠操作。

  2. var nodes_labels = svg.selectAll("text")中仅仅对第一个点进行了标注,用于提示点击操作,也可以对所有的点进行标注的。

总结

d3的github(https://github.com/mbostock/d3/wiki/Gallery)上有很多的例子,大家可以找到自己喜欢的进行学习,同时也需要对svg有一定的学习,方便我们自己进行一些改进。

如有任何知识产权、版权问题或理论错误,还请指正。

转载请注明原作者及以上信息。

d3力学图(force layout)更新