showcase/practices/bpmn

Example page

import exampleData from '../../../data/bpmn.json'
import { type Cell, type CellView, Graph, type Node } from '@antv/x6'

function registerGateway(name: string, symbol: string) {
  Graph.registerNode(
    name,
    {
      inherit: 'polygon',
      attrs: {
        body: {
          refPoints: '0,10 10,0 20,10 10,20',
          strokeWidth: 2,
          stroke: '#5F95FF',
          fill: '#EFF4FF',
        },
        label: {
          text: symbol,
          fontSize: 32,
          fontWeight: 'bold',
          fill: '#5F95FF',
        },
      },
    },
    true,
  )
}

Graph.registerNode(
  'event',
  {
    inherit: 'circle',
    attrs: {
      body: {
        strokeWidth: 2,
        stroke: '#5F95FF',
        fill: '#FFF',
      },
    },
  },
  true,
)

Graph.registerNode(
  'start-event',
  {
    inherit: 'circle',
    attrs: {
      body: {
        strokeWidth: 2,
        stroke: '#52C41A',
        fill: '#FFF',
      },
    },
  },
  true,
)

Graph.registerNode(
  'end-event',
  {
    inherit: 'circle',
    attrs: {
      body: {
        strokeWidth: 4,
        stroke: '#FF4D4F',
        fill: '#FFF',
      },
    },
  },
  true,
)

Graph.registerNode(
  'activity',
  {
    inherit: 'rect',
    markup: [
      {
        tagName: 'rect',
        selector: 'body',
      },
      {
        tagName: 'image',
        selector: 'img',
      },
      {
        tagName: 'text',
        selector: 'label',
      },
    ],
    attrs: {
      body: {
        rx: 6,
        ry: 6,
        stroke: '#5F95FF',
        fill: '#EFF4FF',
        strokeWidth: 1,
      },
      img: {
        x: 6,
        y: 6,
        width: 16,
        height: 16,
        'xlink:href':
          'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*pwLpRr7QPGwAAAAAAAAAAAAAARQnAQ',
      },
      label: {
        fontSize: 12,
        fill: '#262626',
      },
    },
  },
  true,
)

Graph.registerNode(
  'subprocess',
  {
    inherit: 'rect',
    markup: [
      { tagName: 'rect', selector: 'body' },
      { tagName: 'text', selector: 'label' },
      { tagName: 'image', selector: 'badge' },
      { tagName: 'text', selector: 'expand' },
      { tagName: 'text', selector: 'details' },
    ],
    attrs: {
      body: {
        rx: 6,
        ry: 6,
        stroke: '#5F95FF',
        fill: '#FAFBFF',
        strokeWidth: 1,
      },
      label: {
        ref: 'body',
        refX: '50%',
        refY: '50%',
        textAnchor: 'middle',
        textVerticalAnchor: 'middle',
        fontSize: 12,
        fill: '#262626',
      },
      badge: {
        ref: 'body',
        refX: 0,
        refY: 0,
        x: 6,
        y: 6,
        width: 16,
        height: 16,
        'xlink:href':
          'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*pwLpRr7QPGwAAAAAAAAAAAAAARQnAQ',
      },
      expand: {
        ref: 'body',
        refX: '100%',
        refY: '100%',
        x: -8,
        y: -8,
        textVerticalAnchor: 'bottom',
        textAnchor: 'end',
        fontSize: 16,
        fontWeight: 'bold',
        fill: '#5F95FF',
        cursor: 'pointer',
        text: '+',
        event: 'subproc:toggle',
      },
      details: {
        ref: 'body',
        refX: '50%',
        refY: 44,
        y: 0,
        textAnchor: 'middle',
        textVerticalAnchor: 'top',
        fontSize: 12,
        fill: '#8C8C8C',
        display: 'none',
        textWrap: {
          width: -24,
        },
        text: '',
      },
    },
  },
  true,
)

registerGateway('gateway-parallel', '⨁')
registerGateway('gateway-exclusive', '×')

Graph.registerNode(
  'lane',
  {
    inherit: 'rect',
    markup: [
      {
        tagName: 'rect',
        selector: 'body',
      },
      {
        tagName: 'rect',
        selector: 'name-rect',
      },
      {
        tagName: 'text',
        selector: 'name-text',
      },
    ],
    attrs: {
      body: {
        fill: '#FFF',
        stroke: '#5F95FF',
        strokeWidth: 1,
      },
      'name-rect': {
        width: 240,
        height: 30,
        fill: '#5F95FF',
        stroke: '#fff',
        strokeWidth: 0,
      },
      'name-text': {
        ref: 'name-rect',
        refY: 0.5,
        refX: 0.5,
        textAnchor: 'middle',
        fontWeight: 'bold',
        fill: '#fff',
        fontSize: 12,
      },
    },
  },
  true,
)

