马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
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}
- ];
-
- // 初始化SVG
- const 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>
复制代码 本章小结
焦点实现要点
- 力模拟系统构建:
- 多力组合实现复杂布局(电荷力+弹簧力+向心力+碰撞力)
- 参数调优实现不同视觉效果
- 动态交互体系:
- 拖拽行为与物理模拟的协调
- 动态alpha值控制模拟过程
- 实时tick更新机制
- 可视化增强:
- 基于类型的颜色编码
- 交互式高亮关联元素
- 动态工具提示显示
高级特性实现
下章预告:地图可视化
在下一章中,我们将探索:
- 地理数据基础:
- GeoJSON/TopoJSON格式剖析
- 地理投影原理与应用
- 焦点API:
- d3.geoPath() 地理路径天生器
- d3.geoProjection() 投影系统
- d3.zoom() 地图缩放行为
- 高级技术:
- 分级统计图(Choropleth)实现
- 气泡地图叠加
- 地图交互与钻取
- 性能优化:
通过地图可视化的学习,您将掌握D3.js处理地理空间数据的能力,能够创建交互式的地图数据可视化应用。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |