1
头图

开始前在这里贴一下我们的项目地址和官网,欢迎大家访问并为我们的项目点上star⭐️

项目地址:https://github.com/didi/LogicFlow
官网地址:https://site.logic-flow.cn/

引言

在流程图中,边(Edge) 的主要作用是连接两个节点,表示从一个节点到另一个节点的关系或流程。在业务系统中,边通常代表某种逻辑连接,比如状态转移、事件触发、任务流动等。对于复杂的流程图,边不仅仅是两点之间的连接,它还可以传递信息、约束流程的顺序,并通过不同的样式或标记来表达不同的含义。

不同的场景下,边可能需要具备丰富的样式或交互,比如箭头表示方向、虚线表示条件判断、动画表示动态效果等。因此,灵活定义和实现自定义边对于流程图的可视化设计尤为重要。

LogicFlow的边

为了灵活适配不同场景下的需求,LogicFlow的边模型是由 线条、箭头、文本、调整点五个模块组成。用户可以继承基础边类,对边的线条、箭头、文本和调整点进行自定义。

流程图的边

基础边:BaseEdge

属性方法简介

BaseEdgeModel中定义了一些核心属性,用于描述边的几何结构和样式。

属性释义
sourceNodeId起始节点Id
targetNodeId目标节点Id
startPoint起点信息,默认存储的是起始节点上连接该边锚点的坐标信息
endPoint终点信息,默认存储的是目标节点上连接该边锚点的坐标信息
text边文本信息,存储边上文本的内容和位置
properties自定义属性,用于存储不同业务场景下的定制属性
pointsList路径顶点坐标列表

围绕着这些核心属性,LogicFlow设计了支撑边运转的核心方法

方法用途
initEdgeData初始化边的数据和状态
setAnchors设置边的端点,startPoint和endPoint会在这个被赋值
initPoints设置边路径,pointsList会在这个阶段被赋值
formatText将外部传入的文本格式化成统一的文本对象

还有一些渲染使用的样式方法

方法用途
getEdgeStyle设置边样式
getEdgeAnimationStyle设置边动画
getAdjustPointStyle设置调整点样式
getTextStyle设置文本样式
getArrowStyle设置箭头样式
getOutlineStyle设置边外框样式
getTextPosition设置文本位置
运转过程

边实例化时,数据层Model类内部会先调用initeEdgeData方法,将无需处理的属性直接存储下来,设置为监听属性然后触发setAnchors、initPoints和formatText方法,生成边起终点、路径和文本信息存储并监听。

model的运转过程

视图层渲染时,Model中存储的数据会以外部参数的形式传给组件,由不同渲染方法消费。每个渲染方法都是从Model存储的核心数据中获取图形信息、从样式方法中获取图形渲染样式,组装到svg图形上。最终由render函数将不同模块方法返回的内容呈现出来。

视图运行原理

内置衍生边

LogicFlow内部基于基础边衍生提供三种边:直线边、折线边和曲线边。

直线边

在基础边的之上做简单的定制:

  1. 支持样式快速设置
  2. 限制文本位置在线段中间
  3. 使用svg的line元素实现线条的绘制
ViewModel
直线边实现逻辑直线边Model

<p align=center>直线边数据层和视图层源码逻辑</p>

折线边

折线边在Model类的实现上针对边路径计算做了比较多的处理,会根据两个节点的位置、重叠情况,使用 A*查找 结合 曼哈顿距离 计算路径,实时自动生成pointsList数据。在View类中则重写了getEdge方法,使用svg polyline元素渲染路径。
折线边效果

曲线边

曲线边和折线边类似,Model类针对边路径计算做了较多处理,不一样的是,为了调整曲线边的弧度,曲线边额外还提供了两个调整点,边路径也是根据边起终点和两个调整点的位置和距离计算得出,View类里使用svg的path元素渲染路径。

曲线边效果

一起实现一条自定义动画边

自定义边的实现思路和内置边的实现类似:继承基础边 → 重写Model类/View类的方法 → 按需增加自定义方法 → 命名并导出成模块

今天就带大家一起实现一条复杂动画边,话不多说,先看效果:

动画边效果

要实现这样效果的边,我们核心只需要做一件事:重新定义边的渲染内容。

在实际写代码时,主要需要继承视图类,重写getEdge方法。

实现基础边

那我们先声明自定义边,并向getEdge方法中增加逻辑,让它返回基础的折线边。

为了方便预览效果,我们在画布上增加节点和边数据。

