You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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事件里没有限制节点的位置在可视区域内,导致节点可以跑到屏幕外面。

解决方案

  1. 动态获取容器的实际宽高,设置力布局的size为容器尺寸;
  2. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 09:37:31