「数据可视化 D3系列」入门第十一章:力导向图深度解析与实现

D3.js 力导向图深度解析与实现

力导向图核心概念

力导向图是一种通过物理模拟来展示复杂关系网络的图表类型,特别适合表现社交网络、知识图谱、系统拓扑等关系型数据。其核心原理是通过模拟粒子间的物理作用力(电荷斥力、弹簧引力等)自动计算节点的最优布局。

核心API详解

1. 力模拟系统

const simulation = d3.forceSimulation(nodes).force("charge", d3.forceManyBody().strength(-100)) // 节点间作用力.force("link", d3.forceLink(links).id(d => d.id))   // 连接线作用力.force("center", d3.forceCenter(width/2, height/2)) // 向心力.force("collision", d3.forceCollide().radius(20));  // 碰撞检测

2. 关键作用力类型

力类型作用描述常用配置方法
forceManyBody节点间电荷力(正为引力,负为斥力).strength()
forceLink连接线弹簧力.distance().id().strength()
forceCenter向中心点的引力.x().y()
forceCollide防止节点重叠的碰撞力.radius().strength()
forceX/Y沿X/Y轴方向的定位力.strength().x()/.y()

3. 动态控制方法

simulation.alpha(0.3)        // 设置当前alpha值(0-1).alphaTarget(0.1)  // 设置目标alpha值.alphaDecay(0.02)  // 设置衰减率(默认0.0228).velocityDecay(0.4)// 设置速度衰减(0-1).restart()         // 重启模拟.stop()            // 停止模拟.tick()            // 手动推进模拟一步

