我正在将一个公式应用于D3js强制网络图中的群集节点。我不知道如何将我的链接连接到新的群集节点位置。
对于我更改了原始代码的节点:
force.on("tick", function() {
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
按类别(nodeCategory)聚类节点的代码:
node.attr("transform", function(d) {
var xm = d.x + intensity*Math.cos(angle*nodeGroup(d.nodeCategory));
var ym = d.y + intensity*Math.sin(angle*nodeGroup(d.nodeCategory));
return "translate(" + xm + "," + ym + ")";
});
现在,节点根据数据中的类别成功聚类。但是,我不知道如何更新链接(边缘)的代码。链接不再连接到节点并反映非群集节点位置:
edges.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; });
如何将转化应用于链接?任何帮助将不胜感激!
编辑:这是一个说明问题的jsfiddle。
添
答案 0 :(得分:1)
问题是链接位于错误的位置,因为节点通过分组变换从它们的(d.x,d.y)位置移位。因此,d.source.x
和d.source.y
不是您想要链接的位置。
您需要更新d.x和d.y以反映节点的真实位置,以便链接位于您想要的位置。
通常的做法是......
force.on("tick", function(e) {
node.attr("transform", function(d) {
d.x += (intensity*Math.cos(angle*nodeGroup(d.nodeCategory)) - d.x)*e.alpha;
d.y += (intensity*Math.sin(angle*nodeGroup(d.nodeCategory)) - d.y)*e.alpha;
return "translate(" + d.x + "," + d.y + ")";
});
edges.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; });
}
这个想法是让位置调节器具有不同的增益,这是力α的函数。这样,节点随时间衰减的速度就会随之衰减。
这是在上下文中......
//Width and height for SVG area
var w = 500;
var h = 200;
// nb_group, angle, intensity: Used in clustering the nodes
var nb_group=4;
var angle = 2*Math.PI/nb_group;
var intensity = 100;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
var colors = d3.scale.category10()
.range(["#FFFF00", //YELLOW
"#377eb8", //BLUE
"#4daf4a", //GREEN
"#e41a1c", //RED
]);
var dataset = {
"nodes":[
{"id":0,"name":"A","nodeCategory":"1"},
{"id":1,"name":"AA","nodeCategory":"1"},
{"id":2,"name":"B","nodeCategory":"2"},
{"id":3,"name":"BB","nodeCategory":"2"},
{"id":4,"name":"C","nodeCategory":"3"},
{"id":5,"name":"CC","nodeCategory":"3"},
{"id":6,"name":"D","nodeCategory":"4"},
{"id":7,"name":"DD","nodeCategory":"4"},
{"id":8,"name":"DDD","nodeCategory":"4"}
],
"edges":[
{"source":0,"target":2,"value":""},
{"source":1,"target":3,"value":""},
{"source":2,"target":4,"value":""},
{"source":3,"target":5,"value":""},
{"source":4,"target":6,"value":""},
{"source":5,"target":7,"value":""},
{"source":6,"target":8,"value":""},
{"source":7,"target":0,"value":""},
{"source":8,"target":1,"value":""}
]
}
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.gravity(.5)
.charge(-100)
.linkDistance(10)
.size([w, h])
.start();
var drag = force.drag()
.on("dragstart", dragstart);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", "black");
var nodes = svg.selectAll("g.node")
.data(dataset.nodes)
.enter()
.append("g")
.on("dblclick", dblclick)
.call(drag);
nodes.append("circle")
.attr("r", 10)
.style("fill", function(d) { return colors(d.nodeCategory); })
.style("stroke", "black")
// Mousover Node - highlight node by fading the node colour during mouseover
.on('mouseover', function(d){
var nodeSelection = d3.select(this).style({opacity:'0.5'});
})
//Mouseout Node - bring node back to full colour
.on('mouseout', function(d){
var nodeSelection= d3.select(this).style({opacity:'1.0',})
})
// dx sets how close to the node the label appears
nodes.append("text")
.attr("class", "nodetext")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name }); // Just the name
// Edge Paths
var edgepaths = svg.selectAll(".edgepath")
.data(dataset.edges)
.enter()
.append('path')
.attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y},
'id':function(d,i) {return 'edgepath'+i}})
.style("pointer-events", "none");
force.on("tick", function(e) {
// position regulator for nodes must update d.x and d.y BEFORE links are positioned
nodes.attr("transform", function(d) {
d.x += (intensity*Math.cos(angle*(d.nodeCategory)) + w/2 - d.x)*e.alpha;
d.y += (intensity*Math.sin(angle*(d.nodeCategory)) + h/2 - d.y)*e.alpha;
return "translate(" + d.x + "," + d.y + ")";
});
edges.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; });
// ORIGINAL transform for Nodes:
// nodes.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
// New Transform for nodes:
// PROBLEM HERE
edgepaths.attr('d', function(d) { var path='M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y;
//console.log(d)
return path});
});
// Double click to 'unfix' the node and have forces start to act on it again.
function dblclick(d) {
d3.select(this).classed("fixed", d.fixed = false);
}
// Set the "fixed" property of the dragged node to TRUE when a dragstart event is initiated,
// - removes "forces" from acting on that node and changing its position.
function dragstart(d) {
d3.select(this).classed("fixed", d.fixed = true);
}
body { margin: 0; }
svg { outline: 1px solid red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>