d3-force怎么使用?该算法是怎么实现的?

前言|force布局

笔者在fastVG产品图可视化布局中force布局采用D3-force-layout,因此介绍下该布局的一些算法逻辑和基础使用规则。

本文预期收获:

  1. 对于布局算法有更深入的了解。
  2. 在使用d3 & d3-force的时候 有调参规则的经验。
  3. 可结合其他渲染库进行独立使用。

1. 算法说明

D3-force-layout (力布局)模块利用velocity Verlet算法 实现了一个用于模拟粒子上物理力的数值积分器。当然内部的模拟做了简化, 假设每个step(的时间单位步长_Δt = 1 ,所有粒子质量 m = 1。因此,作用在粒子上的力 F 等效于在时间间隔 Δ t上的恒定加速度 a,可以通过简单的方式将其与粒子的速度相加来模拟,然后将其添加到粒子的位置。

通俗简单来说D3-force-layout基于一定的物理规则来定位可视化元素(nodes and edges)。

1. 算法过程

D3 的力布局使用基于物理的模拟器来定位视觉元素。

可以在元素之间设置force(力),例如:

  • **elements(所有元素)**都可以配置为与其他元素相互排斥

  • elements(所有元素)可以被吸引到center(物理中也称为重心,可理解为中心), 通俗来说就是所有节点的平均位置靠近。

  • linked elements (链接元素) 可以设置为fixed distance(固定距离)

  • 利用collision detection(碰撞检测) , elements(元素)可以配置为避免相互交叉.

通过配置, force-layout从而帮助我们以特定方式来进行定位元素。

本文主要讲如何使用D3-force-layout以及如何使用它来创建**网络可视化(network visualisations),集群(clusters)**展示。

请看下面这个force-layout的例子:假设我们有许多circle, 且这些circles分为3类(通过category字段区分) ,然后我们添加forces

  • circles 之间相互吸引(将circles聚集在一起)

  • 碰撞检测(避免circles重叠)

  • circles 被三个重心之一吸引(category 字段 :ABC

在codepen中尝试编辑上面示例

force-layout比其他布局算法需要更多的计算量,因为算法内部的实现是迭代式的。逐步达到最优效果。

算法结论/ 效果

force simulation

一般来说,设置力模拟有 4 个步骤:

  • 创建对象数组 (nodes and edges)

  • 调用forceSimulation,传入对象数组 (nodes)

  • 添加一个或多个force functions(力函数)(例如forceManyBody, forceCenter

  • 设置回调函数, each tick (每次迭代)后更新元素的位置。

看个简单的例子:

less 复制代码
let width = 300, height = 300  
let nodes = \[{}, {}, {}, {}, {}\]  
​  
let simulation = d3.forceSimulation(nodes)  
  .force('charge', d3.forceManyBody())  
  .force('center', d3.forceCenter(width / 2, height / 2))  
  .on('tick', ticked);

我们在这里创建了一个由 5 个对象组成的简单数组,并添加了两个力函数forceManyBodyforceCenter。(其中第一个使元素相互排斥,而第二个将元素吸引到中心点。)

每次模拟迭代时,ticked都会调用该函数。此函数将nodes数组连接到circle元素并更新它们的位置:

javascript 复制代码
function ticked() {  
  var u = d3.select('svg')  
	.selectAll('circle')  
	.data(nodes)  
	.join('circle')  
	.attr('r', 5)  
	.attr('cx', function(d) {  
	  return d.x  
	})  
	.attr('cy', function(d) {  
	  return d.y  
	});  
}

在codepen中尝试编辑上面示例

**force simulations(力模拟)的强大和灵活集中在force functions(力函数)**上,这些函数可以调整元素的位置和速度,以实现吸引、排斥和碰撞检测等多种效果。

D3 内置了很多有用的函数:

  • forceCenter(用于设置系统的重心)

  • forceManyBody(用于使元素相互吸引或排斥)

  • forceCollide(用于防止元素重叠)

  • forceXforceY(用于将元素吸引到给定点)

  • forceLink(用于在连接元素之间创建固定距离)

通过.force()将**force functions (力函数)**添加到模拟中,第一个参数是定义的 id,第二个参数是force functions(力函数)

less 复制代码
simulation.force('charge', d3.forceManyBody())

下面我们展开看一下内置的force functions(力函数)

forceCenter

forceCenter对于将元素作为一个整体围绕centering居中是有用的。如果不设置默认坐标是 [0, 0]。

可以直接设置位置[x,y]初始化:

scss 复制代码
d3.forceCenter(100, 100)

或使用配置功能.x().y()

scss 复制代码
d3.forceCenter().x(100).y(100)

然后使用以下方法将其添加到模拟中:

less 复制代码
simulation.force('center', d3.forceCenter(100, 100))

forceManyBody

forceManyBody使所有元素相互吸引或排斥。可以设置吸引或排斥的强度,.strength()其中正值导致元素相互吸引,而负值将导致元素相互排斥。默认值为-30

less 复制代码
simulation.force('charge', d3.forceManyBody().strength(-20))

在创建网络图时,通常配置元素相互排斥。但对于元素聚集在一起的需求,则需要配置元素的吸引(引力)。
在codepen中尝试编辑上面示例

forceCollide

forceCollide用于避免元素(此处是circle)重叠,并且可以将circle"聚集"在一起。

元素的半径r是通过将访问器函数.radius方法来传递给forceCollide'的,。此函数的第一个参数d是用来data join,可以从中得到半径r

例如:

javascript 复制代码
let numNodes = 100  
let nodes = d3.range(numNodes).map(function(d) {  
  return {radius: Math.random() \* 25}  
})  
​  
let simulation = d3.forceSimulation(nodes)  
  .force('charge', d3.forceManyBody().strength(5))  
  .force('center', d3.forceCenter(width / 2, height / 2))  
  .force('collision', d3.forceCollide().radius(function(d) {  
	return d.radius  
  }))

在codepen中尝试编辑上面示例

forceManyBody将所有节点聚集到一起,并将节点保持在容器的中心 ,forceCollide避免节点重叠。

forceX 和 forceY

forceX和forceY设置元素吸引到 指定的位置。我们可以对所有元素使用一个中心,也可以为每个元素的基础上添加。同时使用 .strength() 配置引力,进行配合。

例如,假设您有许多元素,每个元素都有一个category具有 value01的属性2。您可以添加一个forceX力函数基于元素的category分别将元素吸引到 x 坐标100,300或500的地方:

less 复制代码
let xCenter = \[100, 300, 500\];

let simulation = d3.forceSimulation(nodes)
  .force('charge', d3.forceManyBody().strength(5))
  .force('x', d3.forceX().x(function(d) {
	return xCenter\[d.category\];
  }))
  .force('collision', d3.forceCollide().radius(function(d) {
	return d.radius;
  }));

在codepen中尝试编辑上面示例
forceManyBody将所有节点聚集到一起,然后forceX将节点吸引到特定的 x 坐标。forceCollide避免(组织)节点相交。

如果我们的数据具有相关坐标信息,当然也可以同时使用forceXforceY去定位元素。

javascript 复制代码
...
.force('x', d3.forceX().x(function(d) {
	return d.x;
  }))
.force('y', d3.forceY().y(function(d) {
   return d.y;
}))
...

forceLink将链接的元素移动到一个固定的距离(distance) 。它需要links(一组链接)来指定将哪些元素链接在一起。每个链接对象指定一个source(源)元素和target(目标)元素,其中值是元素的标识id (如果没有id可以用数组的索引):

javascript 复制代码
let links = d3.range(nodes.length - 1).map(function(i) {
	return {
		source: Math.floor(Math.sqrt(i)),
		target: i + 1,
	};
});
let links = \[
  {source: 0, target: 1},
  ...
]

然后,使用.links()方法将links(链接数组)传递给forceLink函数:

less 复制代码
let simulation = d3.forceSimulation(nodes)
  .force('charge', d3.forceManyBody().strength(-100))
  .force('center', d3.forceCenter(width / 2, height / 2))
  .force('link', d3.forceLink().links(links));

在codepen中尝试编辑上面示例
forceManyBody将节点分开,forceCenter使节点与画布容器保持居中,forceLink保持链接节点之间的固定距离。

算法聚类group webgl渲染效果

less 复制代码
d3.forceSimulation(nodes)
    .force("charge", d3.forceManyBody())
    // defaults strength: Math.min(count(link.source), count(link.target));
    // default distance 30
    .force("link", d3.forceLink(layout_links))
	.force('x', d3.forceX().x(function(d) {  // 给定坐标进行节点聚类 group分组
      return groups.indexOf(d.group) * 1200;
    }))
    .force("y", d3.forceY().y(function(d){
      return Math.floor(groups.indexOf(d.group) / 3) * 100;
    }))
    .stop();

最后

本文只是针对一个库的使用介绍,无合适时机引申物理模型相关知识体系。下篇打算针对于d3-force源码:力模型(Force Model), 多种力类型的实现, 多体系统求解[Barnes-Hut 算法] 迭代/约束 事件处理等方面进行深入探讨/交流。

感谢您的阅读,有问题随时请联系沟通。

相关推荐
用户47949283569157 分钟前
Safari 中文输入法的诡异 Bug:为什么输入 @ 会变成 @@? ## 开头 做 @ 提及功能的时候,测试同学用 Safari 测出了个奇怪的问题
前端·javascript·浏览器
没有故事、有酒19 分钟前
Ajax介绍
前端·ajax·okhttp
Kuo-Teng19 分钟前
Leetcode438. 找到字符串中所有字母异位词
java·算法·leetcode
朝新_22 分钟前
【SpringMVC】详解用户登录前后端交互流程:AJAX 异步通信与 Session 机制实战
前端·笔记·spring·ajax·交互·javaee
裴嘉靖25 分钟前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw28242627 分钟前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
gihigo19981 小时前
MATLAB使用遗传算法解决车间资源分配动态调度问题
算法·matlab
墨染点香1 小时前
LeetCode 刷题【138. 随机链表的复制】
算法·leetcode·链表
冴羽1 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript