topology-graph-vue
v1.2.7
Published
High-performance Vue 3 topology graph component library based on PixiJS v8, supporting 50,000+ nodes at 60 FPS
Maintainers
Readme
TopologyGraph Vue
高性能 Vue 3 拓扑图组件库,基于 PixiJS v8,支持 50,000+ 节点 60 FPS 渲染。
特性
- 🚀 极致性能: LOD 多级降级渲染 + 空间索引裁剪 + 对象池复用 + GPU 预览模式 + Float32Array 力导向迭代,5W+ 节点流畅运行
- 🎨 丰富图形: 5 种节点形状 (圆/矩形/菱形/六边形/星形) × 3 种线型 (实线/虚线/点线) × 5 种曲线 (直线/贝塞尔/弧线/阶梯HV/VH)
- 📦 插件化架构: Vue 插件全局注册 + provide/inject 子组件协作,Minimap / SelectionBox / Toolbar 即插即用
- 🔧 完整 TypeScript: 全程类型覆盖,构建自动生成
.d.ts声明文件 - 🎯 丰富交互: 拖拽/缩放/框选(空间索引加速)/多选/撤销重做/搜索聚焦/右键菜单穿透
- 🎨 双主题: 亮色 / 暗色主题一键切换,运行时可自定义覆盖背景颜色和透明度
- 📐 4 种布局: 力导向(Barnes-Hut 近似) / 树形(4 方向展开) / 径向 / 环形
- 📚 LOD 渲染: 4 级降级(detail → normal → simple → dot),缩放时自动切换渲染精度
- 🔒 节点/连线固定渲染: 通过
forceDetail属性强制指定节点或连线始终以 detail 模式渲染,无视 LOD 缩放降级,最小半径 10px 保证可见性 - ✏️ 自定义绘制: 通过
drawNode/drawLine回调完全覆盖默认渲染,内置图片加载缓存和绘制工具集 - 🎬 连线动画: 流动 / 快速流动 / 脉冲 / 虚线流动 4 种动画效果
- 📐 多连线智能递进: 阶梯线(step-hv/step-vh)多连线自动采用几何自适应递进算法,根据节点间短轴长度动态调整折点位置和分离距离,支持高度感知方向判断,彻底解决多连线重叠问题
- 📝 路径文字排列: 所有曲线类型(直线/贝塞尔/弧线/阶梯线)的文字标签自动沿路径方向排列显示,角度和位置精确计算
- 📸 高清图片导出: 支持当前视口截图和 100% 比例完整拓扑图导出,完整导出会包含全部节点、连线、背景网格及边缘文字,支持 PNG/JPEG/WebP 格式和高清倍率
- 🔄 运行时配置热更新:
setOptions()支持动态修改 LOD 阶梯表、性能参数、主题、样式等全部配置项 - ➕ 增量数据操作: 运行时动态添加/删除节点连线 (
addNode/removeNodesByIds等) - 🧠 智能历史管理: 大数据量(>10000 节点) 自动禁用撤销以节省内存,快照数量自适应限制
- 🖱️ 右键事件穿透: 选区框(SelectionBox) 支持右键菜单穿透转发到底层 canvas,完整触发
canvas-contextmenu事件链路 - 🔍 聚焦高亮系统: 支持节点/连线聚焦、搜索飞行定位、闪烁提示等高级交互
安装
npm install topology-graph-vue依赖要求:
- Node.js >= 18.0.0
- Vue ^3.3.0 || ^3.4.0 || ^3.5.0
- PixiJS ^8.17.1 (自动安装)
快速开始
方式一:全局注册 (推荐)
import { createApp } from 'vue'
import TopologyGraph from 'topology-graph-vue'
import 'topology-graph-vue/style.css'
const app = createApp(App)
app.use(TopologyGraph)
app.mount('#app')全局注册后可在模板中直接使用:
| 组件 | 说明 |
|------|------|
| <TopologyGraph> | 主组件(画布 + API 注入) |
| <TopologyGraphMinimap> | 小地图 |
| <TopologyGraphSelectionBox> | 选区操作框(含右键穿透) |
| <TopologyGraphToolbar> | 浮动工具栏 |
<template>
<TopologyGraph @ready="onReady" @canvas-contextmenu="onContextMenu">
<TopologyGraphMinimap position="right-bottom" />
<TopologyGraphSelectionBox />
</TopologyGraph>
</template>
<script setup>
function onReady(api) {
api.setJsonData({
nodes: [
{ id: '1', text: '节点1' },
{ id: '2', text: '节点2' },
],
lines: [{ from: '1', to: '2' }],
})
api.applyLayout({ type: 'force' })
}
function onContextMenu(event, targetType, target) {
console.log('右键点击:', { targetType, target })
}
</script>方式二:按需引入
<script setup>
import { TopologyGraph, Minimap, SelectionBox } from 'topology-graph-vue'
import 'topology-graph-vue/style.css'
</script>
<template>
<TopologyGraph @ready="onReady">
<Minimap position="right-bottom" />
<SelectionBox />
</TopologyGraph>
</template>完整示例
<template>
<div style="width: 100vw; height: 100vh;">
<TopologyGraph :options="{ theme: 'light', enableHistory: true }" @ready="onReady">
<GraphToolbar position="top-right" />
<Minimap :width="200" :height="150" position="right-bottom" />
<SelectionBox />
</TopologyGraph>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { TopologyGraph, Minimap, SelectionBox, GraphToolbar } from 'topology-graph-vue'
import 'topology-graph-vue/style.css'
const graphApi = ref(null)
function onReady(api) {
graphApi.value = api
// 设置数据(兼容 relation-graph JSON 格式)
api.setJsonData({
nodes: [
{ id: '1', text: '节点A', color: '#1890ff' },
{ id: '2', text: '节点B', color: '#52c41a' },
{ id: '3', text: '节点C' },
],
lines: [
{ from: '1', to: '2', showEndArrow: true },
{ from: '2', to: '3' },
],
})
// 应用力导向布局
api.applyLayout({ type: 'force' })
// 运行时动态调整 LOD 配置
api.setOptions({
lodTiers: [
{ minScale: 1.0, nodeMode: 'detail', lineMode: 'detail', nodeDowngradeThreshold: 200, lineDowngradeThreshold: 400 },
{ minScale: 0.3, nodeMode: 'normal', lineMode: 'normal', nodeDowngradeThreshold: 500, lineDowngradeThreshold: 1000 },
{ minScale: 0.2, nodeMode: 'simple', lineMode: 'simple', nodeDowngradeThreshold: 2000, lineDowngradeThreshold: 4000 },
{ minScale: 0.008, nodeMode: 'dot', lineMode: 'simple', nodeDowngradeThreshold: 30000, lineDowngradeThreshold: 30000 },
],
perfConfig: {
nodeTextMinScale: 0.3,
lineTextMinScale: 0.3,
hoverColorMinScale: 0.3,
},
})
// 增量添加新节点示例
const newNode = api.addNode({ id: 'new-1', text: '新增节点', x: 100, y: 200 })
if (newNode) {
api.addLine({ from: '1', to: 'new-1', color: '#fa8c16', curve: 'bezier' })
api.dataUpdated()
}
}
</script>API 参考
组件
TopologyGraph(主组件)
内含 PixiJS Canvas + provide/inject 机制。子组件必须放在其插槽内。
Props
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| width | number | — | 画布宽度 (px) |
| height | number | — | 画布高度 (px) |
| theme | 'light' \| 'dark' | 'light' | 主题 |
| devMode | boolean | false | 显示性能面板 |
| options | Partial<TopologyOptions> | {} | 运行时配置选项 |
width/height未设置时默认为父容器尺寸。options变化时通过watch(deep)自动调用setOptions()同步。
Events
| 事件 | 参数 | 说明 |
|------|------|------|
| ready | (api: GraphApi) | 实例就绪,返回完整 API 对象 |
| node-click | (node: GraphNode, event: MouseEvent) | 节点点击 |
| node-dblclick | (node: GraphNode, event: MouseEvent) | 节点双击 |
| node-hover | (node: GraphNode \| null, event: MouseEvent) | 节点悬停 (离开时 node 为 null) |
| line-click | (line: GraphLine, event: MouseEvent) | 连线点击 |
| line-dblclick | (line: GraphLine, event: MouseEvent) | 连线双击 |
| line-hover | (line: GraphLine \| null, event: MouseEvent) | 连线悬停 |
| node-delete | (node: GraphNode) | 节点删除 (单个/批量均触发) |
| node-drag-start | (node: GraphNode) | 节点拖拽开始 |
| node-drag-move | (node: GraphNode, dx, dy) | 节点拖拽中,dx/dy 为累计位移 |
| node-drag-end | (node: GraphNode, dx, dy) | 节点拖拽结束 |
| selection-change | (selectedIds: Set<string>) | 选中变化 (含 Esc 取消) |
| selectionbox-show | (bounds: SelectionBounds, nodeIds: string[]) | 选区框显示 |
| selectionbox-close | — | 选区框关闭 |
| canvas-click | (event, targetType, target?) | 画布点击 (区分 node/line/canvas) |
| canvas-contextmenu | (event, targetType, target?) | 画布右键(含选区框穿透) |
| canvas-drag-start | — | 画布拖拽开始 |
| canvas-drag-move | (dx, dy) | 画布拖拽中 |
| canvas-drag-end | (dx, dy) | 画布拖拽结束 |
| canvas-zoom | (scale: number) | 缩放结束 |
| viewport-change | (viewport: Viewport) | 视口变化 |
| data-change | ({ nodes: number, lines: number }) | 数据变化 |
canvas-contextmenu 事件穿透: 当用户在选区框(SelectionBox)上右键时,事件会自动穿透到 canvas 层并正确触发此事件。事件参数中的
targetType为'node'/'line'/'canvas'之一,可据此区分右键目标。
Slots
| 插槽 | 说明 | |------|------| | default | 子组件 (Minimap / SelectionBox / GraphToolbar) | | overlay | 自定义叠加层 DOM |
Minimap(小地图)
独立 Canvas 2D 离屏渲染的缩略图导航。支持拖拽平移 + 滚轮缩放 + 点击跳转。
Props
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| width | number | 200 | 宽度 (px) |
| height | number | 150 | 高度 (px) |
| position | MinimapPosition | 'right-bottom' | 位置 |
| dotSize | number | — | 节点半径覆盖 (px) |
| viewportColor | string | — | 视口框颜色 |
| backgroundColor | string | — | 背景颜色 |
MinimapPosition 可选值: 'left-top' \| 'right-top' \| 'left-bottom' \| 'right-bottom' \| 'top-center' \| 'bottom-center' \| 'left-center' \| 'right-center'
SelectionBox(选区操作框)
框选节点后显示操作面板,含选中计数、删除按钮、拖拽移动功能。无需绑定 props/events,通过 inject 自动获取 API。
核心特性:
- 支持 Shift+左键框选 或 直接左键框选两种模式
- 工具栏区域排除(删除按钮不触发穿透)
- 右键事件穿透: 在选区框非工具栏区域右键时,自动将
contextmenu事件转发到底层 canvas,触发完整的canvas-contextmenu事件链路 - 滚轮事件穿透: 在选区框非工具栏区域滚动鼠标滚轮时,自动将
wheel事件转发到底层 canvas,支持正常缩放画布
Slots (作用域)
| 插槽 | 参数 | 说明 |
|------|------|------|
| toolbar | { selectedCount, isDragging, onDelete } | 工具栏扩展 |
| default | { selectedCount, isDragging, onDelete } | 内容区域扩展 |
GraphToolbar(浮动工具栏)
14 个功能按钮的悬浮工具栏,可自定义布局和方位定位。
Props
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| position | string | 'top-right' | 8 个方位之一 |
| direction | 'horizontal' \| 'vertical' | 'horizontal' | 排列方向 |
| layout | ButtonId[][] | 见下方 | 按钮二维数组布局 |
| showExport | boolean | true | 显示导出按钮 |
| showLayout | boolean | true | 显示布局按钮 |
默认 layout:
[
['zoomIn', 'scale', 'zoomOut'],
['fitView', 'resetView', 'fullscreen'],
['force', 'tree:top', 'tree:bottom', 'tree:left', 'tree:right', 'radial', 'circle'],
['download', 'import'],
]ButtonId 全部可选值:
| ID | 功能 | ID | 功能 | |----|------|-----|------| | zoomIn | 放大 | resetView | 重置视图 | | zoomOut | 缩小 | fullscreen | 全屏切换 | | scale | 缩放百分比显示 | force | 力导向布局 | | fitView | 适应全部 | tree:top/bottom/left/right | 树形 4 方向 | | download | 导出图片 | circle | 环形布局 | | import | 导入 JSON | radial | 径向布局 |
GraphApi (@ready 回调返回)
@ready 事件返回的完整 API 对象,按 14 个功能类别组织:
一、响应式状态 & 性能监控
| 属性 | 类型 | 说明 |
|------|------|------|
| stats | Ref<PerfStats> | 性能统计 (fps/frameTime/renderTime/nodeCount/scale 等) |
| isDarkTheme | Ref<boolean> | 当前是否暗色主题 |
| canUndo | Ref<boolean> | 是否可撤销 |
| canRedo | Ref<boolean> | 是否可重做 |
二、视图 & 视口控制
| 方法 | 说明 |
|------|------|
| resetView() | 重置视图 (scale=1, 居中) |
| fitView() | 自适应视图 (包含所有节点) |
| zoomIn() | 放大一级 |
| zoomOut() | 缩小一级 |
| zoomTo(scale?) | 缩放到指定比例 (默认 1.0) |
| panBy(dx, dy) | 按偏移量平移 |
| getViewport() | 获取当前视口 { x, y, scale, width, height } |
| setViewport(x, y, scale) | 设置视口位置和缩放 |
| toggleFullscreen() | 全屏切换 |
| getViewInfo() | 获取详细视口信息 (含边界范围等) |
三、数据管理(导入/导出)
| 方法 | 说明 |
|------|------|
| generateTestData(count) | 生成随机测试数据 |
| setJsonData(data, append?) | 导入 JSON (兼容 relation-graph 格式,append=true 则追加) |
| getJsonData() | 导出当前数据为 JSON |
| doExport() | 导出内部格式 { nodes, lines } |
| importData(data) | 导入内部格式数据 |
四、数据查询 & 删除
| 方法 | 说明 |
|------|------|
| getNodes() | 获取所有节点数组 |
| getLines() | 获取所有连线数组 |
| getNodeById(id) | 按 ID 查找节点 |
| getLineById(id) | 按 ID 查找连线 |
| removeNodeById(id) | 删除单个节点 (同时删除关联连线) |
| removeLineById(id) | 删除单条连线 |
| dataUpdated(clearHighlight?) | 强制刷新渲染 (直接修改节点/连线属性后调用)。可选参数 clearHighlight: boolean(默认 false),设为 true 可清除所有 click/hover/focus 高亮状态 |
五、增量数据操作(CRUD)
运行时动态增删节点和连线,无需重新设置全部数据。
| 方法 | 返回值 | 说明 |
|------|--------|------|
| addNode(nodeData) | GraphNode \| undefined | 添加单个节点 |
| addLine(lineData) | GraphLine \| undefined | 添加单条连线 |
| addNodes(nodes[]) | GraphNode[] | 批量添加节点 |
| addLines(lines[]) | GraphLine[] | 批量添加连线 |
| updateNodePosition(id, x, y, animate?) | void | 更新节点坐标(支持动画) |
| removeNodesByIds(ids[]) | void | 批量删除节点(含关联边) |
| removeLinesByIds(ids[]) | void | 批量删除连线 |
NewNodeData 格式:
interface NewNodeData {
id: string; text?: string; x?: number; y?: number;
radius?: number; color?: string; shape?: NodeShape;
icon?: string; opacity?: number; fixed?: boolean;
}
interface NewLineData {
id?: string; from: string; to: string;
text?: string; color?: string; directed?: boolean;
style?: LineStyle; curve?: LineCurve;
animation?: LineAnimation; showEndArrow?: boolean;
}六、布局算法
| 方法 | 说明 |
|------|------|
| applyLayout(options) | 应用布局算法 |
LayoutOptions 参数:
| 参数 | 类型 | 默认值 | 适用布局 | 说明 |
|------|------|--------|----------|------|
| type | 'force' \| 'tree' \| 'center' \| 'circle' | 'force' | 全部 | 布局类型 |
| iterations | number | 300 | force | 力导向迭代次数 |
| repulsion | number | 2000 | force | 节点斥力强度 (推荐 2000~10000) |
| springLength | number | 60 | force | 弹簧自然长度 (推荐 50~300) |
| springK | number | 0.1 | force | 弹簧刚度系数 (推荐 0.02~0.15) |
| damping | number | 0.3 | force | 速度阻尼 (推荐 0.2~0.6) |
| direction | 'top' \| 'bottom' \| 'left' \| 'right' | 'bottom' | tree | 树形展开方向 |
| levelDistance | number \| number[] | 120 | tree | 层级间距 (数字或逐层数组) |
| minPerWidth | number | 30 | tree | 同层最小间距 |
| maxPerWidth | number | 200 | tree | 同层最大间距 |
| fixedRootNode | boolean | false | tree | 固定根节点位置 |
| startAngle | number | 0 | center/circle | 起始角度 (弧度) |
| angleStep | 'auto' \| number | 'auto' | center/circle | 角度间距 |
其他布局方法:
| 方法 | 说明 |
|------|------|
| startLayout() | 开始自动布局 |
| refresh() | 刷新当前布局 |
七、节点交互 & 定位
| 方法 | 说明 |
|------|------|
| focusOnNode(nodeId) | 平移+缩放到指定节点居中 (带动画) |
| flashNode(nodeId, duration?) | 节点闪烁效果 (默认 600ms) |
| searchAndFlyTo(query) | 按文本搜索节点并飞向首个匹配 |
| deleteSelected() | 删除当前选中节点及关联连线 |
| moveSelectedNodesBy(dx, dy) | 批量移动选中节点 |
| saveCurrentHistory() | 手动保存历史快照 |
八、聚焦高亮
| 方法 | 说明 |
|------|------|
| focusNodeById(id) | 高亮指定节点及邻边 (清除其他高亮) |
| focusLineById(id) | 高亮指定连线 (清除其他高亮) |
| cancelFocus() | 清除所有聚焦高亮效果 |
| isFocusing() | 是否处于聚焦模式 |
九、选择 & 编辑
| 方法 | 说明 |
|------|------|
| setEditingNodes(ids) | 设置编辑选框 (空数组隐藏,与选中独立) |
| setSelectedIds(ids) | 设置选中 ID 集合 |
| selectAll() | 选中全部节点 |
| clearSelection() | 清除所有选择(强制清空所有节点的 selected 状态,包括手动设置的 node.selected=true) |
| getSelectedNodeIds() | 获取当前选中 ID 集合 (Set<string>) |
| clearClickHighlight() | 清除点击高亮状态(clickNodeHighlight / clickLineHighlight),不影响选中状态(selected)和聚焦状态(focus) |
十、样式控制
| 方法 | 说明 |
|------|------|
| toggleTheme() | 切换亮色/暗色主题 |
| setNodeShape(shape) | 设置全局节点形状 ('circle' \| 'rect' \| 'diamond' \| 'hexagon' \| 'star') |
| setLineStyle(style) | 设置全局连线样式 ('solid' \| 'dashed' \| 'dotted') |
| setLineCurve(curve) | 设置全局曲线类型 ('straight' \| 'bezier' \| 'arc' \| 'step-hv' \| 'step-vh') |
| setLineWidth(w) | 设置全局连线宽度倍率 |
| setCustomDrawCallbacks(callbacks) | 注册自定义绘制回调 |
| applyFilter(color?, text?) | 颜色/文字筛选高亮 |
十一、历史操作(撤销/重做)
| 方法 | 说明 |
|------|------|
| undo() | 撤销上一步操作 |
| redo() | 重做下一步操作 |
大图 (>10000 节点 或 >20000 连线) 自动禁用以节省内存。
十二、导出 & 信息查询
| 方法 | 说明 |
|------|------|
| downloadAsImage(filename?, options?) | 异步下载当前视口可见区域为图片,返回 Promise<void> |
| downloadFullGraphAsImage(filename?, options?) | 异步按 100% 比例完整下载全部拓扑图内容,返回 Promise<void>;包含全部节点、连线、背景网格和边缘文字 |
| getViewInfo() | 获取详细视图信息 |
ImageExportOptions:
{
format?: 'image/png' | 'image/jpeg' | 'image/webp' // downloadAsImage 默认 'image/webp',downloadFullGraphAsImage 默认 'image/png'
quality?: number // 0~1,JPEG/WebP 有效
scale?: number // 导出分辨率倍率,用于优化锯齿;完整导出默认 2,最高 4
}十三、小地图
| 属性/方法 | 类型 | 说明 |
|-----------|------|------|
| minimapNodes | ShallowRef<MinimapNode[]> | 小地图轻量节点数据 |
| getNodesForMinimap() | () => MinimapNode[] | 获取简化节点列表 |
十四、选区框 & 交互模式
| 属性/方法 | 类型 | 说明 |
|-----------|------|------|
| selBoxVisible | Ref<boolean> | 选区框是否显示 |
| selBoxBounds | Ref<SelectionBounds \| null> | 选区边界矩形 |
| selBoxCount | Ref<number> | 选区内节点数 |
| updateSelectionBounds() | void | 手动触发选区框边界更新 |
| setDirectSelection(enabled) | void | 切换直接框选模式 (true=左键直接拖拽; false=需Shift)
TopologyOptions (配置选项)
通过组件 options prop 或 setOptions() 传入,支持运行时热更新。
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| width | number | — | 画布宽度 |
| height | number | — | 画布高度 |
| theme | 'light' \| 'dark' | 'light' | 主题 |
| enableHistory | boolean | true | 启用撤销/重做 |
| nodeShape | NodeShape | 'circle' | 默认节点形状 |
| nodeSize | number | 60 | 默认节点半径 |
| lineStyle | LineStyle | 'solid' | 默认连线样式 |
| lineCurve | LineCurve | 'straight' | 默认曲线类型 |
| lineWidth | number | 1 | 全局连线宽度倍率 |
| lineFontSize | number | 0 | 全局连线文字大小 (0=自动计算) |
| zoomStep | number | 0.05 | 缩放步长比例 |
| gridColor | string | — | 网格颜色 (CSS 色) |
| gridSubColor | string | — | 子网格颜色 |
| canvasBackground | string | — | 画布背景颜色 |
| focusZoom | number | — | 聚焦目标缩放比例 |
| lodTiers | LODTier[] | — | LOD 阶梯表 (覆盖默认 4 级) |
| lodConfig | Partial<LODConfig> | — | LOD 详细配置 |
| perfConfig | object | — | 性能参数 (见下方) |
| themes | Record<'light'\|'dark', Partial<ThemeConfig>> | — | 自定义主题覆盖 |
| hoverNodeHighlight | boolean | false | hover 高亮节点 |
| hoverNodeLineHighlight | boolean | false | hover 高亮关联边 |
| hoverLineHighlight | boolean | false | hover 高亮边 |
| clickNodeHighlight | boolean | false | click 高亮节点 |
| clickNodeLineHighlight | boolean | false | click 高亮关联边 |
| clickLineHighlight | boolean | false | click 高亮边 |
perfConfig 支持的字段:
| 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| | nodeTextMinScale | number | 0.5 | 显示节点文字的最小缩放比例 | | lineTextMinScale | number | 0.5 | 显示连线文字的最小缩放比例 | | hoverColorMinScale | number | 0.5 | hover 变色的最小缩放比例 |
lodTiers 示例:
{
lodTiers: [
{ minScale: 1.0, nodeMode: 'detail', lineMode: 'detail', nodeDowngradeThreshold: 200, lineDowngradeThreshold: 400 },
{ minScale: 0.5, nodeMode: 'normal', lineMode: 'normal', nodeDowngradeThreshold: 500, lineDowngradeThreshold: 1000 },
{ minScale: 0.2, nodeMode: 'simple', lineMode: 'simple', nodeDowngradeThreshold: 2000, lineDowngradeThreshold: 4000 },
{ minScale: 0.008, nodeMode: 'dot', lineMode: 'simple', nodeDowngradeThreshold: 30000, lineDowngradeThreshold: 30000 },
]
}数据类型
GraphNode
interface GraphNode {
id: string
text: string // 显示文本
x: number // 世界坐标 X
y: number // 世界坐标 Y
radius: number // 半径 (px, 世界坐标单位)
color: string // CSS 颜色字符串
shape?: NodeShape // 'circle' | 'rect' | 'diamond' | 'hexagon' | 'star'
icon?: string // 图标 URL
selected?: boolean // 是否选中
hovered?: boolean // 是否悬停
highlighted?: boolean // 是否高亮
borderColor?: string // 边框颜色
borderWidth?: number // 边框宽度
fontColor?: string // 文字颜色 (覆盖主题默认)
fontSize?: number // 文字大小 px (覆盖默认计算值)
opacity?: number // 透明度 0~1
fixed?: boolean // 固定位置 (力导向不受影响)
disabledDrag?: boolean // 禁止拖拽
flashing?: boolean // 正在闪烁
expanded?: boolean // 树形展开状态
isHide?: boolean // 是否隐藏
forceDetail?: boolean // 强制 detail 渲染(无视 LOD 降级,始终显示图标+文字,最小半径 10px)
data?: any // 用户自定义数据
}GraphLine
interface GraphLine {
id: string
source: string // 起始节点 ID
target: string // 目标节点 ID
text?: string // 连线标签文本
color: string // CSS 颜色
width: number // 线宽 (世界坐标单位)
directed: boolean // 是否有方向
style?: LineStyle // 'solid' | 'dashed' | 'dotted'
curve?: LineCurve // 'straight' | 'bezier' | 'arc' | 'step-hv' | 'step-vh'
animation?: LineAnimation // 0=无 | 1=流动 | 2=快速流动 | 3=脉冲 | 4=虚线流动
animationSpeed?: number // 动画速度倍率 (默认 1.0)
fontColor?: string // 文字颜色
fontSize?: number // 文字大小 px
opacity?: number
showStartArrow?: boolean // 起点箭头
showEndArrow?: boolean // 终点箭头
fromJunctionPoint?: JunctionPoint // 起始接入点
toJunctionPoint?: JunctionPoint // 终止接入点
isHide?: boolean // 是否隐藏
forceDetail?: boolean // 强制始终渲染(无视 LOD 降级,连线+箭头+文字始终可见)
data?: any
}JunctionPoint 可选值: 'border' | 'left' | 'right' | 'top' | 'bottom' | 'ltrb' | 'tb' | 'lr'
GraphJsonData (导入格式)
兼容 relation-graph 字段名,支持混合使用:
interface GraphJsonData {
rootId?: string
nodes: Array<{
id: string; text?: string; name?: string; x?; y?; radius?;
width?: number // 自动转换为 radius = width / 2
color?; shape?; icon?; fontColor?; borderColor?
borderWidth?; opacity?; fixed?; disabledDrag?; data?
}>
lines: Array<{
id?; from?; to?; source?; target? // 支持 from/to 和 source/target 两套字段名
text?; label?; color?; fontColor?; fontSize?; lineWidth?
style?; curve?; animation?; animationSpeed?
showEndArrow?; showStartArrow?; directed?; data?
}>
}自定义绘制
通过 setCustomDrawCallbacks 覆盖节点/边的默认渲染管线:
api.setCustomDrawCallbacks({
drawNode(ctx) {
// ctx.node - 当前节点数据 (GraphNode)
// ctx.graphics - PixiJS Graphics 对象
// ctx.screenX, ctx.screenY - 屏幕坐标 (已应用视口变换)
// ctx.screenRadius - 屏幕半径 (已应用缩放)
// ctx.viewportScale - 当前缩放比例
// ctx.nodeMode - LOD 模式 ('detail' | 'simple' | 'dot')
// ctx.draw - DrawHelpers 绘制工具集
// ctx.drawDefault() - 回退到默认渲染
const { node, screenX, screenY, screenRadius, draw } = ctx
// 示例:带图标的自定义节点
if (node.icon || node.data?.icon) {
draw.drawImage(node.icon || node.data?.icon,
screenX - screenRadius, screenY - screenRadius, screenRadius * 2)
} else {
draw.drawCircle(screenX, screenY, screenRadius, node.color)
}
draw.drawText(node.text, screenX, screenY + screenRadius + 8, { fontSize: 12 })
},
drawLine(ctx) {
// ctx.line - 当前边数据 (GraphLine)
// ctx.graphics - PixiJS Graphics 对象
// ctx.sourceScreenX/Y - 起点屏幕坐标
// ctx.targetScreenX/Y - 终点屏幕坐标
// ctx.lineWidth - 实际线宽 (考虑缩放)
// ctx.lineColor - 线颜色 (PixiJS 数字格式)
// ctx.viewportScale - 当前缩放
// ctx.nodeMode - LOD 模式
// ctx.draw - DrawHelpers
// ctx.drawDefault() - 回退默认渲染
}
})DrawHelpers (绘制工具集)
| 方法 | 签名 | 说明 |
|------|------|------|
| loadImage | (url: string) => Promise<boolean> | 加载并缓存图片,返回是否成功 |
| drawImage | (url, x, y, w?, h?) => boolean | 绘制已缓存图片,失败返回 false |
| drawCircle | (x, y, radius, fillStyle?) => void | 绘制填充圆 |
| drawRect | (x, y, w, h, fillStyle?) => void | 绘制填充矩形 |
| drawText | (text, x, y, options?) => void | 绘制文字 (fontSize/color/align) |
fillStyle支持 CSS 颜色 ('#ff0000') 或 PixiJS 数字颜色 (0xff0000)。
事件系统
核心类 TopologyGraphCore (继承 EventEmitter) 支持的全部事件:
节点事件:
| 事件 | 参数 | 说明 |
|------|------|------|
| node-click | (node, event) | 节点点击 |
| node-dblclick | (node, event) | 节点双击 |
| node-hover | (node \| null, event) | 节点悬停 (离开时 null) |
| node-delete | (node: GraphNode) | 节点删除 |
| node-drag-start | (node: GraphNode) | 节点拖拽开始 |
| node-drag-move | (node, dx, dy) | 拖拽中累计位移 |
| node-drag-end | (node, dx, dy) | 拖拽结束总位移 |
连线事件:
| 事件 | 参数 | 说明 |
|------|------|------|
| line-click | (line, event) | 连线点击 |
| line-dblclick | (line, event) | 连线双击 |
| line-hover | (line \| null, event) | 连线悬停 |
选择事件:
| 事件 | 参数 | 说明 |
|------|------|------|
| selection-change | (selectedIds: Set<string>) | 选择变化 |
| selectionbox-show | (bounds, nodeIds[]) | 选区框显示 |
| selectionbox-close | — | 选区框关闭 |
视口 & 画布事件:
| 事件 | 参数 | 说明 |
|------|------|------|
| canvas-click | (event, targetType, target?) | 画布点击 |
| canvas-contextmenu | (event, targetType, target?) | 右键菜单(含选区框穿透) |
| canvas-drag-start | — | 画布拖拽开始 |
| canvas-drag-move | (dx, dy) | 画布拖拽中 |
| canvas-drag-end | (dx, dy) | 画布拖拽结束 |
| canvas-zoom | (scale: number) | 缩放结束 |
| viewport-change | (viewport: Viewport) | 视口变化 |
数据/布局/配置事件:
| 事件 | 参数 | 说明 |
|------|------|------|
| data-change | ({ nodes, lines }) | 数据变化 |
| perf-stats | (stats: PerfStats) | 性能统计更新 |
| history-change | ({ canUndo, canRedo }) | 历史状态变化 |
| layout-start | — | 布局开始 |
| layout-end | — | 布局结束 |
| options-change | (options: TopologyOptions) | 配置变更 |
监听方式:
// Vue 组件: @事件名
<TopologyGraph @node-click="(node) => console.log(node.text)" />
// Core API: .on()
graph.on('node-click', (node) => console.log('clicked:', node.id))
graph.on('node-hover', (node) => {
if (!node) console.log('离开节点')
})Window 级别拖拽:
node-drag-*和canvas-drag-*事件注册在window上,即使鼠标离开画布也能持续接收事件,确保拖拽不卡顿。
独立使用(非 Vue 场景)
import { TopologyGraphCore } from 'topology-graph-vue'
// 不依赖 Vue 的纯 JS 用法
const canvas = document.getElementById('canvas') as HTMLCanvasElement
const graph = await TopologyGraphCore.create(canvas, {
width: 800,
height: 600,
theme: 'light',
})
graph.setJsonData({ nodes: [...], lines: [...] })
graph.applyLayout({ type: 'force' })
graph.fitView()
graph.on('node-click', (node) => console.log('clicked:', node.id))Composable API
import { useTopologyGraph } from 'topology-graph-vue'用于自定义集成的 composable。一般不需要手动使用,TopologyGraph 组件内部已经集成。
构建产物
dist/
├── index.js # ESM bundle
├── index.umd.cjs # UMD bundle (CommonJS 兼容)
├── topology-graph-vue.css # 样式文件
└── types/ # TypeScript 声明文件
├── *.d.ts # 所有类型声明
└── components/*.d.ts # 组件声明独立 HTML 打包 (Demo/预览):
dist-standalone/index.html # 单文件 HTML (内联所有 JS/CSS/SVG)npm run build:standalone # 构建
npm run preview:standalone # 预览开发
# 安装依赖 (Node >= 18, 推荐 Volta 固定 20.20.0)
npm install
# 开发服务器 (端口 5173)
npm run dev
# 库模式构建 (ESM + UMD)
npm run build
# 独立 HTML 构建
npm run build:standalone
# 类型检查
npm run typecheck
# 代码质量
npm run lint # ESLint 检查 + 自动修复
npm run format # Prettier 格式化
# 发布 (standard-version 生成 CHANGELOG)
npm run release性能调优指南
LOD 阶梯表调优
LOD (Level of Detail) 根据缩放比例自动切换渲染精度:
| minScale | 节点模式 | 边模式 | 效果 | |----------|---------|--------|------| | >= 1.0 | detail (详情) | detail (详情) | 显示图标/文字/边框/箭头 | | >= 0.5 | normal (普通) | normal (普通) | 仅文字,无边框细节 | | >= 0.2 | simple (简化) | simple (简化) | 无文字,纯色填充 | | < 0.008 | dot (点状) | simple | 极小圆点,大量节点场景 |
forceDetail 强制渲染:
在 dot/simple 模式下,可通过 forceDetail 属性强制指定节点或连线始终以 detail 模式渲染:
// 节点设置 forceDetail
api.setJsonData({
nodes: [
{ id: '1', text: '始终详情', forceDetail: true }, // 始终显示图标+文字,最小半径 10px
{ id: '2', text: '普通节点' }, // 遵循 LOD 降级
],
lines: [
{ from: '1', to: '2', forceDetail: true }, // 始终显示连线+箭头+文字
],
})
detail/normal模式下forceDetail不生效(所有节点本身已是 detail 渲染)。仅在dot/simple降级模式中起作用。
关键阈值:
nodeDowngradeThreshold: 该模式下最大渲染节点数,超出则降级lineDowngradeThreshold: 该模式下最大渲染连线数
力导向参数建议
| 参数 | 默认值 | 节点少 (<500) | 节点多 (>5000) | |------|--------|---------------|----------------| | repulsion | 2000 | 1000~2000 | 5000~10000 | | springLength | 60 | 40~80 | 80~150 | | springK | 0.1 | 0.05~0.1 | 0.02~0.08 | | damping | 0.3 | 0.2~0.3 | 0.4~0.6 | | iterations | 300 | 100~200 | 300~500 |
Changelog
详见 CHANGELOG.md。
