Spring Boot集成Neo4j电影示例中D3.js图形溢出及拖拽复位问题求助
解决D3力导向图的两个问题:超出屏幕与拖拽回弹
我来帮你搞定这两个困扰你的D3力导向图问题,咱们一步步拆解解决:
问题1:拖拽图形后松开鼠标回到原位置
问题根源
你用的force.drag()是D3力布局的默认拖拽行为,但默认情况下,力模拟在拖拽结束后还会继续运行tick事件,试图把节点拉回它计算的“最优”布局位置,所以才会出现回弹。
解决方案
自定义拖拽行为,在拖拽开始时暂停力模拟,并且可以固定节点位置,这样松开鼠标后节点就会停在拖拽后的位置:
修改drawGraph函数里的节点拖拽部分:
// 自定义拖拽行为,替代默认的force.drag() var drag = force.drag() .on("dragstart", function(d) { // 阻止事件冒泡,避免干扰其他交互 d3.event.sourceEvent.stopPropagation(); // 暂停力模拟,停止布局计算 force.stop(); // 标记节点为固定,防止后续力模拟改变位置 d.fixed = true; // 记录拖拽开始时的位置,避免节点跳动 d.x = d3.event.x; d.y = d3.event.y; }) .on("dragend", function(d) { // 如果你希望拖拽后节点还能参与布局,可以把下面的注释打开 // d.fixed = false; // force.start(); }); // 绑定自定义拖拽到节点上 var node = svg.selectAll(".node") .data(graph.nodes).enter() .append("circle") .attr("class", function (d) { return "node "+d.label }) .attr("r", 10) .call(drag);
问题2:图形超出屏幕范围
问题根源
你的力布局size用了固定的1000x1000,没有匹配容器的实际尺寸;而且tick事件里没有限制节点的位置在可视区域内,导致节点可以跑到屏幕外面。
解决方案
- 动态获取容器的实际宽高,设置力布局的
size为容器尺寸; - 在
tick事件里限制节点的坐标在容器范围内,防止超出。
修改drawGraph函数的开头和tick部分:
function drawGraph() { var query=$("#search").find("input[name=search]").val(); // 修正清空旧图形的代码,去掉多余的转义符 d3.selectAll("svg > *").remove(); // 动态获取容器#graph的实际宽高 var container = d3.select("#graph"); var containerWidth = container.node().clientWidth; var containerHeight = container.node().clientHeight; // 留一点边距,避免节点贴容器边缘 var margin = 20; var width = containerWidth - margin * 2; var height = containerHeight - margin * 2; var force = d3.layout.force() .charge(-200).linkDistance(30) .size([width, height]); // 使用容器实际尺寸,而不是固定值 var svg = d3.select("#graph").selectAll("svg"); d3.json("/graph?movieName="+encodeURIComponent(query), function(error, graph) { if (error) return; force.nodes(graph.nodes).links(graph.links).start(); var link = svg.selectAll(".link") .data(graph.links).enter() .append("line").attr("class", "link"); // 这里是刚才修改的拖拽部分,就不重复写了 // html title attribute node.append("title") .text(function (d) { return d.titleName; }) // 修改tick事件,限制节点位置在容器范围内 force.on("tick", function() { 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) { // 限制x坐标在margin到width+margin之间 return d.x = Math.max(margin, Math.min(width + margin, d.x)); }) .attr("cy", function(d) { // 限制y坐标在margin到height+margin之间 return d.y = Math.max(margin, Math.min(height + margin, d.y)); }); }); }); }
另外,你初始化SVG的代码也可以调整一下,让viewBox更适配容器:
$(document).ready(function(){ var margin = { top: 20, right: 20, bottom: 20, left: 20 }, container = d3.select("#graph"), containerWidth = container.node().clientWidth, containerHeight = container.node().clientHeight, width = containerWidth - margin.right - margin.left, height = containerHeight - margin.top - margin.bottom; var svg = container.append("svg") .attr("width", '100%') .attr("height", '100%') .attr('viewBox', `0 0 ${containerWidth} ${containerHeight}`) .attr('preserveAspectRatio','xMidYMid meet') // 让图形居中显示,且不超出容器 .append("g") .attr("transform", `translate(${margin.left}, ${margin.top})`) .attr("pointer-events", "all"); });
这样修改后,你的力导向图就不会超出屏幕,而且拖拽节点后也会保持在新位置啦!
内容的提问来源于stack exchange,提问作者compengr
相关产品推荐
相关产品推荐

