D3.js力导向图绘制详解:五种核心力模型与仿真控制

作者:灵感回路

时间:2026-10-25

浏览:522

本文详解D3.js力导向图的绘制原理与实现。涵盖向心力、碰撞力、连接力、电荷力及径向力五种核心模型,解析alpha衰减、速度衰减等关键参数对仿真稳定性的影响,并演示如何通过监听tick事件实现节点动态更新与拖拽交互。

文章目录

力导向图是一种基于物理模拟的绘图算法,其核心采用了velocity Verlet数值积分器来模拟粒子的运动状态。仿真逻辑设定任意单位时间步长Δt为1,且所有粒子的质量常量m均为1。在此假设下,作用于每个粒子的合力F等同于在单位时间Δt内产生的恒定加速度a。通过为每个粒子累加速度并计算其位置,即可实现仿真的动态更新。

在二维或三维空间中配置节点,节点之间通过连线相连,这些连线长度尽量保持相等且避免交叉。节点与连线均受到力的作用,力的大小由节点与连线的相对位置决定。系统根据计算出的力来推导节点和连线的运动轨迹,并逐步降低系统能量,直至达到能量极低的稳定状态。

力

力

D3 Force是一种基于物理运动原理的绘图算法。初始阶段为所有节点分配任意初始配置,随后根据各节点的属性驱动其运动,这种驱动机制即为节点间的力。

力主要分为斥力与引力两类。节点间的相互作用表现为斥力,而整体图形存在引力约束,类似于地心引力使人无法无限跳跃并最终落回地面。在多种力的交互下,节点发生碰撞并聚拢,逐渐收敛至稳定位置。可通过alpha属性控制整个过程的速度,并设置摩擦力velocityDecay以调整运动速度。

五种力

D3提供了五种力模型,具体实现可参考相关Demo。

向心力

  • d3.forceCenter([x, y]):用于创建一个中心作用力。
  • center.x:用于设置中心作用力的x坐标。
  • center.y:用于设置中心作用力的y坐标。

向心力可将所有节点统一向指定位置<x,y>移动。该力强制修改节点位置但不改变速度,以避免节点在目标位置附近产生抖动。此力有助于保持所有节点位于视口中心。

力

碰撞力

节点间的相互排斥力,用于防止节点重合。可通过strength参数设置斥力的强弱。

  • d3.forceCollide:创建一个圆形区域的碰撞检测力模型。
  • collide.radius:设置碰撞半径。
  • collide.strength:设置碰撞检测力模型的强度。
  • collide.iterations:设置迭代次数。

力

连接力(弹簧力)

使用d3.forceLink将两个节点添加连线后,可设置连接力。该力根据两节点间的距离拉近或推远节点,力的强弱与距离成正比,类似弹簧,故又称弹簧力。

  • d3.forceLink:创建一个link(弹簧)作用力。
  • link.links:设置弹簧作用力的边。
  • link.id:设置边元素中节点的查找方式,支持索引或id字符串。
  • link.distance:设置link的距离。
  • link.strength:设置link的强度。
  • link.iterations:设置迭代次数。

力

电荷力

模拟所有节点间的相互作用力。若值为正,节点相互吸引;若为负,则相互排斥。该力模拟电荷吸引力,其强弱与节点间距离相关。

  • d3.forceManyBody:创建一个电荷作用力模型。
  • manyBody.strength:设置电荷力模型的强度。
  • manyBody.theta:设置Barnes–Hut算法的精度。
  • manyBody.distanceMin:限制节点之间的最小距离。
  • manyBody.distanceMax:限制节点之间的最大距离。

力

径向力

设定一个圆,使所有节点受到指向圆心的力,从而聚集到圆周上。

  • d3.forceRadial:创建一个环形布局的作用力。
  • radial.strength:设置力强度。
  • radial.radius:设置目标半径。
  • radial.x:设置环形作用力的目标中心x坐标。
  • radial.y:设置环形作用力的目标中心y坐标。

力

这五种力可以叠加使用。

力向导图

创建力向导图时,需先创建新的力学模型d3.forceSimulation,并指定节点nodes。

1.d3.forceSimulation:创建一个新的力学仿真。

2.simulation.restart:重新启动仿真的定时器。

3.simulation.stop:停止仿真的定时器。

4.simulation.tick:进行一步仿真模拟。

5.simulation.nodes:设置仿真的节点。

每个node必须为对象类型,仿真系统将自动添加以下属性:

  • index:节点在nodes数组中的索引。
  • x:节点当前的x坐标。
  • y:节点当前的y坐标。
  • vx:节点当前的x方向速度。
  • vy:节点当前的y方向速度。

5.simulation.alpha:设置当前的alpha值。

6.simulation.alphaMin:设置最小alpha阈值。

7.simulation.alphaDecay:设置alpha衰减率。

若设置为0,仿真将永远不会停止。

8.simulation.alphaTarget:设置目标alpha值。

