d3.js - d3 中的跳跃式转换强制有向图
问题描述
我在 d3 中有一个力有向图,我想在多个重叠的数据集之间进行转换。我希望节点顺利过渡,但我在故障排除方面遇到了困难。我试图密切关注Bostock 的示例,但我的节点在没有任何过渡的情况下跳跃。我用夸张的缓慢过渡做了一个例子来展示正在发生的事情。保留在图中的节点会立即跳转到它们的新位置并在模拟开始时冻结。这是我的笔。以下是相关代码:
const cb = {"edges":[{"source":"a","target":"b","value":1},{"source":"a","target":"c","value":1},{"source":"b","target":"c","value":1},{"source":"c","target":"d","value":1}, {"source":"d","target":"e","value":1},{"source":"d","target":"f","value":1},{"source":"g","target":"h","value":1},{"source":"a","target":"g","value":1}],"nodes":[{"id":"a","pop":12.00328963067508,"size":5},{"id":"b","pop":12.391087593534877,"size":5},{"id":"c","pop":12.384324067681156,"size":5},{"id":"d","pop":13.991090521661292,"size":6},
{"id":"e","pop":13.991090521661292,"size":6},
{"id":"f","pop":13.991090521661292,"size":6},
{"id":"g","pop":13.991090521661292,"size":6},{"id":"h","pop":13.991090521661292,"size":6}]}
const sp = {"edges":[{"source":"a","target":"b","value":1},{"source":"a","target":"e","value":1},{"source":"b","target":"f","value":1},{"source":"e","target":"f","value":1}],"nodes":[{"id":"a","pop":12.00328963067508,"size":5},{"id":"b","pop":12.391087593534877,"size":5},{"id":"e","pop":13.063656176168433,"size":6},{"id":"f","pop":12.52608275807238,"size":5}]}
// set margins and canvas size
const margin = {
top: 10,
right: 20,
bottom: 30,
left: 30
};
const width = 600 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
// set up canvas
const svg = d3.select('.chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.call(responsivefy)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// set up selections
let link = svg.append("g")
.attr("class", "links")
.selectAll("line");
let node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle");
// set up color scale
const color = d3.scaleSequential()
.domain([8, 15])
.interpolator(d3.interpolateInferno);
// set up simulation basic parameters
const simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
function render(graph) {
// node selection and data handling
node = node
.data(graph.nodes, function(d) { return d.id; });
// node general update pattern
node.exit()
.transition()
.duration(1500)
.attr("r", 0)
.remove();
node.transition().duration(1500)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
node = node
.enter().append("circle")
.call(function(node) {
node.transition().duration(1500)
.attr("r", function(d) { return d.size; })
})
.attr("fill", function(d) { return color(d.pop); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.merge(node);
// give all nodes a title with their id for hover identification
node.append("title")
.text(function(d) { return d.id; });
// link selection, data handling
link = link
.data(graph.edges, function(d) { return d.source + "-" + d.target; });
// link general update pattern with attrTween to keep links connected to disappearing nodes
link
.exit()
.transition()
.duration(1500)
.attr("stroke-opacity", 0)
.attr("stroke-width", 0)
/* .attrTween("x1", function(d) {
return function() {
return d.source.x;
};
})
.attrTween("x2", function(d) {
return function() {
return d.target.x;
};
})
.attrTween("y1", function(d) {
return function() {
return d.source.y;
};
})
.attrTween("y2", function(d) {
return function() {
return d.target.y;
};
}) */
.remove();
link = link
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); })
.merge(link);
// add nodes and links to the siumlation
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.edges);
// restart the simulation
simulation.alpha(1).restart();
// set the ticked function to constantly update node and link position
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;
});
}
};
// initial render
render(cb)
// dragging functions
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;
}
// responsivefy from https://brendansudol.com/writing/responsive-d3
function responsivefy(svg) {
// get container + svg aspect ratio
const container = d3.select(svg.node().parentNode),
width = parseInt(svg.style("width")),
height = parseInt(svg.style("height")),
aspect = width / height;
// add viewBox and preserveAspectRatio properties,
// and call resize so that svg resizes on inital page load
svg.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMinYMid")
.call(resize);
// to register multiple listeners for same event type,
// you need to add namespace, i.e., 'click.foo'
// necessary if you call invoke this function for multiple svgs
// api docs: https://github.com/mbostock/d3/wiki/Selections#on
d3.select(window).on("resize." + container.attr("id"), resize);
// get width of container and resize svg to fit it
function resize() {
const targetWidth = parseInt(container.style("width"));
svg.attr("width", targetWidth);
svg.attr("height", Math.round(targetWidth / aspect));
}
}
<html lang="en">
<head>
<meta charset="utf-8">
<title>Updating Graph</title>
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<button onclick=render(sp)>render sp graph</button>
<button onclick=render(cb)>render cb graph</button>
<div class="chart"></div>
</body>
</html>
提前致谢!
解决方案
推荐阅读
- c# - Asp 按钮导致点击时重定向和空白页面(在 ASCX 页面中)
- python - Jupyter 中自动完成 Pandas 列名的问题
- javascript - 页面中 < 之后的文本未显示
- javascript - 在对象数组中使用可选链接并进行破坏
- ajax - 使用 MVC 5 在本地主机上使用 IISExpress 的 Ajax POSTing 和 CORS 预检错误
- c# - PowerShell SDK + 远程
- node.js - NodeJS 中的全局变量
- azure - 如何使用 Docker-compose 文件在 Azure WebApp 上部署 Grafana?
- python - 填写第一个卡号字段后,我在页面上找不到任何 iframe。如何解决这个问题
- c# - Winforms:改变图表的属性