react+antvX6制作可编辑图表5——Port连接柱

3,152 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

image.png

前情提要:   上一期我们讲了如何创建基础图形,节点和边,这一期我们讲一讲连接柱Port。

连接桩 Port

链接桩是节点上的固定连接点,很多图应用都有链接桩,并且有些应用还将链接桩分为输入链接桩输出连接桩

创建节点时我们可以通过 ports 选项来配置链接桩,像下面这样:

const node = new Node({
  ports: {
    groups: { ... }, // 链接桩组定义
    items: [ ... ],  // 链接桩
  }
})

// 或者
const node = new Node({
  ports: [ ... ],  // 链接桩
})

链接桩 Markup 也就是连接柱的样式,可以在单个链接桩、链接桩群组和节点的 portMarkup 选项三个位置指定,优先级从高到低。 知道了链接桩的 DOM 结构,我们就可以来定制链接桩的样式:

graph.addNode({
  x: 60,
  y: 60,
  width: 160,
  height: 80,
  label: 'Rect With Ports',
  ports: [
    {
      id: 'port1',
      attrs: {
        circle: {
          r: 6,
          magnet: true,
          stroke: '#31d0c6',
          strokeWidth: 2,
          fill: '#fff',
        },
      },
    },
    {
      id: 'port2',
      attrs: {
        circle: {
          r: 6,
          magnet: true,
          stroke: '#31d0c6',
          strokeWidth: 2,
          fill: '#fff',
        },
      },
    },
    {
      id: 'port3',
      attrs: {
        circle: {
          r: 6,
          magnet: true,
          stroke: '#31d0c6',
          strokeWidth: 2,
          fill: '#fff',
        },
      },
    },
  ],
})

image.png 值得注意的是,我们给 circle 指定了 magnet: true 这个特殊属性,使链接桩在连线交互时可以被连接上。

上面代码中每个链接桩的样式都一样,显得有点冗长,我们可以通过 group 选项来设置链接桩分组,使该组中的链接桩具有相同的行为和样式。

看下面如何使用链接桩分组来定义链接桩样式:

graph.addNode({
  x: 60,
  y: 60,
  width: 160,
  height: 80,
  label: 'Rect With Ports',
  ports: {
    groups: {
      group1: { 
        attrs: {
          circle: {
            r: 6,
            magnet: true,
            stroke: '#31d0c6',
            strokeWidth: 2,
            fill: '#fff',
          },
        },
      },
    },
    items: [
      {
        id: 'port1',
        group: 'group1', // 指定分组名称
      },
      {
        id: 'port2',
        group: 'group1', // 指定分组名称
      },
      {
        id: 'port3',
        group: 'group1', // 指定分组名称
      },
    ],
  },
})

链接桩标签

另外,还可以为链接桩指定标签文本。链接桩标签的 Markup 可以在单个链接桩、链接桩群组和节点的 portLabelMarkup 选项三个位置指定,优先级从高到低。

      {
        id: 'port1',
        group: 'group1',
        attrs: {
          text: {          // 标签选择器
            text: 'port1', // 标签文本
          },
        },
      },

image.png

连接到链接桩

graph.addEdge({
  source: { x: 40, y: 100 },
  target: { 
    cell: rect, 
    port: 'port1', // 链接桩 ID
  },
})

graph.addEdge({
  source: { x: 40, y: 100 },
  target: { 
    cell: rect, 
    port: 'port2', // 链接桩 ID
  },
})

graph.addEdge({
  source: { x: 40, y: 100 },
  target: { 
    cell: rect, 
    port: 'port3', // 链接桩 ID
  },
})

image.png

实践

链接桩选项多、配置代码长,推荐的做法是,基于群组将链接桩的通用选项定义为节点的默认选项。例如我们可以定义一个矩形节点,然后为该矩形节点设置预定义的输入和输出链接桩。

