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
相关产品推荐
相关产品推荐

