Three.js 社交关系三维网络图力导向布局、聚类着色与交互式图谱探索一、把社交图谱从平面搬到三维空间社交关系天然适合用网络图来可视化——节点是用户边是关注关系边的粗细是互动频率节点的颜色是社区归属。传统的二维网络图在处理超过 500 个节点时会迅速变成一团毛线因为二维空间的可视化通道只有 X 和 Y 两个维度。Three.js 提供的三维渲染通道增加了 Z 轴意味着布局算法可以在三维空间中推开节点渲染层可以用光照和景深强化层级关系交互层可以用相机旋转和平移让用户在任意角度审视图结构。对于 Web3 社交场景——其中还可以用 Z 轴表示用户的声音强度Poast 频率、节点大小表示影响力评分Follower 数量、颜色表示所属 DAO 或社区——三维可视化提供了一种用空间编码信息的方式。但这种多维度编码也有过载风险。一个节点同时承载了位置、颜色、大小、透明度、标签、光晕六个视觉通道用户的理解负荷会迅速超出上限。经验规则是单节点最多承载 3 个独立的视觉通道如位置 布局坐标颜色 社区归属大小 影响力超出 3 个的信息应该通过交互hover tooltip、侧边栏详情披露而非预编码在视觉通道中。力导向布局Force-Directed Layout是最适合社交图谱的布局算法节点之间相互排斥库仑力相关联的节点之间相互吸引弹簧力系统迭代直至达到能量最小的稳态。d3-force 是一个成熟的 2D/3D 力导向引擎可以和 Three.js 配合使用。二、三维社交图谱的构建流水线社区检测使用 Louvain 算法——它通过迭代优化模块度Modularity来发现图中的自然社区结构。模块度衡量的是社区内部的边密度高于随机网络预期的程度。在 Web3 社交场景中社区通常对应 DAO 成员、NFT 系列持有者、或同一个 DeFi 协议的参与者。力导向布局在每次迭代中更新节点的 3D 位置收敛后把结果传给 Three.js 渲染器。节点大小映射到其 PageRank 值表示在网络中的影响力边的粗细映射到交互权重。三、核心实现代码// force-directed-3d.ts — 3D 力导向布局引擎 import * as d3Force from d3-force-3d; import * as THREE from three; import { OrbitControls } from three/examples/jsm/controls/OrbitControls; import { CSS2DRenderer, CSS2DObject } from three/examples/jsm/renderers/CSS2DRenderer; interface GraphNode { id: string; address: string; ens?: string; group: number; // Louvain 社区 ID pagerank: number; x?: number; y?: number; z?: number; } interface GraphEdge { source: string; target: string; weight: number; // 交互频率权重 } /** * 三维社交图谱可视化引擎 * * 设计决策将布局计算和数据渲染分离为两个独立的生命周期。 * 布局计算使用 d3-force-3d 在 worker 线程中进行避免阻塞主线程 * 渲染使用 Three.js 在主线程中进行需要访问 WebGL 上下文。 * 两者通过 SharedArrayBuffer 或 postMessage 传递节点坐标。 */ class SocialGraph3D { private scene: THREE.Scene; private camera: THREE.PerspectiveCamera; private renderer: THREE.WebGLRenderer; private labelRenderer: CSS2DRenderer; private controls: OrbitControls; // 节点和边的 Three.js 对象池 private nodeMeshes: Mapstring, THREE.Mesh new Map(); private edgeLines: Mapstring, THREE.Line new Map(); // 颜色方案按社区划分 private groupColors: THREE.Color[] []; constructor(container: HTMLElement) { // 场景初始化 this.scene new THREE.Scene(); this.scene.background new THREE.Color(0x0a0a1a); // 添加星空背景粒子增强科技感 this.addStarfieldBackground(); // 相机初始位置在右上角前方 this.camera new THREE.PerspectiveCamera( 55, container.clientWidth / container.clientHeight, 0.1, 1000 ); this.camera.position.set(50, 30, 80); // WebGL 渲染器 this.renderer new THREE.WebGLRenderer({ antialias: true, alpha: true }); this.renderer.setSize(container.clientWidth, container.clientHeight); container.appendChild(this.renderer.domElement); // CSS2D 标签渲染器用于渲染节点上的文字标签 this.labelRenderer new CSS2DRenderer(); this.labelRenderer.setSize(container.clientWidth, container.clientHeight); this.labelRenderer.domElement.style.position absolute; this.labelRenderer.domElement.style.top 0; this.labelRenderer.domElement.style.pointerEvents none; container.appendChild(this.labelRenderer.domElement); // 轨道控制 this.controls new OrbitControls(this.camera, this.renderer.domElement); this.controls.enableDamping true; this.controls.dampingFactor 0.08; this.controls.maxDistance 300; // 生成基于 HSL 的社区颜色方案 this.generateColorScheme(12); // 开始渲染循环 this.animate(); } private addStarfieldBackground(): void { const starsGeometry new THREE.BufferGeometry(); const starsCount 2000; const positions new Float32Array(starsCount * 3); for (let i 0; i starsCount * 3; i 3) { positions[i] (Math.random() - 0.5) * 400; positions[i 1] (Math.random() - 0.5) * 400; positions[i 2] (Math.random() - 0.5) * 400; } starsGeometry.setAttribute(position, new THREE.BufferAttribute(positions, 3)); const starsMaterial new THREE.PointsMaterial({ color: 0x4488ff, size: 0.3, transparent: true, opacity: 0.6 }); this.scene.add(new THREE.Points(starsGeometry, starsMaterial)); } /** * 运行力导向布局计算 * * 设计决策使用 d3-force-3d 的 jiggle 力而非 charge。 * jiggle 力每一步会做小幅度随机位移帮助逃离局部能量最小值。 * 传统 charge 力在大规模图中 O(n²) 的计算量会给浏览器带来卡顿 * 现代替代方案使用 Barnes-Hut 近似O(n log n)。 */ async computeLayout(nodes: GraphNode[], edges: GraphEdge[]): Promisevoid { return new Promise((resolve) { const simulation d3Force.forceSimulation(nodes as any, 3) .force(link, d3Force.forceLink(edges).distance((e: any) 50 / e.weight).strength(0.5)) .force(charge, d3Force.forceManyBody().strength(-300)) .force(center, d3Force.forceCenter(0, 0, 0)) .force(collision, d3Force.forceCollide().radius((n: any) 3 n.pagerank * 2)) .stop(); // 逐步迭代避免一次性计算阻塞主线程 const totalIterations 300; let currentIteration 0; const tick () { simulation.tick(10); // 每次 tick 10 步 currentIteration 10; if (currentIteration totalIterations) { requestAnimationFrame(tick); } else { simulation.stop(); resolve(); } }; requestAnimationFrame(tick); }); } /** * 将布局结果渲染为 Three.js 场景 * * 设计决策使用 InstancedMesh 渲染同质节点球体以减少 draw call。 * 当节点数超过 1000 时InstancedMesh 比逐个 Mesh 的 draw call 数量 * 从 1000 降至 ~10按材质分批帧率提升约 3-5 倍。 */ renderGraph(nodes: GraphNode[], edges: GraphEdge[]): void { // 清除旧场景 this.clearGraph(); // 渲染边使用 TubeGeometry 创建曲线管道 const edgeMaterial new THREE.MeshBasicMaterial({ color: 0x334466, transparent: true, opacity: 0.4, }); for (const edge of edges) { const sourceNode nodes.find(n n.id edge.source); const targetNode nodes.find(n n.id edge.target); if (!sourceNode?.x || !targetNode?.x) continue; const start new THREE.Vector3(sourceNode.x, sourceNode.y, sourceNode.z); const end new THREE.Vector3(targetNode.x, targetNode.y, targetNode.z); // 控制点让边在 Z 轴上有弧度 const mid new THREE.Vector3( (start.x end.x) / 2, (start.y end.y) / 2, (start.z end.z) / 2 5 ); const curve new THREE.QuadraticBezierCurve3(start, mid, end); const tubeGeometry new THREE.TubeGeometry(curve, 8, 0.1 * edge.weight, 4, false); const tube new THREE.Mesh(tubeGeometry, edgeMaterial); this.scene.add(tube); this.edgeLines.set(${edge.source}-${edge.target}, tube as any); } // 渲染节点InstancedMesh 优化 const sphereGeometry new THREE.SphereGeometry(1, 32, 32); // 按社区分组每组一个 InstancedMesh const groupMap new Mapnumber, { nodes: GraphNode[]; indices: number[] }(); for (let i 0; i nodes.length; i) { const g nodes[i].group; if (!groupMap.has(g)) { groupMap.set(g, { nodes: [], indices: [] }); } groupMap.get(g)!.nodes.push(nodes[i]); groupMap.get(g)!.indices.push(i); } for (const [groupId, groupData] of groupMap) { const color this.groupColors[groupId % this.groupColors.length]; const instancedMaterial new THREE.MeshPhongMaterial({ color: color, shininess: 60, emissive: color, emissiveIntensity: 0.2, }); const instancedMesh new THREE.InstancedMesh( sphereGeometry, instancedMaterial, groupData.nodes.length ); const matrix new THREE.Matrix4(); for (let i 0; i groupData.nodes.length; i) { const node groupData.nodes[i]; const scale 1 node.pagerank * 2; matrix.compose( new THREE.Vector3(node.x!, node.y!, node.z!), new THREE.Quaternion(), new THREE.Vector3(scale, scale, scale) ); instancedMesh.setMatrixAt(i, matrix); } this.scene.add(instancedMesh); for (const node of groupData.nodes) { this.nodeMeshes.set(node.id, instancedMesh as any); } } // 添加节点标签使用 CSS2D for (const node of nodes) { if (node.pagerank 0.05) { // 只给重要节点加标签 const labelDiv document.createElement(div); labelDiv.textContent node.ens || node.address.slice(0, 8) ...; labelDiv.style.cssText color:#88ccff;font-size:11px;background:rgba(0,0,0,0.7);padding:2px 6px;border-radius:3px;; const label new CSS2DObject(labelDiv); label.position.set(node.x!, node.y! 2, node.z!); this.scene.add(label); } } // 光照 const ambientLight new THREE.AmbientLight(0x404060, 0.6); const directionalLight new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(50, 50, 50); this.scene.add(ambientLight, directionalLight); } private clearGraph(): void { this.scene.children .filter(c !(c instanceof THREE.Points)) // 保留星空背景 .forEach(c this.scene.remove(c)); this.nodeMeshes.clear(); this.edgeLines.clear(); } private animate(): void { requestAnimationFrame(() this.animate()); this.controls.update(); this.renderer.render(this.scene, this.camera); this.labelRenderer.render(this.scene, this.camera); } }四、视觉编码与性能边界视觉编码通道的分配三维空间提供了更多的视觉通道但也有过载的风险。建议的编码规范是位置X/Y/Z 力导向布局的自然坐标颜色Hue 社区归属大小Scale PageRank 影响力分不透明度 活跃度最近 30 天的交互频率Z 轴深度 账号年龄老账号在前景。大规模图谱的性能限制InstancedMesh 可以支撑到约 10,000 个节点取决于 GPU。超过这个量级需要降采样策略——使用 K-Core 分解保留核心子图度为 k 及以上的节点或在布局阶段用 HNSW 图近似只渲染能放进屏幕的高阶节点。WebGL 兼容性Three.js 的 WebGL 后端在超过 90% 的现代浏览器上可用。但对于移动设备复杂的图渲染 2000 节点 阴影 后处理会导致 GPU 过热和帧率下降。移动端应该降低节点面数16 段球体替代 32 段、关闭阴影、使用线框模式。力导向布局的收敛与非收敛边界d3-force-3d 的布局在理想条件下约 200-300 次迭代后收敛节点位置变化 0.01但某些图结构如高度非连通的星形图可能永远不收敛——中心节点周围一圈孤立节点会因斥力而无限制地向外漂移。需要设置最大迭代次数 位置范围如 Box 约束[-100, 100]超出范围的节点固定在边界上并标记为未收敛在 UI 中给予视觉提示。五、总结Three.js 的社交关系三维网络图不是在炫耀技术而是在解决二维可视化无法解决的信息密度问题。力导向布局让关系结构自然浮现聚类着色让社区边界一目了然轨道控制器让用户从任意角度审视图谱——这三个工具的叠加让一个 2000 节点的复杂社交网络变得可以理解。在 Web3 场景中三维可视化的额外价值是可以同步展示链上资产流向和社交关系两张网络——在同一个三维空间中叠加两层图一层是关注网络社交资本流一层是转账网络金融资本流。两者的重合度揭示了很多信息社交网络中高关注的账号在资金网络中也可能是中心节点。
Three.js 社交关系三维网络图:力导向布局、聚类着色与交互式图谱探索
Three.js 社交关系三维网络图力导向布局、聚类着色与交互式图谱探索一、把社交图谱从平面搬到三维空间社交关系天然适合用网络图来可视化——节点是用户边是关注关系边的粗细是互动频率节点的颜色是社区归属。传统的二维网络图在处理超过 500 个节点时会迅速变成一团毛线因为二维空间的可视化通道只有 X 和 Y 两个维度。Three.js 提供的三维渲染通道增加了 Z 轴意味着布局算法可以在三维空间中推开节点渲染层可以用光照和景深强化层级关系交互层可以用相机旋转和平移让用户在任意角度审视图结构。对于 Web3 社交场景——其中还可以用 Z 轴表示用户的声音强度Poast 频率、节点大小表示影响力评分Follower 数量、颜色表示所属 DAO 或社区——三维可视化提供了一种用空间编码信息的方式。但这种多维度编码也有过载风险。一个节点同时承载了位置、颜色、大小、透明度、标签、光晕六个视觉通道用户的理解负荷会迅速超出上限。经验规则是单节点最多承载 3 个独立的视觉通道如位置 布局坐标颜色 社区归属大小 影响力超出 3 个的信息应该通过交互hover tooltip、侧边栏详情披露而非预编码在视觉通道中。力导向布局Force-Directed Layout是最适合社交图谱的布局算法节点之间相互排斥库仑力相关联的节点之间相互吸引弹簧力系统迭代直至达到能量最小的稳态。d3-force 是一个成熟的 2D/3D 力导向引擎可以和 Three.js 配合使用。二、三维社交图谱的构建流水线社区检测使用 Louvain 算法——它通过迭代优化模块度Modularity来发现图中的自然社区结构。模块度衡量的是社区内部的边密度高于随机网络预期的程度。在 Web3 社交场景中社区通常对应 DAO 成员、NFT 系列持有者、或同一个 DeFi 协议的参与者。力导向布局在每次迭代中更新节点的 3D 位置收敛后把结果传给 Three.js 渲染器。节点大小映射到其 PageRank 值表示在网络中的影响力边的粗细映射到交互权重。三、核心实现代码// force-directed-3d.ts — 3D 力导向布局引擎 import * as d3Force from d3-force-3d; import * as THREE from three; import { OrbitControls } from three/examples/jsm/controls/OrbitControls; import { CSS2DRenderer, CSS2DObject } from three/examples/jsm/renderers/CSS2DRenderer; interface GraphNode { id: string; address: string; ens?: string; group: number; // Louvain 社区 ID pagerank: number; x?: number; y?: number; z?: number; } interface GraphEdge { source: string; target: string; weight: number; // 交互频率权重 } /** * 三维社交图谱可视化引擎 * * 设计决策将布局计算和数据渲染分离为两个独立的生命周期。 * 布局计算使用 d3-force-3d 在 worker 线程中进行避免阻塞主线程 * 渲染使用 Three.js 在主线程中进行需要访问 WebGL 上下文。 * 两者通过 SharedArrayBuffer 或 postMessage 传递节点坐标。 */ class SocialGraph3D { private scene: THREE.Scene; private camera: THREE.PerspectiveCamera; private renderer: THREE.WebGLRenderer; private labelRenderer: CSS2DRenderer; private controls: OrbitControls; // 节点和边的 Three.js 对象池 private nodeMeshes: Mapstring, THREE.Mesh new Map(); private edgeLines: Mapstring, THREE.Line new Map(); // 颜色方案按社区划分 private groupColors: THREE.Color[] []; constructor(container: HTMLElement) { // 场景初始化 this.scene new THREE.Scene(); this.scene.background new THREE.Color(0x0a0a1a); // 添加星空背景粒子增强科技感 this.addStarfieldBackground(); // 相机初始位置在右上角前方 this.camera new THREE.PerspectiveCamera( 55, container.clientWidth / container.clientHeight, 0.1, 1000 ); this.camera.position.set(50, 30, 80); // WebGL 渲染器 this.renderer new THREE.WebGLRenderer({ antialias: true, alpha: true }); this.renderer.setSize(container.clientWidth, container.clientHeight); container.appendChild(this.renderer.domElement); // CSS2D 标签渲染器用于渲染节点上的文字标签 this.labelRenderer new CSS2DRenderer(); this.labelRenderer.setSize(container.clientWidth, container.clientHeight); this.labelRenderer.domElement.style.position absolute; this.labelRenderer.domElement.style.top 0; this.labelRenderer.domElement.style.pointerEvents none; container.appendChild(this.labelRenderer.domElement); // 轨道控制 this.controls new OrbitControls(this.camera, this.renderer.domElement); this.controls.enableDamping true; this.controls.dampingFactor 0.08; this.controls.maxDistance 300; // 生成基于 HSL 的社区颜色方案 this.generateColorScheme(12); // 开始渲染循环 this.animate(); } private addStarfieldBackground(): void { const starsGeometry new THREE.BufferGeometry(); const starsCount 2000; const positions new Float32Array(starsCount * 3); for (let i 0; i starsCount * 3; i 3) { positions[i] (Math.random() - 0.5) * 400; positions[i 1] (Math.random() - 0.5) * 400; positions[i 2] (Math.random() - 0.5) * 400; } starsGeometry.setAttribute(position, new THREE.BufferAttribute(positions, 3)); const starsMaterial new THREE.PointsMaterial({ color: 0x4488ff, size: 0.3, transparent: true, opacity: 0.6 }); this.scene.add(new THREE.Points(starsGeometry, starsMaterial)); } /** * 运行力导向布局计算 * * 设计决策使用 d3-force-3d 的 jiggle 力而非 charge。 * jiggle 力每一步会做小幅度随机位移帮助逃离局部能量最小值。 * 传统 charge 力在大规模图中 O(n²) 的计算量会给浏览器带来卡顿 * 现代替代方案使用 Barnes-Hut 近似O(n log n)。 */ async computeLayout(nodes: GraphNode[], edges: GraphEdge[]): Promisevoid { return new Promise((resolve) { const simulation d3Force.forceSimulation(nodes as any, 3) .force(link, d3Force.forceLink(edges).distance((e: any) 50 / e.weight).strength(0.5)) .force(charge, d3Force.forceManyBody().strength(-300)) .force(center, d3Force.forceCenter(0, 0, 0)) .force(collision, d3Force.forceCollide().radius((n: any) 3 n.pagerank * 2)) .stop(); // 逐步迭代避免一次性计算阻塞主线程 const totalIterations 300; let currentIteration 0; const tick () { simulation.tick(10); // 每次 tick 10 步 currentIteration 10; if (currentIteration totalIterations) { requestAnimationFrame(tick); } else { simulation.stop(); resolve(); } }; requestAnimationFrame(tick); }); } /** * 将布局结果渲染为 Three.js 场景 * * 设计决策使用 InstancedMesh 渲染同质节点球体以减少 draw call。 * 当节点数超过 1000 时InstancedMesh 比逐个 Mesh 的 draw call 数量 * 从 1000 降至 ~10按材质分批帧率提升约 3-5 倍。 */ renderGraph(nodes: GraphNode[], edges: GraphEdge[]): void { // 清除旧场景 this.clearGraph(); // 渲染边使用 TubeGeometry 创建曲线管道 const edgeMaterial new THREE.MeshBasicMaterial({ color: 0x334466, transparent: true, opacity: 0.4, }); for (const edge of edges) { const sourceNode nodes.find(n n.id edge.source); const targetNode nodes.find(n n.id edge.target); if (!sourceNode?.x || !targetNode?.x) continue; const start new THREE.Vector3(sourceNode.x, sourceNode.y, sourceNode.z); const end new THREE.Vector3(targetNode.x, targetNode.y, targetNode.z); // 控制点让边在 Z 轴上有弧度 const mid new THREE.Vector3( (start.x end.x) / 2, (start.y end.y) / 2, (start.z end.z) / 2 5 ); const curve new THREE.QuadraticBezierCurve3(start, mid, end); const tubeGeometry new THREE.TubeGeometry(curve, 8, 0.1 * edge.weight, 4, false); const tube new THREE.Mesh(tubeGeometry, edgeMaterial); this.scene.add(tube); this.edgeLines.set(${edge.source}-${edge.target}, tube as any); } // 渲染节点InstancedMesh 优化 const sphereGeometry new THREE.SphereGeometry(1, 32, 32); // 按社区分组每组一个 InstancedMesh const groupMap new Mapnumber, { nodes: GraphNode[]; indices: number[] }(); for (let i 0; i nodes.length; i) { const g nodes[i].group; if (!groupMap.has(g)) { groupMap.set(g, { nodes: [], indices: [] }); } groupMap.get(g)!.nodes.push(nodes[i]); groupMap.get(g)!.indices.push(i); } for (const [groupId, groupData] of groupMap) { const color this.groupColors[groupId % this.groupColors.length]; const instancedMaterial new THREE.MeshPhongMaterial({ color: color, shininess: 60, emissive: color, emissiveIntensity: 0.2, }); const instancedMesh new THREE.InstancedMesh( sphereGeometry, instancedMaterial, groupData.nodes.length ); const matrix new THREE.Matrix4(); for (let i 0; i groupData.nodes.length; i) { const node groupData.nodes[i]; const scale 1 node.pagerank * 2; matrix.compose( new THREE.Vector3(node.x!, node.y!, node.z!), new THREE.Quaternion(), new THREE.Vector3(scale, scale, scale) ); instancedMesh.setMatrixAt(i, matrix); } this.scene.add(instancedMesh); for (const node of groupData.nodes) { this.nodeMeshes.set(node.id, instancedMesh as any); } } // 添加节点标签使用 CSS2D for (const node of nodes) { if (node.pagerank 0.05) { // 只给重要节点加标签 const labelDiv document.createElement(div); labelDiv.textContent node.ens || node.address.slice(0, 8) ...; labelDiv.style.cssText color:#88ccff;font-size:11px;background:rgba(0,0,0,0.7);padding:2px 6px;border-radius:3px;; const label new CSS2DObject(labelDiv); label.position.set(node.x!, node.y! 2, node.z!); this.scene.add(label); } } // 光照 const ambientLight new THREE.AmbientLight(0x404060, 0.6); const directionalLight new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(50, 50, 50); this.scene.add(ambientLight, directionalLight); } private clearGraph(): void { this.scene.children .filter(c !(c instanceof THREE.Points)) // 保留星空背景 .forEach(c this.scene.remove(c)); this.nodeMeshes.clear(); this.edgeLines.clear(); } private animate(): void { requestAnimationFrame(() this.animate()); this.controls.update(); this.renderer.render(this.scene, this.camera); this.labelRenderer.render(this.scene, this.camera); } }四、视觉编码与性能边界视觉编码通道的分配三维空间提供了更多的视觉通道但也有过载的风险。建议的编码规范是位置X/Y/Z 力导向布局的自然坐标颜色Hue 社区归属大小Scale PageRank 影响力分不透明度 活跃度最近 30 天的交互频率Z 轴深度 账号年龄老账号在前景。大规模图谱的性能限制InstancedMesh 可以支撑到约 10,000 个节点取决于 GPU。超过这个量级需要降采样策略——使用 K-Core 分解保留核心子图度为 k 及以上的节点或在布局阶段用 HNSW 图近似只渲染能放进屏幕的高阶节点。WebGL 兼容性Three.js 的 WebGL 后端在超过 90% 的现代浏览器上可用。但对于移动设备复杂的图渲染 2000 节点 阴影 后处理会导致 GPU 过热和帧率下降。移动端应该降低节点面数16 段球体替代 32 段、关闭阴影、使用线框模式。力导向布局的收敛与非收敛边界d3-force-3d 的布局在理想条件下约 200-300 次迭代后收敛节点位置变化 0.01但某些图结构如高度非连通的星形图可能永远不收敛——中心节点周围一圈孤立节点会因斥力而无限制地向外漂移。需要设置最大迭代次数 位置范围如 Box 约束[-100, 100]超出范围的节点固定在边界上并标记为未收敛在 UI 中给予视觉提示。五、总结Three.js 的社交关系三维网络图不是在炫耀技术而是在解决二维可视化无法解决的信息密度问题。力导向布局让关系结构自然浮现聚类着色让社区边界一目了然轨道控制器让用户从任意角度审视图谱——这三个工具的叠加让一个 2000 节点的复杂社交网络变得可以理解。在 Web3 场景中三维可视化的额外价值是可以同步展示链上资产流向和社交关系两张网络——在同一个三维空间中叠加两层图一层是关注网络社交资本流一层是转账网络金融资本流。两者的重合度揭示了很多信息社交网络中高关注的账号在资金网络中也可能是中心节点。