D3力导向图及树状布局变换

D3力导向图及树状布局变换

d3的力导向图是表现关系型数据比较方便且直观的方法,但是会遇到节点比较多且层级关系混乱的情况,这时树状布局就比较方便了,如何不破坏原来结构以最小的代价变换树状布局呢?下面将为大家介绍。

最终效果

width="100%" height="600" src="//en.jsrun.net/aXiKp/embedded/all/light/" allowfullscreen="allowfullscreen">

代码解析

首先我们需要准备关系型数据,数据包括节点数据nodes和连线关系数据links,links的source和target分别表示源和目标的index。

var nodes = [
  {value:"66666666",type:"home",index:"0"},
  {value:"11111111111",type:"phone",index:"1"},
  {value:"22222222222",type:"phone",index:"2"},
  {value:"33333333333",type:"phone",index:"3"},
  {value:"44444444444",type:"phone",index:"4"},
  {value:"55555555555",type:"phone",index:"5"},
  {value:"aaa",type:"weixin",index:"6"},
  {value:"bbb",type:"weixin",index:"7"},
  {value:"ccc",type:"weixin",index:"8"},
  {value:"ddd",type:"weixin",index:"9"},
  {value:"eee",type:"weixin",index:"10"},
  {value:"fff",type:"weixin",index:"11"},
];
var links = [
  {source:0,target:1},
  {source:0,target:2},
  {source:0,target:3},
  {source:0,target:4},
  {source:0,target:5},
  {source:2,target:6},
  {source:2,target:7},
  {source:2,target:8},
  {source:3,target:9},
  {source:3,target:10},
  {source:3,target:11},
]

绘制力导向图

//新建画布
var svg = d3.select("#forceMap").append("svg")
                        .attr("width",width)
            .attr("height",height)
            .attr("id","forceSvg");
//创建group,svg的绘制中为了避免混乱及后续事件的添加,建议使用g标签将画布分组。            
var mapG = svg.append("g")
.attr("id","forceGroup");
//使用d3的力学布局,通过设定的属性,将数据计算
var force = d3.layout.force()
                    .nodes(nodes)
                    .links(links)
                    .size([width,height])
                    .linkDistance(100)
                    .charge([-1250])
                    .gravity(0.5)
                    .friction(0.5);
force.start();//开始计算
//绘制线,svg的覆盖顺序是后面标签覆盖前面的,所以为了避免线在点上面,要先画line
var linkG = mapG.selectAll(".link")
.data(links)
.enter()
.append("line")
.attr("class","link")
.attr("stroke","#ccc");
//绘制点
var nodeG = mapG.selectAll(".node")
.data(nodes)
.enter()
.append("circle")
.attr("class","node")
.attr("r",8)
.attr("fill",function(d){
  switch(d.type){
    case "home": return "red";break;
    case"phone": return "blue";break;
    case "weixin": return "green";break;
  }
});

//tick是力导向图每一次运动需要计算的过程
force.on("tick", function () {
                    linkG.attr("x1", function (d) {
                        return d.source.x;
                    })
                    .attr("y1", function (d) {
                        return d.source.y;
                    })
                    .attr("x2", function (d) {
                        return d.target.x;
                    })
                    .attr("y2", function (d) {
                        return d.target.y;
                    });


                nodeG.attr("cx", function (d) {
                    return d.x
                })
                .attr("cy", function(d){
                  return d.y
                });
});

在tick中,d3自带的布局计算帮我们完成了点和线每一次运动的坐标,
我们只需要在tick中重新赋给页面中的点和线即可完成运动效果。

绘制树状布局

在这里,我们利用d3的tree layout完成点的坐标计算。
前提是必须把关系型数据改变为树状层级数据。
用concat拷贝数组,避免影响全局数据。

