IT评测·应用市场-qidao123.com技术社区

标题: 「数据可视化 D3系列」入门第十一章:力导向图深度剖析与实现 [打印本页]

作者: 郭卫东    时间: 2025-4-19 11:50
标题: 「数据可视化 D3系列」入门第十一章:力导向图深度剖析与实现
D3.js 力导向图深度剖析与实现

力导向图焦点概念

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

1. 力模拟系统

  1. const simulation = d3.forceSimulation(nodes)
  2.     .force("charge", d3.forceManyBody().strength(-100)) // 节点间作用力
  3.     .force("link", d3.forceLink(links).id(d => d.id))   // 连接线作用力
  4.     .force("center", d3.forceCenter(width/2, height/2)) // 向心力
  5.     .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. 动态控制方法

  1. simulation
  2.     .alpha(0.3)        // 设置当前alpha值(0-1)
  3.     .alphaTarget(0.1)  // 设置目标alpha值
  4.     .alphaDecay(0.02)  // 设置衰减率(默认0.0228)
  5.     .velocityDecay(0.4)// 设置速度衰减(0-1)
  6.     .restart()         // 重启模拟
  7.     .stop()            // 停止模拟
  8.     .tick()            // 手动推进模拟一步
复制代码
增强版力导向图实现

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>高级力导向图</title>
  6.     <script src="https://d3js.org/d3.v5.min.js"></script>
  7.     <style>
  8.         .node {
  9.             stroke: #fff;
  10.             stroke-width: 1.5px;
  11.         }
  12.         .link {
  13.             stroke: #999;
  14.             stroke-opacity: 0.6;
  15.         }
  16.         .link-text {
  17.             font-size: 10px;
  18.             fill: #333;
  19.             pointer-events: none;
  20.         }
  21.         .node-text {
  22.             font-size: 12px;
  23.             font-weight: bold;
  24.             pointer-events: none;
  25.         }
  26.         .tooltip {
  27.             position: absolute;
  28.             padding: 8px;
  29.             background: rgba(0,0,0,0.8);
  30.             color: white;
  31.             border-radius: 4px;
  32.             pointer-events: none;
  33.             font-size: 12px;
  34.         }
  35.     </style>
  36. </head>
  37. <body>
  38.     <div class="controls">
  39.         <button id="reset">重置布局</button>
  40.         <button id="addNode">添加节点</button>
  41.         <span>斥力强度: <input type="range" id="charge" min="-200" max="0" value="-100"></span>
  42.     </div>
  43.     <svg width="800" height="600"></svg>
  44.     <div class="tooltip"></div>
  45. <script>
  46.     // 配置参数
  47.     const config = {
  48.         margin: {top: 20, right: 20, bottom: 20, left: 20},
  49.         nodeRadius: 12,
  50.         linkDistance: 150,
  51.         chargeStrength: -100,
  52.         collisionRadius: 20
  53.     };
  54.    
  55.     // 数据准备
  56.     const nodes = [
  57.         {id: 0, name: "湖南邵阳", type: "location"},
  58.         {id: 1, name: "山东莱州", type: "location"},
  59.         {id: 2, name: "广东阳江", type: "location"},
  60.         {id: 3, name: "山东枣庄", type: "location"},
  61.         {id: 4, name: "赵丽泽", type: "person"},
  62.         {id: 5, name: "王恒", type: "person"},
  63.         {id: 6, name: "张欣鑫", type: "person"},
  64.         {id: 7, name: "赵明山", type: "person"},
  65.         {id: 8, name: "班长", type: "role"}
  66.     ];
  67.    
  68.     const links = [
  69.         {source: 0, target: 4, relation: "籍贯", value: 1.3},
  70.         {source: 4, target: 5, relation: "舍友", value: 1},
  71.         {source: 4, target: 6, relation: "舍友", value: 1},
  72.         {source: 4, target: 7, relation: "舍友", value: 1},
  73.         {source: 1, target: 6, relation: "籍贯", value: 2},
  74.         {source: 2, target: 5, relation: "籍贯", value: 0.9},
  75.         {source: 3, target: 7, relation: "籍贯", value: 1},
  76.         {source: 5, target: 6, relation: "同学", value: 1.6},
  77.         {source: 6, target: 7, relation: "朋友", value: 0.7},
  78.         {source: 6, target: 8, relation: "职责", value: 2}
  79.     ];
  80.    
  81.     // 初始化SVG
  82.     const svg = d3.select('svg');
  83.     const width = +svg.attr('width');
  84.     const height = +svg.attr('height');
  85.     const tooltip = d3.select('.tooltip');
  86.    
  87.     // 创建画布
  88.     const g = svg.append('g')
  89.         .attr('transform', `translate(${config.margin.left}, ${config.margin.top})`);
  90.    
  91.     // 颜色比例尺
  92.     const colorScale = d3.scaleOrdinal()
  93.         .domain(['location', 'person', 'role'])
  94.         .range(['#66c2a5', '#fc8d62', '#8da0cb']);
  95.    
  96.     // 创建力导向图模拟
  97.     const simulation = d3.forceSimulation(nodes)
  98.         .force("link", d3.forceLink(links).id(d => d.id)
  99.         .force("charge", d3.forceManyBody().strength(config.chargeStrength))
  100.         .force("center", d3.forceCenter(width/2, height/2))
  101.         .force("collision", d3.forceCollide(config.collisionRadius))
  102.         .force("x", d3.forceX(width/2).strength(0.05))
  103.         .force("y", d3.forceY(height/2).strength(0.05));
  104.    
  105.     // 创建连接线
  106.     const link = g.append('g')
  107.         .selectAll('.link')
  108.         .data(links)
  109.         .enter().append('line')
  110.         .attr('class', 'link')
  111.         .attr('stroke-width', d => Math.sqrt(d.value));
  112.    
  113.     // 创建连接线文字
  114.     const linkText = g.append('g')
  115.         .selectAll('.link-text')
  116.         .data(links)
  117.         .enter().append('text')
  118.         .attr('class', 'link-text')
  119.         .text(d => d.relation);
  120.    
  121.     // 创建节点组
  122.     const node = g.append('g')
  123.         .selectAll('.node')
  124.         .data(nodes)
  125.         .enter().append('g')
  126.         .attr('class', 'node')
  127.         .call(d3.drag()
  128.             .on('start', dragStarted)
  129.             .on('drag', dragged)
  130.             .on('end', dragEnded)
  131.         )
  132.         .on('mouseover', showTooltip)
  133.         .on('mouseout', hideTooltip);
  134.    
  135.     // 添加节点圆形
  136.     node.append('circle')
  137.         .attr('r', config.nodeRadius)
  138.         .attr('fill', d => colorScale(d.type))
  139.         .attr('stroke-width', 2);
  140.    
  141.     // 添加节点文字
  142.     node.append('text')
  143.         .attr('class', 'node-text')
  144.         .attr('dy', 4)
  145.         .text(d => d.name);
  146.    
  147.     // 模拟tick事件处理
  148.     simulation.on('tick', () => {
  149.         link
  150.             .attr('x1', d => d.source.x)
  151.             .attr('y1', d => d.source.y)
  152.             .attr('x2', d => d.target.x)
  153.             .attr('y2', d => d.target.y);
  154.         
  155.         linkText
  156.             .attr('x', d => (d.source.x + d.target.x)/2)
  157.             .attr('y', d => (d.source.y + d.target.y)/2);
  158.         
  159.         node
  160.             .attr('transform', d => `translate(${d.x},${d.y})`);
  161.     });
  162.    
  163.     // 拖拽事件处理
  164.     function dragStarted(d) {
  165.         if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  166.         d.fx = d.x;
  167.         d.fy = d.y;
  168.     }
  169.    
  170.     function dragged(d) {
  171.         d.fx = d3.event.x;
  172.         d.fy = d3.event.y;
  173.     }
  174.    
  175.     function dragEnded(d) {
  176.         if (!d3.event.active) simulation.alphaTarget(0);
  177.         d.fx = null;
  178.         d.fy = null;
  179.     }
  180.    
  181.     // 工具提示
  182.     function showTooltip(d) {
  183.         tooltip.transition()
  184.             .duration(200)
  185.             .style('opacity', 0.9);
  186.         tooltip.html(`<strong>${d.name}</strong><br/>类型: ${d.type}`)
  187.             .style('left', (d3.event.pageX + 10) + 'px')
  188.             .style('top', (d3.event.pageY - 28) + 'px');
  189.         
  190.         // 高亮相关节点和连接线
  191.         node.select('circle').attr('opacity', 0.2);
  192.         d3.select(this).select('circle').attr('opacity', 1);
  193.         
  194.         link.attr('stroke-opacity', 0.1);
  195.         link.filter(l => l.source === d || l.target === d)
  196.             .attr('stroke-opacity', 0.8)
  197.             .attr('stroke', '#ff0000');
  198.     }
  199.    
  200.     function hideTooltip() {
  201.         tooltip.transition()
  202.             .duration(500)
  203.             .style('opacity', 0);
  204.         
  205.         // 恢复所有元素样式
  206.         node.select('circle').attr('opacity', 1);
  207.         link.attr('stroke-opacity', 0.6)
  208.             .attr('stroke', '#999');
  209.     }
  210.    
  211.     // 交互控制
  212.     d3.select('#reset').on('click', () => {
  213.         simulation.alpha(1).restart();
  214.         nodes.forEach(d => {
  215.             d.fx = null;
  216.             d.fy = null;
  217.         });
  218.     });
  219.    
  220.     d3.select('#addNode').on('click', () => {
  221.         const newNode = {
  222.             id: nodes.length,
  223.             name: `新节点${nodes.length}`,
  224.             type: ['location', 'person', 'role'][Math.floor(Math.random()*3)]
  225.         };
  226.         nodes.push(newNode);
  227.         
  228.         // 随机连接到现有节点
  229.         if (nodes.length > 1) {
  230.             const randomTarget = Math.floor(Math.random() * (nodes.length - 1));
  231.             links.push({
  232.                 source: newNode.id,
  233.                 target: randomTarget,
  234.                 relation: ['连接', '关系', '关联'][Math.floor(Math.random()*3)],
  235.                 value: Math.random() * 2 + 0.5
  236.             });
  237.         }
  238.         
  239.         // 更新模拟
  240.         simulation.nodes(nodes);
  241.         simulation.force('link').links(links);
  242.         
  243.         // 重新绘制元素
  244.         updateGraph();
  245.     });
  246.    
  247.     d3.select('#charge').on('input', function() {
  248.         simulation.force('charge').strength(+this.value);
  249.         simulation.alpha(0.3).restart();
  250.     });
  251.    
  252.     // 更新图形函数
  253.     function updateGraph() {
  254.         // 更新连接线
  255.         const newLinks = link.data(links).enter()
  256.             .append('line')
  257.             .attr('class', 'link')
  258.             .attr('stroke-width', d => Math.sqrt(d.value));
  259.         link.merge(newLinks);
  260.         
  261.         // 更新连接线文字
  262.         const newLinkText = linkText.data(links).enter()
  263.             .append('text')
  264.             .attr('class', 'link-text')
  265.             .text(d => d.relation);
  266.         linkText.merge(newLinkText);
  267.         
  268.         // 更新节点
  269.         const newNode = node.data(nodes).enter()
  270.             .append('g')
  271.             .attr('class', 'node')
  272.             .call(d3.drag()
  273.                 .on('start', dragStarted)
  274.                 .on('drag', dragged)
  275.                 .on('end', dragEnded)
  276.             )
  277.             .on('mouseover', showTooltip)
  278.             .on('mouseout', hideTooltip);
  279.         
  280.         newNode.append('circle')
  281.             .attr('r', config.nodeRadius)
  282.             .attr('fill', d => colorScale(d.type))
  283.             .attr('stroke-width', 2);
  284.         
  285.         newNode.append('text')
  286.             .attr('class', 'node-text')
  287.             .attr('dy', 4)
  288.             .text(d => d.name);
  289.         
  290.         node.merge(newNode);
  291.         
  292.         simulation.alpha(1).restart();
  293.     }
  294. </script>
  295. </body>
  296. </html>
复制代码
本章小结

焦点实现要点

高级特性实现

下章预告:地图可视化

在下一章中,我们将探索:
通过地图可视化的学习,您将掌握D3.js处理地理空间数据的能力,能够创建交互式的地图数据可视化应用。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4