LogicFlow 默认有一些节点。但有时候,默认的节点完成不了项目需求。这个就需要自定义节点,刚好LogicFlow 支持自定义节点。
https://07.logic-flow.cn/guide/advance/customNode.html
如上图,现在需要一个输入口,n 个输出口,这个 n 是大于 0 的整数。一般也就最多 10 以下的整数吧。默认的节点就不支持,这里就需要自定义了。
要解决以下问题。
- 节点支持 resize (继承 RectResize,就可以实现)
- 根据输出口的数字动态生成锚点(1 个默认输入锚点, n 个输出锚点)
- 在锚点旁边加入数字进行标识(大于 1 个的时候,需要和数据一一对应,所以需要标识)
开始
建立 FreeConditionNode.ts
节点, 填充以下代码。
import { type AnchorsOffsetItem, h } from '@logicflow/core'
import { RectResize } from '@logicflow/extension'
class FreeConditionModel extends RectResize.model {
initNodeData(data: any): void {
super.initNodeData(data)
this.text.draggable = false // 不允许文本被拖动
this.text.editable = false // 不允许文本被编辑
this.outNum = data.properties.outNum ?? 0
}
setAttributes() {
// 设置自定义锚点
const w = this.width
const h = this.height
const padding = 24
const anchors: AnchorsOffsetItem[] = [[0, -h / 2]]
if (this.outNum === 1) {
anchors.push([0, h / 2])
} else if (this.outNum > 1) {
for (let i = 0; i < this.outNum; i++) {
anchors.push([(i * (w - padding * 2)) / (this.outNum - 1) + padding - w / 2, h / 2])
}
}
this.anchorsOffset = anchors
}
}
class FreeConditionView extends RectResize.view {
getShape(): any {
const { model } = this.props
const { x, y, width, height, radius, anchorsOffset } = model
const style = model.getNodeStyle()
const rect: any = h('rect', {
...style,
x: x - width / 2,
y: y - height / 2,
width,
height,
rx: radius,
ry: radius
})
const texts = anchorsOffset.map((anchor: AnchorsOffsetItem, index: number) => {
return this.renderTextAtAnchor(index, anchor, width, height, x, y)
})
return h('g', {}, [rect, super.getControlGroup(), super.getResizeShape(), ...texts])
}
renderTextAtAnchor(
index: number,
anchor: AnchorsOffsetItem,
width: number,
height: number,
x: number,
y: number
): any {
const textX = anchor[0] + x
const textY = anchor[1] + y - 12
const text = index === 0 ? '' : index
return h(
'text',
{
x: textX,
y: textY,
'text-anchor': 'middle',
alignmentBaseline: 'middle',
fill: 'black'
},
text
)
}
}
export default {
type: 'free-condition',
view: FreeConditionView,
model: FreeConditionModel
}
之所以把 model 放上边,是因为在 model 中,会随着具体项目,加入一些具体项目的数据,而 view 写好了,几乎是不变的,也不需要再去关心的。
注册。
// 基础图形
import LogicFlow from '@logicflow/core'
import RectNode from './basic/RectNode'
import RectRadiusNode from './basic/RectRadiusNode'
import DiamondNode from './basic/DiamondNode'
import FreeConditionNode from './basic/FreeConditionNode'
import ProLine from './edge/ProLine'
export const registerCustomElement = (lf:LogicFlow) => {
// 注册基础图形
lf.register(RectNode)
lf.register(RectRadiusNode)
lf.register(DiamondNode)
lf.register(FreeConditionNode)
lf.register(ProLine)
}
使用。
import LogicFlow from '@logicflow/core'
import { Dagre } from '@logicflow/layout'
import { SelectionSelect, InsertNodeInPolyline, Snapshot, Menu } from '@logicflow/extension'
import { registerCustomElement } from './node/index'
let lf: LogicFlow
lf = new LogicFlow({
container: container,
overlapMode: 1,
autoWrap: true,
metaKeyMultipleSelected: true,
keyboard: {
enabled: true
},
grid: {
visible: false,
size: 10
},
background: {
backgroundImage:
'url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImdyaWQiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTSAwIDEwIEwgNDAgMTAgTSAxMCAwIEwgMTAgNDAgTSAwIDIwIEwgNDAgMjAgTSAyMCAwIEwgMjAgNDAgTSAwIDMwIEwgNDAgMzAgTSAzMCAwIEwgMzAgNDAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2QwZDBkMCIgb3BhY2l0eT0iMC4yIiBzdHJva2Utd2lkdGg9IjEiLz48cGF0aCBkPSJNIDQwIDAgTCAwIDAgMCA0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDBkMGQwIiBzdHJva2Utd2lkdGg9IjEiLz48L3BhdHRlcm4+PC9kZWZzPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JpZCkiLz48L3N2Zz4=")',
backgroundRepeat: 'repeat'
},
plugins: [SelectionSelect, InsertNodeInPolyline, Snapshot, Dagre, Menu]
})
lf.setTheme({
baseEdge: { strokeWidth: 1 },
baseNode: { strokeWidth: 1 },
nodeText: { overflowMode: 'autoWrap', lineHeight: 1.5 },
edgeText: { overflowMode: 'autoWrap', lineHeight: 1.5 }
})
lf.extension.menu.setMenuConfig({
nodeMenu: [
{
text: "复制",
callback(node: any) {
lf.cloneNode(node.id);
},
},
{
text: "删除",
callback(node: any) {
lf.deleteNode(node.id);
},
}
],
edgeMenu: [
{
text: "删除",
callback(node: any) {
lf.deleteEdge(node.id);
},
},
], // 删除默认的边右键菜单
graphMenu: [], // 覆盖默认的边右键菜单,与false表现一样
});
registerCustomElement(lf)
if (dialog.content) {
lf.render(JSON.parse(dialog.content))
} else {
lf.render()
}
lf.on("node:click,edge:click", onNodeClick);