图可视化与关系网络
概述
图可视化(Graph Visualization)用于展示节点(Node)和边(Edge)组成的关系网络,常见于社交网络、知识图谱、组织架构、依赖分析等场景。
核心概念
图的数据结构
interface GraphNode {
id: string;
label: string;
x?: number;
y?: number;
size?: number;
color?: string;
[key: string]: unknown;
}
interface GraphEdge {
source: string; // 起始节点 ID
target: string; // 目标节点 ID
weight?: number;
label?: string;
}
interface GraphData {
nodes: GraphNode[];
edges: GraphEdge[];
}
图布局算法
| 布局 | 原理 | 适用场景 | 时间复杂度 |
|---|---|---|---|
| 力导向(Force-Directed) | 物理模拟(弹簧+电荷) | 通用关系网络 | / |
| 层次布局(Dagre) | DAG 分层排列 | 有向无环图、流程图 | |
| 圆形布局 | 节点均匀排列在圆上 | 固定拓扑 | |
| 径向布局 | 从中心向外辐射 | 以某节点为中心 | |
| 树形布局 | 根 → 叶递归排列 | 树形结构 |
力导向布局原理
// 简化的力导向布局实现
interface ForceNode extends GraphNode { vx: number; vy: number }
function forceLayout(nodes: ForceNode[], edges: GraphEdge[], iterations: number): void {
const k = Math.sqrt((800 * 600) / nodes.length); // 理想距离
for (let iter = 0; iter < iterations; iter++) {
// 1. 斥力:每对节点之间互相排斥(库仑力)
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
const dx = nodes[j].x! - nodes[i].x!;
const dy = nodes[j].y! - nodes[i].y!;
const dist = Math.max(Math.sqrt(dx * dx + dy * dy), 0.01);
const force = (k * k) / dist; // 斥力与距离成反比
const fx = (dx / dist) * force;
const fy = (dy / dist) * force;
nodes[i].vx -= fx;
nodes[i].vy -= fy;
nodes[j].vx += fx;
nodes[j].vy += fy;
}
}
// 2. 引力:连接的节点之间互相吸引(弹簧力)
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
edges.forEach((e) => {
const source = nodeMap.get(e.source)!;
const target = nodeMap.get(e.target)!;
const dx = target.x! - source.x!;
const dy = target.y! - source.y!;
const dist = Math.sqrt(dx * dx + dy * dy);
const force = dist / k; // 引力与距离成正比
const fx = (dx / dist) * force;
const fy = (dy / dist) * force;
source.vx += fx;
source.vy += fy;
target.vx -= fx;
target.vy -= fy;
});
// 3. 应用速度(带阻尼衰减)
const damping = 0.9 - (iter / iterations) * 0.8;
nodes.forEach((n) => {
n.x! += n.vx * damping;
n.y! += n.vy * damping;
n.vx *= 0.5;
n.vy *= 0.5;
});
}
}
技术选型
| 库 | 底层渲染 | 特点 | 适用场景 |
|---|---|---|---|
| AntV G6 | Canvas/SVG | 功能全面、布局丰富 | 关系分析、知识图谱 |
| vis.js | Canvas | 轻量易用 | 简单网络图 |
| Cytoscape.js | Canvas | 生物信息学背景 | 复杂图分析 |
| D3-force | SVG | 灵活可定制 | 自定义力导向 |
| Sigma.js | WebGL | 超大图渲染 | 大规模网络 |
交互设计
常见交互
// 节点拖拽
function enableNodeDrag(canvas: HTMLCanvasElement, nodes: GraphNode[]): void {
let dragNode: GraphNode | null = null;
canvas.addEventListener('pointerdown', (e) => {
const { offsetX, offsetY } = e;
dragNode = nodes.find((n) =>
Math.hypot(n.x! - offsetX, n.y! - offsetY) < (n.size || 10)
) || null;
});
canvas.addEventListener('pointermove', (e) => {
if (!dragNode) return;
dragNode.x = e.offsetX;
dragNode.y = e.offsetY;
render(); // 重绘
});
canvas.addEventListener('pointerup', () => { dragNode = null; });
}
高亮关联
点击节点时高亮其直接邻居,其余节点灰化:
function highlightNeighbors(graph: GraphData, selectedId: string): void {
const neighborIds = new Set<string>();
neighborIds.add(selectedId);
graph.edges.forEach((e) => {
if (e.source === selectedId) neighborIds.add(e.target);
if (e.target === selectedId) neighborIds.add(e.source);
});
graph.nodes.forEach((n) => {
n.color = neighborIds.has(n.id) ? n.color : '#ddd'; // 非邻居灰化
});
}
大规模图优化
| 数据规模 | 优化策略 |
|---|---|
| < 500 节点 | 直接渲染,SVG 可用 |
| 500 - 5000 | Canvas 渲染,力导向优化 |
| 5000 - 50000 | WebGL 渲染(Sigma.js),LOD |
| > 50000 | 聚合/采样,服务端计算布局 |
常见面试问题
Q1: 力导向布局的原理是什么?
答案:
模拟物理系统:所有节点之间有斥力(库仑力,防止重叠),有边连接的节点之间有引力(弹簧力,保持连接)。通过迭代模拟直到系统达到能量最低的平衡态。D3 用 Barnes-Hut 算法把 优化到 。
Q2: 如何优化大规模图的渲染?
答案:
- 渲染层:Canvas/WebGL 替代 SVG
- 布局计算:Web Worker 或服务端计算
- LOD:远距离只显示节点,近距离显示标签和边
- 聚合:社区检测后合并密集子图
- 虚拟化:只渲染视口内节点
Q3: 图可视化的常见布局算法有哪些?
答案:
力导向(通用关系网络)、层次/Dagre(有向无环图)、圆形(等价关系)、径向(以某节点为中心)、树形(层级结构)。选择取决于数据特征和展示目标。