Antv-g6实现流程编辑器

854 阅读2分钟

上图

image.png

自定义图和交互


import * as G6 from '@antv/g6';

import nodeShapes from './Node';
import lines from './Edge';
import behaviors from './Behavior';

export default function registry() {
  // 注册自定义行为
  Reflect.ownKeys(behaviors).forEach((key) => {
    const myKey = key as string;
    G6.registerBehavior(myKey, behaviors[myKey]);
  });
  // 注册自定义连线
  Reflect.ownKeys(lines).forEach((key) => {
    const myKey = key as string;
    G6.registerEdge(myKey, lines[myKey], 'line');
  });
  // 注册自定义节点
  Reflect.ownKeys(nodeShapes).forEach((key) => {
    const myKey = key as string;
    G6.registerNode(`custom-${myKey}`, nodeShapes[myKey], 'node');
  });
}

自定义节点

const user: ShapeOptions = {
  drawShape(cfg?: ModelConfig | undefined, group?: IGroup | undefined): IShape {
    const [wrapWidth, wrapHeight] = cfg?.size as Array<number>;
    const referenceX = (cfg?.x as number) - wrapWidth / 2;
    const referenceY = (cfg?.y as number) - wrapHeight / 2;
    const radius = (cfg?.radius as number) || 4;

    // 构建外层的包裹成层
    const wrapId = cfg?.id || uuid();
    const shape = group?.addShape((cfg?.shapeType as string), {
      attrs: {
        id: wrapId,
        x: referenceX,
        y: referenceY,
        width: wrapWidth,
        height: wrapHeight,
        radius,
        fill: cfg?.fill as string,
        stroke: cfg?.stroke as string,
        name: 'wrap-box',
        editable: cfg?.editable,
        shapeType: 'rect',
      },
      draggable: true,
    });

    if (group) {
      // title-box
      group.addShape('rect', {
        attrs: {
          x: referenceX + 1,
          y: referenceY + 1,
          width: wrapWidth - 2,
          height: 24,
          radius: [radius - 1, radius - 1, 0, 0],
          parentId: wrapId,
          fill: cfg?.titleFill as string,
          name: 'title-box',
        },
      });

      // label
      group.addShape('text', {
        attrs: {
          textBaseline: 'top',
          x: referenceX + 24,
          y: referenceY + 8,
          lineHeight: 20,
          text: cfg?.title,
          fill: '#fff',
          parentId: wrapId,
          name: 'title',
        },
      });

      // name
      group.addShape('text', {
        attrs: {
          textBaseline: 'top',
          x: referenceX + 24,
          y: referenceY + 36,
          lineHeight: 20,
          text: cfg?.name,
          fill: '#000',
          parentId: wrapId,
          name: 'name',
        },
      });

      // close Icon
      group.addShape('image', {
        attrs: {
          x: referenceX + wrapWidth - 20,
          y: referenceY + 4,
          width: 16,
          height: 16,
          cursor: 'pointer',
          img: delIcon,
          parentId: wrapId,
          name: 'close-icon',
          opacity: 0,
          isClose: true,
        },
      });

      // first Icon
      if (cfg?.titleImg) {
        group.addShape('image', {
          attrs: {
            x: referenceX + 4,
            y: referenceY + 5,
            width: 16,
            height: 16,
            cursor: 'pointer',
            img: cfg?.titleImg,
          },
          name: 'title-icon',
        });
      }
    }
    return shape as IShape;
  },
  setState(name, value, item?: INode | IEdge | ICombo| undefined) {
    if (item) {
      const group = item.getContainer();
      const shape = group.get('children')[0];
      const closeIcon = group.findAll((circle) => circle.attrs.isClose);
      const children = group.findAll((g) => g.attrs.parentId === shape.attrs.id);
      const model = item.getModel();
      const selectStyles = () => {
        shape.attr('stroke', 'rgb(50,150,250)');
        shape.attr('cursor', 'pointer');
        if (model.key !== 'START') {
          closeIcon.forEach((circle) => {
            circle.attr('opacity', 1);
          });
        }
        children.forEach((child) => {
          child.attr('cursor', 'pointer');
        });
      };
      const unSelectStyles = () => {
        shape.attr('stroke', '#ddd');
        shape.attr('cursor', 'default');
        closeIcon.forEach((circle) => {
          circle.attr('opacity', 0);
        });
        children.forEach((child) => {
          child.attr('cursor', 'default');
        });
      };
      switch (name) {
        case 'selected':
          if (value) {
            selectStyles();
          } else {
            unSelectStyles();
          }
          break;
        case 'hover':
          if (value) {
            selectStyles();
          } else {
            unSelectStyles();
          }
          break;
        default:
          break;
      }
    }
  },
};

开发体验

Antv-G6提供了相当的便利,使得像如此的工具变得简单。同时它也是可视化工具库的解决方案。