vue3-network-graph
A Vue 3 + SVG network topology graph component: node dragging, infinite pan & zoom, Ctrl-drag marquee multi-select, mouse-to-SVG coordinate APIs. Supports 8 icon node types + text nodes + border-only nodes. No third-party chart dependencies.
1,236
| Install | |
|---|---|
npm install vue3-network-graph |
|
| Latest Version: | 0.1.7 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Sep 28, 2026 |
| Links: | Gitee · npm |
Maintainer: zjy-web
vue3-network-graph 使用说明
基于 Vue 3 + SVG 的轻量级网络拓扑图组件,无第三方图表库依赖。支持节点拖拽、画布无限平移、滚轮缩放、连线流向动画、三种连线模式、同节点对多连线防重叠、连线文字沿线直立等能力。
目录
名称
vue3-network-graph - Vue3 网络拓扑图组件
描述
一个纯 SVG 实现的轻量级网络拓扑图组件,提供以下核心能力:
核心功能
- 多种连线模式:直线(straight)/ 折线(polyline)/ 曲线(curve)
- 智能多线处理:同一对节点的多条连线自动防重叠,任何模式下都保持平行
- 连线文字直立:
fromText/centerText/toText沿路径切向旋转,超过±90° 自动翻转保持可读 - 丰富的节点类型:8 种图标节点 + 文本节点 + 纯边框节点
- 2D/3D 视图:支持 2D 平面和 2.5D 卡片风格
- 流向动画:虚线流光 + 粒子效果,可单独控制每条连线的动画开关
交互能力
- 节点操作:拖拽移动、拖拽调整尺寸、多选整体拖动
- 画布操作:无限平移(空白左键/中键)、滚轮缩放(0.2x–5x)
- 选择模式:单击选中、Ctrl+ 点击多选、Ctrl+ 左键框选
- 连线创建:从节点手柄拖拽到目标节点自动连线
- 图层管理:Z 轴排序、右键菜单、快捷键操作(需启用
enableZOrder)
性能优化
- 自动降级:装饰动效根据规模自动关闭(连线>200 关流光、>500 关发光、节点>300 关呼吸)
- 大图性能:千级节点可流畅平移/拖拽,密集图形下节点优先响应点击
使用方法
1. 安装
npm install vue3-network-graph
# 或:pnpm add vue3-network-graph | yarn add vue3-network-graph
2. 引入组件
// main.js
import { createApp } from "vue";
import { Topology } from "vue3-network-graph";
import "vue3-network-graph/style.css";
const app = createApp(App);
app.component("Topology", Topology);
app.mount("#app");
或局部引入:
<script setup>
import { Topology } from "vue3-network-graph";
import "vue3-network-graph/style.css";
</script>
3. 基本使用
<template>
<Topology
ref="topoRef"
:editable="true"
v-model:nodes="myNodes"
v-model:edges="myEdges"
:config="config"
@node-click="handleNodeClick"
@selection-change="handleSelectionChange"
/>
</template>
<script setup>
import { ref } from "vue";
import { Topology } from "vue3-network-graph";
const myNodes = ref([
{ id: "node1", label: "节点 1", type: "core", state: "normal", x: 0, y: 0 },
{
id: "node2",
label: "节点 2",
type: "server",
state: "normal",
x: 200,
y: 100,
},
]);
const myEdges = ref([
{ id: "edge1", from: "node1", to: "node2", centerText: "连接" },
]);
const config = ref({
showGrid: true,
nodeDraggable: true,
nodeResizable: true,
});
</script>
使用例子
完整示例
<template>
<div class="topology-container">
<Topology
ref="topoRef"
:editable="true"
v-model:nodes="nodes"
v-model:edges="edges"
v-model:line-mode="lineMode"
v-model:view-mode="viewMode"
:groups="groups"
:config="config"
@node-click="onNodeClick"
@edge-select="onEdgeSelect"
@selection-change="onSelectionChange"
@data-change="onDataChange"
@edge-create="onEdgeCreate"
>
<!-- 自定义节点图标插槽 -->
<template #node-icon="{ node, color, viewMode }">
<circle r="16" :fill="color" opacity="0.9" />
</template>
</Topology>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { Topology } from "vue3-network-graph";
import "vue3-network-graph/style.css";
// 节点数据
const nodes = ref([
{
id: "core",
label: "核心交换机",
nodeType: "core",
state: "normal",
x: 0,
y: 0,
width: 80,
height: 80,
},
{
id: "server1",
label: "Web 服务器",
nodeType: "server",
state: "normal",
x: 300,
y: -150,
},
{
id: "db1",
label: "数据库",
nodeType: "database",
state: "idle",
x: 300,
y: 150,
},
// 纯边框节点示例
{
id: "border-box",
label: "",
nodeType: "border-only",
state: "none",
x: -400,
y: -200,
width: 250,
height: 150,
borderWidth: 2,
borderColor: "#00f0ff",
},
]);
// 连线数据
const edges = ref([
{ id: "e1", from: "core", to: "server1", speed: 1.5, endMarker: "arrow" },
{
id: "e2",
from: "core",
to: "db1",
speed: 2,
centerText: "查询流量",
endMarker: "arrow",
},
]);
// 分组示例
const groups = ref([
{
id: "g1",
label: "应用区",
color: "#409eff",
nodeIds: ["server1", "db1"],
padding: 20,
},
]);
// 连线模式和视图模式
const lineMode = ref("curve"); // straight | polyline | curve
const viewMode = ref("2d"); // 2d | 3d
// 配置项
const config = ref({
selectedBorderColor: "#ffd400",
selectedLineColor: "#ffd400",
nodeDraggable: true,
nodeResizable: true,
enableZoom: true,
enablePan: true,
enableMarquee: true,
showGrid: true,
gridColor: "#1a2240",
gridStep: 40,
gridOpacity: 0.5,
radialGradient: {
startColor: "#0f1729",
endColor: "#060a14",
},
showStatusDot: true,
motionFx: true,
maxSelectNodes: "unlimited",
maxSelectEdges: "unlimited",
enableZOrder: false,
});
// 组件实例引用
const topoRef = ref(null);
// 事件处理函数
const onNodeClick = (node) => {
console.log("节点被点击:", node);
};
const onEdgeSelect = (edge) => {
console.log("连线被选中:", edge);
};
const onSelectionChange = (selection) => {
console.log("选中变化:", selection);
console.log("选中节点:", selection.nodes);
console.log("选中连线:", selection.edges);
};
const onDataChange = (payload) => {
console.log("数据变动:", payload);
console.log("变动摘要:", payload.summary);
};
const onEdgeCreate = (edge) => {
console.log("新建连线:", edge);
// 持久化新创建的连线
edges.value = [...edges.value, edge];
};
// 方法调用示例
const handleMethods = () => {
// 获取当前数据
const data = topoRef.value.getData();
// 添加节点
topoRef.value.addNodes([
{ label: "新节点", nodeType: "server", x: 500, y: 0 },
]);
// 添加连线
topoRef.value.addEdges([{ from: "core", to: "new-node-id" }]);
// 更新节点
topoRef.value.updateNodes([{ id: "core", label: "更新后的名称" }]);
// 删除选中元素
topoRef.value.removeSelected();
// 坐标转换
const svgPos = topoRef.value.clientToSvg(event);
const clientPos = topoRef.value.svgToClient(node.x, node.y);
// 图层管理(需启用 enableZOrder)
topoRef.value.moveToFront("node-id");
topoRef.value.moveToBack("node-id");
topoRef.value.moveLayerUp("node-id");
topoRef.value.moveLayerDown("node-id");
};
</script>
<style scoped>
.topology-container {
width: 100%;
height: 600px;
background: radial-gradient(ellipse at center, #0f1729 0%, #060a14 100%);
}
</style>
全局配置属性说明
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
selectedBorderColor |
String |
#fbbf24 |
选中节点的描边颜色 |
selectedLineColor |
String |
#fbbf24 |
选中连线的颜色 |
nodeDraggable |
Boolean |
true |
节点拖拽开关(需 editable) |
nodeResizable |
Boolean |
true |
节点尺寸调整开关(需 editable) |
enableZoom |
Boolean |
true |
滚轮缩放开关 |
enablePan |
Boolean |
true |
画布拖拽平移开关 |
enableMarquee |
Boolean |
true |
Ctrl + 左键框选开关 |
maxSelectNodes |
Number | "unlimited" |
不限制 | 最多选中节点数:0(禁止)/ 1 / 2 或任意非负整数;不设或 "unlimited" 不限制 |
maxSelectEdges |
Number | "unlimited" |
不限制 | 最多选中连线数(合并线按整组内全部连线计数) |
showGrid |
Boolean |
true |
网格背景(设 false 关闭) |
gridColor |
String |
#1a2240 |
网格线颜色 |
gridStep |
Number |
40 |
网格间距(1x 缩放下的 px,随缩放联动) |
gridOpacity |
Number |
0.5 |
网格叠层透明度(0–1) |
radialGradient |
Object |
见说明 | 画布背景径向渐变 |
showCenterPoint |
Boolean |
false |
画布中心十字标记 |
showEdgeTextBg |
Boolean |
false |
连线文字底牌 |
edgeTextBgColor |
String |
#0a0e1a |
连线文字底牌颜色 |
showNodeHandles |
Boolean |
false |
节点手柄(开启后节点上下左右出现四个小圆圈,拖到另一节点松开自动连线) |
showStatusDot |
Boolean |
true |
节点右上角状态圆点开关 |
labelGap |
Number |
18 |
节点标签与节点盒边缘的固定间距(1x 缩放下 px) |
watchDataChanges |
Boolean |
false |
组件内数据变动检测(防抖 300ms 后触发 data-change) |
fx |
Boolean |
自动 | 装饰动效总开关:false 全关 / true 全开 / 不设 = 自动 |
motionFx |
Boolean |
自动 | 连接线动画(流光 + 粒子)单项覆盖 |
glowFx |
Boolean |
自动 | 连线发光单项覆盖 |
breatheFx |
Boolean |
自动 | 呼吸灯单项覆盖 |
fxMotionMaxEdges |
Number |
200 |
流光 + 粒子动画自动关闭的连线数阈值 |
fxGlowMaxEdges |
Number |
500 |
连线发光自动关闭的连线数阈值 |
fxBreatheMaxNodes |
Number |
300 |
呼吸灯自动关闭的节点数阈值 |
enableZOrder |
Boolean |
false |
启用 Z 轴层级排序:启用后节点按 zIndex 属性渲染,支持图层操作 API、右键菜单和快捷键 |
radialGradient 配置
// 简单模式
radialGradient: {
startColor: "#0f1729",
endColor: "#060a14"
}
// 高级模式(自定义渐变中心和色带)
radialGradient: {
cx: "50%",
cy: "50%",
r: "50%",
stops: [
{ offset: "0%", color: "#0f1729", opacity: 1 },
{ offset: "60%", color: "#1e293b", opacity: 0.8 },
{ offset: "100%", color: "#060a14", opacity: 1 }
]
}
节点属性说明
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
id |
String |
✅ | - | 节点唯一标识 |
label |
String | null |
❌ | null |
节点显示文本(支持 \n 换行),纯边框节点和文本节点可设为空 |
nodeType |
String |
❌ | "server" |
节点类型(见下方类型列表) |
state |
String |
❌ | "none" |
节点状态(见下方状态列表) |
x |
Number |
✅ | - | 节点 X 坐标(SVG 坐标系) |
y |
Number |
✅ | - | 节点 Y 坐标(SVG 坐标系) |
width |
Number |
❌ | 60 |
节点宽度(纯边框节点必需) |
height |
Number |
❌ | 60 |
节点高度(纯边框节点必需) |
rotation |
Number |
❌ | 0 |
节点旋转角度(度) |
fontColor |
String |
❌ | "#ffffff" |
文字颜色 |
fontSize |
Number |
❌ | 11 |
文字大小 |
fontWeight |
Number | String |
❌ | 500 |
字重(100–900) |
borderColor |
String |
❌ | "#94a3b8" |
边框颜色 |
borderWidth |
Number |
❌ | 0 |
边框宽度(0 为无边框) |
borderStyle |
"full" | "dashed" | "none" |
❌ | "full" |
边框样式:"full" 实线,"dashed" 虚线,"none" 无边框 |
background |
String |
❌ | "#0f1729" |
背景颜色 |
borderRadius |
Number |
❌ | 6 |
圆角半径(px) |
labelPosition |
"top" | "center" | "bottom" |
❌ | "bottom" |
标签位置 |
zIndex |
Number |
❌ | 0 |
Z 轴层级索引(需启用 enableZOrder) |
节点类型(nodeType)
| 类型 | 图标 | 说明 |
|---|---|---|
"core" |
六边形 | 核心设备 |
"server" |
矩形堆叠 | 服务器 |
"firewall" |
带横线的矩形 | 防火墙 |
"loadbalancer" |
圆形 + 折线 | 负载均衡 |
"database" |
圆柱体 | 数据库 |
"cdn" |
同心圆 | CDN 节点 |
"cache" |
六边形 + 圆心 | 缓存 |
"queue" |
带竖线的矩形 | 队列 |
"text" |
文本行 | 纯文本节点 |
"border-only" |
空心矩形 | 纯边框节点(仅显示边框轮廓) |
节点状态(state)
| 状态 | 颜色 | 说明 |
|---|---|---|
"none" |
隐藏 | 无状态指示 |
"normal" |
绿色 | 正常 |
"warning" |
黄色 | 警告 |
"error" |
红色 | 错误 |
"info" |
蓝色 | 信息 |
"active" |
青色 | 活跃 |
"disabled" |
灰色 | 禁用 |
"locked" |
紫色 | 锁定 |
"master" |
深红 | 主节点 |
"expert" |
粉红 | 专家节点 |
"advanced" |
橙色 | 高级节点 |
"adept" |
浅橙 | 熟练节点 |
"awakened" |
蓝色 | 觉醒节点 |
"dormant" |
灰紫 | 休眠节点 |
连线属性说明
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
id |
String |
✅ | - | 连线唯一标识(自动生成 uuid) |
from |
String |
✅ | - | 起始节点 id |
to |
String |
✅ | - | 目标节点 id |
speed |
Number |
❌ | 1 |
流速倍率(动画速度) |
animated |
Boolean |
❌ | true |
单条线动画开关(false 关闭该线流光/粒子) |
fromAnchor |
"center" | "edge" |
❌ | "edge" |
起点锚点:"center" 中心 / "edge" 边界交点 |
toAnchor |
"center" | "edge" |
❌ | "edge" |
终点锚点:"center" 中心 / "edge" 边界交点 |
fromText |
String | Object |
❌ | "" |
起点文字(见下方文字格式) |
centerText |
String | Object |
❌ | "" |
中心文字(见下方文字格式) |
toText |
String | Object |
❌ | "" |
终点文字(见下方文字格式) |
endMarker |
"none" | "arrow" | "dot" |
❌ | "none" |
终点标记:"none" 无 / "arrow" 箭头 / "dot" 圆点 |
fontSize |
Number |
❌ | 8 |
连线文字大小 |
fontColor |
String |
❌ | "#00f0ff" |
连线文字颜色 |
连线文字格式
// 字符串简写形式
fromText: "起点文字"
// 对象完整形式
fromText: {
text: "起点文字", // 文字内容
rotation: 0, // 额外旋转角(度)
fontSize: 10, // 字号
color: "#ffffff" // 颜色
}
调用方法
通过模板 ref 调用组件暴露的方法:
<template>
<Topology ref="topoRef" />
</template>
<script setup>
import { ref } from "vue";
const topoRef = ref(null);
// 所有方法调用示例
const methods = () => {
// === 数据方法 ===
// 获取当前最新数据(深拷贝)
const { nodes, edges } = topoRef.value.getData();
// 重置数据变动检测基线
topoRef.value.resetDataBaseline();
// 组件外全量替换数据集
topoRef.value.setData({ nodes: nextNodes });
// 删除当前选中的节点与连线(删节点时级联删除所有与该节点相连的连线)
const removed = topoRef.value.removeSelected();
// 添加节点(数组或单个)
topoRef.value.addNodes([{ label: "新节点", x: 0, y: 0 }]);
// 添加连线(数组或单个)
topoRef.value.addEdges([{ from: "a", to: "b" }]);
// 按 id 局部更新节点(浅合并,undefined 忽略,未知 id 跳过)
topoRef.value.updateNodes([{ id: "node-1", label: "改名", width: 120 }]);
// 按 id 局部更新连线
topoRef.value.updateEdges([{ id: "edge-1", speed: 2 }]);
// === 图层管理(需 enableZOrder: true)===
// 上一层
topoRef.value.moveLayerUp("node-id");
// 下一层
topoRef.value.moveLayerDown("node-id");
// 最上层
topoRef.value.moveToFront("node-id");
// 最底层
topoRef.value.moveToBack("node-id");
// === 坐标转换 ===
// 场景一:鼠标坐标 → SVG 坐标(参数可传事件对象或坐标对)
const p1 = topoRef.value.clientToSvg(e); // 直接传事件对象
const p2 = topoRef.value.clientToSvg(clientX, y); // 传坐标对
// 场景二:SVG 坐标 → 屏幕坐标(输入 node.x / node.y,返回 clientX/clientY)
const node = topoRef.value.getData().nodes[0];
const { x, y } = topoRef.value.svgToClient(node.x, node.y);
menu.style.left = `${x}px`;
menu.style.top = `${y}px`;
};
</script>
事件说明
| 事件名 | 参数 | 说明 |
|---|---|---|
update:nodes |
nodes |
节点数据更新(v-model:nodes);拖拽/尺寸调整过程中持续触发 |
update:lineMode |
string |
连线模式变更(v-model:line-mode) |
update:viewMode |
string |
视图模式变更(v-model:view-mode) |
node-click |
node |
节点被点击 |
node-select |
node | null |
单选节点变化(多选场景请用 selection-change) |
edge-select |
edge | null |
连线选中变化(合并线附带 bundleEdges 数组) |
selection-change |
{ nodes, edges } |
选中集合变化时触发;nodes/edges 为选中的节点与连线对象数组 |
data-change |
{ nodes, edges, summary } |
watchDataChanges 开启时,数据变动防抖(300ms)后触发 |
edge-create |
edge |
节点手柄拖拽连接两个节点后触发 |
edge-remove |
edges |
删除连线后触发(含因节点删除而级联删除的连线) |
edge-update |
edges |
updateEdges() 修改连线后触发 |
node-drag-start |
node |
节点拖拽开始;拖动多选集合时同样触发,payload 为被按住的那个节点 |
node-drag-end |
— | 节点拖拽结束 |
node-resize-start |
node |
尺寸调整开始 |
node-resize-end |
node | null |
尺寸调整结束 |
selection-change 事件详解
// 支持多种选择方式
// 1. Ctrl+ 点击多选
// 2. Ctrl+ 左键框选
// 3. 普通点击单选
// 4. 点击空白处清空
@selection-change="(sel) => {
console.log('选中节点:', sel.nodes.map(n => n.id));
console.log('选中连线:', sel.edges.map(e => e.id));
}"
交互操作
| 操作 | 手势 | 说明 |
|---|---|---|
| 选中节点/连线 | 左键单击 | 重复点击已选中项取消选中;点击空白处清空全部选中 |
| 多选(点选) | Ctrl(macOS ⌘)+ 单击 | 将节点/连线加入或移出选中集合,支持节点与连线混合多选 |
| 多选(框选) | Ctrl(macOS ⌘)+ 左键拖动 | 在空白画布拉出矩形选框,与选框相交的节点并入当前选中(并集语义;仅节点参与框选) |
| 拖动单个节点 | 按住节点区域任意处拖动 | 需 editable 开启且 config.nodeDraggable !== false;拖动未选中的节点会先选中它 |
| 拖动多个节点 | 拖动任一已选中节点 | 选中集合整体移动,所有节点位移一致;松开后选中状态保留 |
| 画布平移(无限) | 空白处按住左键拖动;或任意位置按住中键拖动 | 平移无边界限制;内容与网格一起移动;不改变选中状态 |
| 缩放 | 滚轮 | 以鼠标位置为中心缩放(0.2x–5x) |
| 调整节点尺寸 | 选中单个节点后拖动 8 个控制点 | 需 editable 且 config.nodeResizable !== false |
| 拖拽创建连线 | 从节点手柄拖到目标节点 | 需 config.showNodeHandles: true |
图层管理快捷键(需启用 enableZOrder)
| 快捷键 | 操作 |
|---|---|
Ctrl/Cmd + Shift + ↑ |
上一层 |
Ctrl/Cmd + Shift + ↓ |
下一层 |
Ctrl/Cmd + Shift + ← |
最底层 |
Ctrl/Cmd + Shift + → |
最上层 |
交互优先级说明
- 节点与连线重叠处:点击/拖动作用于节点;连线在未被遮挡处正常点击——密集图形下节点始终可选中、可拖动
- 点击与拖动区分:左键在空白处的「点击」与「拖动」按小阈值区分(平移 4px / 框选 3px / 节点拖动 3px):小于阈值仍视为点击(清空或切换选中),超过阈值才进入拖动
补充说明
数据同步机制
props.nodes / props.edges 为深度同步——父级整体替换数组、或就地修改字段,组件内都会跟随更新;组件内的拖拽/尺寸/增删改也会经 update:nodes、edge-create、edge-update、edge-remove 即时外发。父级把组件发出的数据原样写回不会造成循环或重复重建。
加载一整套新数据时,先更新你的 nodes / edges,再调用 setData() 清理组件内残留状态。
TypeScript 支持
import type { TopologyInstance } from "vue3-network-graph";
const topoRef = ref<TopologyInstance | null>(null);
// 类型安全的方法调用
topoRef.value?.getData();
topoRef.value?.clientToSvg(event);
坐标系说明
- 组件的节点坐标(
node.x/node.y)位于 SVG 用户坐标系 - 画布的缩放与平移由
viewBox实现,因此该坐标系与屏幕像素之间的换算随视图状态变化 clientToSvg使用矩阵变换,自动折算当前缩放与平移,返回的就是与node.x/node.y同源的坐标svgToClient是它的逆变换:输入节点坐标,返回屏幕clientX/clientY,适合与 DOM 浮层配合
许可证
MIT © 2026 vnodegit
Related Packages
@livereader/graphly-d3
Library for creating powerful and interactive graph visualizations with ease.
4,176
31
@grafloria/vue
Vue 3 bindings for Grafloria Diagrams and Grafloria Dashboards — an MIT diagram...
3,136
12
Version History
| Version | Released | License |
|---|---|---|
| 0.1.7 | MIT | |
| 0.1.6 | MIT | |
| 0.1.5 | MIT | |
| 0.1.4 | MIT | |
| 0.1.3 | MIT | |
| 0.1.2 | MIT | |
| 0.1.1 | MIT | |
| 0.1.0 | MIT |