求适用于P5js的简易力导向图绘制算法及相关资源
嘿,我完全懂你想要的——那种不用啃厚重教材、也不用直接搬D3复杂逻辑的轻量方案!作为用P5.js做过不少可视化小工具的人,我推荐从简化版力导向算法入手,它直观、易实现,完全适配P5.js的画布操作逻辑,下面一步步给你拆解:
最适合入门的简化力导向算法(P5.js友好版)
这个版本砍掉了复杂的物理模型,只保留两个核心力:节点间的排斥力(避免重叠)和连边的拉力(让相连节点保持关联),用基础的坐标计算就能搞定。
核心思路
- 给每个节点初始随机位置
- 每一帧循环执行三件事:
- 计算所有节点间的排斥力,让它们互相推开
- 计算每条连边的拉力,把相连节点拉向彼此
- 更新节点位置,重新绘制画布
P5.js代码示例(极简可运行版)
直接复制就能跑,参数还能随便调着玩:
// 节点数组:每个节点存坐标和半径 let nodes = []; // 边数组:每个元素存两个节点的索引 let edges = []; function setup() { createCanvas(800, 600); // 生成10个随机节点 for (let i = 0; i < 10; i++) { nodes.push({ x: random(width), y: random(height), r: 12 }); } // 生成随机边(避免重复和自连) for (let i = 0; i < nodes.length; i++) { let connectTo = floor(random(nodes.length)); if (connectTo !== i && !edges.some(e => (e[0] === i && e[1] === connectTo) || (e[0] === connectTo && e[1] === i) )) { edges.push([i, connectTo]); } } } function draw() { background(240); // 1. 计算排斥力:所有节点互相推开 const repelForce = 500; // 系数越大,推得越开 for (let i = 0; i < nodes.length; i++) { const a = nodes[i]; for (let j = i + 1; j < nodes.length; j++) { const b = nodes[j]; // 计算距离和方向 const dx = b.x - a.x; const dy = b.y - a.y; let dist = sqrt(dx*dx + dy*dy); dist = dist < 1 ? 1 : dist; // 避免除以0 // 距离越近,排斥力越大 const force = repelForce / (dist * dist); // 分解力到x/y方向,反向推开 const fx = (dx / dist) * force; const fy = (dy / dist) * force; a.x -= fx; a.y -= fy; b.x += fx; b.y += fy; } } // 2. 计算拉力:相连节点互相拉近 const pullForce = 0.1; // 系数越大,拉得越紧 const idealLength = 100; // 边的理想长度,控制节点间距 for (const edge of edges) { const a = nodes[edge[0]]; const b = nodes[edge[1]]; const dx = b.x - a.x; const dy = b.y - a.y; const dist = sqrt(dx*dx + dy*dy); // 计算当前距离和理想长度的差值 const diff = dist - idealLength; const force = diff * pullForce; const fx = (dx / dist) * force; const fy = (dy / dist) * force; // 拉近两个节点 a.x += fx; a.y += fy; b.x -= fx; b.y -= fy; } // 3. 限制节点在画布内(可选,避免跑出去) for (const node of nodes) { node.x = constrain(node.x, node.r, width - node.r); node.y = constrain(node.y, node.r, height - node.r); } // 绘制边 stroke(150); strokeWeight(1); for (const edge of edges) { line(nodes[edge[0]].x, nodes[edge[0]].y, nodes[edge[1]].x, nodes[edge[1]].y); } // 绘制节点 fill(50); noStroke(); for (const node of nodes) { ellipse(node.x, node.y, node.r*2); } }
为什么这个版本适合你?
- 逻辑极简:没有复杂公式,靠距离算力,看完就能懂,改参数就能调效果
- P5原生友好:全用P5基础函数,不用额外依赖,直接在画布上操作
- 扩展性强:想升级的话,加个速度变量、重力效果、边界约束都很容易
不用力导向的替代方案
如果觉得力导向还是有点麻烦,试试这两种超简单的布局:
- 环形布局:所有节点均匀放在一个圆上,适合展示小图,核心代码几行就能搞定:
// 在setup里生成环形节点 const numNodes = 10; const radius = 200; const centerX = width/2; const centerY = height/2; for (let i = 0; i < numNodes; i++) { const angle = (TWO_PI / numNodes) * i; nodes.push({ x: centerX + cos(angle)*radius, y: centerY + sin(angle)*radius, r: 12 }); } - 层次布局:把节点按层级排列(比如树状结构),从上到下/从左到右分布,适合有明确层级关系的图。
内容的提问来源于stack exchange,提问作者A Poor
相关产品推荐
相关产品推荐

