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
diagram-vue

A Vue 3 SVG-based diagram component library.

2,508 104
vnodes

Vue components to create svg interactive graphs, diagrams or node visual tools.

6,438 141
topology-graph-vue

基于 PixiJS v8 的高性能 Vue 3 拓扑图组件库,支持 50,000+ 节点以 60 FPS 流畅渲染

5,108
@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