自定义边实现
import { h, PolylineEdge, PolylineEdgeModel } from '@logicflow/core'

class CustomAnimateEdge extends PolylineEdge {
  // 重写 getEdge 方法,定义边的渲染
  getEdge() {
    const { model } = this.props
    const { points, arrowConfig } = model
    const style = model.getEdgeStyle()
    return h('g', {}, [
      h('polyline', {
        points,
        ...style,
        ...arrowConfig,
        fill: 'none',
        strokeLinecap: 'round',
      }),
    ])
  }
}

class CustomAnimateEdgeModel extends PolylineEdgeModel {}

export default {
  type: 'customAnimatePolyline',
  model: CustomAnimateEdgeModel,
  view: CustomAnimateEdge,
}
定义画布渲染内容
lf.render({
  nodes: [
    {
      id: '1',
      type: 'rect',
      x: 150,
      y: 320,
      properties: {},
    },
    {
      id: '2',
      type: 'rect',
      x: 630,
      y: 320,
      properties: {},
    },
  ],
  edges: [
    {
      id: '1-2-1',
      type: 'customPolyline',
      sourceNodeId: '1',
      targetNodeId: '2',
      startPoint: { x: 200, y: 320 },
      endPoint: { x: 580, y: 320 },
      properties: {
        textPosition: 'center',
        style: {
          strokeWidth: 10,
        },
      },
      text: { x: 390, y: 320, value: '边文本3' },
      pointsList: [
        { x: 200, y: 320 },
        { x: 580, y: 320 },
      ],
    },
    {
      id: '1-2-2',
      type: 'customPolyline',
      sourceNodeId: '1',
      targetNodeId: '2',
      startPoint: { x: 150, y: 280 },
      endPoint: { x: 630, y: 280 },
      properties: {
        textPosition: 'center',
        style: {
          strokeWidth: 10,
        },
      },
      text: { x: 390, y: 197, value: '边文本2' },
      pointsList: [
        { x: 150, y: 280 },
        { x: 150, y: 197 },
        { x: 630, y: 197 },
        { x: 630, y: 280 },
      ],
    },
    {
      id: '1-2-3',
      type: 'customPolyline',
      sourceNodeId: '2',
      targetNodeId: '1',
      startPoint: { x: 630, y: 360 },
      endPoint: { x: 150, y: 360 },
      properties: {
        textPosition: 'center',
        style: {
          strokeWidth: 10,
        },
      },
      text: { x: 390, y: 458, value: '边文本4' },
      pointsList: [
        { x: 630, y: 360 },
        { x: 630, y: 458 },
        { x: 150, y: 458 },
        { x: 150, y: 360 },
      ],
    },
    {
      id: '1-2-4',
      type: 'customPolyline',
      sourceNodeId: '1',
      targetNodeId: '2',
      startPoint: { x: 100, y: 320 },
      endPoint: { x: 680, y: 320 },
      properties: {
        textPosition: 'center',
        style: {
          strokeWidth: 10,
        },
      },
      text: { x: 390, y: 114, value: '边文本1' },
      pointsList: [
        { x: 100, y: 320 },
        { x: 70, y: 320 },
        { x: 70, y: 114 },
        { x: 760, y: 114 },
        { x: 760, y: 320 },
        { x: 680, y: 320 },
      ],
    },
  ],
})

然后我们就能获得一个这样内容的画布:

基础动画边

添加动画

LogicFlow提供的边动画能力其实是svg 属性和css属性的集合,目前主要支持了下述这些属性。

type EdgeAnimation = {
    stroke?: Color; // 边颜色, 本质是svg stroke属性
    strokeDasharray?: string; // 虚线长度与间隔设置, 本质是svg strokeDasharray属性
    strokeDashoffset?: NumberOrPercent; // 虚线偏移量, 本质是svg strokeDashoffset属性
    animationName?: string; // 动画名称,能力等同于css animation-name
    animationDuration?: `${number}s` | `${number}ms`; // 动画周期时间,能力等同于css animation-duration
    animationIterationCount?: 'infinite' | number; // 动画播放次数,能力等同于css animation-iteration-count
    animationTimingFunction?: string; // 动画在周期内的执行方式,能力等同于css animation-timing-function
    animationDirection?: string; // 动画播放顺序,能力等同于css animation-direction
};

接下来我们就使用这些属性实现虚线滚动效果。

