关于流程图库,看这一篇就够了~

867 阅读15分钟

简介

传统场景下,流程图经常被用于业务系统中快速满足各类可流程化的需求,比如审批流、工单流转、客服语音电话转接配置等,流程图库通过提供丰富的流程图交互和编辑功能,并支持灵活的节点自定义和插件拓展来满足丰富的定制需求。

随着 AI 的发展,流程图的应用在 AI 模型训练领域也开始发力,在模型训练流程可视化、机器学习算法设计、数据处理工作流以及模型评估和验证工作流等场景的应用,帮助研究人员和开发者直观的理解和管理复杂的模型训练流程。像 Dify 就是其中优质的产品,它的直观界面结合了人工智能工作流程、RAG管道、代理功能、模型管理、可观察性等功能,让开发者能够快速从原型到生产。

image.png

那如果有类似的需求,我们该选择什么流程图库来实现这样的功能呢?下面我将介绍一下市面上优质的流程图库开源项目和我们 LogicFlow 的一些设计思路给大家,做以参考。

一些优秀的流程图开源库

X6

X6 是由 AntV 团队开发的图形编辑引擎,专注于流程图、图标、图形编辑,主要用于构建复杂的流程图、知识图谱、关系图等。

它的核心特点包括:

  1. 快速上手,极易定制:X6 提供了丰富的 API 和配置项,用户可以通过组合自定义节点、边、布局以及事件处理器;
  2. 模型-视图分离:X6 的设计采用 MVC(模型-视图-控制其)架构,模型与视图分离,使得图形和流程更加灵活,能够实现复杂的交互;
  3. 强大的事件系统:支持细粒度的事件订阅和处理机制,适合开发复杂交互的图形应用;
  4. 集成和扩展性强:X6 可以轻松集成到现有的前端框架(如 React、Vue、Angular)中,提供了丰富的差价,并且能通过自定义插件拓展功能。

ReactFlow

ReactFlow 是一个专为 React 应用设计的库,旨在让开发者更轻松的构建交互式流程图。它侧重于 React 的生态系统,简化了与 React 的集成与状态管理。

它的核心特点包括:

  1. React 优化:完全基于 React,所有的图形和交互通过 React  的状态机制和组件进行管理,利用 React 的虚拟 DOM 优化性能;
  2. 内置支持节点和边: React 的节点定义是基于 HTML,它可以利用 HTML 和 CSS 更方便快捷的自定义节点内容,也没有额外的学习成本;
  3. 可拖拽与缩放:ReactFlow 内置了拖拽、缩放等基本交互功能,使得图形编辑和操作更加直观;
  4. 灵活的事件管理:通过 React 的机制处理图形的交互事件和状态变化,便于与 React 项目中的其他组件协作。

mxGraph

mxGraph 是热门的开源项目 draw.io 中用来实现流程图绘制的库。它是一个功能非常强大的库,具有较长的开发历史。也正是为此,该库的实现方法较为原始,基于面向对象类的方式拓展,非常适合构建跨平台的可视化应用。

它的核心特点包括:

  1. 纯前端实现,无依赖: mxGraph 是一个独立的 JavaScript 库,不依赖于其它框架;
  2. 支持复杂图形编辑:mxGraph 提供了丰富的功能,如图形拖拽、缩放、旋转、布局管理、自动对齐等,是构建复杂图形编辑器的理想选择;
  3. 内置布局算法:提供了多种图形布局算法,如层次布局、力导向布局、圆形布局等,适合不同的可视化场景;
  4. 跨平台支持:由于它是纯 JavaScript 实现的,无需依赖特定的框架,能在各种平台(如 Web、桌面)上运行;
  5. 事件处理:支持对节点和边的各种操作(如选择、删除、拖拽)的事件监听。

BPMN.js

BPMN.js 是一个专门绘制和编辑 BPMN 2.0 (Business Process Mode and Notation 业务流程模型与符号)标准的库,重点在于业务流程建模与图表的可视化。