Graph.registerEdge(
  'bpmn-edge',
  {
    inherit: 'edge',
    attrs: {
      line: {
        stroke: '#A2B1C3',
        strokeWidth: 2,
        targetMarker: 'classic',
      },
    },
  },
  true,
)

const graph = new Graph({
  container: document.getElementById('container') as HTMLElement,
  grid: true,
  connecting: {
    router: 'orth',
  },
  mousewheel: true,
  translating: {
    restrict(this: Graph, cellView: CellView | null) {
      if (cellView?.cell?.isNode()) {
        const cell = cellView.cell as Node
        if (cell.shape === 'lane') {
          return this.transform.getGraphArea()
        }
        const parentId = cell.prop('parent')
        if (parentId) {
          const parentNode = this.getCellById(parentId)
          if (parentNode?.isNode()) {
            return parentNode.getBBox().moveAndExpand({
              x: 0,
              y: 30,
              width: 0,
              height: -30,
            })
          }
        }
        return this.transform.getGraphArea()
      }
      return this.transform.getGraphArea()
    },
  },
})

graph.on('subproc:toggle', ({ node }: { node: Node }) => {
  if (!node || node.shape !== 'subprocess') return

  const data = node.getData() || {}
  const expanded = !!data.expanded
  const next = !expanded

  node.attr('expand/text', next ? '-' : '+')
  node.attr('details/display', next ? 'block' : 'none')

  const defaultDetails = '1、病例证明(如有)\n2、工作 backup'
  const rawDetails = data.details
  const detailsText = rawDetails
    ? Array.isArray(rawDetails)
      ? rawDetails.join('\n')
      : String(rawDetails)
    : defaultDetails
  if (next) {
    node.attr('details/text', detailsText)
  }

  const lines = String(detailsText).split(/\n/).length
  const size = node.getSize()
  const expandedHeight = Math.max(70, 40 + lines * 18 + 24)
  node.size(size.width, next ? expandedHeight : 70)

  if (next) {
    node.attr({
      label: { textVerticalAnchor: 'top', refY: 18, refX: '50%', ref: 'body' },
      details: {
        textVerticalAnchor: 'top',
        refY: 40,
        refX: '50%',
        ref: 'body',
      },
    })
  } else {
    node.attr('label', {
      textVerticalAnchor: 'middle',
      refY: '50%',
      refX: '50%',
      ref: 'body',
    })
  }

  node.setData({ ...data, expanded: next })
})

Promise.resolve(exampleData)
  .then((data) => {
    const cells: Cell[] = []
    const nodeMap: Record<string, Node> = {}

    data.forEach((item: any) => {
      if (item.shape !== 'bpmn-edge') {
        const node = graph.createNode(item)
        nodeMap[item.id] = node as Node
        cells.push(node)
      }
    })

    data.forEach((item: any) => {
      if (item.shape === 'bpmn-edge') {
        const edge = graph.createEdge(item)
        if (item.label) {
          edge.setLabels([
            {
              attrs: {
                label: {
                  text: item.label,
                  fill: '#8C8C8C',
                  fontSize: 11,
                },
              },
            },
          ])
        }
        const sourceId = edge.getSourceCellId()
        const targetId = edge.getTargetCellId()
        const sParent = nodeMap[sourceId]?.prop('parent')
        const tParent = nodeMap[targetId]?.prop('parent')
        if (sParent && tParent && sParent !== tParent) {
          edge.attr('line/strokeDasharray', '5,5')
        }
        cells.push(edge)
      }
    })
    graph.resetCells(cells)

    Object.values(nodeMap).forEach((node) => {
      const parentId = node.prop('parent') as string | undefined
      if (parentId) {
        const parentNode = nodeMap[parentId]
        if (parentNode) {
          parentNode.addChild(node)
        }
      }
    })

    Object.values(nodeMap).forEach((node) => {
      if (node.shape === 'activity' && node.prop('parent') === 'lane-system') {
        node.attr(
          'img/xlink:href',
          'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*mUotQqntWJQAAAAAF_AAAAgAemJ7AQ/original',
        )
      }
    })

    graph.zoomToFit({ padding: 10, maxScale: 1 })
  })