function drawTree(){
  var middleData = {};
  var linksBak = links.concat();
  var nodesBak = nodes.concat();
  //将数据整理为树状结构
  nodesBak.forEach(function(d){
            if(d.index == 0){
                var temp = {
                    name:d.index,
                    children:[]
                };
                var treeData = toTreeData(linksBak);
                function toTreeData(data){
                    var pos={};
                    var tree=[];
                    var i=0;
                    while(data.length!=0){
                        if(data[i].source.index==d.index){
                            tree.push({
                                name:data[i].target.index,
                                children:[]
                            });
                            pos[data[i].target.index]=[tree.length-1];
                            data.splice(i,1);
                            i--;
                        }else{
                            var posArr=pos[data[i].source.index];
                            if(posArr!=undefined){

                                var obj=tree[posArr[0]];
                                for(var j=1;j<posArr.length;j++){
                                    obj=obj.children[posArr[j]];
                                }

                                obj.children.push({
                                    name:data[i].target.index,
                                    children:[]
                                });
                                pos[data[i].target.index]=posArr.concat([obj.children.length-1]);
                                data.splice(i,1);
                                i--;
                            }
                        }
                        i++;
                        if(i>data.length-1){
                            i=0;
                        }
                    }
                    return tree;
                }
                temp.children = treeData;
                middleData = temp;
            }
        });
  //使用树状布局计算位置
  var tree = d3.layout.tree()
            .size([450,450]);
  var tempNodes = tree.nodes(middleData);
  //重启布局以改变位置
  force.start();
  force.on("tick",function(){
  //在运动前强制修改节点坐标为树状结构
    tempNodes.forEach(function(d,i){
    nodes[d.name].x = d.x;
    nodes[d.name].y = d.y
     });
        linkG.attr("x1", function (d) {
                        return d.source.x;
                    })
                    .attr("y1", function (d) {
                        return d.source.y+10;
                    })
                    .attr("x2", function (d) {
                        return d.target.x;
                    })
                    .attr("y2", function (d) {
                        return d.target.y+10;
                    });


                nodeG.attr("cx", function (d) {
                    return d.x
                })
                .attr("cy", function(d){
                  return d.y+10
                });
  })


}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个简单的d3导向的代码示例,包含了节点、边和节点拖动等功能: ```html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>导向</title> <script src="https://d3js.org/d3.v5.min.js"></script> </head> <body> <svg width="600" height="600"></svg> <script> // 创建节点和边的数据 var nodes = [{id: 'A'}, {id: 'B'}, {id: 'C'}, {id: 'D'}, {id: 'E'}]; var links = [{source: 'A', target: 'B'}, {source: 'B', target: 'C'}, {source: 'C', target: 'D'}, {source: 'D', target: 'E'}, {source: 'E', target: 'A'}]; // 创建导向模拟器 var simulation = d3.forceSimulation(nodes) .force('link', d3.forceLink(links).id(function(d) { return d.id; })) .force('charge', d3.forceManyBody()) .force('center', d3.forceCenter(300, 300)) .on('tick', ticked); // 创建节点和边的SVG元素 var svg = d3.select('svg'); var link = svg.selectAll('line') .data(links) .enter().append('line') .attr('stroke', '#999') .attr('stroke-width', '1'); var node = svg.selectAll('circle') .data(nodes) .enter().append('circle') .attr('r', 10) .attr('fill', '#ccc') .call(d3.drag() .on('start', dragstarted) .on('drag', dragged) .on('end', dragended)); // 节点拖动相关的函数 function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } // 每个时间段更新节点和边的位置 function ticked() { link.attr('x1', function(d) { return d.source.x; }) .attr('y1', function(d) { return d.source.y; }) .attr('x2', function(d) { return d.target.x; }) .attr('y2', function(d) { return d.target.y; }); node.attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }); } </script> </body> </html> ``` 在这个示例中,我们首先创建了节点和边的数据,并使用 `d3.forceSimulation()` 函数创建了导向模拟器。然后,我们创建了节点和边的SVG元素,并使用 `d3.drag()` 函数为节点添加了拖动事件。最后,在模拟器的每个时间段里,我们更新了节点和边的位置。如果你需要更详细的解释,请参考d3官方文档。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值