9.simulation.velocityDecay:设置速度衰减率。

10.simulation.force:添加或移除一个力模型。

11.simulation.find:根据指定位置找出最近的节点。

12.simulation.on:添加或移除事件监听器。

  • tick:仿真内部定时器每次tick之后触发。
  • end:当alpha小于alphaMin时,仿真内部定时器停止。
const simulation = d3.forceSimulation(nodes) 
// 连接力
.force('link', d3.forceLink()) 
// 在 y轴方向上施加一个力 
.force('y', d3.forceY().strength(0.025)) 
// 电荷力 
.force('charge', d3.forceManyBody()) 
// 碰撞力
.force('collision', d3.forceCollide().radius(d => 4)) 
//  向心力
.force('center', d3.forceCenter(width / 2, height / 2))

接下来绘制文章开头所示的关系图,具体实现可参考Demo。

创建模拟数据

const nodes = [
    {name: "张三"}
    ...
]

const links = [
    { source: 0, target: 1, relation: "关系1"}
    ...
]

创建力模型

let simulation = d3.forceSimulation(nodes)
    .force("link", d3.forceLink(links).distance(100)) // 连接力

绘制节点和连线

// 画线
function drawLine() {
    let lines = svg.append("g")
        .selectAll(".force-line")
        .data(links)
        .enter()
        .append("line")
        .attr("class", "line")
        .attr("stroke", "#999")
        .attr("stroke-width", "1px");
    return lines;
}
let lines = drawLine();

// 画节点节点盒子

function drawCircle() {
    let nodeGroups = svg.append("g")
        .attr("class", "nodes-box")
        .selectAll(".force-node")
        .data(nodes)
        .enter()
        .append("g")
        .attr("class", "force-circle")

    nodeGroups.append("circle")
        .attr("class", "force-circle")
        .attr("r", 20)
        .style("fill",(d, i) => color(i));

    nodeGroups.append("text")
        .attr("class", "force-text")
        .attr("dy", ".33em")
        .attr("font-size", "12px")
        .attr("text-anchor", "middle")
        .style("fill", "#eee")
        .text(d => d.name);
    return nodeGroups;
}
let nodesCircle = drawCircle();

此时仅能在画布左上角原点位置看到circle图形。由于力学模型动态计算节点和连线位置,需动态更新其位置<x, y>。为此,需监听tick事件。

力

监听 tick

let simulation = d3.forceSimulation(nodes)
    .force("link", d3.forceLink(links).distance(100));
    .on("tick", ticked);
    
function ticked() 
    lines
        .attr("x1", (d) => d.source.x)
        .attr("y1", (d) => d.source.y)
        .attr("x2", (d) => d.target.x)
        .attr("y2", (d) => d.target.y);

    // 这里就不适合 去改变circle的圆心位置了,因为有文字存在,改变整个circleGroup的transform
    nodesCircle.attr("transform", function (d) {
        // d.fx=d.x;d.fy=d.y; 固定位置
        return "translate(" + d.x + ", " + d.y + ")";
    });
}

力

可添加向心力,使整个图形显示在画布中心。

添加向心力

let simulation = d3.forceSimulation(nodes)
    .force("center", d3.forceCenter(width / 2, height / 2)) // 用指定的x坐标和y坐标创建一个居中力
    .force("link", d3.forceLink(links).distance(100)) //
    .on("tick", ticked);

力

此时发现节点出现重合现象,可为节点添加碰撞力使其分离。

添加碰撞力

let simulation = d3.forceSimulation(nodes)
    .force("charge", d3.forceManyBody().strength(-200)) // 电荷力 相互之间的作用力
    .force("center", d3.forceCenter(width / 2, height / 2)) // 用指定的x坐标和y坐标创建一个居中力
    .force("link", d3.forceLink(links).distance(100)) //
    .on("tick", ticked);

力

添加拖拽效果

d3.drag的详细用法将在后续章节介绍,本章暂不深入。

let nodeGroups = svg.append("g")
    ...
    .call(
        d3.drag().on("start", started).on("drag", dragged).on("end", ended)
    );
// 拖拽
function started(event) {
    if (!event.active) simulation.alphaTarget(0.3).restart();
    event.subject.fx = event.subject.x;
    event.subject.fy = event.subject.y;
}

function dragged(event) {
    event.subject.fx = event.x;
    event.subject.fy = event.y;
}

function ended(event) {
    if (!event.active) simulation.alphaTarget(0);
    event.subject.fx = null;
    event.subject.fy = null;
}

力

整个拖拽过程表现如下:

  • 连接力:拖拽任意节点,其余节点会同向运动。
  • 碰撞力:在拖拽过程中,各节点之间不会重合。
  • 向心力:拖拽无法到达任意位置,受向心力影响,节点最终仍会向中心靠拢。

以上为D3.js实现力向导图的绘制教程详解,更多关于D3.js力向导图的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章