LogicFlow自定义边(Edge)

2023/1/2 LogicFlow

LogicFlow 是一款流程图编辑框架,提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。LogicFlow支持前端研发自定义开发各种逻辑编排场景,如流程图、ER图、BPMN流程等。在工作审批配置、机器人逻辑编排、无代码平台流程配置都有较好的应用。

🎄Hi~ 大家好,我是小鑫同学,一位长期从事前端开发的编程爱好者,我将使用更为实用的案例输出更多的编程知识,同时我信奉分享是成长的唯一捷径,在这里也希望我的每一篇文章都能成为你技术落地的参考~

1. 准备工作:

1. 初始化项目:

1.1 初始化环境:

使用pnpm create vite创建项目,选择Vue框架及TypeScript变体;

1.2 安装核心模块:

安装核心模块:pnpm add @logicflow/core

2. 初始化容器及LogicFlow对象:

2.1 准备容器:

准备一个container容器,并通过css初始化容器的尺寸;

<template>
  <div ref="container" class="container"></div>
</template>

<style scoped>
.container {
  width: 500px;
  height: 400px;
}
</style>

1
2
3
4
5
6
7
8
9
10
11

2.2 导入模块:

导入LogicFlow和对应的样式依赖模块;

<script setup lang="ts">
import LogicFlow from "@logicflow/core";
import "@logicflow/core/dist/style/index.css";
</script>
1
2
3
4

2.3 实例化&渲染:

onMounted后对LogicFlow对象实例化并进行渲染,为了能看到初始容器建议开启grid选项;

<script setup lang="ts">
import { onMounted, ref } from "vue";

const container = ref();
const lf = ref<LogicFlow>();

