jmGraph 流程图组件
基于 jmGraph 的「工作流流程图(DAG)」组件。原生 JavaScript 实现,零框架依赖:不绑定 Vue / React,使用方在业务侧自行封装即可。
设计理念
- 只做原生实现:
createFlowGraph返回的是FlowGraphComponent实例,面向 DOM 容器,命令式 API。 - 框架无关:所有逻辑、状态、生命周期都收敛在
FlowGraphComponent(core/ComponentBase.js)中。 - 使用方自封装:Vue / React 等框架只需把「挂载 / 更新 / 卸载」映射到组件 API(
new+mount(el)、数据走update(patch)、destroy()),无需引入额外适配层。
发布与引入(第三方使用)
本组件随 jmgraph 包一起发布,不需要单独安装。组件源码位于 jmgraph/src/components,包已通过 exports 暴露子路径,因此第三方项目可直接引入。
组件既可经根入口
jmgraph直接导入(见「方式零」),也可经下面的子路径导入。根入口已随npm run build一并把组件构建进dist/jmgraph.js/jmgraph.min.js(UMD 全局jmGraph.createFlowGraph可用)。
1. 安装
npm install jmgraph # 或 pnpm add jmgraph / yarn add jmgraph
2. 引入路径(Node / 打包器)
jmgraph 提供了两条等价的子路径(指向同一文件):
// 方式零:直接从根入口导入(随包主入口一并构建,UMD/ESM 均可用)
import jmGraph, { createFlowGraph, FlowGraphComponent } from 'jmgraph';
// 推荐:简短子路径
import { createFlowGraph, FlowGraphComponent } from 'jmgraph/components';
// 等价:完整源码子路径
import { createFlowGraph } from 'jmgraph/src/components';
3. CDN / ESM 直引(无需打包,浏览器 <script type=module>)
<!-- 方式一:jsDelivr 直接指向包内文件 -->
<script type="module">
import { createFlowGraph } from 'https://cdn.jsdelivr.net/npm/jmgraph/src/components/index.js';
</script>
<!-- 方式二:esm.sh 自动处理依赖解析(推荐给纯前端原型) -->
<script type="module">
import { createFlowGraph } from 'https://esm.sh/jmgraph/components';
</script>
4. 底层元数据(自定义面板 / 图例用)
import { NODE_TYPES, STATUS_META, typeColor } from 'jmgraph/components';
已移除
components/vue、components/react子入口。框架用户请参考文末「框架封装示例」自行实现。
5. TypeScript
jmgraph/components 自 3.3.0 起随包提供官方类型声明(src/components/index.d.ts,经 exports 的 types 条件自动命中),覆盖全部选项、事件、FlowStage / ContextMenuInfo / RunStatusMap 等数据结构与命令式 API:
import {
createFlowGraph, FlowGraphComponent,
type FlowStage, type FlowGraphOptions, type ContextMenuInfo
} from 'jmgraph/components';
const options: FlowGraphOptions = {
mode: 'edit',
stages: [{ id: 'fetch', type: 'fetch' }],
onContextMenu: (info: ContextMenuInfo) => { /* ... */ }
};
const fg: FlowGraphComponent = createFlowGraph('#app', options);
需要 TypeScript
moduleResolution: "bundler"/"node16"/"nodenext"(或按路径解析到package.json的exports)。
快速开始
<div id="app" style="width: 800px; height: 600px;"></div>
<script type="module">
import { createFlowGraph } from 'jmgraph/src/components';
const stages = [
{ id: 'fetch_user', type: 'fetch', inputs: [], outputs: ['user'], dependsOn: [] },
{ id: 'normalize', type: 'transform', inputs: [{ from: 'fetch_user.user' }], outputs: ['rows'], dependsOn: ['fetch_user'] },
{ id: 'score', type: 'script', inputs: [{ from: 'normalize.rows' }], outputs: ['scores'], dependsOn: ['normalize'] }
];
const fg = createFlowGraph('#app', {
stages,
mode: 'view',
onSelect: id => console.log('selected:', id),
onError: (err, ctx) => console.error(ctx, err)
});
// fg 是 FlowGraphComponent 实例,可用全部命令式方法 + 事件订阅
fg.autoLayout();
fg.fit();
</script>
第三方最小可运行示例
把下面内容保存为 index.html,直接用浏览器打开即可(通过 esm.sh 从 npm 拉取 jmgraph,无需本地安装)。这就是「其它项目利用 jmgraph 包使用本组件」的最小形态:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>jmGraph FlowGraph - 第三方使用示例</title>
<style>
html, body { margin: 0; height: 100%; }
#app { width: 100vw; height: 100vh; }
</style>
</head>
<body>
<div id="app"></div>
<script type="module">
// 第三方项目:直接 import 已发布的 jmgraph 包的组件子路径
import { createFlowGraph, STATUS_META } from 'https://esm.sh/jmgraph/components';
const stages = [
{ id: 'fetch_user', type: 'fetch', inputs: [], outputs: ['user'], dependsOn: [] },
{ id: 'normalize', type: 'transform', inputs: [{ from: 'fetch_user.user' }], outputs: ['rows'], dependsOn: ['fetch_user'] },
{ id: 'score', type: 'script', inputs: [{ from: 'normalize.rows' }], outputs: ['scores'],dependsOn: ['normalize'] }
];
const fg = createFlowGraph('#app', {
stages,
mode: 'edit', // 'view' 只读 | 'edit' 可编辑(拖拽/连线)
onSelect: id => console.log('选中:', id),
onError: (err, ctx) => console.error(ctx, err)
});
// 命令式 API 全部可用
fg.autoLayout();
fg.fit();
// 模拟运行状态
setTimeout(() => fg.setRunStatus({
fetch_user: 'success', normalize: 'running', score: 'pending'
}), 800);
</script>
</body>
</html>
如果使用本地构建工具(Vite / Webpack / Rspack 等),把上面 import 换成:
import { createFlowGraph } from 'jmgraph/components';
即可,其余代码完全一致。
配置项(options)
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
stages |
Array |
[] |
流程节点数据 |
mode |
'view' \| 'edit' |
'view' |
交互模式:view 只读可平移/缩放;edit 可拖动节点、拖连接点建边、增删 |
autoFit |
boolean |
true |
挂载后自动适应视图 |
autoResize |
boolean |
true |
基于 ResizeObserver 自动监听容器尺寸变化并 resize |
width / height |
number \| null |
null |
初始画布尺寸,缺省取容器尺寸 |
graphOptions |
object \| null |
null |
透传给底层 jmGraph 构造参数(如 dprScale / hitIndex / minZoom) |
layout |
object \| null |
null |
布局参数({ vGap, hGap, pad }) |
onSelect |
(id: string\|null) => void |
— | 选中节点变化 |
onChange |
(stages: Array) => void |
— | 数据变化(增删改) |
onEdgeClick |
(edge: {source,target}) => void |
— | 点击连线 |
onNodeEdit |
(id: string) => void |
— | 触发节点重命名 / 编辑 |
onContextMenu |
(info: ContextMenuInfo) => void |
— | 右键菜单:命中节点/连线/空白画布时回调 { type, id, stage, edge, x, y, clientX, clientY },已自动 preventDefault 原生菜单 |
onLog |
(msg: string, level?: string) => void |
— | 运行日志 |
onReady |
(instance) => void |
— | 实例就绪(可拿到完整 api) |
onError |
(err: Error, ctx: string) => void |
— | 内部异常上报(不冒泡到宿主应用) |
实例 API
返回值为 FlowGraphComponent 实例。
数据
| 方法 | 说明 |
|---|---|
getStages() |
获取当前 stages 快照(与内部模型隔离,改返回值不影响画布) |
setStages(stages) |
全量替换并重建 |
addStage(type, worldPos?) |
新增节点(返回新 id) |
removeStage(id) |
删除节点;同时清理其他节点对该节点 dependsOn 与 inputs.from 的引用 |
renameStage(id, newId) |
重命名(自动改写依赖引用与 inputs.from 前缀);重名返回 false |
updateStage(id, patch) |
局部更新单个节点 |
运行态
| 方法 | 说明 |
|---|---|
setRunStatus(map) / getRunStatus() |
设置 / 获取运行状态映射 { [id]: 'success' \| 'running' \| ... } |
run() |
模拟执行(拓扑序逐节点推进,返回 Promise) |
isRunning() |
是否正在执行 |
resetRun() |
重置运行状态 |
视图
| 方法 | 说明 |
|---|---|
autoLayout() |
自动分层布局 |
fit(padding?) |
适应视图 |
zoomIn() / zoomOut() |
放大 / 缩小 |
resetView() |
1:1 复位 |
getZoom() |
当前缩放比例 |
exportPNG(name?) |
导出 PNG |
resize(width?, height?) |
手动重算画布尺寸 |
attachMinimap(canvas) |
绑定小地图 canvas |
选中 / 模式
| 方法 | 说明 |
|---|---|
select(id) / getSelected() |
选中 / 当前选中 id |
nodeAt(world) |
命中测试:世界坐标 → 节点 |
setMode(mode) / getMode() |
切换 / 读取交互模式 |
生命周期
| 方法 | 说明 |
|---|---|
mount(container) |
挂载到容器(原生 createFlowGraph 内部已调用) |
update(partial) |
增量同步配置(stages / mode 变化走增量,不重建画布) |
destroy() |
销毁:释放画布、监听与定时器 |
逃生舱
fg.graph:底层jmGraph实例,可用全部原生能力(screenToWorld、getPosition、registerShape等)。
事件
除 onXxx 配置回调外,所有事件均可通过 fg.on(evt, fn) 订阅(回调经错误边界包裹,互不影响):
| 事件 | 参数 |
|---|---|
select |
id |
change |
stages(快照副本) |
edgeClick |
edge |
nodeEdit |
id |
contextMenu |
info(与 onContextMenu 等价) |
ready |
instance |
log |
msg, level |
error |
err, context |
const off = fg.on('select', id => renderDetail(id));
const off2 = fg.on('contextMenu', info => {
if (info.type === 'node') openMenu(info.id, info.clientX, info.clientY);
});
// off() / off2() 取消订阅
内置键盘快捷键
Delete:edit模式且有选中节点时删除该节点(焦点在输入框/可编辑区域时不拦截)Esc:取消当前选中
运行状态(runStatus)
runStatus 由外部驱动(如对接真实执行引擎)。组件内置 run() 为本地模拟:
fg.setRunStatus({ fetch_user: 'running', score: 'success' });
状态值见 STATUS_META:idle / pending / running / success / failed / skipped。
进阶
子类化
import { FlowGraphComponent } from 'jmgraph/src/components';
class MyFlow extends FlowGraphComponent {
_afterMount() {
super._afterMount();
// 自定义挂载后逻辑
}
}
const fg = new MyFlow({ stages });
fg.mount('#app');
高级渲染参数
createFlowGraph('#app', {
stages,
graphOptions: { dprScale: true, hitIndex: true, minZoom: 0.2, maxZoom: 8 }
});
框架封装示例
组件不提供 Vue / React 适配,下面是推荐的自封装写法(官方示例 example/flow-graph.html 即原生用法)。
Vue 3
<script setup>
import { ref, shallowRef, onMounted, onBeforeUnmount, watch } from 'vue';
import { FlowGraphComponent } from 'jmgraph/src/components';
const props = defineProps({ stages: Array, mode: { default: 'view' } });
const emit = defineEmits(['select', 'change']);
const el = ref(null);
const comp = shallowRef(null);
onMounted(() => {
comp.value = new FlowGraphComponent({ stages: props.stages, mode: props.mode });
comp.value.mount(el.value);
comp.value.on('select', id => emit('select', id));
comp.value.on('change', s => emit('change', s));
});
onBeforeUnmount(() => comp.value && comp.value.destroy());
// props 变化增量同步,不重建画布
watch(() => props.stages, v => comp.value && comp.value.update({ stages: v }), { deep: true });
watch(() => props.mode, v => comp.value && comp.value.update({ mode: v }));
</script>
<template>
<div ref="el" class="jm-flow-graph" style="width:100%;height:100%"></div>
</template>
React
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
import { FlowGraphComponent } from 'jmgraph/src/components';
export const FlowGraph = forwardRef(function FlowGraph(props, ref) {
const { stages = [], mode = 'view', onSelect, onChange } = props;
const elRef = useRef(null);
const compRef = useRef(null);
useEffect(() => {
const c = new FlowGraphComponent({ stages, mode });
c.mount(elRef.current);
c.on('select', id => onSelect && onSelect(id));
c.on('change', s => onChange && onChange(s));
compRef.current = c;
return () => c.destroy();
}, []);
useEffect(() => { compRef.current && compRef.current.update({ stages }); }, [stages]);
useEffect(() => { compRef.current && compRef.current.update({ mode }); }, [mode]);
useImperativeHandle(ref, () => ({
autoLayout: () => compRef.current && compRef.current.autoLayout(),
getGraph: () => compRef.current && compRef.current.graph
}));
return <div ref={elRef} className="jm-flow-graph" style= />;
});
运行示例
npm run dev
# 浏览器打开 example/flow-graph.html
该示例完整演示:拖拽添加节点、连接点建边、右键菜单、自动布局、缩放 / 平移 / 小地图、运行状态模拟与导出 PNG。