首页
/ d3 forceLink 链接力详解:用弹簧模型稳定力导向图布局(d3 v7)

d3 forceLink 链接力详解:用弹簧模型稳定力导向图布局(d3 v7)

2026-09-04 21:25:53作者:凌朦慧Richard

本文围绕 d3 官方文档 link.md 讲解 d3-force 模块中最核心的布局力——链接力(link force):它如何用弹簧模型把相连节点拉向目标距离,以及 forceLink 的每个配置方法(linksiddistancestrengthiterations)的完整参数语义、默认值与可变性(mutability)陷阱。读完本文,你将能在 d3(v7.9.0,依赖 d3-force ^3.0.0)中正确地为网络图、层级图与晶格结构配置链接力,并理解参数何时重新计算、何时不重新计算。

链接力是什么:一条有目标长度的弹簧

d3-force 模块基于 velocity Verlet 数值积分器模拟作用在节点(粒子)上的物理力,可用于可视化网络、层级结构以及解决碰撞(气泡图)等场景,模块总览见 d3-force.md。链接力(link force)是该模块中负责“结构”的力:

链接力按期望的链接距离(link distance)把相连节点拉近或推远,力的大小与两节点当前距离和目标距离的差值成正比,类似弹簧力。

也就是说,每条 link 都等价于一根弹簧:节点间距偏离 distance 时产生恢复力,偏离越大力越大。这决定了链接力本身不是“把图撑开”的力——撑开由 many-body 斥力 完成,链接力负责让“该连着”的节点维持在一个合理间距上。两者配合才能形成稳定的力导向布局。

forceLink(links):创建链接力

创建方式(默认参数):

const link = d3.forceLink(links).id((d) => d.id);
  • forceLink(links) 用指定的 links 数组和默认参数创建一个新链接力;
  • 若不提供 links,默认为空数组 []
  • 注意:该函数是“不纯”的(impure),它会修改(mutate)传入的 links 数组,详见下文 link.links 一节。

链接力最终通过 simulation.force(name, force) 挂到仿真器上,仿真器每步 tick 时以新的 alpha 调用它;完整机制见 simulation.force

const simulation = d3.forceSimulation(nodes)
    .force("charge", d3.forceManyBody())
    .force("link", d3.forceLink(links))
    .force("center", d3.forceCenter());

link.links(links):设置并解析 link 数组

如果提供了 links,则设置该力关联的链接数组,并为每条 link 重新计算 distancestrength 参数,返回该力;如果不提供,返回当前的 links 数组(默认空数组)。