它的核心特点是:

  1. 专注于 BPMN: BPMN.js 专为 BPMN 标准设计,能够准确渲染 BPMN 图表,包括所有标准的 BPMN 元素(任务、事件、网关、泳道等);
  2. 高度定制的 BPMN 编辑器:该库自带了编辑器和模型转换功能,用户可以拖拽、调整和编辑流程图,并生成符合 BPMN 标准的 XML 文件;
  3. 扩展性和插件机制:开发者可以通过插件扩展 BPMN.js 的功能,适应更复杂的业务流程建模需求;
  4. 符合业务标准:BPMN.js 生成的图表完全符合 BPMN 2.0 标准,便于与其他 BPMN 工具和系统集成。

大概总结一下,X6 是基于HTML和 SVG,封装 DOM 操作方法,通过自己实现的异步调度方法,实现流程图的绘制;ReactFlow 基于 React 的渲染调度能力,使用 HTML 自定义节点,基于 zustand 进行状态管理;mxGraph.js 是开源项目 draw.io 中实现图渲染的库,使用 SVG 自定义节点和边,基于原生JS 实现 DOM 更新、图形变换以及事件监听。BPMN.js 是基于 BPMN 规范实现的流程图绘制库。

LogicFlow 的设计思路

而我们 LogicFlow 立项于 20 年初,我们在设计初期,在图渲染方案、模块抽象、事件封装设计模式选择、可拓展性以及工具中心上充分思考,向着「可视化模型、高可定制性、灵活易拓展」的目标,争取为用户带来优秀的体验。我们也本着持续向优秀前进的原则,持续不断地跟竞品做能力拉齐。

随着 2.0 版本的发布,LogicFlow 从 配置化能力增强、增加新插件、官网门户优化以及源码开发体验四方面进行了全面升级 ,在功能性和稳定性方面都得到了显著提升,为用户和开发者提供了更强大的工具和能力支持。

下面我将从架构模型拆解各模块的设计思路,希望能做到通俗易懂,让大家也能够参与我们的开源项目贡献。

图渲染方案

前端绘制图形无非就是 HTML + CSS、Canvas、SVG,我们综合做了一下对比,列出了相应的优劣势

image.png

在流程图的场景下,不需要渲染大量的节点(最多几千个元素),对于动画的诉求也不高。SVG 基于 DOM 的特性会更适合我们,一个是学习成本和开发成本更低,另一个是基于 DOM 可以做的拓展也更多。不过 SVG 标签内部并不支持插入其它比如 div 这种标签,所以在实现某些功能的时候,都需要结合其它 HTML 标签。

所以我们最终选择使用 HTML + SVG 来完成图的渲染,SVG 负责图形、线的部分,HTML 用来实现文本、菜单、背景等图层。 

模块抽象

image.png

首先,我们构建多个图层来承担不同的职责,以方便实现功能及拓展。画布层是一个 SVG 图层,所有节点、线都绘制在画布层,也负责监听图上的各种事件。工具层用于渲染一些内部工具,比如多选、对齐、文本编辑等内置工具。背景层用于用户添加自定义背景,网格层负责渲染网格。

然后,基础图形(Basic Shape) 的职责是基于 SVG 对图形渲染的封装,提供默认样式、把用户传入的属性做转换等,主要包含 Rect、Circle、Ellipse、Polygon、Path、Polyline、Line 等,方便 LogicFlow 内部复用,比如圆形节点和锚点都是基于 Circle 实现的;节点和线上的锚点、线上的箭头也是基于基础图形实现的。

接着我们定义了 BaseNode 和 BaseEdge 作为基础元素,它们分别是节点和线通用能力的封装,基于基础图形、锚点、文本,并封装了对事件和样式的处理。继承 BaseNode 或 BaseEdge,通过重写 getShape 方法我们就可以得到 RectNode、CircleNode 等可渲染的节点。用户也可以通过继承基础元素或内置元素自定义自己需要的节点。

因为流程图是富交互或者说重编辑的,有了这几个基础的模块,接下来要做的就是富交互的方案设计,即用户在图上的任何操作都要给出响应。比如我拖拽一个节点,那关联的线需要跟着移动,还需要能识别出在某个水平线上有没有其他节点(对齐线)。