增强版力导向图实现

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>高级力导向图</title><script src="https://d3js.org/d3.v5.min.js"></script><style>.node {stroke: #fff;stroke-width: 1.5px;}.link {stroke: #999;stroke-opacity: 0.6;}.link-text {font-size: 10px;fill: #333;pointer-events: none;}.node-text {font-size: 12px;font-weight: bold;pointer-events: none;}.tooltip {position: absolute;padding: 8px;background: rgba(0,0,0,0.8);color: white;border-radius: 4px;pointer-events: none;font-size: 12px;}</style>
</head>
<body><div class="controls"><button id="reset">重置布局</button><button id="addNode">添加节点</button><span>斥力强度: <input type="range" id="charge" min="-200" max="0" value="-100"></span></div><svg width="800" height="600"></svg><div class="tooltip"></div><script>// 配置参数const config = {margin: {top: 20, right: 20, bottom: 20, left: 20},nodeRadius: 12,linkDistance: 150,chargeStrength: -100,collisionRadius: 20};// 数据准备const nodes = [{id: 0, name: "湖南邵阳", type: "location"},{id: 1, name: "山东莱州", type: "location"},{id: 2, name: "广东阳江", type: "location"},{id: 3, name: "山东枣庄", type: "location"},{id: 4, name: "赵丽泽", type: "person"},{id: 5, name: "王恒", type: "person"},{id: 6, name: "张欣鑫", type: "person"},{id: 7, name: "赵明山", type: "person"},{id: 8, name: "班长", type: "role"}];const links = [{source: 0, target: 4, relation: "籍贯", value: 1.3},{source: 4, target: 5, relation: "舍友", value: 1},{source: 4, target: 6, relation: "舍友", value: 1},{source: 4, target: 7, relation: "舍友", value: 1},{source: 1, target: 6, relation: "籍贯", value: 2},{source: 2, target: 5, relation: "籍贯", value: 0.9},{source: 3, target: 7, relation: "籍贯", value: 1},{source: 5, target: 6, relation: "同学", value: 1.6},{source: 6, target: 7, relation: "朋友", value: 0.7},{source: 6, target: 8, relation: "职责", value: 2}];// 初始化SVGconst svg = d3.select('svg');const width = +svg.attr('width');const height = +svg.attr('height');const tooltip = d3.select('.tooltip');// 创建画布const g = svg.append('g').attr('transform', `translate(${config.margin.left}, ${config.margin.top})`);// 颜色比例尺const colorScale = d3.scaleOrdinal().domain(['location', 'person', 'role']).range(['#66c2a5', '#fc8d62', '#8da0cb']);// 创建力导向图模拟const simulation = d3.forceSimulation(nodes).force("link", d3.forceLink(links).id(d => d.id).force("charge", d3.forceManyBody().strength(config.chargeStrength)).force("center", d3.forceCenter(width/2, height/2)).force("collision", d3.forceCollide(config.collisionRadius)).force("x", d3.forceX(width/2).strength(0.05)).force("y", d3.forceY(height/2).strength(0.05));// 创建连接线const link = g.append('g').selectAll('.link').data(links).enter().append('line').attr('class', 'link').attr('stroke-width', d => Math.sqrt(d.value));// 创建连接线文字const linkText = g.append('g').selectAll('.link-text').data(links).enter().append('text').attr('class', 'link-text').text(d => d.relation);// 创建节点组const node = g.append('g').selectAll('.node').data(nodes).enter().append('g').attr('class', 'node').call(d3.drag().on('start', dragStarted).on('drag', dragged).on('end', dragEnded)).on('mouseover', showTooltip).on('mouseout', hideTooltip);// 添加节点圆形node.append('circle').attr('r', config.nodeRadius).attr('fill', d => colorScale(d.type)).attr('stroke-width', 2);// 添加节点文字node.append('text').attr('class', 'node-text').attr('dy', 4).text(d => d.name);// 模拟tick事件处理simulation.on('tick', () => {link.attr('x1', d => d.source.x).attr('y1', d => d.source.y).attr('x2', d => d.target.x).attr('y2', d => d.target.y);linkText.attr('x', d => (d.source.x + d.target.x)/2).attr('y', d => (d.source.y + d.target.y)/2);node.attr('transform', d => `translate(${d.x},${d.y})`);});// 拖拽事件处理function dragStarted(d) {if (!d3.event.active) simulation.alphaTarget(0.3).restart();d.fx = d.x;d.fy = d.y;}function dragged(d) {d.fx = d3.event.x;d.fy = d3.event.y;}function dragEnded(d) {if (!d3.event.active) simulation.alphaTarget(0);d.fx = null;d.fy = null;}// 工具提示function showTooltip(d) {tooltip.transition().duration(200).style('opacity', 0.9);tooltip.html(`<strong>${d.name}</strong><br/>类型: ${d.type}`).style('left', (d3.event.pageX + 10) + 'px').style('top', (d3.event.pageY - 28) + 'px');// 高亮相关节点和连接线node.select('circle').attr('opacity', 0.2);d3.select(this).select('circle').attr('opacity', 1);link.attr('stroke-opacity', 0.1);link.filter(l => l.source === d || l.target === d).attr('stroke-opacity', 0.8).attr('stroke', '#ff0000');}function hideTooltip() {tooltip.transition().duration(500).style('opacity', 0);// 恢复所有元素样式node.select('circle').attr('opacity', 1);link.attr('stroke-opacity', 0.6).attr('stroke', '#999');}// 交互控制d3.select('#reset').on('click', () => {simulation.alpha(1).restart();nodes.forEach(d => {d.fx = null;d.fy = null;});});d3.select('#addNode').on('click', () => {const newNode = {id: nodes.length,name: `新节点${nodes.length}`,type: ['location', 'person', 'role'][Math.floor(Math.random()*3)]};nodes.push(newNode);// 随机连接到现有节点if (nodes.length > 1) {const randomTarget = Math.floor(Math.random() * (nodes.length - 1));links.push({source: newNode.id,target: randomTarget,relation: ['连接', '关系', '关联'][Math.floor(Math.random()*3)],value: Math.random() * 2 + 0.5});}// 更新模拟simulation.nodes(nodes);simulation.force('link').links(links);// 重新绘制元素updateGraph();});d3.select('#charge').on('input', function() {simulation.force('charge').strength(+this.value);simulation.alpha(0.3).restart();});// 更新图形函数function updateGraph() {// 更新连接线const newLinks = link.data(links).enter().append('line').attr('class', 'link').attr('stroke-width', d => Math.sqrt(d.value));link.merge(newLinks);// 更新连接线文字const newLinkText = linkText.data(links).enter().append('text').attr('class', 'link-text').text(d => d.relation);linkText.merge(newLinkText);// 更新节点const newNode = node.data(nodes).enter().append('g').attr('class', 'node').call(d3.drag().on('start', dragStarted).on('drag', dragged).on('end', dragEnded)).on('mouseover', showTooltip).on('mouseout', hideTooltip);newNode.append('circle').attr('r', config.nodeRadius).attr('fill', d => colorScale(d.type)).attr('stroke-width', 2);newNode.append('text').attr('class', 'node-text').attr('dy', 4).text(d => d.name);node.merge(newNode);simulation.alpha(1).restart();}
</script>
</body>
</html>

本章小结

核心实现要点

  1. 力模拟系统构建

    • 多力组合实现复杂布局(电荷力+弹簧力+向心力+碰撞力)
    • 参数调优实现不同视觉效果
  2. 动态交互体系

    • 拖拽行为与物理模拟的协调
    • 动态alpha值控制模拟过程
    • 实时tick更新机制
  3. 可视化增强

    • 基于类型的颜色编码
    • 交互式高亮关联元素
    • 动态工具提示显示

高级特性实现

  1. 动态数据更新

    • 节点/连接的实时添加
    • 模拟系统的热更新
  2. 交互控制面板

    • 力参数实时调节
    • 布局重置功能
  3. 视觉优化

    • 智能碰撞检测
    • 连接线权重可视化
    • 焦点元素高亮

下章预告:地图可视化

在下一章中,我们将探索:

  1. 地理数据基础

    • GeoJSON/TopoJSON格式解析
    • 地理投影原理与应用
  2. 核心API

    • d3.geoPath() 地理路径生成器
    • d3.geoProjection() 投影系统
    • d3.zoom() 地图缩放行为
  3. 高级技术

    • 分级统计图(Choropleth)实现
    • 气泡地图叠加
    • 地图交互与钻取
  4. 性能优化

    • 大数据量地图渲染
    • 拓扑简化技术
    • 动态加载策略

通过地图可视化的学习,您将掌握D3.js处理地理空间数据的能力,能够创建交互式的地图数据可视化应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/79435.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

音频格式转换

1. 下载ffmpeg https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-7.1.1-full_build.7z 2. 配置ffmpeg环境变量 3.安装pydub pip install pydub 4.编写转化工具代码 from pydub import AudioSegment def convertM4aToWav(m4a,wav):sound AudioSegment.from_file(m4a, f…

基于spring boot 集成 deepseek 流式输出 的vue3使用指南

本文使用deepseek API接口流式输出的文章。 环境要求 jdk17 spring boot 3.4 代码如下: package com.example.controller;import jakarta.annotation.PostConstruct; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.mes…

微博辐射源和干扰机

微波辐射源和干扰机是电子战和通信领域中的两个重要概念&#xff0c;它们在军事、民用及科研中具有广泛应用。以下是两者的详细解析及其相互关系&#xff1a; ‌1. 微波辐射源‌ ‌定义‌&#xff1a; 微波辐射源是指能够主动发射微波&#xff08;频率范围通常为 ‌300 MHz&…

2025年4月16日华为留学生笔试第三题300分

📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 03. 智慧城市网络优化 问题描述 K小姐是一家智慧城市服务提供商的网络架构师。她负责规划城市边缘计算节点的布局,以提供更快速、稳定的网络服务。 城市内有 n n

多线程编程的简单案例——单例模式[多线程编程篇(3)]

目录 前言 1.wati() 和 notify() wait() 和 notify() 的产生原因 如何使用wait()和notify()? 案例一:单例模式 饿汉式写法: 懒汉式写法 对于它的优化 再次优化 结尾 前言 如何简单的去使用jconsloe 查看线程 (多线程编程篇1)_eclipse查看线程-CSDN博客 浅谈Thread类…

pytorch基本操作2

torch.clamp 主要用于对张量中的元素进行截断&#xff08;clamping&#xff09;&#xff0c;将其限制在一个指定的区间范围内。 函数定义 torch.clamp(input, minNone, maxNone) → Tensor 参数说明 input 类型&#xff1a;Tensor 需要进行截断操作的输入张…

一次制作参考网杂志的阅读书源的实操经验总结(附书源)

文章目录 一、背景介绍二、书源文件三、详解制作书源&#xff08;一&#xff09;打开Web服务&#xff08;二&#xff09;参考网结构解释&#xff08;三&#xff09;阅读书源 基础&#xff08;四&#xff09;阅读书源 发现&#xff08;五&#xff09;阅读书源 详细&#xff08;六…

并发设计模式实战系列(2):领导者/追随者模式

&#x1f31f; ​大家好&#xff0c;我是摘星&#xff01;​ &#x1f31f; 今天为大家带来的是并发设计模式实战系列&#xff0c;第二章领导者/追随者&#xff08;Leader/Followers&#xff09;模式&#xff0c;废话不多说直接开始~ 目录 领导者/追随者&#xff08;Leader/…

自求导实现线性回归与PyTorch张量详解

目录 前言一、自求导的方法实现线性回归1.1自求导的方法实现线性回归的理论讲解1.1.1 线性回归是什么&#xff1f;1.1.2线性回归方程是什么&#xff1f;1.1.3散点输入1.2参数初始化1.2.1 参数与超参数1.2.1.1 参数定义1.2.1.2 参数内容1.2.1.3 超参数定义1.2.1.4 超参数内容1.…

2025年机电一体化、机器人与人工智能国际学术会议(MRAI 2025)

重要信息 时间&#xff1a;2025年4月25日-27日 地点&#xff1a;中国济南 官网&#xff1a;http://www.icmrai.org 征稿主题 机电一体化机器人人工智能 传感器和执行器 3D打印技术 智能控制 运动控制 光电系统 光机电一体化 类人机器人 人机界面 先进的运动控制 集成制造系…

线性代数 | 知识点整理 Ref 3

注&#xff1a;本文为 “线性代数 | 知识点整理” 相关文章合辑。 因 csdn 篇幅合并超限分篇连载&#xff0c;本篇为 Ref 3。 略作重排&#xff0c;未整理去重。 图片清晰度限于引文原状。 如有内容异常&#xff0c;请看原文。 《线性代数》总复习要点、公式、重要结论与重点释…

CFD中的动量方程非守恒形式详解

在计算流体力学&#xff08;CFD&#xff09;中&#xff0c;动量方程可以写成守恒形式和非守恒形式&#xff0c;两者在数学上等价&#xff0c;但推导方式和应用场景不同。以下是对非守恒形式的详细解释&#xff1a; 1. 动量方程的守恒形式 首先回顾守恒形式的动量方程&#xff…

Leetcode 1504. 统计全 1 子矩形

1.题目基本信息 1.1.题目描述 给你一个 m x n 的二进制矩阵 mat &#xff0c;请你返回有多少个 子矩形 的元素全部都是 1 。 1.2.题目地址 https://leetcode.cn/problems/count-submatrices-with-all-ones/description/ 2.解题方法 2.1.解题思路 单调栈 时间复杂度&…

【Docker】运行错误提示 unknown shorthand flag: ‘d‘ in -d ----详细解决方法

使用docker拉取Dify的时候遇到错误 错误提示 unknown shorthand flag: d in -dUsage: docker [OPTIONS] COMMAND [ARG...]错误原因解析 出现 unknown shorthand flag: d in -d 的根本原因是 Docker 命令格式与当前版本不兼容&#xff0c;具体分为以下两种情况&#xff1a; 新…

华为OD机试真题——攀登者2(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 A卷 200分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析&#xff1b; 并提供Java、python、JavaScript、C、C语言、GO六种语言的最佳实现方式&#xff01; 2025华为OD真题目录全流程解析/备考攻略/经验分享 华为OD机试真题《攀登者2…

qt硬件与软件通信中 16进制与十进制转化

1. 首先上代码, 这是在qt语言上的操作 截取 01 03 0C 00 00 00 00 00 00 00 0C 00 0C 00 0C 93 70 这串16进制数值进行处理&#xff0c;截取这样一段内容 00 0C 00 0C 00 0C 字节数组转字符串。从bytearray数组转换为string. QString CustomTcpSocket::recieveInfo() {QByteArr…

图形变换算法

一、学习目的 &#xff08;1&#xff09;掌握多面体的存储方法。 &#xff08;2&#xff09;掌握图形的几何变换及投影变换。 &#xff08;3&#xff09;掌握三维形体不同投影方法的投影图的生成原理。 &#xff08;4&#xff09;掌握多面体投影图绘制的编程方法。 二、学…

【JAVAFX】自定义FXML 文件存放的位置以及使用

情况 1&#xff1a;FXML 文件与调用类在同一个包中&#xff08;推荐&#xff09; 假设类 MainApp 的包是 com.example&#xff0c;且 FXML 文件放在 resources/com/example 下&#xff1a; 项目根目录 ├── src │ └── sample │ └── Main.java ├── src/s…

Ubuntu20.04安装企业微信

建议先去企业微信官网看一下有没有linux版本&#xff0c;没有的话在按如下方式安装&#xff0c;不过现在是没有的。 方案 1、使用docker容器 2、使用deepin-wine 3、使用星火应用商店 4. 使用星火包deepin-wine 5、使用ukylin-wine 本人对docker不太熟悉&#xff0c;现…

CSS appearance 属性:掌握UI元素的原生外观

在现代网页设计中&#xff0c;为了达到一致的用户体验&#xff0c;我们有时需要让HTML元素模仿操作系统的默认控件样式。CSS中的appearance属性提供了一种简便的方式来控制这些元素是否以及如何显示其默认外观。本文将详细介绍appearance属性&#xff0c;并通过实际代码示例来展…