Write the Code. Change the World.

3月 13

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);

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注