数据可视化交互与力模拟技术全解析
1. 触摸交互
在数据可视化中,触摸交互是提升用户体验的重要手段。我们可以通过
touchstart
和
touchend
事件来实现触摸交互,同时也能使用相同的模式处理浏览器支持的其他触摸事件。W3C 推荐的触摸事件类型如下:
-
touchstart
:当用户在触摸表面放置触摸点时触发。
-
touchend
:当用户从触摸表面移除触摸点时触发。
-
touchmove
:当用户在触摸表面移动触摸点时触发。
-
touchcancel
:当触摸点以特定于实现的方式被中断时触发。
2. 缩放与平移行为
缩放和平移是数据可视化中常用且实用的技术,尤其适用于基于 SVG 的可视化,因为矢量图形不会像位图那样出现像素化问题。在处理大型数据集时,缩放功能特别有用,当无法一次性可视化整个数据集时,就需要采用缩放和深入挖掘的方法。
2.1 准备工作
在浏览器中打开以下文件的本地副本:
https://github.com/NickQiZhu/d3-cookbook/blob/master/src/chapter10/zoom.html
2.2 代码实现
<script type="text/javascript">
var width = 960, height = 500, r = 50;
var data = [
[width / 2 - r, height / 2 - r],
[width / 2 - r, height / 2 + r],
[width / 2 + r, height / 2 - r],
[width / 2 + r, height / 2 + r]
];
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(
d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoom)
)
.append("g");
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", r)
.attr("transform", function (d) {
return "translate(" + d + ")";
});
function zoom() {
svg.attr("transform", "translate("
+ d3.event.translate
+ ")scale(" + d3.event.scale + ")");
}
</script>
2.3 工作原理
使用 D3 实现缩放和平移效果所需的代码量很少。大部分工作由 D3 库完成,我们只需定义缩放行为。具体步骤如下:
1. 在 SVG 容器上定义缩放行为:
var svg = d3.select("body").append("svg")
.attr("style", "1px solid black")
.attr("width", width)
.attr("height", height)
.call(
d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoom)
)
.append("g");
d3.behavior.zoom
会自动创建事件监听器,处理 SVG 容器上的低级缩放和平移手势,并将其转换为高级的 D3 缩放事件。默认的事件监听器支持鼠标和触摸事件。
scaleExtent
定义了允许的缩放范围,这里设置为 1 到 10 倍。
2. 定义缩放事件处理函数:
function zoom() {
svg.attr("transform", "translate("
+ d3.event.translate
+ ")scale(" + d3.event.scale + ")");
}
在
zoom
函数中,我们将实际的缩放和平移操作委托给 SVG 变换。D3 缩放事件已经计算出了必要的平移和缩放值,我们只需将它们嵌入到 SVG 的
transform
属性中。缩放事件包含以下属性:
-
scale
:表示当前缩放比例的数字。
-
translate
:表示当前平移向量的二元数组。
缩放函数的作用是将通用的缩放和平移事件转换为 SVG 特定的变换,因为 D3 缩放行为是作为通用的缩放行为支持机制设计的,并非专门为 SVG 设计。此外,缩放函数还可以执行其他任务,例如在用户进行缩放操作时加载额外的数据,实现深入挖掘功能。
3. 拖动行为
拖动是交互式可视化中另一种常见的行为,可用于图形重新排列或用户输入。
3.1 准备工作
在浏览器中打开以下文件的本地副本:
https://github.com/NickQiZhu/d3-cookbook/blob/master/src/chapter10/drag.html
3.2 代码实现
<script type="text/javascript">
var width = 960, height = 500, r = 50;
var data = [
[width / 2 - r, height / 2 - r],
[width / 2 - r, height / 2 + r],
[width / 2 + r, height / 2 - r],
[width / 2 + r, height / 2 + r]
];
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var drag = d3.behavior.drag()
.on("drag", move);
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", r)
.attr("transform", function (d) {
return "translate(" + d + ")";
})
.call(drag);
function move(d) {
var x = d3.event.x,
y = d3.event.y;
if(inBoundaries(x, y))
d3.select(this)
.attr("transform", function (d) {
return "translate(" + x + ", " + y + ")";
});
}
function inBoundaries(x, y){
return (x >= (0 + r) && x <= (width - r))
&& (y >= (0 + r) && y <= (height - r));
}
</script>
3.3 工作原理
D3 的拖动支持与缩放支持模式类似。主要的拖动功能由
d3.behavior.drag
函数提供,它会自动创建适当的低级事件监听器,处理给定元素上的拖动手势,并将低级事件转换为高级的 D3 拖动事件,支持鼠标和触摸事件。
var drag = d3.behavior.drag()
.on("drag", move);
在
move
函数中,我们根据拖动事件提供的信息,使用 SVG 变换将被拖动的元素移动到合适的位置。同时,我们还检查元素是否在 SVG 边界内,以防止元素被拖出 SVG 区域。
function move(d) {
var x = d3.event.x,
y = d3.event.y;
if(inBoundaries(x, y))
d3.select(this)
.attr("transform", function (d) {
return "translate(" + x + ", " + y + ")";
});
}
function inBoundaries(x, y){
return (x >= (0 + r) && x <= (width - r))
&& (y >= (0 + r) && y <= (height - r));
}
D3 拖动行为支持的事件类型如下:
-
dragstart
:拖动手势开始时触发。
-
drag
:元素被拖动时触发,
d3.event
包含
x
和
y
属性,表示元素当前的绝对拖动坐标,还包含
dx
和
dy
属性,表示元素相对于手势开始时位置的坐标。
-
dragend
:拖动手势结束时触发。
4. 力模拟技术
力模拟是 D3 中非常有趣的一个方面,它可以为可视化添加令人惊叹的效果。D3 的力模拟支持是作为一种额外的布局实现的,最初是为了实现力导向图而创建的。它使用基于标准 Verlet 积分的粒子运动模拟,并支持简单的约束。
4.1 重力与电荷
在这个示例中,我们将介绍两种基本力:重力和电荷。力布局的设计目标之一是在粒子级别上大致模拟牛顿运动方程,电荷是这种模拟的一个主要特征。此外,力布局还实现了伪重力,即一种弱几何约束,可防止可视化元素逃离 SVG 画布。
4.1.1 准备工作
在浏览器中打开以下文件的本地副本:
https://github.com/NickQiZhu/d3-cookbook/blob/master/src/chapter11/gravity-and-charge.html
4.1.2 代码实现
<script type="text/javascript">
var w = 1280, h = 800,
force = d3.layout.force()
.size([w ,h])
.gravity(0)
.charge(0)
.friction(0.7);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
force.on("tick", function () {
svg.selectAll("circle")
.attr("cx", function (d) {return d.x;})
.attr("cy", function (d) {return d.y;});
});
svg.on("mousemove", function () {
var point = d3.mouse(this),
node = {x: point[0], y: point[1]};
svg.append("circle")
.data([node])
.attr("class", "node")
.attr("cx", function (d) {return d.x;})
.attr("cy", function (d) {return d.y;})
.attr("r", 1e-6)
.transition()
.attr("r", 4.5)
.transition()
.delay(7000)
.attr("r", 1e-6)
.each("end", function () {
force.nodes().shift();
})
.remove();
force.nodes().push(node);
force.start();
});
function changeForce(charge, gravity) {
force.charge(charge).gravity(gravity);
}
</script>
<div class="control-group">
<button onclick="changeForce(0, 0)">
No Force
</button>
<button onclick="changeForce(-60, 0)">
Mutual Repulsion
</button>
<button onclick="changeForce(60, 0)">
Mutual Attraction
</button>
<button onclick="changeForce(0, 0.02)">
Gravity
</button>
<button onclick="changeForce(-30, 0.1)">
Gravity with Repulsion
</button>
</div>
4.1.3 工作原理
在深入研究代码之前,我们先了解一下重力、电荷和摩擦力的概念:
-
电荷
:用于模拟粒子之间的相互 n 体作用力。负值会导致节点相互排斥,正值会导致节点相互吸引。默认电荷值为 -30,电荷值也可以是一个函数,在力模拟开始时为每个节点进行评估。
-
重力
:力布局中的重力模拟并非模拟物理重力,而是一种弱几何约束,类似于从布局中心连接到每个节点的虚拟弹簧。默认重力强度为 0.1,节点离中心越远,重力强度越强,在布局中心附近重力强度几乎为零,因此重力最终会克服排斥电荷,防止节点逃离布局。
-
摩擦力
:D3 力布局中的摩擦力并非标准的物理摩擦系数,而是实现为速度衰减。在模拟的每个时间步,粒子的速度会按指定的摩擦力进行缩放。值为 1 表示无摩擦环境,值为 0 会使所有粒子立即失去速度而冻结。不建议使用超出 [0, 1] 范围的值,因为可能会使布局不稳定。
下面我们来看不同力设置下的效果:
-
零力布局
:
var w = 1280, h = 800,
force = d3.layout.force()
.size([w ,h])
.gravity(0)
.charge(0)
.friction(0.7);
在这种设置下,当用户移动鼠标时,会在 SVG 上创建节点。节点对象的坐标设置为当前鼠标位置,每个创建的节点都需要添加到布局的节点数组中,并在节点的可视化表示被移除时从数组中移除。调用
start
函数启动力模拟,此时布局允许我们通过鼠标移动放置一系列节点。
-
相互排斥
:
function changeForce(charge, gravity) {
force.charge(charge).gravity(gravity);
}
changeForce(-60, 0);
将电荷设置为负值,同时保持重力为零,会产生相互排斥的力场。为了将力布局中操作的数据与图形元素连接起来,我们需要注册一个
tick
事件监听器:
force.on("tick", function () {
svg.selectAll("circle")
.attr("cx", function (d) {return d.x;})
.attr("cy", function (d) {return d.y;});
});
在每个时间步,根据力布局的计算更新所有圆形元素的位置。
-
相互吸引
:
changeForce(60, 0);
将电荷设置为正值会使粒子之间产生相互吸引的效果。
-
重力
:
changeForce(0, 0.02);
开启重力并关闭电荷,会产生类似于相互吸引的效果,但可以注意到随着鼠标远离中心,重力拉力呈线性缩放。
-
重力与排斥结合
:
changeForce(-30, 0.1);
同时开启重力和相互排斥,会使所有粒子达到一种力的平衡,既不会逃离布局,也不会相互碰撞。
力布局支持的事件类型如下:
-
start
:模拟开始时触发。
-
tick
:模拟的每个时间步触发。
-
end
:模拟结束时触发。
综上所述,通过触摸交互、缩放平移、拖动以及力模拟等技术,我们可以为数据可视化添加丰富的交互性和动态效果,提升用户体验和数据展示的效果。这些技术不仅适用于数据可视化领域,还在其他许多领域有实际应用。
数据可视化交互与力模拟技术全解析
5. 力模拟的应用拓展
力模拟技术在数据可视化中有着广泛的应用,除了前面提到的基本力的设置和效果展示,还可以通过力模拟来实现更复杂的可视化效果,例如力导向图。力导向图是一种常用的可视化方式,用于展示节点之间的关系,通过节点之间的引力和斥力来布局节点,使得关系紧密的节点靠近,关系疏远的节点远离。
5.1 构建力导向图的基本步骤
- 定义节点和链接数据 :首先需要定义节点和链接的数据结构,节点表示可视化中的元素,链接表示节点之间的关系。
- 创建力布局 :使用 D3 的力布局函数创建力布局,并设置相关参数,如重力、电荷、摩擦力等。
- 绑定数据到图形元素 :将节点和链接的数据绑定到 SVG 图形元素上,如圆形表示节点,线条表示链接。
-
启动力模拟
:调用力布局的
start方法启动力模拟,在模拟过程中,根据节点的位置更新图形元素的位置。
5.2 示例代码
// 定义节点和链接数据
var nodes = [
{ id: 1 },
{ id: 2 },
{ id: 3 }
];
var links = [
{ source: 0, target: 1 },
{ source: 1, target: 2 }
];
// 创建力布局
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.gravity(0.1)
.charge(-100)
.friction(0.9);
// 创建 SVG 容器
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// 绑定链接数据到线条元素
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link");
// 绑定节点数据到圆形元素
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5);
// 启动力模拟
force.on("tick", function () {
link.attr("x1", function (d) { return d.source.x; })
.attr("y1", function (d) { return d.source.y; })
.attr("x2", function (d) { return d.target.x; })
.attr("y2", function (d) { return d.target.y; });
node.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
});
force.start();
6. 不同交互技术的对比
| 交互技术 | 适用场景 | 实现难度 | 效果特点 |
|---|---|---|---|
| 触摸交互 | 移动设备或支持触摸的屏幕,需要用户直接与可视化元素进行触摸操作 | 较低,主要通过处理触摸事件实现 | 提供直观的触摸操作体验,增强用户与可视化的互动性 |
| 缩放与平移 | 处理大型数据集或需要详细查看局部数据的场景 | 中等,需要设置缩放范围和处理缩放事件 | 可以灵活查看数据的整体和局部细节,避免数据过于拥挤 |
| 拖动行为 | 需要用户对可视化元素进行位置调整或重新排列的场景 | 中等,需要处理拖动手势和边界检测 | 允许用户自由移动元素,实现个性化的布局 |
| 力模拟 | 展示节点之间关系、动态布局的场景 | 较高,需要理解物理模拟原理和力布局的参数设置 | 可以生成自然、动态的布局效果,突出节点之间的关系 |
7. 交互技术的综合应用
在实际的数据可视化项目中,通常会综合使用多种交互技术,以提供更丰富的用户体验。例如,在一个地理信息可视化项目中,可以使用缩放与平移技术让用户查看不同范围的地图,使用触摸交互让用户在移动设备上进行操作,使用拖动行为让用户调整地图上的标记位置,使用力模拟技术展示地理区域之间的关系。
以下是一个简单的 mermaid 流程图,展示了综合应用交互技术的流程:
graph LR
A[数据准备] --> B[创建可视化元素]
B --> C[添加交互功能]
C --> D{用户操作}
D -->|缩放平移| E[更新视图]
D -->|触摸交互| F[处理触摸事件]
D -->|拖动行为| G[移动元素位置]
D -->|力模拟| H[更新力布局]
E --> D
F --> D
G --> D
H --> D
8. 总结
通过本文的介绍,我们了解了数据可视化中多种交互技术的实现方法和应用场景,包括触摸交互、缩放与平移、拖动行为和力模拟技术。这些技术可以为数据可视化添加丰富的交互性和动态效果,提升用户体验和数据展示的效果。在实际应用中,可以根据项目的需求和特点选择合适的交互技术,并综合使用多种技术以实现更复杂的可视化效果。同时,需要注意不同交互技术的实现难度和性能影响,合理设置参数以达到最佳的效果。
希望本文能够帮助你更好地掌握数据可视化中的交互技术,为你的可视化项目带来更多的创意和价值。
超级会员免费看

被折叠的 条评论
为什么被折叠?



