pixi-graph-engine
v0.1.1
Published
High-performance canvas graph engine based on PixiJS 8 + Vue 3 + RBush
Downloads
240
Maintainers
Readme
pixi-graph-engine 组件使用说明
基于 PixiJS + Vue 3 构建的高性能数据流图引擎。
支持 1000+ 节点流畅渲染、字段级连线、自定义节点类型、HTML 编辑叠层。
目录
快速开始
方式一:Vue 插件(推荐)
import { createApp } from 'vue'
import PixiGraphEngine from 'pixi-graph-engine'
import App from './App.vue'
const app = createApp(App)
app.use(PixiGraphEngine)
app.mount('#app')<!-- 在模板中直接使用 -->
<template>
<PixiGraphCanvas />
</template>方式二:按需引入
import { PixiGraphCanvas, MiniMap, Toolbar } from 'pixi-graph-engine'方式三:底层 API 自定义
import {
store, addNode, addEdge,
createNode, createEdge,
computeLayout,
createPixiApp, initScene, renderNode, renderEdge,
rebuildIndex, initEventSystem
} from 'pixi-graph-engine'架构概览
┌─────────────────────────────────────────────────────────┐
│ Vue 3 UI 层 │
│ PixiGraphCanvas / NodeEditor / MiniMap / Toolbar │
│ PortOverlay / NodeActionsOverlay / NodeHoverToolbar │
├─────────────────────────────────────────────────────────┤
│ PixiJS Canvas 层(高性能渲染) │
│ nodeRenderer ←→ edgeRenderer │
│ scene(container 管理) │
├─────────────────────────────────────────────────────────┤
│ Core 数据层 │
│ store(响应式) node edge viewport port │
│ nodeTypeRegistry iconRegistry layoutConstants │
├─────────────────────────────────────────────────────────┤
│ Layout Engine │
│ object / array / table / jsonTree / text │
├─────────────────────────────────────────────────────────┤
│ Interaction 系统 │
│ hitTest(rbush) drag select eventSystem │
└─────────────────────────────────────────────────────────┘渲染策略:节点背景、header、字段行由 PixiJS 绘制(GPU 加速);
编辑表单、full 模式内容展示、工具栏、连线按钮由 Vue HTML 叠层覆盖。
核心数据模型
NodeModel
interface NodeModel {
id: string // 唯一标识
type: NodeType // 'object' | 'array' | 'table' | 'json' | 'text'
x: number // 世界坐标 X
y: number // 世界坐标 Y
data: any // 节点数据(格式见内置节点类型)
layout: {
readonly: { width: number; height: number } // Canvas 只读尺寸
edit: { width: number; height: number } // 编辑面板尺寸
}
state: {
selected: boolean // 是否被选中
hover: boolean // 是否悬停
editing: boolean // 是否进入编辑态
}
// 可选:节点级外观覆盖(优先级高于类型注册表)
meta?: {
icon?: string // 图标字符(unicode / emoji / font icon 码点)
fontFamily?: string // 图标字体
headerColor?: number // header 徽章颜色(0xRRGGBB)
label?: string // header 显示标签(不填默认显示类型名)
}
customType?: string // 自定义类型名(对应 nodeTypeRegistry 注册 key)
status?: NodeStatus // 'idle' | 'loading' | 'success' | 'error'
}创建节点:
import { createNode, computeLayout, addNode } from 'pixi-graph-engine'
const node = createNode({
id: 'node-1',
type: 'object',
x: 100,
y: 200,
data: {
name: { type: 'string', value: 'Alice' },
score: { type: 'string', value: '95' }
}
})
node.layout = computeLayout(node) // 必须:计算并注入布局尺寸
addNode(node)EdgeModel
interface EdgeModel {
id: string
source: string // 源节点 id
target: string // 目标节点 id
// 可选:精确到字段行的端口引用
sourcePort?: PortRef // { nodeId, portType: 'node'|'field', fieldKey? }
targetPort?: PortRef
}创建边:
import { createEdge, addEdge } from 'pixi-graph-engine'
const edge = createEdge({
id: 'edge-1',
source: 'node-1',
target: 'node-2'
})
addEdge(edge)带端口的精确连线(字段级):
const edge = createEdge({
id: 'edge-2',
source: 'node-1',
target: 'node-2',
sourcePort: { nodeId: 'node-1', portType: 'field', fieldKey: 'name' },
targetPort: { nodeId: 'node-2', portType: 'node' }
})FieldDef(字段类型系统)
Object 节点的 data 字段支持两种格式:
普通格式(key → 值):
data: { name: 'Alice', age: 25 }FieldDef 格式(key → 带类型标注的字段定义):
data: {
name: { type: 'string', value: 'Alice' },
tags: { type: 'array', value: ['admin', 'user'] },
profile: { type: 'object', value: { role: 'admin' } },
metrics: { type: 'table', value: { columns: ['month', 'val'], rows: [['Jan', '90']] } },
raw: { type: 'json', value: { source: 'api', ts: 1700000000 } }
}FieldDef 格式能驱动字段级类型徽章显示(渲染层自动识别 { type, value } 结构)。
内置节点类型
| 类型 | node.type | data 格式 | 渲染样式 |
|---|---|---|---|
| Object | 'object' | Record<string, any \| FieldDef> | 双栏 Key/Value 行,支持字段类型徽章 |
| Array | 'array' | any[] | 索引+值列表行 |
| Table | 'table' | { columns: string[], rows: any[][] } | 列头行 + 数据行表格 |
| JSON | 'json' | any(任意可序列化对象) | 语法高亮格式化展示 |
| Text | 'text' | string \| { text: string } | 等宽字体内容区域 |
各类型 data 示例:
// object
data: { username: { type: 'string', value: 'bob' }, age: { type: 'string', value: '30' } }
// array
data: ['alpha', 'beta', 'gamma']
// table
data: { columns: ['id', 'name'], rows: [['1', 'Alice'], ['2', 'Bob']] }
// json
data: { api: 'v2', status: 'ok', nested: { count: 42 } }
// text
data: { text: 'Hello world\nLine 2' }
// 或者直接字符串
data: 'Hello world'自定义节点类型
通过 registerNodeType 注册,将自定义类型名绑定到一组配置,节点设置 customType 后自动应用。
import { registerNodeType } from 'pixi-graph-engine'
registerNodeType('my-source', {
base: 'object', // 底层数据类型(决定布局算法和编辑器)
headerBackground: 0x0f766e, // header 徽章颜色
fontIcon: { icon: '⛁' }, // 自定义图标
showStatus: true, // 显示状态指示器(idle/loading/success/error)
editable: true, // 显示编辑按钮
clickBehavior: 'select', // 'select'(默认)| 'edit'(点击直接进入编辑)
heightMode: 'compact', // 'compact'(默认)| 'full'(HTML 全量展示)
hoverToolbar: {
position: 'top-end', // 工具栏位置(见下方说明)
actions: `<button onclick="...">操作</button>`
}
})
// 使用时在节点上设置 customType
const node = createNode({
id: 'n1', type: 'object', x: 0, y: 0,
customType: 'my-source',
data: { host: { type: 'string', value: 'db.local' } }
})NodeTypeConfig 完整选项
interface NodeTypeConfig {
// ── 必填 ──────────────────────────────────
base: 'object' | 'array' | 'table' | 'json' | 'text'
// ── 外观 ──────────────────────────────────
headerHeight?: number // header 高度(px,默认 40)
headerBackground?: number // header 徽章背景色(0xRRGGBB)
fontIcon?: {
icon: string // 图标字符
fontFamily?: string // 图标字体名(需预加载)
}
// ── 状态 ──────────────────────────────────
showStatus?: boolean // 是否显示右上角状态指示器(默认 false)
// ── 交互 ──────────────────────────────────
clickBehavior?: 'select' | 'edit' // 默认 'select'
editable?: boolean // 显示编辑按钮(默认 false)
hoverToolbar?: HoverToolbarConfig // hover 时弹出工具栏
// ── 内容高度 ──────────────────────────────
heightMode?: 'compact' | 'full' // 默认 'compact'
defaultHeight?: number // full 模式初始占位高度(默认 200)
}高度模式 compact vs full
| 模式 | 渲染方式 | 适用场景 |
|---|---|---|
| compact | 全部在 PixiJS Canvas 中绘制,最多显示 5 行 | 大量节点,性能优先 |
| full | Canvas 只绘制 header + 背景,内容区由 HTML overlay 覆盖,高度自适应 | 需要展示全量数据、滚动等 |
// full 模式示例
registerNodeType('detail-table', {
base: 'table',
heightMode: 'full',
defaultHeight: 200 // 初始占位,mount 后自动同步实际高度
})Hover 工具栏
interface HoverToolbarConfig {
position?: ToolbarPosition // 工具栏位置,默认 'top-end'
actions: Component | string // Vue 组件 或 HTML 字符串
}
type ToolbarPosition =
| 'top-start' // 节点上方,左对齐
| 'top-end' // 节点上方,右对齐(默认)
| 'top-right' // 节点右侧,顶部对齐(向右展开)
| 'top-left' // 节点左侧,顶部对齐(向左展开)
| 'bottom-start' // 节点下方,左对齐
| 'bottom-end' // 节点下方,右对齐HTML 字符串模式:
hoverToolbar: {
position: 'top-end',
actions: `
<div style="display:flex;gap:4px;">
<button onclick="myAction()">操作</button>
</div>
`
}Vue 组件模式(接收 node prop):
import { defineComponent, h } from 'vue'
hoverToolbar: {
position: 'top-end',
actions: defineComponent({
props: ['node'],
setup(props) {
return () => h('button', {
onClick: () => console.log('node id:', props.node.id)
}, '操作')
}
})
}图标系统
图标优先级:node.meta.icon > 类型注册表 registerNodeTypeIcon > 内置默认。
内置默认(无需配置):
| 类型 | 徽章文字 |
|---|---|
| object | Obj |
| array | Arr |
| table | Table |
| json | JSON |
| text | Str |
注册类型级图标(覆盖内置默认):
import { registerNodeTypeIcon, setDefaultIconFont } from 'pixi-graph-engine'
// 使用 emoji / unicode
registerNodeTypeIcon('object', { icon: '⬡' })
// 使用 icon font(需预加载字体)
setDefaultIconFont('Material Icons')
registerNodeTypeIcon('database', { icon: '\ue1d0' })
// 批量注册
registerNodeTypeIcons({
api: { icon: '\ue8b8' },
service: { icon: '\ue8b5', fontFamily: 'Material Icons Outlined' }
})节点级覆盖(优先级最高):
const node = createNode({
id: 'n1', type: 'object', x: 0, y: 0, data: {},
meta: {
icon: '★',
headerColor: 0xef4444, // 红色徽章
label: '特殊节点'
}
})节点运行时状态(需 showStatus: true):
// 在注册类型时启用
registerNodeType('my-type', { base: 'object', showStatus: true })
// 运行时修改节点状态
node.status = 'loading' // 旋转圆环
node.status = 'success' // 绿色对勾
node.status = 'error' // 红色叉号
node.status = 'idle' // 默认灰点连线与端口系统
端口类型
| PortType | 描述 |
|---|---|
| 'node' | 整个节点的端口(header 右侧圆点) |
| 'field' | 特定字段/行的端口(各行右侧 "+" 按钮) |
只有 object / array / table 类型节点支持字段级端口。
手动连线交互
用户点击节点右侧 "+" 圆形按钮开始拖拽连线,鼠标悬停在目标节点上时自动检测落点:
- 悬停在节点 header 区域 → 创建节点级端口连线
- 悬停在字段行 → 创建字段级端口连线
目标节点入口侧自动选择:源节点在目标左侧 → 从目标左侧进入;在右侧 → 从右侧进入(最优路径)。
端口坐标 API
import { getPortWorldPosition, getTargetPortWorldPosition } from 'pixi-graph-engine'
// 获取源端口世界坐标
const src = getPortWorldPosition(sourceNode, { nodeId: 'n1', portType: 'field', fieldKey: 'name' })
// 获取目标端口最优入口坐标(根据源 X 坐标自动选边)
const tgt = getTargetPortWorldPosition(targetNode, { nodeId: 'n2', portType: 'node' }, src.x)Store API
全局响应式状态(基于 Vue reactive)。
import { store, addNode, removeNode, addEdge, setActiveNode, setSelected } from 'pixi-graph-engine'
// 读取
store.nodes // Map<string, NodeModel>
store.edges // Map<string, EdgeModel>
store.viewport // { x, y, zoom }
store.activeNodeId // string | null(当前进入编辑态的节点)
store.selectedNodeIds // Set<string>
// 操作
addNode(node) // 添加节点到 store
removeNode('node-1') // 从 store 移除节点
addEdge(edge) // 添加边
setActiveNode('node-1') // 打开编辑面板
setActiveNode(null) // 关闭编辑面板
setSelected('node-1', true) // 选中节点
setSelected('node-1', false) // 取消选中Layout Engine
布局引擎负责根据节点数据计算 Canvas 尺寸(layout.readonly / layout.edit)。
每次创建节点后必须调用 computeLayout 写入尺寸,否则节点无法正常渲染。
import { computeLayout, invalidateLayoutCache } from 'pixi-graph-engine'
// 创建时计算
node.layout = computeLayout(node)
// 数据变更后刷新(先清缓存再重算)
invalidateLayoutCache(node)
node.layout = computeLayout(node)各类型默认尺寸参考:
| 类型 | 只读宽 | 只读高(示例) | |---|---|---| | object | 260px | header(40) + 字段行数×30px | | array | 240px | header(40) + 条目数×30px | | table | max(240, 列数×120)px | header(40) + 列头(28) + 数据行×26px | | json | 260px | 自适应(最大 400px) | | text | 自适应 | 自适应 |
Viewport 视口
视口采用「世界坐标 ↔ 屏幕坐标」两套体系,节点位置均为世界坐标。
import { worldToScreen, screenToWorld, applyViewportToPan, applyViewportZoom } from 'pixi-graph-engine'
// 坐标转换
const screen = worldToScreen(node.x, node.y) // → { x, y }(屏幕 px)
const world = screenToWorld(e.clientX, e.clientY) // → { x, y }(世界坐标)
// 平移(通常在 pointermove 中调用)
applyViewportToPan(movementX, movementY)
// 缩放(deltaSign: 1 放大 / -1 缩小)
applyViewportZoom(deltaSign, centerX, centerY)
// 读取当前视口状态
const { x, y, zoom } = store.viewportUI 组件
PixiGraphCanvas
全功能画布组件,内部集成所有子组件(Canvas、叠层、工具栏、MiniMap、连线交互等)。
<template>
<PixiGraphCanvas />
</template>该组件会自动接管 store 中的节点/边并完成渲染,使用前向 store 添加数据即可。
NodeEditor(编辑面板)
点击节点后弹出的编辑面板(通过 Overlay.vue 承载),支持对各字段进行增删改、类型切换。
触发方式:
// 编程式打开
setActiveNode('node-id')
// 通过 clickBehavior 配置自动触发(注册类型时设置)
clickBehavior: 'edit'支持的字段编辑类型:
| FieldType | 编辑控件 |
|---|---|
| string | 单行文本输入 |
| array | 可增删的列表项 |
| object | JSON 格式 textarea |
| table | JSON 格式 textarea({columns, rows}) |
| json | JSON 格式 textarea |
| custom | 多行文本 textarea |
MiniMap
小地图组件,实时同步画布内容与视口位置。
<template>
<MiniMap ref="miniMapRef" />
</template>位置与尺寸配置(运行时可动态修改):
import { setMinimapPosition, setMinimapSize, setMinimapVisible } from 'pixi-graph-engine'
setMinimapPosition('bottom-right') // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
setMinimapSize(200, 150) // width, height(px)
setMinimapVisible(false) // 显示/隐藏Toolbar
顶部工具栏,包含:选择工具 / 平移工具 / 框选。
<template>
<Toolbar />
</template>编程切换工具模式:
import { setTool } from 'pixi-graph-engine'
setTool('select') // 选择(默认)
setTool('pan') // 平移
// 读取当前工具
import { toolState } from 'pixi-graph-engine'
toolState.current // 'select' | 'pan'快捷键(内置):
| 按键 | 功能 |
|---|---|
| Space + 拖拽 | 临时平移 |
| Esc | 取消连线 |
| 鼠标中键/右键拖拽 | 平移画布 |
| 滚轮 | 缩放 |
X6 迁移适配器
提供从 AntV X6 图数据直接迁移的工具函数。
import { fromX6Node, fromX6Edge, migrateGraph } from 'pixi-graph-engine'
// 单节点迁移
const node = fromX6Node(x6Node)
// 单边迁移
const edge = fromX6Edge(x6Edge)
// 整图批量迁移
const { nodes, edges } = migrateGraph(x6Graph)
nodes.forEach(n => {
n.layout = computeLayout(n)
addNode(n)
})
edges.forEach(e => addEdge(e))完整初始化示例
// main.ts 或 App.vue onMounted
import {
registerNodeType,
createNode, createEdge,
computeLayout,
addNode, addEdge,
createPixiApp, initScene,
renderNode, renderEdge,
rebuildIndex, initEventSystem,
store
} from 'pixi-graph-engine'
// 1. 注册自定义节点类型(可选)
registerNodeType('data-source', {
base: 'object',
headerBackground: 0x0f766e,
showStatus: true,
editable: true,
heightMode: 'compact',
hoverToolbar: {
position: 'top-end',
actions: '<button onclick="alert(1)">刷新</button>'
}
})
// 2. 初始化 PixiJS
const canvas = document.querySelector('canvas')!
const app = await createPixiApp(canvas)
const { nodes: nodesLayer, edges: edgesLayer } = initScene(app)
// 3. 创建节点和边
const n1 = createNode({
id: 'n1', type: 'object', x: 100, y: 100,
customType: 'data-source',
data: {
host: { type: 'string', value: 'db.example.com' },
port: { type: 'string', value: '5432' }
},
status: 'success'
})
n1.layout = computeLayout(n1)
addNode(n1)
const n2 = createNode({
id: 'n2', type: 'array', x: 450, y: 100,
data: ['alpha', 'beta', 'gamma']
})
n2.layout = computeLayout(n2)
addNode(n2)
const e1 = createEdge({ id: 'e1', source: 'n1', target: 'n2' })
addEdge(e1)
// 4. 渲染所有节点和边
store.nodes.forEach(node => renderNode(node, nodesLayer))
store.edges.forEach(edge => renderEdge(edge, store.nodes, edgesLayer))
// 5. 构建空间索引 & 初始化交互
rebuildIndex(store.nodes)
const cleanup = initEventSystem(canvas)
// 组件卸载时清理
onUnmounted(() => cleanup())注意事项
computeLayout必须在addNode前调用,否则节点尺寸为初始占位值{240×68}。- 节点数据变更后(如编辑字段),需调用
invalidateLayoutCache(node)清除缓存,然后重新computeLayout和refreshNode。 - full 模式节点的高度由 HTML overlay 实际渲染后测量同步,初次渲染会有一次短暂的高度跳变(
defaultHeight为初始占位)。 - 字段级连线(
portType: 'field')仅支持object / array / table类型节点。 - 图标字体(如 Material Icons)需在项目中预先加载后再调用
setDefaultIconFont。