onMounted(() => {
  lf.value = new LogicFlow({
    container: container.value,
    grid: true,
  })
  lf.value.render();
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

2.4 预览效果:

启动项目后在浏览器访问即可看到这个500*400的点阵图,这个就是后面绘制节点的区域;

image.png

2. 自定义边(Edge):

2.1 准备自定义模板:

准备自定义边(Edge)模板,分别继承PolygonNodePolylineEdgeModel并导出typeviewmodel三个选项:

import { PolylineEdge, PolylineEdgeModel } from "@logicflow/core";

class CustomEdgeNode extends PolylineEdge {
	// 官网指示:由于边的编辑复杂度问题,绝大多数情况下,自定义边时不推荐自定义view
}

class CustomEdgeModel extends PolylineEdgeModel {

}

export default {
    type: "CustomEdge",
    view: CustomEdgeNode,
    model: CustomEdgeModel,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

2.2 优先注册&使用:

优先进行注册并定义数据,方便自定义边(Edge)实时预览;

<script setup lang="ts">
// 导入自定义节点
import CustomEdge from "./edges/CustomEdge";

// 准备两个Node,并使用自定义Edge来连接它们俩
const graphData = {
  nodes: [
    {
      id: '242b1b6c-1721-4b10-b4ad-c895094cf332',
      type: 'rect',
      x: 100,
      y: 100
    },
    {
      id: 'e59d6ecd-68f7-4d50-8e3f-29e67b6e5f16',
      type: 'circle',
      x: 300,
      y: 200
    }
  ],
  edges: [
    {
      sourceNodeId: '242b1b6c-1721-4b10-b4ad-c895094cf332',
      targetNodeId: 'e59d6ecd-68f7-4d50-8e3f-29e67b6e5f16',
      type: 'CustomEdge',
    }
  ]
}

onMounted(() => {
  // 在执行render前进行注册
  lf.value.register(CustomEdge);
  lf.value.render(graphData);
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

2.3 自定义Edge&Text&Outline的风格:

对于Edge风格的自定义可以通过重写不同的函数来实现,如重写:getEdgeStyle()getTextStyle()getOutlineStyle(),还有最后对箭头风格的自定义;

class CustomEdgeModel extends PolylineEdgeModel {
    getEdgeStyle() {
        const style = super.getEdgeStyle();
        const { properties } = this;
        if (properties.isActived) {
            style.strokeDasharray = "4 4";
        }
        style.stroke = "orange";
        return style;
    }
    getTextStyle() {
        const style = super.getTextStyle();
        style.color = "#3451F1";
        style.fontSize = 20;
        style.background && (style.background.fill = "#F2F131");
        return style;
    }
    getOutlineStyle() {
        const style = super.getOutlineStyle();
        style.stroke = "red";
        style.hover && (style.hover.stroke = "red");
        return style;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

2.4 保存锚点信息:

默认情况下LF只记录Node之间的信息,但是如果遇到不同的锚点代表不同含义的业务场景时,就可以通过重写getData()函数来支持记录对应锚点的ID;

// 定义类型:支持源锚点和目标锚点ID的记录属性
type ExtendEdgeData = {
    sourceAnchorId: string;
    targetAnchorId: string
} & EdgeData & { pointsList: { x: any; y: any; }[]; }

class CustomEdgeModel extends PolylineEdgeModel {
    getData(): ExtendEdgeData {
        const data = super.getData() as ExtendEdgeData;
        data.sourceAnchorId = this.sourceAnchorId;
        data.targetAnchorId = this.targetAnchorId;
        return data;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

2.5 开启边(Edge)动画:

为了表示Node之间流动的效果,可以通过lf.openEdgeAnimation(edgeId)启动默认动画,也可以通过重写getEdgeAnimationStyle()函数来自定义动画的属性;

  1. 开启默认动画:为需要开启动画的Edge增加id字段,并在执行渲染函数render()后开启动画:
<script setup lang="ts">
const graphData = {
  edges: [
    {
      id: '702a4d2f-b516-4769-adb0-5a4f4f5c07a9',
      sourceNodeId: '242b1b6c-1721-4b10-b4ad-c895094cf332',
      targetNodeId: 'e59d6ecd-68f7-4d50-8e3f-29e67b6e5f16',
      type: 'CustomEdge',
    }
  ]
}

onMounted(() => {
  lf.value.render(graphData);
  // 执行渲染后开启动画
  lf.value.openEdgeAnimation("702a4d2f-b516-4769-adb0-5a4f4f5c07a9");
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. 自定义动画属性:重写getEdgeAnimationStyle()函数设置动画属性,重写setAttributes()函数开启动画:
class CustomEdgeModel extends PolylineEdgeModel {

  	setAttributes(): void {
        this.isAnimation = true;
    }
    
    getEdgeAnimationStyle() {
        const style = super.getEdgeAnimationStyle();
        style.strokeDasharray = "5 5";
        style.animationDuration = "10s";
        return style;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

2.6 自定义节点间边(Edge)的类型:

默认节点间边的类型可以在实例化LF时通过edgeType选项进行调整,也可以使用lf.setDefaultEdgeType(edgeType)函数来指定;除此之外,为了满足不同的业务节点使用不同类型的边来表示还可以通过实例化LF时通过设置edgeGenerator函数进行显示规则的定义;

  1. 通过函数设置默认类型:
<script setup lang="ts">
onMounted(() => {
  lf.value.setDefaultEdgeType("CustomEdge");
})
</script>
1
2
3
4
5
  1. 通过选项设置默认类型:
<script setup lang="ts">
onMounted(() => {
  lf.value = new LogicFlow({
    edgeType: "CustomEdge",
  })
})
</script>
1
2
3
4
5
6
7
  1. 为不同节点设置不同的类型:
<script setup lang="ts">
onMounted(() => {
  lf.value = new LogicFlow({
    edgeGenerator(sourceNode, targetNode, currentEdge?) {
      if (sourceNode.type === 'rect') return 'CustomEdge'
    },
  })
})
</script>
1
2
3
4
5
6
7
8
9

2.7 自定义箭头的类型:

通过setTheme()函数中提供的arrow选项,可以指定默认Edge箭头的风格;也可以在继承PolylineEdge后通过重写getEndArrow()函数来实现更多显示风格的

<script setup lang="ts">

onMounted(() => {
  lf.value.setTheme({
    arrow: {
      offset: 4, // 箭头垂线长度
      verticalLength: 2, // 箭头底线长度
    }
  })
})
</script>
1
2
3
4
5
6
7
8
9
10
11

通过预留的业务扩展属性来区分不同的箭头风格:

class CustomEdgeNode extends PolylineEdge {
    getEndArrow() {
        const { model } = this.props;
        const { properties: { arrowType } } = model;
        const { stroke, strokeWidth } = this.getArrowStyle();
        const pathAttr = {
            stroke,
            strokeWidth
        }
        if (arrowType === 'empty') {  // 空心箭头
            return h('path', {
                ...pathAttr,
                fill: '#FFF',
                d: 'M -10 0  -20 -5 -30 0 -20 5 z'
            })
        } else if (arrowType === 'half') { // 半箭头
            return (
                h('path', {
                    ...pathAttr,
                    d: 'M 0 0 -10 5'
                })
            )
        }
        return h('path', {
            ...pathAttr,
            d: 'M 0 0 -10 -5 -10 5 z'
        })
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

如果看完觉得有收获,欢迎点赞、评论、分享支持一下。你的支持和肯定,是我坚持写作的动力~

Last Updated: 2023/2/1 03:59:51