使用 D3.js v4 对力向图进行不稳定缩放
Erratic zoom on force directed graph with D3.js v4
我在 D3.js (v4) 中有这个力导向图:
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<style>
svg text {
pointer-events: none;
}
</style>
<body>
<script type="text/javascript">
var r = 7, w=window.innerWidth, h=window.innerHeight, nodes = [], colors = d3.scaleOrdinal(d3.schemeCategory20c);
var force = d3.forceSimulation()
.velocityDecay(0.9)
.alphaDecay(0)
.force("charge", d3.forceManyBody())
.force("repel", d3.forceManyBody().strength(-140).distanceMax(100).distanceMin(10))
.force("center", d3.forceCenter(w /2, h/2))
.force("x", d3.forceX(w / 2))
.force("y", d3.forceY(h / 2));
var zoom = d3.zoom()
.scaleExtent([0.3, 8])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.call(zoom);
d3.json("tiles1.json", function (data) {
var root = d3.hierarchy(data);
var nodes = root.descendants();
var links = root.links();
force.nodes(nodes);
force.force("link", d3.forceLink(links).strength(1).distance(50));
var link = svg.selectAll("line")
.data(links)
.enter().insert("line")
.style("stroke", "#999")
.style("stroke-width", "1px");
var nodeElements = svg.selectAll("circle.node")
.data(nodes)
.enter().append("circle")
.attr("r", r)
.style("fill", function(d) {
return colors(d.parent && d.parent.data.name);
})
.style("stroke", "#0b5698")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded));
var labels = svg.selectAll(".mytext")
.data(nodes)
.enter()
.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.data.name })
.style("text-anchor", "middle")
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 7);
force.on("tick", function(e) {
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; });
nodeElements.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
labels.attr("x", function(d){ return d.x -10; })
.attr("y", function (d) {return d.y + 10; });
});
});
function dragStarted(d) {
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragEnded(d) {
d.fx = null;
d.fy = null;
}
function zoomed() {
svg.attr("transform", d3.event.transform);
}
</script>
</body>
</html>
下面是tiles1.json;为了简单起见,我在这里使用只有两个节点的图。
{ "name": "test", "children": [{
"name": "test"
}]
}
一切正常,除了缩放操作:不是将缩放集中在点击点上,整个图形实际上都在平移到左上角,[0,0] 原点,即使我的图形的中心原点是屏幕的中心 (d3.forceCenter(w/2, h/2)
)。
我不是 D3 的专家,但我知道我在代码中做错了什么,或者缺少了什么,我不明白是什么以及为什么。
我需要你的帮助,提前谢谢。
几件事:
- 您调用
.call(zoom)
的东西是事件处理程序,即寻找要缩放的事件的元素。这不应该直接是 SVG。通常的形式是在 SVG 顶部放置一个不可见的矩形来捕获这些事件。
- 您不能像现在这样将 SVG 缩放应用于
svg
元素。这有点违反直觉,但是 svg
标签是一个 HTML 元素,它里面的东西是 svg
元素。所以你会怎么做?将您的绘图包裹在应用缩放的 g
中。
将这两个想法放在一起,你最终会得到:
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<style>
svg text {
pointer-events: none;
}
</style>
<body>
<script type="text/javascript">
var r = 7, w=window.innerWidth, h=window.innerHeight, nodes = [], colors = d3.scaleOrdinal(d3.schemeCategory20c);
var force = d3.forceSimulation()
.velocityDecay(0.9)
.alphaDecay(0)
.force("charge", d3.forceManyBody())
.force("repel", d3.forceManyBody().strength(-140).distanceMax(100).distanceMin(10))
.force("center", d3.forceCenter(w /2, h/2))
.force("x", d3.forceX(w / 2))
.force("y", d3.forceY(h / 2));
var zoom = d3.zoom()
.scaleExtent([0.3, 8])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
var g = svg.append("g");
svg.append("rect")
.attr("width", w)
.attr("height", h)
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom);
d3.json("https://jsonblob.com/api/eb8b41b1-0c23-11e7-a0ba-092e370a78bd", function (data) {
var root = d3.hierarchy(data);
var nodes = root.descendants();
var links = root.links();
force.nodes(nodes);
force.force("link", d3.forceLink(links).strength(1).distance(50));
var link = g.selectAll("line")
.data(links)
.enter().insert("line")
.style("stroke", "#999")
.style("stroke-width", "1px");
var nodeElements = g.selectAll("circle.node")
.data(nodes)
.enter().append("circle")
.attr("r", r)
.style("fill", function(d) {
return colors(d.parent && d.parent.data.name);
})
.style("stroke", "#0b5698")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded));
var labels = g.selectAll(".mytext")
.data(nodes)
.enter()
.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.data.name })
.style("text-anchor", "middle")
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 7);
force.on("tick", function(e) {
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; });
nodeElements.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
labels.attr("x", function(d){ return d.x -10; })
.attr("y", function (d) {return d.y + 10; });
});
});
function dragStarted(d) {
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragEnded(d) {
d.fx = null;
d.fy = null;
}
function zoomed() {
g.attr("transform", d3.event.transform);
}
</script>
</body>
</html>
我在 D3.js (v4) 中有这个力导向图:
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<style>
svg text {
pointer-events: none;
}
</style>
<body>
<script type="text/javascript">
var r = 7, w=window.innerWidth, h=window.innerHeight, nodes = [], colors = d3.scaleOrdinal(d3.schemeCategory20c);
var force = d3.forceSimulation()
.velocityDecay(0.9)
.alphaDecay(0)
.force("charge", d3.forceManyBody())
.force("repel", d3.forceManyBody().strength(-140).distanceMax(100).distanceMin(10))
.force("center", d3.forceCenter(w /2, h/2))
.force("x", d3.forceX(w / 2))
.force("y", d3.forceY(h / 2));
var zoom = d3.zoom()
.scaleExtent([0.3, 8])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.call(zoom);
d3.json("tiles1.json", function (data) {
var root = d3.hierarchy(data);
var nodes = root.descendants();
var links = root.links();
force.nodes(nodes);
force.force("link", d3.forceLink(links).strength(1).distance(50));
var link = svg.selectAll("line")
.data(links)
.enter().insert("line")
.style("stroke", "#999")
.style("stroke-width", "1px");
var nodeElements = svg.selectAll("circle.node")
.data(nodes)
.enter().append("circle")
.attr("r", r)
.style("fill", function(d) {
return colors(d.parent && d.parent.data.name);
})
.style("stroke", "#0b5698")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded));
var labels = svg.selectAll(".mytext")
.data(nodes)
.enter()
.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.data.name })
.style("text-anchor", "middle")
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 7);
force.on("tick", function(e) {
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; });
nodeElements.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
labels.attr("x", function(d){ return d.x -10; })
.attr("y", function (d) {return d.y + 10; });
});
});
function dragStarted(d) {
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragEnded(d) {
d.fx = null;
d.fy = null;
}
function zoomed() {
svg.attr("transform", d3.event.transform);
}
</script>
</body>
</html>
下面是tiles1.json;为了简单起见,我在这里使用只有两个节点的图。
{ "name": "test", "children": [{
"name": "test"
}]
}
一切正常,除了缩放操作:不是将缩放集中在点击点上,整个图形实际上都在平移到左上角,[0,0] 原点,即使我的图形的中心原点是屏幕的中心 (d3.forceCenter(w/2, h/2)
)。
我不是 D3 的专家,但我知道我在代码中做错了什么,或者缺少了什么,我不明白是什么以及为什么。
我需要你的帮助,提前谢谢。
几件事:
- 您调用
.call(zoom)
的东西是事件处理程序,即寻找要缩放的事件的元素。这不应该直接是 SVG。通常的形式是在 SVG 顶部放置一个不可见的矩形来捕获这些事件。 - 您不能像现在这样将 SVG 缩放应用于
svg
元素。这有点违反直觉,但是svg
标签是一个 HTML 元素,它里面的东西是svg
元素。所以你会怎么做?将您的绘图包裹在应用缩放的g
中。
将这两个想法放在一起,你最终会得到:
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<style>
svg text {
pointer-events: none;
}
</style>
<body>
<script type="text/javascript">
var r = 7, w=window.innerWidth, h=window.innerHeight, nodes = [], colors = d3.scaleOrdinal(d3.schemeCategory20c);
var force = d3.forceSimulation()
.velocityDecay(0.9)
.alphaDecay(0)
.force("charge", d3.forceManyBody())
.force("repel", d3.forceManyBody().strength(-140).distanceMax(100).distanceMin(10))
.force("center", d3.forceCenter(w /2, h/2))
.force("x", d3.forceX(w / 2))
.force("y", d3.forceY(h / 2));
var zoom = d3.zoom()
.scaleExtent([0.3, 8])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
var g = svg.append("g");
svg.append("rect")
.attr("width", w)
.attr("height", h)
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom);
d3.json("https://jsonblob.com/api/eb8b41b1-0c23-11e7-a0ba-092e370a78bd", function (data) {
var root = d3.hierarchy(data);
var nodes = root.descendants();
var links = root.links();
force.nodes(nodes);
force.force("link", d3.forceLink(links).strength(1).distance(50));
var link = g.selectAll("line")
.data(links)
.enter().insert("line")
.style("stroke", "#999")
.style("stroke-width", "1px");
var nodeElements = g.selectAll("circle.node")
.data(nodes)
.enter().append("circle")
.attr("r", r)
.style("fill", function(d) {
return colors(d.parent && d.parent.data.name);
})
.style("stroke", "#0b5698")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded));
var labels = g.selectAll(".mytext")
.data(nodes)
.enter()
.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.data.name })
.style("text-anchor", "middle")
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 7);
force.on("tick", function(e) {
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; });
nodeElements.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
labels.attr("x", function(d){ return d.x -10; })
.attr("y", function (d) {return d.y + 10; });
});
});
function dragStarted(d) {
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragEnded(d) {
d.fx = null;
d.fy = null;
}
function zoomed() {
g.attr("transform", d3.event.transform);
}
</script>
</body>
</html>