Shape.Rect.define({
  shape: 'my-rect',
  width: 180,
  height: 80,
  ports: {
    groups: {
        // 输入链接桩群组定义
      in: { 
        position: 'top', //定义连接柱的位置,如果不配置,将显示为默认样式
        label: {
          position: 'top', //定义标签的位置
        },
        attrs: {   //定义连接柱的样式
          circle: {
            r: 6,   //半径
            magnet: true,
            stroke: '#31d0c6',
            strokeWidth: 2,
            fill: '#fff',
          },
        },
      },
      // 输出链接桩群组定义
      out: {
        position: 'bottom',
        label: {
          position: 'bottom',
        },
        attrs: {
          circle: {
            r: 6,
            magnet: true,
            stroke: '#31d0c6',
            strokeWidth: 2,
            fill: '#fff',
          },
        },
      },
    },
  },
})
graph.addNode({   
  x: 60,
  y: 50,  //x,y坐标
  shape: 'my-rect',
  label: 'In Ports & Out Ports',
  ports: [
    {
      id: 'port1',
      group: 'in',
    },
    {
      id: 'port2',
      group: 'in',
    },
    {
      id: 'port3',
      group: 'in',
    },
    {
      id: 'port4',
      group: 'out',
    },
    {
      id: 'port5',
      group: 'out',
    },
  ],
})

image.png

但是改了这个配置后,样式统一了,但也就无法定制其他的样式了。

position 位置

链接桩布局算法只能通过 groups 中的 position 选项来指定,因为布局算法在计算链接桩位置是需要考虑到群组中的所有连接桩,我们在单个链接桩中可以通过 args 选项来影响该链接桩的布局结果。

官方默认提供了下面几种链接桩布局算法,同时支持自定义链接桩布局算法并注册使用,点击下面的链接可以了解每种布局算法的使用方法。

官方提供的这些布局,除了absolute,都会将你的连接柱经行均匀分布。absolute的用法:

graph.addNode({
  ports: {
    groups: {
      group1: {
        position: {
          name: 'absolute',
          args: { x: 0, y: 0 },
        },
      },
    },
    items: [
      {
        group: 'group1',
        args: {
          x: '60%',
          y: 32,
          angle: 45,
        },
      },
    ]
  },
})

或者

graph.addNode({
  x: 100,
  y: 60,
  width: 280,
  height: 120,
  attrs: {
    body: {
      fill: '#f5f5f5',
      stroke: '#d9d9d9',
      strokeWidth: 1,
    },
  },
  ports: {
    groups: {
      group1: {
        attrs: {
          circle: {
            r: 6,
            magnet: true,
            stroke: '#31d0c6',
            strokeWidth: 2,
            fill: '#fff',
          },
          text: {
            fontSize: 12,
            fill: '#888',
          },
        },
        position: {
          name: 'absolute',
        },
      },
    },
    items: [
      {
        id: 'port1',
        group: 'group1',
        // 通过 args 指定绝对位置
        args: {
          x: 0,
          y: 60,
        },
        attrs: {
          text: { text: '{ x: 0, y: 60 }' },
        },
      },
      {
        id: 'port2',
        group: 'group1',
        // 通过 args 指定绝对位置和链接桩的旋转角度
        args: {
          x: 0.6,
          y: 32,
          angle: 45,
        },
        // 自定义链接桩渲染的 SVG
        markup: [
          {
            tagName: 'path',
            selector: 'path',
          },
        ],
        zIndex: 10,
        attrs: {
          path: {
            d: 'M -6 -8 L 0 8 L 6 -8 Z',
            magnet: true,
            fill: 'red',
          },
          text: {
            text: '{ x: 0.6, y: 32, angle: 45 }',
            fill: 'red',
          },
        },
      },
      {
        id: 'port3',
        group: 'group1',
        // 通过 args 指定绝对位置
        args: {
          x: '100%',
          y: '100%',
        },
        attrs: {
          text: { text: "{ x: '100%', y: '100%' }" },
        },
        label: {
          position: {
            name: 'right',
          },
        },
      },
    ],
  },
})

image.png