我在下面的d3.js强制布局代码主要适用。它使用锚标签,以便标签在拥挤的图形中更加明显。我还找到了一个平移和缩放示例并实现了它。平移和缩放大多数都有效,但是当点击一个节点时它会平移并移动节点,这意味着它不会真正移动节点。我只是想让它移动节点而不是在点击节点时平移。我创建了一个jsfiddle here。
代码:
var links = [
{ source: "a", target: "b", indicator_type: "x"},
{ source: "a", target: "c", indicator_type: "x"},
{ source: "e", target: "d", indicator_type: "x"},
{ source: "e", target: "d", indicator_type: "x"},
{ source: "e", target: "b", indicator_type: "y"},
{ source: "e", target: "c", indicator_type: "y"},
];
var w = 850,
h = 500;
var fill = d3.scale.category20();
var labelDistance = 0;
var vis = d3.select("#relationship_graph")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.attr("pointer-events", "all")
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append('svg:g');
vis.append("rect")
.attr("width", w)
.attr("height", h)
.attr("opacity", 0);
function redraw() {
vis.attr("transform",
"translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
var nodes = {};
var labelAnchors = [];
var labelAnchorLinks = [];
// Compute the distinct nodes from the links.
links.forEach(function (link) {
link.source = nodes[link.source] || (nodes[link.source] = {
label: link.source,
type: "notice",
indicator_type: "notice"
});
link.target = nodes[link.target] || (nodes[link.target] = {
label: link.target,
type: "indicator",
indicator_type: link.indicator_type
});
link.weight = 1;
});
for (var node in nodes) {
var a = labelAnchors.push({
node: nodes[node]
});
var b = labelAnchors.push({
node: nodes[node]
});
labelAnchorLinks.push({
source: (a - 1),
target: (b - 1),
weight: 1
});
};
var force = d3.layout.force()
.size([w,h])
.nodes(d3.values(nodes))
.links(links)
.gravity(1)
.linkDistance(60)
.charge(-3000)
.linkStrength(function (x) {
return x.weight * 5
});
force.start();
var force2 = d3.layout.force()
.nodes(labelAnchors)
.links(labelAnchorLinks)
.gravity(0)
.linkDistance(0)
.linkStrength(8)
.charge(-300)
.size([w, h]);
force2.start();
var link = vis.selectAll("line.link")
.data(links)
.enter()
.append("svg:line")
.attr("class", "link")
.style("stroke", "#CCC");
var node = vis.selectAll("g.node")
.data(force.nodes())
.enter()
.append("svg:g")
.attr("class", "node");
node.append("svg:circle")
.attr("r", function (d) {
return d.type == "notice" ? 10 : 7
})
.style({
fill: function (d) {
return fill(d.indicator_type)
},
stroke: "#CCC",
});
node.call(force.drag);
var anchorLink = vis.selectAll("line.anchorLink")
.data(labelAnchorLinks)
//.enter().append("svg:line").attr("class", "anchorLink").style("stroke", "#999");
var anchorNode = vis.selectAll("g.anchorNode")
.data(force2.nodes())
.enter()
.append("svg:g")
.attr("class", "anchorNode");
anchorNode.append("svg:circle").attr("r", 0).style("fill", "#FFF");
anchorNode.append("svg:text").text(function (d, i) {
return i % 2 == 0 ? "" : d.node.label;
})
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 10);
var updateLink = function () {
this.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;
});
}
var updateNode = function () {
this.attr({
"transform": function (d) {
return "translate(" + d.x + "," + d.y + ")";
}
});
}
force.on("tick", function () {
force2.start();
node.call(updateNode);
anchorNode.each(function (d, i) {
if (i % 2 == 0) {
d.x = d.node.x;
d.y = d.node.y;
} else {
var b = this.childNodes[1].getBBox();
var diffX = d.x - d.node.x;
var diffY = d.y - d.node.y;
var dist = Math.sqrt(diffX * diffX + diffY * diffY);
var shiftX = b.width * (diffX - dist) / (dist * 2);
shiftX = Math.max(-b.width, Math.min(0, shiftX));
var shiftY = 5;
this.childNodes[1]
.setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
}
});
anchorNode.call(updateNode);
link.call(updateLink);
anchorLink.call(updateLink);
});