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

D3.js散点图点重叠问题:新手如何实现点并排显示?

解决D3.js散点图重合点的力碰撞生效问题

嘿,我明白你遇到的困扰了——作为D3新手,想用力碰撞来分开重叠的散点,但配置后没效果对吧?咱们来拆解一下问题所在,然后一步步修复:

核心问题:你没用到力模拟计算后的坐标

你的代码里,tickActions函数始终用原始的xValue和yValue(也就是基于比例尺的固定位置)来设置圆点的位置,完全忽略了力模拟为每个节点计算出的x和y属性。这就导致力模拟跑了,但圆点根本没跟着它的计算结果移动!

另外,你当前用的forceX和forceY是把所有点往画布中间拉,这和散点图需要锚定在原始数据坐标的需求冲突,这也是效果不对的原因之一。

修复步骤

1. 给数据点添加固定锚点

在初始化力模拟前,给每个数据对象设置fx和fy属性,这两个属性是D3力模拟的固定位置锚点,会让节点保持在原始散点位置附近,同时允许碰撞力调整位置:

// 在初始化force之前添加这段
data.forEach(d => {
  d.fx = xValue(d); // 原始的x轴比例尺计算值
  d.fy = yValue(d); // 原始的y轴比例尺计算值
});

2. 调整力模拟配置

去掉forceX和forceY,只保留碰撞力,因为我们已经用fx/fy锚定了原始位置:

var force = d3.forceSimulation(data)
  .velocityDecay(0.2)
  .force("collide", d3.forceCollide().radius(5.5).iterations(5)) // 半径和你设置的circle r一致
  .on("tick", tickActions);

3. 修改tick函数,用力模拟计算后的坐标

更新tickActions,让圆点使用力模拟计算出的x和y属性:

function tickActions() {
  svg.selectAll("circle")
    .attr("cx", d => d.x)
    .attr("cy", d => d.y);
}

4. 修正数据选择器的小问题

你绑定数据时用了selectAll("dot"),但D3中没有<dot>元素,改成selectAll("circle")更规范:

maintenanceTaskDot = svg.selectAll("circle") // 这里改成circle
  .data(data)
  .enter()
  .append("circle")
  // ... 其余属性保持不变

完整修改后的代码片段

constructScatterPoint(){ 
  svg = d3.select("#graphDiv").append("svg") 
    .attr("width", width + margin.left + margin.right) 
    .attr("height", height + margin.top + margin.bottom) 
    .append("g") 
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 

  svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(xScale)).append("text") 
    .attr("class", "label") 
    .attr("x", width) 
    .attr("y", -6) 
    .style("text-anchor", "end") 
    .text("Periode") 
    .attr("stroke","black"); 

  svg.append("g").call(d3.axisLeft(yScale)).append("text") 
    .attr("class", "label") 
    .attr("x", 0) 
    .attr("y", -6) 
    .text("Durée") 
    .attr("stroke","black"); 

  // 新增:给数据添加固定锚点
  data.forEach(d => {
    d.fx = xValue(d);
    d.fy = yValue(d);
  });

  // 修改力模拟配置,去掉forceX/forceY
  var force = d3.forceSimulation(data) 
    .velocityDecay(0.2) 
    .force("collide", d3.forceCollide().radius(5.5).iterations(5)) 
    .on("tick", tickActions); 

  // 修改选择器为circle
  maintenanceTaskDot = svg.selectAll("circle").data(data).enter().append("circle") 
    .attr("stroke", "var(--greydark)") 
    .attr("stroke-width", 1.5) 
    .attr("r", 5) 
    .attr("fill", function(d){ 
      if(d.maintenance_work=="A") {return "var(--purple)"}
      else if(d.maintenance_work=="H"){return "var(--pink)"}
      return "var(--cyan)"
    } ) 
    .style("opacity",0.5) 
    .on('mouseover', showTaskCode.show) 
    .on('mouseout',showTaskCode.hide) 
    .on("click", clickOnCircle) 

  // 修改tick函数,使用力模拟计算的坐标
  function tickActions() { 
    svg.selectAll("circle") 
      .attr("cx", d => d.x) 
      .attr("cy", d => d.y) 
  } 
}

原理说明

  • fx和fy会让力模拟的节点“锚定”在原始的散点位置,不会被力拉走,只会在碰撞时稍微偏移来避免重叠。
  • forceCollide会检测节点间的距离,当小于设置的半径时推开彼此,这样重叠的点就会在锚点附近散开,同时保持散点图的原始分布逻辑。

内容的提问来源于stack exchange,提问作者FlopJack

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 20:17:31