antv 实现自定义两端箭头的贝塞尔曲线边

啊、需求你如此丰富,促使我进步。

总之又搞起antv了。

先看需求图

design_img.jpg

没时间记录分析过程,直接上代码:

获取二次贝塞尔曲线控制点

  function getPoint(sx, sy, ex, ey) {
    // 获取开始,结束点所在直线1公式
    const k = (ey - sy) / (ex - sx);
    const b = (k * (sy + ey) + (sx + ex)) / (2 * k);
    // 以开始点为旋转中心,逆时针旋转30度,获得该直线2公式
    const angle = Math.atan2(ey - sy, ex - sx) / (Math.PI / 180) + 30;
    const k1 = Math.tan((Math.PI * angle) / 180);
    const b1 = sy - k1 * sx;
    // 中间控制点即为直线1和2的交点
    const c1x = (b - b1) / (k1 + 1 / k);
    const c1y = k1 * c1x + b1;
    // 以数组形式返回中间控制点坐标
    return [c1x, c1y];
  }

第一种方式在quadratic上拓展

            G6.registerEdge(
              'line-arrow',
              {
                labelPosition: 'center',
                labelAutoRotate: true,
                getShapeStyle: function getShapeStyle(cfg) {
                  const { startPoint } = cfg;
                  const { endPoint } = cfg;

                  const b = getPoint(startPoint.x, startPoint.y, endPoint.x, endPoint.y);

                  const style = G6.Util.mix(
                    {},
                    G6.Global.defaultEdge.style,
                    {
                      stroke: 'rgba(194, 192, 203, 1)',
                      lineWidth: 1,
                      lineDash: [2, 2, 2],
                      path: [
                        ['M', startPoint.x, startPoint.y],
                        ['Q', b[0], b[1], endPoint.x, endPoint.y],
                      ],
                      startArrow: {
                        path: 'M 0,0 L -6,-3 L -6,3 Z',
                      },
                      endArrow: {
                        path: 'M 0,0 L -6,-3 L -6,3 Z',
                      },
                    },
                    cfg.style
                  );
                  return style;
                },
              },
              'quadratic'
            );

第二种方式,直接自己定义边

            G6.registerEdge(
              'line-arrow',
                 draw: function draw(cfg, group) {
                   const { startPoint, endPoint } = cfg;
                
                   const b = getPoint(startPoint.x, startPoint.y, endPoint.x, endPoint.y);
                
                   const shape = group.addShape('path', {
                     attrs: {
                       path: [
                         ['M', startPoint.x, startPoint.y],
                         ['Q', b[0], b[1], endPoint.x, endPoint.y],
                       ],
                       stroke: 'rgba(194, 192, 203, 1)',
                       lineWidth: 1,
                       lineDash: [2, 2, 2],
                       startArrow: {
                         path: 'M 0,0 L -6,-3 L -6,3 Z',
                         fill: '#333',
                         stroke: '#666',
                       },
                       endArrow: {
                         path: 'M 0,0 L -6,-3 L -6,3 Z',
                         fill: '#333',
                         stroke: '#666',
                       },
                     },
                   });
                   const a = [(startPoint.x + endPoint.x) / 2, (startPoint.y + endPoint.y) / 2];
                   group.addShape('text', {
                     attrs: {
                       text: cfg.label,
                       fill: 'rgba(194, 192, 203, 1)',
                       x: (a[0] + b[0]) / 2,
                       y: (a[1] + b[1]) / 2,
                     },
                   });
                   return shape;
                 },
              },
            );

graphin完整代码:

示例data:

{
    nodes: [
      {
        id: 'node-0',
        label: 'node-node-0',
        data: { id: 'node-0', label: 'node-0', properties: [] },
        shape: 'CircleNode',
        size: 78,
        style: { nodeSize: 24 },
      },
      {
        id: 'node-1',
        label: 'node-node-1',
        data: { id: 'node-1', label: 'node-1', properties: [] },
        shape: 'CircleNode',
        size: 26,
        style: { nodeSize: 24 },
      },
    ],
    edges: [
      {
        shape: 'line-arrow',
        source: 'node-0',
        target: 'node-1',
        label: 'edge-0_1',
        labelCfg: {
          autoRotate: true,
          style: {
            stroke: 'white',
            lineWidth: 5,
            fill: 'rgba(194, 192, 203, 1)',
          },
        },
        data: {
          shape: 'line-arrow',
          source: 'node-0',
          target: 'node-1',
          label: 'edge-0_1',
          properties: [],
        },
      },
    ]
}

            
      <Graphin
        style={{ width: '100%' }}
        ref={graphRef}
        data={{ nodes, edges }}
        register={{
          edgeShape: (G6) => {
            G6.registerEdge(
              'line-arrow',
              {
                labelPosition: 'center',
                labelAutoRotate: true,
                getShapeStyle: function getShapeStyle(cfg) {
                  const { startPoint } = cfg;
                  const { endPoint } = cfg;

                  const b = getPoint(startPoint.x, startPoint.y, endPoint.x, endPoint.y);

                  const style = G6.Util.mix(
                    {},
                    G6.Global.defaultEdge.style,
                    {
                      stroke: 'rgba(194, 192, 203, 1)',
                      lineWidth: 1,
                      lineDash: [2, 2, 2],
                      path: [
                        ['M', startPoint.x, startPoint.y],
                        ['Q', b[0], b[1], endPoint.x, endPoint.y],
                      ],
                      startArrow: {
                        fill: 'rgba(194, 192, 203, 1)',
                        path: 'M 0,0 L -6,-3 L -6,3 Z',
                        lineDash: [0, 0, 0],
                      },
                      endArrow: {
                        fill: 'rgba(194, 192, 203, 1)',
                        path: 'M 0,0 L -6,-3 L -6,3 Z',
                        lineDash: [0, 0, 0],
                      },
                    },
                    cfg.style
                  );
                  return style;
                },
              },
              'quadratic'
            );
            return [
              {
                name: 'test',
                register: () => {},
              },
            ];
          },
        }}
        layout={{
          name: 'force',
          options: { animation: true, enableWorker: true, damping: 0.5, repulsion: 160 * 5 },
        }}
        options={setting}
      />