简介
力导向图(Force-Directed Graph)是 d3.js 提供的一种十分经典的绘图算法。通过在二维空间里配置节点和连线,在各种各样力的作用下,节点间相互碰撞和运动并在这个过程中不断地降低能量,最终达到一种能量很低的安定状态,形成一种稳定的力导向图。
数据
直接上代码
var nodes = [//节点集
{ name: "湖南邵阳" },
{ name: "山东莱州" },
{ name: "广东阳江" },
{ name: "山东枣庄" },
{ name: "泽" },
{ name: "恒" },
{ name: "鑫" },
{ name: "明山" },
{ name: "班长" }
];
var edges = [//边集
{ source: 0, target: 4, relation: "籍贯", value: 1.3 },
{ source: 4, target: 5, relation: "舍友", value: 1 },
{ source: 4, target: 6, relation: "舍友", value: 1 },
{ source: 4, target: 7, relation: "舍友", value: 1 },
{ source: 1, target: 6, relation: "籍贯", value: 2 },
{ source: 2, target: 5, relation: "籍贯", value: 0.9 },
{ source: 3, target: 7, relation: "籍贯", value: 1 },
{ source: 5, target: 6, relation: "同学", value: 1.6 },
{ source: 6, target: 7, relation: "朋友", value: 0.7 },
{ source: 6, target: 8, relation: "职责", value: 2 }
];
主程序
完整代码
var width = 700;
var height = 700;
var dome = document.getElementById('svgAll');
var svg = d3.create("svg").attr("width", width).attr("height", height);
var g = svg.append("g").attr("transform", "translate(10,10)");
//指定缩放范围,缩放功能
const zoom = d3
.zoom()
.scaleExtent([0.2, 4])
.on("zoom", (e) => {
g.attr("transform", `translate(${e.transform.x},${e.transform.y}) scale(${e.transform.k})`);
});
//动画持续时间
var initialTransform = d3.zoomIdentity.translate(20, 20).scale(2);//初始缩放平移
g.transition()
.duration(1000)
.call(zoom.transform, d3.zoomIdentity);
svg.call(zoom);
// 颜色
var colorScale = d3.scaleOrdinal()
.domain(d3.range(nodes.length))
.range(d3.schemeCategory10);
// 力导向图
var forceSimulation = d3.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter());
// 节点数据
forceSimulation.nodes(nodes)
.on("tick", ticked);
//边数据
forceSimulation.force("link")
.links(edges)
.distance(function (d) {//每一边的长度
return d.value * 100;
})
// 中心
forceSimulation.force("center")
.x(width / 2)
.y(height / 2);
//绘制边
var links = g.append("g")
.selectAll("line")
.data(edges)
.enter()
.append("line")
.attr("stroke", function (d, i) {
return colorScale(i);
})
.attr("stroke-width", 1);
// 边文字
var linksText = g.append("g")
.selectAll("text")
.data(edges)
.enter()
.append("text")
.text(function (d) {
return d.relation;
})
// 节点分组
var gs = g.selectAll(".circleText")
.data(nodes)
.enter()
.append("g")
.attr("transform", function (d, i) {
var cirX = d.x;
var cirY = d.y;
return "translate(" + cirX + "," + cirY + ")";
})
.call(d3.drag()
.on("start", started)
.on("drag", dragged)
.on("end", ended)
);
//节点
gs.append("circle")
.attr("r", 10)
.attr("fill", function (d, i) {
return colorScale(i);
})
//节点文字
gs.append("text")
.attr("x", -10)
.attr("y", -20)
.attr("dy", 10)
.text(function (d) {
return d.name;
})
// ticked函数
function ticked() {
links.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; });
linksText.attr("x", function (d) {
return (d.source.x + d.target.x) / 2;
}).attr("y", function (d) {
return (d.source.y + d.target.y) / 2;
});
gs.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; });
}
// 拖拽事件
function started(e, d) {
forceSimulation.alphaTarget(0.8).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(e, d) {
d.fx = e.x;
d.fy = e.y;
}
function ended(e, d) {
forceSimulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
dome.appendChild(svg.node());