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

如何在D3.js力导向图中增强非关联节点间的作用力?

D3.js力导向图:实现非关联节点的自定义作用力

问题描述

我使用d3.js构建了一个力导向图,部分代码如下:

simulation
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("nodes", d3.forceManyBody())
    .force(
        "links",
        d3
            .forceLink(links)
            .id(d => d.id)
            .distance(d => 5 * (d.source.size + d.target.size))

    )
    .on("tick", ticked);

其中这一行定义了关联节点间的作用力:

.distance(d => 5 * (d.source.size + d.target.size))

但我希望实现非关联节点间的作用力(理想情况下该作用力随自由度增加而增强),请问该如何实现?

实现方案

方法1:基于forceManyBody扩展自定义强度

forceManyBody支持传入自定义强度函数,我们可以先预处理关联关系,再针对非关联节点设置随自由度变化的作用力:

  1. 先构建关联关系的查询Map,方便快速判断节点是否关联:
// 预处理所有节点的关联关系
const linkedNodesMap = new Map();
links.forEach(link => {
    // 为源节点添加目标节点关联
    if (!linkedNodesMap.has(link.source.id)) linkedNodesMap.set(link.source.id, new Set());
    linkedNodesMap.get(link.source.id).add(link.target.id);
    // 为目标节点添加源节点关联
    if (!linkedNodesMap.has(link.target.id)) linkedNodesMap.set(link.target.id, new Set());
    linkedNodesMap.get(link.target.id).add(link.source.id);
});
  1. 修改forceManyBody的强度设置:
simulation
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("nodes", d3.forceManyBody()
        .strength((node, _, nodes) => {
            // 计算当前节点的自由度:总节点数 - 关联节点数 - 1(排除自身)
            const linkedCount = linkedNodesMap.get(node.id)?.size || 0;
            const freedomDegree = nodes.length - linkedCount - 1;
            
            // 仅对非关联节点施加作用力,强度随自由度线性增强
            return (otherNode) => {
                const isUnlinked = !linkedNodesMap.get(node.id)?.has(otherNode.id);
                return isUnlinked ? freedomDegree * 0.1 : 0;
                // 0.1为强度系数,可根据画布大小、节点数量自行调整
            };
        })
    )
    .force("links", d3.forceLink(links)
        .id(d => d.id)
        .distance(d => 5 * (d.source.size + d.target.size))
    )
    .on("tick", ticked);

方法2:完全自定义力模型

如果forceManyBody的扩展不够灵活,可以直接用d3.force()创建自定义力,更精细控制作用力逻辑:

const customUnlinkedForce = d3.force()
    .strength(0.1) // 基础强度系数
    .initialize(nodes => {
        // 初始化时构建关联关系Map
        const linkedMap = new Map();
        links.forEach(link => {
            if (!linkedMap.has(link.source.id)) linkedMap.set(link.source.id, new Set());
            if (!linkedMap.has(link.target.id)) linkedMap.set(link.target.id, new Set());
            linkedMap.get(link.source.id).add(link.target.id);
            linkedMap.get(link.target.id).add(link.source.id);
        });
        this.linkedMap = linkedMap;
    })
    .force((alpha, nodes) => {
        // 遍历所有节点对,处理非关联节点的作用力
        for (let i = 0; i < nodes.length; i++) {
            const nodeA = nodes[i];
            const linkedIds = this.linkedMap.get(nodeA.id) || new Set();
            const freedomDegree = nodes.length - linkedIds.size - 1;
            
            for (let j = i + 1; j < nodes.length; j++) {
                const nodeB = nodes[j];
                if (!linkedIds.has(nodeB.id)) {
                    // 计算节点间距离
                    const dx = nodeB.x - nodeA.x;
                    const dy = nodeB.y - nodeA.y;
                    const distance = Math.sqrt(dx * dx + dy * dy);
                    if (distance === 0) continue;
                    
                    // 计算作用力大小,结合自由度与模拟alpha系数
                    const force = freedomDegree * alpha * this.strength() / distance;
                    
                    // 施加斥力(如需引力,将force取反即可)
                    nodeA.vx += force * dx;
                    nodeA.vy += force * dy;
                    nodeB.vx -= force * dx;
                    nodeB.vy -= force * dy;
                }
            }
        }
    });

// 将自定义力加入模拟
simulation
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("unlinked", customUnlinkedForce)
    .force("links", d3.forceLink(links)
        .id(d => d.id)
        .distance(d => 5 * (d.source.size + d.target.size))
    )
    .on("tick", ticked);

注意事项

  • 自由度的计算逻辑可根据业务需求调整,比如结合节点的其他属性(权重、类型等)
  • 强度系数需要根据实际画布尺寸、节点数量调试,避免布局过度分散或拥挤
  • 若需要引力而非斥力,只需将作用力的方向取反即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 18:30:54