每条 link 是如下形状的对象:

  • source — link 的源节点(见 simulation.nodes
  • target — link 的目标节点
  • index — 在 links 数组中的从零起始下标,由该方法自动赋值

为了方便,link 的 source/target 可以用数字或字符串标识符初始化(而不是对象引用),配合 link.id 使用。

可变性警告(原文档明确标注):

该函数是不纯的。链接力被初始化(或重新初始化,例如节点或链接发生变化时)时,它可能修改传入的 links。任何不是对象的 link.sourcelink.target 都会被替换为对应 node 的对象引用。

这意味着初始化后,你的原始 JSON 里的 "source": "Alice" 会变成指向节点对象本身——这也是为什么很多代码在 tick 回调里可以直接写 d.source.xd.target.y

另外一个重要约定:如果 links 数组本身被修改了(向仿真中增删链接),必须再次调用 link.links(新数组) 通知该力;力不会为指定数组做防御性拷贝。nodes 的修改同理,见 simulation.nodes

link.id(id):节点标识符访问器

如果提供了 id,把节点 id 访问器设置为指定函数并返回该力;否则返回当前的 id 访问器,默认为返回数字 node.index

function id(d) {
  return d.index;
}

默认的 id 访问器允许每条 link 的 sourcetargetnodes 数组的从零起始下标 表示。例如:

const nodes = [
  {"id": "Alice"},
  {"id": "Bob"},
  {"id": "Carol"}
];

const links = [
  {"source": 0, "target": 1}, // Alice → Bob
  {"source": 1, "target": 2} // Bob → Carol
];

再换一个返回字符串的 id 访问器:

function id(d) {
  return d.id;
}

此时就可以用命名的 source 和 target:

const nodes = [
  {"id": "Alice"},
  {"id": "Bob"},
  {"id": "Carol"}
];

const links = [
  {"source": "Alice", "target": "Bob"},
  {"source": "Bob", "target": "Carol"}
];

这一点在用 JSON 表示图时尤其有用,因为 JSON 不允许对象引用(这也是为什么网络图数据通常直接以这种 nodes + links 的扁平结构存储)。id 访问器在力初始化时(例如 nodeslinks 变化时)对每个节点被调用一次,参数为节点对象及其从零起始下标。

link.distance(distance):目标距离访问器

如果提供了 distance,把距离访问器设置为指定数字或函数,对每条 link 重新求值距离访问器,并返回该力;否则返回当前距离访问器,默认为:

function distance() {
  return 30;
}

距离访问器对每条 link 被调用,参数为 link 及其从零起始 index。求出的数字会被保存在内部——因此每条 link 的距离只在力被初始化、或调用该方法传入新 distance 时重新计算,而不是在每次施加力时重新计算。这是一个值得记住的性能与语义细节:写 distance(30) 之后,tick 过程中不会重复求值。

link.strength(strength):弹簧强度的默认公式

如果提供了 strength,把强度访问器设置为指定数字或函数,对每条 link 重新求值,并返回该力;否则返回当前强度访问器,默认为:

function strength(link) {
  return 1 / Math.min(count(link.source), count(link.target));
}

其中 count(node) 返回以该节点作为 source 或 target 的链接数。这个默认公式的动机是:自动降低连到高度数节点(hub)的链接强度,从而提升整体稳定性——枢纽节点连接的每条边都弱一些,避免它被周围大量弱约束拉扯得剧烈抖动。

强度访问器同样对每条 link 被调用一次(参数为 linkindex),结果保存在内部,只在力初始化或传入新 strength 时重新计算,不会在每次施加力时重复求值。

link.iterations(iterations):迭代次数与结构刚性

如果提供了 iterations,把每次施加时的迭代次数设置为指定数字并返回该力;否则返回当前迭代次数,默认为 1

增加迭代次数会大幅提高约束的刚性(rigidity),对晶格(lattice)等复杂结构非常有用,但同时也成倍增加每次施加力时的求值成本。这个取舍在文档仓库自带的可交互示例 ExampleLinkForce.vue 中体现得很直接——它构造了一个 20×20 的节点晶格,横向、纵向相邻节点之间共生成 760 条链接,然后配置:

simulation = d3.forceSimulation(nodes)
    .force("charge", d3.forceManyBody().strength(-40))
    .force("link", d3.forceLink(links).strength(1).distance(10).iterations(10))
    .force("x", d3.forceX())
    .force("y", d3.forceY())
    .on("tick", ticked);

可以看到这个示例同时动用了三个链接力参数:strength(1) 覆盖默认的按度数倒数公式、distance(10) 把目标边长压到 10px、iterations(10) 把单次施加的迭代提到 10 倍以“锁死”网格结构。这正是文档所说“迭代数提高刚性”的典型用法:默认 1 次迭代的链接力更像“软弹簧”,而 10 次迭代会让相邻节点距离非常接近设定值,即使把某个节点拖到很远的地方再松手,网格也能迅速弹回整齐的形态(示例中还配合了 d3.drag() + simulation.find() 实现拖拽,拖拽期间用 alphaTarget(0.3).restart() 重新加热仿真)。

与仿真器的协作方式

链接力不是独立运行的,它遵循 d3-force 的统一力协议(见 Custom forces 一节):

  1. 通过 simulation.force 绑定,或 simulation.nodes 改变节点时,力的 initialize(nodes) 被调用——id 访问器在此对每个节点求值,source/target 的字符串/数字标识符在此被替换为节点对象引用,distancestrength 的缓存值也在此计算;
  2. 仿真器每步 tick 时以当前 alpha 调用该力;链接力读取每条 link 两端节点的当前位置,按“距离偏差 × 强度 × alpha”的比例修改两端节点的速度。

因为 alpha 会随仿真冷却逐渐衰减(见 simulation.alpha),链接力施加的力也随之减弱,布局在大约 300 步(默认 alphaDecay 下)后趋于静止。这也是为什么交互拖拽时要重新设置 alphaTarget 来“加热”仿真。

实践要点小结

结合 link.md 与仓库示例,可以归纳出以下要点:

  • 数据是扁平 JSON 结构nodes 为对象数组,linkssource/target 用 id 或下标,通过 .id(d => d.id) 解析——这是从数据到图布局的标准路径;
  • 两个缓存distancestrength 求值结果都缓存在力内部,只在初始化或重新调用访问器方法时刷新;调整布局参数后,必要时需重新调用对应方法或重新 links()/nodes() 触发 re-initialize;
  • 不纯函数forceLink(links)link.links(...) 都会原地修改你传入的 links 数组(source/target 被替换为对象引用、写入 index),不要假设原始数据保持不变;
  • 默认 strength 自带防抖1 / Math.min(deg(source), deg(target)) 自动削弱 hub 节点上的边,普通网络图通常无需自定义;
  • iterations 是刚性旋钮:默认 1;需要晶格、分子结构等强几何约束时提高到 5~10,代价是求值成本线性增长(参见 ExampleLinkForce.vueiterations(10) 的用法);
  • 动态增删节点/链接:修改数组后必须重新调用 simulation.nodes / link.links(),力不做防御性拷贝。

d3 v7 中,d3.forceLinkd3-force ^3.0.0 提供,并通过 src/index.jsexport * from "d3-force" 统一导出,安装 d3 包后即可直接使用 d3.forceLink(...);本仓库中所有 d3-force 相关文档页面(simulationcentercollidemany-bodyposition)均可作为配套参考,围绕链接力构建完整的力导向布局。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384