AntV 入门系列:G6 图可视化实战
什么是图可视化?
你有没有想过,如何直观地展示人与人之间的关系?或者如何画出一个系统的架构图?这就是图可视化要解决的问题!
图(Graph) 由两种元素组成:
- 节点(Node):代表实体,比如人、公司、设备等
- 边(Edge):代表实体之间的关系
G6 就是专门用来处理这种关系数据的神器!
案例一:创建一个简单的社交关系图谱
让我们从一个简单的例子开始------创建一个朋友关系图谱。
安装 G6
首先安装 G6 库:
bash
npm install @antv/g6
创建 HTML 文件
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>社交关系图谱</title>
</head>
<body>
<div id="container" style="width: 800px; height: 600px;"></div>
<script src="./index.js"></script>
</body>
</html>
创建关系图谱
javascript
import G6 from '@antv/g6';
// 定义数据:节点和边
const data = {
nodes: [
{ id: 'Alice', label: 'Alice', x: 200, y: 300 },
{ id: 'Bob', label: 'Bob', x: 400, y: 150 },
{ id: 'Charlie', label: 'Charlie', x: 600, y: 300 },
{ id: 'David', label: 'David', x: 400, y: 450 },
],
edges: [
{ source: 'Alice', target: 'Bob', label: '朋友' },
{ source: 'Bob', target: 'Charlie', label: '同事' },
{ source: 'Charlie', target: 'David', label: '同学' },
{ source: 'David', target: 'Alice', label: '家人' },
],
};
// 创建图实例
const graph = new G6.Graph({
container: 'container',
width: 800,
height: 600,
// 默认节点样式
defaultNode: {
size: 60,
style: {
fill: '#5B8FF9',
stroke: '#5B8FF9',
lineWidth: 2,
},
labelCfg: {
style: {
fill: '#fff',
fontSize: 14,
},
},
},
// 默认边样式
defaultEdge: {
style: {
stroke: '#e2e2e2',
lineWidth: 2,
},
labelCfg: {
autoRotate: true,
style: {
fill: '#666',
fontSize: 12,
},
},
},
});
// 加载数据
graph.data(data);
// 渲染图表
graph.render();
运行后你会看到一个漂亮的社交关系图!
案例二:组织结构图
让我们来创建一个公司的组织结构图。
数据准备
javascript
const orgData = {
nodes: [
{ id: 'CEO', label: 'CEO', x: 400, y: 50 },
{ id: 'CTO', label: 'CTO', x: 200, y: 180 },
{ id: 'COO', label: 'COO', x: 600, y: 180 },
{ id: 'TechLead', label: '技术负责人', x: 200, y: 310 },
{ id: 'ProductLead', label: '产品负责人', x: 600, y: 310 },
{ id: 'Dev1', label: '开发工程师', x: 200, y: 440 },
{ id: 'Dev2', label: '开发工程师', x: 200, y: 550 },
],
edges: [
{ source: 'CEO', target: 'CTO' },
{ source: 'CEO', target: 'COO' },
{ source: 'CTO', target: 'TechLead' },
{ source: 'COO', target: 'ProductLead' },
{ source: 'TechLead', target: 'Dev1' },
{ source: 'TechLead', target: 'Dev2' },
],
};
创建树形布局
javascript
const graph = new G6.Graph({
container: 'container',
width: 800,
height: 600,
defaultNode: {
type: 'rect', // 矩形节点
size: [120, 50],
style: {
fill: '#5B8FF9',
stroke: '#5B8FF9',
lineWidth: 2,
radius: 8,
},
labelCfg: {
style: {
fill: '#fff',
fontSize: 14,
},
},
},
defaultEdge: {
type: 'polyline', // 折线
style: {
stroke: '#91cc75',
lineWidth: 2,
},
},
layout: {
type: 'dagre', // 树形布局
rankdir: 'TB', // 从上到下
nodesep: 20,
ranksep: 50,
},
});
graph.data(orgData);
graph.render();
案例三:流程图编辑器
让我们创建一个简单的流程图编辑器,支持拖拽创建节点!
创建编辑器
javascript
const graph = new G6.Graph({
container: 'container',
width: 800,
height: 600,
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node', 'click-select'],
},
defaultNode: {
size: 80,
style: {
fill: '#5B8FF9',
stroke: '#5B8FF9',
lineWidth: 2,
},
labelCfg: {
style: {
fill: '#fff',
fontSize: 14,
},
},
},
defaultEdge: {
style: {
stroke: '#e2e2e2',
lineWidth: 2,
endArrow: {
path: G6.Arrow.triangle(10, 10, 0),
fill: '#e2e2e2',
},
},
},
});
// 初始节点
const data = {
nodes: [
{ id: 'start', label: '开始', x: 100, y: 300, type: 'circle', style: { fill: '#91cc75' } },
{ id: 'process1', label: '处理步骤1', x: 350, y: 300 },
{ id: 'decision', label: '判断', x: 600, y: 300, type: 'diamond', style: { fill: '#fac858' } },
{ id: 'end', label: '结束', x: 850, y: 300, type: 'circle', style: { fill: '#ee6666' } },
],
edges: [
{ source: 'start', target: 'process1' },
{ source: 'process1', target: 'decision' },
{ source: 'decision', target: 'end' },
],
};
graph.data(data);
graph.render();
// 添加右键菜单
graph.on('contextmenu', (e) => {
const { x, y } = e.canvasPoint;
const node = {
id: `node-${Date.now()}`,
label: '新节点',
x,
y,
};
graph.addItem('node', node);
});
案例四:知识图谱
让我们创建一个知识图谱,展示《西游记》中的人物关系!
数据准备
javascript
const journeyData = {
nodes: [
{ id: '唐僧', label: '唐僧', category: '师徒', x: 400, y: 80 },
{ id: '孙悟空', label: '孙悟空', category: '师徒', x: 200, y: 220 },
{ id: '猪八戒', label: '猪八戒', category: '师徒', x: 400, y: 220 },
{ id: '沙僧', label: '沙僧', category: '师徒', x: 600, y: 220 },
{ id: '白龙马', label: '白龙马', category: '师徒', x: 400, y: 360 },
{ id: '如来佛祖', label: '如来佛祖', category: '神仙', x: 700, y: 100 },
{ id: '观音菩萨', label: '观音菩萨', category: '神仙', x: 100, y: 100 },
{ id: '牛魔王', label: '牛魔王', category: '妖怪', x: 150, y: 400 },
{ id: '白骨精', label: '白骨精', category: '妖怪', x: 650, y: 400 },
],
edges: [
{ source: '唐僧', target: '孙悟空', label: '徒弟' },
{ source: '唐僧', target: '猪八戒', label: '徒弟' },
{ source: '唐僧', target: '沙僧', label: '徒弟' },
{ source: '唐僧', target: '白龙马', label: '坐骑' },
{ source: '孙悟空', target: '牛魔王', label: '结拜兄弟' },
{ source: '观音菩萨', target: '唐僧', label: '指导' },
{ source: '如来佛祖', target: '孙悟空', label: '降服' },
{ source: '孙悟空', target: '白骨精', label: '打败' },
],
};
创建知识图谱
javascript
// 定义节点类型样式
const categoryStyles = {
'师徒': { fill: '#5B8FF9', stroke: '#5B8FF9' },
'神仙': { fill: '#91cc75', stroke: '#91cc75' },
'妖怪': { fill: '#ee6666', stroke: '#ee6666' },
};
const graph = new G6.Graph({
container: 'container',
width: 800,
height: 500,
defaultNode: {
size: 60,
labelCfg: {
style: {
fill: '#fff',
fontSize: 14,
},
},
},
defaultEdge: {
style: {
stroke: '#e2e2e2',
lineWidth: 2,
},
labelCfg: {
style: {
fill: '#666',
fontSize: 12,
},
},
},
// 自定义节点样式
nodeStateStyles: {
hover: {
scale: 1.1,
},
},
});
// 加载数据并设置样式
graph.data({
nodes: journeyData.nodes.map(node => ({
...node,
style: {
...categoryStyles[node.category],
},
})),
edges: journeyData.edges,
});
// 添加图例
const legend = new G6.Legend({
container: document.getElementById('container'),
data: [
{ name: '师徒', marker: { symbol: 'circle', fill: '#5B8FF9' } },
{ name: '神仙', marker: { symbol: 'circle', fill: '#91cc75' } },
{ name: '妖怪', marker: { symbol: 'circle', fill: '#ee6666' } },
],
});
graph.render();
// 添加交互效果
graph.on('node:mouseenter', (e) => {
const item = e.item;
graph.setItemState(item, 'hover', true);
});
graph.on('node:mouseleave', (e) => {
const item = e.item;
graph.setItemState(item, 'hover', false);
});
进阶技巧:自定义节点
G6 允许我们创建自定义形状的节点!
创建自定义节点
javascript
// 注册自定义节点
G6.registerNode('custom-node', {
draw(cfg, group) {
const { label } = cfg;
// 创建圆形背景
const circle = group.addShape('circle', {
attrs: {
x: 0,
y: 0,
r: 30,
fill: '#5B8FF9',
stroke: '#5B8FF9',
lineWidth: 2,
},
name: 'circle-shape',
});
// 创建文字
const text = group.addShape('text', {
attrs: {
x: 0,
y: 0,
text: label,
fill: '#fff',
fontSize: 14,
textAlign: 'center',
textBaseline: 'middle',
},
name: 'text-shape',
});
// 添加装饰元素
const star = group.addShape('polygon', {
attrs: {
points: [
[0, -35], [5, -28], [12, -30], [8, -23], [10, -15], [0, -20], [-10, -15], [-8, -23], [-12, -30], [-5, -28],
],
fill: '#FFD700',
},
name: 'star-shape',
});
return group;
},
});
// 使用自定义节点
const graph = new G6.Graph({
container: 'container',
width: 800,
height: 600,
defaultNode: {
type: 'custom-node',
},
});
实战练习:创建一个任务流程图
让我们来一个实战练习!创建一个简单的任务流程图。
需求分析
我们需要创建一个包含以下节点的流程图:
- 开始节点(圆形,绿色)
- 任务节点(矩形,蓝色)
- 判断节点(菱形,黄色)
- 结束节点(圆形,红色)
代码实现
javascript
const flowData = {
nodes: [
{ id: 'start', label: '开始', type: 'circle', style: { fill: '#91cc75', r: 30 } },
{ id: 'task1', label: '收集需求', type: 'rect', style: { fill: '#5B8FF9', width: 100, height: 40 } },
{ id: 'decision1', label: '需求明确?', type: 'diamond', style: { fill: '#fac858', width: 80, height: 80 } },
{ id: 'task2', label: '设计方案', type: 'rect', style: { fill: '#5B8FF9', width: 100, height: 40 } },
{ id: 'task3', label: '开发实现', type: 'rect', style: { fill: '#5B8FF9', width: 100, height: 40 } },
{ id: 'decision2', label: '测试通过?', type: 'diamond', style: { fill: '#fac858', width: 80, height: 80 } },
{ id: 'end', label: '结束', type: 'circle', style: { fill: '#ee6666', r: 30 } },
],
edges: [
{ source: 'start', target: 'task1' },
{ source: 'task1', target: 'decision1' },
{ source: 'decision1', target: 'task2', label: '是' },
{ source: 'decision1', target: 'task1', label: '否' },
{ source: 'task2', target: 'task3' },
{ source: 'task3', target: 'decision2' },
{ source: 'decision2', target: 'end', label: '是' },
{ source: 'decision2', target: 'task3', label: '否' },
],
};
const graph = new G6.Graph({
container: 'container',
width: 900,
height: 500,
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node'],
},
layout: {
type: 'dagre',
rankdir: 'LR', // 从左到右
nodesep: 20,
ranksep: 60,
},
defaultEdge: {
style: {
stroke: '#e2e2e2',
lineWidth: 2,
endArrow: {
path: G6.Arrow.triangle(8, 10, 0),
fill: '#e2e2e2',
},
},
labelCfg: {
style: {
fill: '#666',
fontSize: 12,
},
},
},
});
graph.data(flowData);
graph.render();