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

如何在D3.js力导向模拟中实现节点间的吸引效果?

D3.js 实现选中节点间的自定义吸引力

要实现选中父节点与子节点后,子节点受父节点吸引且其余节点重新排列,核心是利用D3力导向模拟的自定义力函数,结合模拟状态重置来实现。以下是具体修改方案:

关键修改点

  • 存储选中节点的数据对象而非仅ID,方便直接获取节点坐标
  • 添加自定义吸引力,在模拟的每一步对选中的子节点施加朝向父节点的力
  • 选中节点后重置模拟的alpha值,强制触发重新布局

修改后的完整代码

// parent: red, children: blue
let svg, width = 800, height = 400, radius, nodes, x, y, simulation;
// 改为存储节点数据对象,而非ID
let parentNode = null, childNode = null;
let data = [
    {id: 0, size: 0.5 },
    {id: 1, size: 0.25 },
    {id: 2, size: 0.125 },
    {id: 3, size: 0.75 },
    {id: 4, size: 0.8 },
    {id: 5, size: 0.4 },
    {id: 6, size: 0.25 },
    {id: 7, size: 0.5 }
];

const tick = () => { 
    nodes.attr("cx", d_ => d_.x ).attr("cy", d_ => d_.y );
    
    // 自定义吸引力逻辑:仅当父节点和子节点都选中时生效
    if (parentNode && childNode) {
        // 计算子节点朝向父节点的向量
        const dx = parentNode.x - childNode.x;
        const dy = parentNode.y - childNode.y;
        // 施加吸引力(可调整strength值控制力度)
        const attractionStrength = 0.05;
        childNode.vx += dx * attractionStrength;
        childNode.vy += dy * attractionStrength;
    }
}

svg = d3.select("body").append("svg").attr("viewBox", `0 0 ${width} ${height}`);
let background = svg.append("rect").attr("width", width).attr("height", height).attr("fill", "#444444");

radius = d3.scaleLinear().domain([0.0, 1.0]).range([32, 64]);
x = d3.scaleLinear().domain([0, data.length]).range([64, width - 64]);
y = d3.scaleLinear().domain([0, data.length]).range([64, height - 64]);

simulation = d3.forceSimulation()
    .force("x", d3.forceX(d_ => x(d_.id)).strength(0.1))
    .force("y", d3.forceY(height / 2).strength(0.05))
    .force("collide", d3.forceCollide().radius(d => radius(Number(d.size)) + 10))
    .alpha(1).restart();

nodes = svg.selectAll(null)
    .data(data)
    .enter()
    .append("circle")
    .attr("r", d_ => radius(d_.size))
    .attr("fill", "#FFFFFF")
    .on("mouseover", (event_, d_) => { 
        if (d_ !== parentNode && d_ !== childNode) { 
            d3.select(event_.currentTarget).attr("fill", "#888888"); 
        } 
    })
    .on("mouseout", (event_, d_) => { 
        if (d_ !== parentNode && d_ !== childNode) { 
            d3.select(event_.currentTarget).attr("fill", "#FFFFFF"); 
        } 
    })
    .on("click", (event_, d_) => {
        if (parentNode === null) { 
            // 选中父节点
            parentNode = d_; 
            d3.select(event_.currentTarget).attr("fill", "#FF0000"); 
        } else if (d_ !== parentNode) { 
            // 选中子节点
            childNode = d_; 
            d3.select(event_.currentTarget).attr("fill", "#0000FF");
            // 重置模拟alpha,强制重新布局
            simulation.alpha(0.8).restart();
        }
    });

simulation.nodes(data).on("tick", tick);
<script src="https://d3js.org/d3.v7.min.js"></script>

代码解释

  1. 选中节点存储:将原有的ID存储改为直接存储节点数据对象,避免额外的节点查找开销,同时简化坐标获取逻辑。
  2. 自定义吸引力:在tick回调中,当父节点和子节点都被选中时,计算两者间的位移向量,给子节点的速度属性(vx/vy)添加分量,实现子节点朝向父节点的移动效果,可通过调整attractionStrength控制吸引力大小。
  3. 模拟重置:选中子节点后调用simulation.alpha(0.8).restart(),提高模拟的活跃度,让所有节点重新进入动态布局状态,实现其余节点的重新排列。
  4. 交互逻辑优化:鼠标悬停/移出的判断条件改为直接对比节点对象,避免ID重复带来的潜在问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 20:10:01