MVVM + Virtual DOM

首先我们考虑到整个图编辑器具备很多状态信息,并且要实现编辑图上各模块的响应就必须要有状态的通信能力。其次如果要实现类似 redo/undo 这类功能,那整个图就一定需要根据数据得出渲染,即 fn(state) => View,比较好的方式就是通过 Model 来驱动 View。

最终我们选择基于 MVVM 这个广泛被应用于当前前端工程中的设计模式来构建 LogicFlow 的图编辑器,定义图的 View 和 Model 层,使工程代码具备一定的解耦。与此同时,引入 Mobx 来实现我们的状态管理、数据响应的能力,一张图基于一份 Model 做状态的通信。使用 Mobx 的另一个原因是:只要我想,就可以做到最细粒度的数据绑定(观测),可以减少没必要的渲染。

以下是 LogicFlow 图编辑器的 MVVM 示意图

image.png

通过上图看到,View 层(Graph、节点、边等)通过数据绑定,会在 Model 发生变化之后做出响应/更新。前面我们提到了关于图的渲染是基于 SVG + HTML 实现的,那要做 View 层的更新无非就是命令式和声明式两个选择:

  1. 命令式:比如 jQuery 的 api,$('.rectNode').attrs({ x: 1, y: 2 }),像这种方式操作 DOM 代码其实比较繁琐,在重交互的场景下写代码会比较冗余。antv/x6 就是目前比较流行的一个支持通过命令式的方式来绘图的库

  2. 声明式:比如 React、Vue 这类 View 框架,其中一个比较核心的能力就是坐到了 state => UI,通过声明式的方式来构建 DOM,只要状态发生变化,那 UI 就更新

除了考虑到命令式在操作 DOM 的场景下写代码会比较繁琐之外,还有一个原因就是操作 DOM 的成本问题,在基于 State 更新 UI 的设计下,我们自然而然想到了引入 Virtual DOM 来解决某些场景下的更新效率,这也可以一定程度上弥补「基于 SVG 渲染图形」可能造成的渲染性能问题。

总之,选择 MVVM 的设计模式并引入 Virtual DOM,最根本的两个原因便是提升我们图编辑器场景下的开发效率,以及在 HTML + SVG 的图渲染方案下,可以追求更好的性能表现

事件系统

介绍了在“状态”和“响应”我们做的设计,那要收集到用户的各类“操作”并及时上报和冒泡,就需要一套事件系统。最主要的就是复用和统一上报。

image.png

复用即怎么保证所有节点和线都具备默认的事件回调,以及针对复杂事件(拖拽)的处理逻辑如何共用。

  • 针对复杂事件的处理,我们封装了 StepperDrag 类,支持拖拽时按步长进行移动,也通过 mousemove、mousedown、mouseup 来模拟 H5 的 dragEnter、dragEnd 和 drop 事件。DnD 则是通过抽象 dragSource 和 dragtarget 两个实体来实现 drag 和 drop 的交互,比如拖拽创建节点
  • 在前面「模块抽象」提到了内部有 BaseNode 和 BaseEdge 这样的抽象,内置节点和自定义节点通过继承基类来实现通用的能力,所以 LogicFlow 内部默认的事件回调实际是通过继承来复用的
  • EventCenter。通过事件总线统一上报,把内部捕获到的所有用户行为事件,按照一定的规范和格式 emit(ev, args) 都上报到 EventCenter,最终冒泡到 LogicFlow 类,由 LogicFlow 类统一跟宿主交互。此外,图编辑器内任何地方也都可以通过 EventCenter 做时间的触发和监听

工具中心