边的动画样式是取的 model.getEdgeAnimationStyle() 方法的返回值,在内部这个方法是取全局主题的edgeAnimation属性的值作为返回的,默认情况下默认的动画是这样的效果:

default-edge-animation

开发者可以通过修改全局样式来设置边动画样式;但如果是只是指定类型边需要设置动画部分,则需要重写getEdgeAnimationStyle方法做自定义,就像下面这样:

class ConveyorBeltEdgeModel extends PolylineEdgeModel {
  // 自定义动画
  getEdgeAnimationStyle() {
    const style = super.getEdgeAnimationStyle()
    style.strokeDasharray = '40 160' // 虚线长度和间隔
    style.animationDuration = '10s' // 动画时长
    style.stroke = 'rgb(130, 179, 102)' // 边颜色
    return style
  }
}

然后在getEdge方法中加上各个动画属性

// 改写getEdge方法内容
const animationStyle = model.getEdgeAnimationStyle()
const {
  stroke,
  strokeDasharray,
  strokeDashoffset,
  animationName,
  animationDuration,
  animationIterationCount,
  animationTimingFunction,
  animationDirection,
} = animationStyle

return h('g', {}, [
  h('polyline', {
    // ...
    strokeDasharray,
    stroke,
    style: {
      strokeDashoffset: strokeDashoffset,
      animationName,
      animationDuration,
      animationIterationCount,
      animationTimingFunction,
      animationDirection,
    },
  }),
])

我们就得到了定制样式的动画边:

基础边动画

添加渐变颜色和阴影

最后来增加样式效果,我们需要给这些边增加渐变颜色和阴影。
SVG提供了元素linearGradient定义线性渐变,我们只需要在getEdge返回的内容里增加linearGradient元素,就能实现边颜色线性变化的效果。
实现阴影则是使用了SVG的滤镜能力实现。

// 继续改写getEdge方法内容
return h('g', {}, [
  h('linearGradient', { // svg 线性渐变元素
    id: 'linearGradient-1',
    x1: '0%',
    y1: '0%',
    x2: '100%',
    y2: '100%',
    spreadMethod: 'repeat',
  }, [
    h('stop', { // 坡度1,0%颜色为#36bbce
      offset: '0%',
      stopColor: '#36bbce'
    }),
    h('stop', { // 坡度2,100%颜色为#e6399b
      offset: '100%',
      stopColor: '#e6399b'
    })
  ]),
  h('defs', {}, [
    h('filter', { // 定义滤镜
      id: 'filter-1',
      x: '-0.2',
      y: '-0.2',
      width: '200%',
      height: '200%',
    }, [
      h('feOffset', { // 定义输入图像和偏移量
        result: 'offOut',
        in: 'SourceGraphic',
        dx: 0,
        dy: 10,
      }),
      h('feGaussianBlur', { // 设置高斯模糊
        result: 'blurOut',
        in: 'offOut',
        stdDeviation: 10,
      }),
      h('feBlend', { // 设置图像和阴影的混合模式
        mode: 'normal',
        in: 'SourceGraphic',
        in2: 'blurOut',
      }),
    ]),
  ]),
  h('polyline', {
    points,
    ...style,
    ...arrowConfig,
    strokeDasharray,
    stroke: 'url(#linearGradient-1)', // 边颜色指向渐变元素
    filter: 'url(#filter-1)', // 滤镜指向前面定义的滤镜内容
    fill: 'none',
    strokeLinecap: 'round',
    style: {
      strokeDashoffset: strokeDashoffset,
      animationName,
      animationDuration,
      animationIterationCount,
      animationTimingFunction,
      animationDirection,
    },
  }),
])

就得到了我们的自定义动画边

最终动画边效果

结尾

在流程图中,边不仅仅是节点之间的连接,更是传递信息、表达逻辑关系的重要工具。通过 LogicFlow,开发者可以轻松地创建和自定义边,以满足不同的业务场景需求。从基础的直线边到复杂的曲线边,甚至动画边,LogicFlow 都为开发者提供了高度的灵活性和定制能力。

希望能通过这篇文章抛砖引玉,帮助你了解在 LogicFlow 中创建和定制边的核心技巧,打造出符合你业务需求的流程图效果。

如果这篇文章对你有帮助,请为我们的项目点上star,非常感谢ღ( ´・ᴗ・` )

项目传送门:https://github.com/didi/LogicFlow


LogicFlow官方号
4 声望0 粉丝

开源流程图编辑框架,欢迎大家一起共建~