工具中心的定位是解决某类特定问题的 utils,比如上面提到的 StepperDrag 和 EventCenter。此外,在图编辑的过程中,如果要实现比较好的交互效果,实际有很多复杂的计算逻辑要处理。

  • 坐标系。浏览器的 clientX、clientY 坐标系,以及 SVG 图本身的坐标系,当出现图的缩放和平移的时候,两个坐标系显然是不同的,有些场景就需要做坐标系的转换
  • Algorithm。是专门通过几何、算法来处理一些可视化的问题。比如:当一个节点在同一方向有多条折线连出的时候,如何做路径的合并以展示起来更美观
  • History。主要提供 redo 和 undo 的能力。通过两个栈来存储 undos 和 redos,并限制最大长度,得益于 MVVM 的设计模式,能方便的做数据变化的观测和 Model 驱动 View。

可扩展性

在程序世界中,小到一个方法,一个服务,再到一个开发框架 react,小程序开发框架,大到一个 Chrome 类的应用平台,都具备自己的可扩展性,这也是软件开发过程中要考虑的一种设计选择。对于 LogicFlow,是解决某个领域问题的开发框架,首先 API 要具备可扩展性;此外 LogicFlow 还提供了视图层,在 View 部分应该能够让用户做二次开发。在这两个扩展方向确定之后,最主要的还是结合业务需求,要能满足当前和未来一段时间内预见的业务场景,但也不能过度设计。

API 上的设计

首先,LogicFlow 在面向用户使用这一层,完全是基于面向对象的设计模式封装的,最大的好处是几乎每个程序员都熟悉它的使用,使用成本低。通过下面初始化方式便可以了解

const lf = new LogicFlow({
  container: document.querySelector('#graph'), // 获取渲染器容器
  width: 700, // 或者通过 CSS 控制 container 容器的大小即可
  height: 600,
  background: {
    color: '#f0f0f0',
  },
  grid: {
    type: 'dot',
    size: 20,
  },
});
lf.render({ nodes: [], edges: [] }); // 在界面上渲染视图

通过 class LogicFlow,用户实例化一次便得到一个流程图的实例,状态也是私有的,各种使用方法通过 lf 的实例调用即可。关于 API 拓展的设计总结来看:

  1. 面向对象的设计模式,LogicFlow 内部做好封装,用户可以做继承、重写接口/方法
  2. 方法的设计,首先是要有固定类型的输入和输出。此外,LogicFlow 也提供了类似于 extends 的方法,通过 LogicFlow.use(fn) 在原型上拓展方法
  3. 通过观察者的模式做通信,即提供 on 方法供宿主系统订阅 LogicFlow 各内部事件
  4. 图的数据可定制。无论是一个节点或线上有哪些自定义的业务属性,还是流程图要导出什么样的数据,都应该能够定制

插件化

View 层的拓展性,除了用户能够定制展示方式之外,最重要的是插件化,因为在流程可视化这条路上,不同的业务场景下需要的能力不尽相同,LogicFlow 很难做到支持所有的场景,所以提供好的插拔能力,让用户二次开发是比较好的选择。目前,在 UI 界面上,我们开放了两个能力:

  1. 节点和线支持二次开发,即自定义节点、线
  2. 可开发 UI 组件注册到 LogicFlow 的组件画布内

基于插件化的思路,我们已经支持了不同的业务系统,并在这个过程中把一些稍微通用的能力沉淀出来,并封装到 @logicflow/extension 包,比如用来支持 BPMN 规范的节点。目前 extension 内的拓展主要分了四类:UI 组件自定义节点APIAdapter。关于插件的详细设计和具体实现再次不展开,有相关的文章介绍,可以在官网查看阅读。

整体架构图

最终,如下图所示 LogicFlow 目前的架构图,@logicflow/core 提供了流程图编辑器的基础能力。右边 @logicflow/extension 是基于 core 包的拓展能力开发的插件

image.png

结语

在 LogicFlow 的发展过程中,我们深知开发者体验的重要性。我们致力于让每一位开发者都能轻松上手,迅速解决实际问题。通过不断优化,我们希望 LogicFlow 在流程图编辑领域能够成为开发者首选的工具,我们为此也会不断坚持前行。

非常欢迎大家积极参与贡献,也欢迎大家联系我们与我们交流,如果能有个 Star 就更好啦~