# AntV

X6 是图编辑引擎 提供了开箱即用的交互组件和简单易用的节点定制能力 方便我们快速搭建流程图、DAG 图、ER 图等图应用 功能类似于 ProcessOn

G6 是图可视化引擎 它在高定制能力的基础上 提供了一系列设计优雅、便于使用的图可视化解决方案 能帮助开发者搭建属于自己的图可视化、图分析、或图编辑器应用

G2 是图可视化语法 一套面向常规统计图表 以数据驱动的高交互可视化图形语法 具有高度的易用性和扩展性 功能类似于 ECharts

# 安装 X6 (opens new window)

X6 支持 HTML、React、Vue、Angular 的使用 这次我们基于 Vue3 的环境来使用 X6

  • 通过 npm install @antv/x6-vue-shape 安装 X6

  • /src/views/antvDemo.vue 中引入 X6

    <script lang="ts" setup>
    // 引入 X6 依赖包
    import '@antv/x6-vue-shape'; 
    // 使用 Graph 进行图表的载体
    import { Graph } from '@antv/x6';
    </script>
    
    1
    2
    3
    4
    5
    6

# 初尝 X6

​ 通过 X6 提供 graph.addNode() 能在页面中生成节点 并且通过 graph.addEdge() 使得节点关联起来

# 初始化画布

  • /src/App.Vue 中初始化页面样式

    <template>
      <div id="App">
        <router-view></router-view>
      </div>
    </template>
    
    <script lang="ts" setup></script>
    
    <style lang="scss">
    html,
    body,
    #App {
      height: 100%;
    }
    
    * {
      margin: 0;
      padding: 0;
    }
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  • /src/view/antvDemo.vue 中初始化画布

    <template>
      <div id="pageIndex"></div>
    </template>
    
    <script lang="ts" setup>
    import '@antv/x6-vue-shape';
    import { Graph } from '@antv/x6';
    import { onMounted } from '@vue/runtime-core';
    let graph = null;
    
    onMounted(() => {
      // 初始化画布
      graph = new Graph({
        container: document.getElementById('pageIndex'),
        width: document.body.clientWidth,
        height: document.body.clientHeight,
        background: {
          color: '#fffbe6',
        },
        snapline: true, // 对齐参考线
      });
    });
    </script>
    
    <style lang="scss" scoped></style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
  • 此时页面中就可以看到 浅黄色的画布

# 生成画布节点

  • X6 是通过 JSON 数据 (opens new window)来快速定义 画布节点和连接线

    const nodeOne = {
      id: 'nodeOne',
      shape: 'rect',
      x: 40,
      y: 40,
      width: 80,
      height: 40,
      label: 'hello',
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 通过 graph.addNode(nodeOne) 添加节点到画布中

  • 通过 graph.addEdge() 将多个节点用线连接起来

    onMounted(() => {
      const nodeOne = graph.addNode({
        id: 'nodeOne',
        x: 40,
        y: 40,
        width: 80,
        height: 40,
        label: 'hello',
      });
    
      const nodeTwo = graph.addNode({
        id: 'nodeTwo',
        x: 160,
        y: 180,
        width: 80,
        height: 40,
        label: 'x6',
      });
    
      graph.addEdge({
        source: nodeOne,
        target: nodeTwo,
      });
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
  • 此时 在页面就能看到两个节点被一条线给连接 并且 不管节点如何拖动 始终都是连接状态

# 深入 X6

​ 跟着文档进一步了解 X6 的魅力吧

# 画布 Graph

Graph 是图的载体 它包含了图上的所有元素 (节点、边) 同时挂载了图的相关操作 (交互监听、元素操作、渲染)

  • 通过 拖拽 (opens new window) 或者 滚动条 (opens new window) 来实现画布平移

    const graph = new Graph({
      panning: {
        enabled: true, // 是否允许拖拽画布
        modifiers: 'shift', // 拖拽动作的修饰符
      },
    })
    
    graph.isPannable() // 画布是否可以平移
    graph.enablePanning() // 启用画布平移
    graph.disablePanning() // 禁止画布平移
    graph.togglePanning() // 切换画布平移状态
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

# 网格 Grid

​ 网格是渲染、移动节点的最小单位 网格默认大小为 10px 渲染节点时表示以 10 为最小单位对齐到网格 如位置为 { x: 24, y: 38 }的节点渲染到画布后的实际位置为 { x: 20, y: 40 } 移动节点时表示每次移动最小距离为 10px

  • 网格类型 grid: Boolean|Object

    const graph = new Graph({
      grid:true
      grid: {
        size: 10, // 网格大小 10px
        visible: true, // 绘制网格,默认绘制 dot 类型网格
        type: ['dot' | 'mesh'], // 网格样式 [ 小圆点 | 直线 ]
      },
    })
    
    1
    2
    3
    4
    5
    6
    7
    8

# 节点 Node

​ 通过 JSON 数据来快速添加两个矩形节点和一条边到画布中 并简单介绍了如何定制节点样式

  • 基本属性

    属性名 类型 默认值 描述
    id String undefined 唯一标识符
    markup Markup undefined 定义节点内部构成结构
    attrs Object {} 节点属性样式
    shape String Rect 渲染图形 可自定义
    view String undefined 渲染的视图
    zIndex Number 0 画布层级 默认自动生成
    visible Boolean true 是否可见
    parent String undefined 父节点
    children String [] 子节点
    data any undefined 节点绑定的业务数据
    x Number 0 节点横轴坐标
    y Number 0 节点纵轴坐标
    width Number 1 节点宽度
    height Number 1 节点高度
    angle Number 0 节点旋转角度
  • 内置节点

    const nodeCylinder = graph.addNode({
      id: 'nodeCylinder',
      x: 460,
      y: 480,
      width: 100,
      height: 200,
      label: '圆柱',
      shape: 'cylinder', // [Rect矩形、Circle圆形、Ellipse椭圆形、Cylinder圆柱形]
      angle: -30,
      attrs: {
        body: {
          fill: 'blue',
        },
        label: {
          fill: 'white',
        },
      },
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

#Edge

​ 多个节点依赖着 Edge 来连接着 我们也可以自定义连接线的样式

  • 基本属性

    属性名 类型 默认值 描述
    source TerminalData undefined 起始点
    target TerminalData undefined 目标点
    vertices Point.PointLike[] undefined 必经的路径点
    router RouterData normal 路线的计算方式
    label Label undefined 单个标签
  • 基本案例

    const edge = graph.addEdge({
      source: { cell: nodeOne, port: 'out-port-1' },
      target: nodeCircle,
      label: 'hasMany',
      vertices: [
        { x: 100, y: 200 },
        { x: 300, y: 120 },
      ],
    });
    
    // 后期添加标签
    edge.setLabels(['edge']); 
    edge.appendLabel('edge');
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  • 自定义箭头样式 (opens new window)

    edge.attr({
      line: {
        // 起始点样式
        sourceMarker: 'block',
        // 终点样式
        targetMarker: {
          // SVG元素渲染箭头
          tagName: 'path',
          // 填充颜色
          fill: 'yellow',
          // 描边颜色
          stroke: 'green',
          // 描边宽度
          strokeWidth: 2,
          // 自定义箭头路径 需要定义一个向左指向坐标原点的箭头
          d: 'M 20 -10 0 0 20 10 Z',
        },
      },
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

# 群组 Group

​ 通过父子嵌套来实现群组,并提供了一系列获取和设置嵌套关系的方法 (opens new window)

  • 基本用法

    graph = new Graph({
      container: document.getElementById('pageIndex'),
      width: document.body.clientWidth,
      height: document.body.clientHeight,
      panning: {
        enabled: true,
        modifiers: 'shift',
      },
      grid: true,
      background: {
        color: '#fffbe6',
      },
      // 限制子节点的移动范围
      translating: {
        restrict(view) {
          const cell = view.cell;
          if (cell.isNode()) {
            const parent = cell.getParent();
            if (parent) {
              return parent.getBBox();
              // 只限制子节点不移动
              // return {
              //   x: cell.getBBox().x,
              //   y: cell.getBBox().y,
              //   width: 0,
              //   height: 0,
              // };
            }
          }
          return null;
        },
      },
      // 禁止所有节点移动
      interacting: {
        nodeMovable: false,
      },
    });
    
    const child = graph.addNode({
      x: 120,
      y: 80,
      width: 120,
      height: 60,
      zIndex: 10,
      label: 'Child',
      attrs: {
        body: {
          fill: 'green',
        },
        label: {
          fill: '#fff',
        },
      },
    });
    
    const parent = graph.addNode({
      x: 80,
      y: 40,
      width: 320,
      height: 240,
      zIndex: 1,
      label: 'Parent\n(try to move me)',
    });
    
    // 两个节点产生关系
    parent.addChild(child);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66

# 连接桩 Port

​ 链接桩是节点上的固定连接点 并且分为 输入链接桩输出连接桩

  • 基本使用

    Shape.Rect.define({
      shape: 'my-rect',
      width: 160,
      height: 80,
      ports: {
        groups: {
          left: {
            position: 'left',
            attrs: {
              circle: {
                r: 6,
                magnet: true, // 定义连接桩功能是否启用
                stroke: '#31d0c6',
                strokeWidth: 2,
                fill: '#fff',
              },
            },
          },
          right: {
            position: 'right',
            attrs: {
              circle: {
                r: 6,
                magnet: true,
                stroke: '#31d0c6',
                strokeWidth: 2,
                fill: '#fff',
              },
            },
          },
        },
      },
    });
    
    graph.addNode({
      x: 600,
      y: 600,
      shape: 'my-rect',
      label: 'Melon Port',
      ports: [
        {
          id: 'port1',
          group: 'left',
        },
        {
          id: 'port2',
          group: 'left',
        },
        {
          id: 'port3',
          group: 'right',
        },
        {
          id: 'port4',
          group: 'right',
        },
      ],
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58

# 节点和边的交互 - 连线规则 Connecting

​ 定义连接关系

  • 基本使用

    graph = new Graph({
      connecting: {
        // 连线时进入连接桩范围后自动吸附
        snap: true,
        // 禁止背景吸附连接点
        allowBlank: false,
        // 不允许节点之间重复连接
        allowMulti: false,
        // 不允许连接节点 只能连接到连接桩
        allowNode: false,
      },
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  • 连线的回调函数

    // 连接时回调
    graph.on('edge:connected', args => {
      console.log(args);
    });
    
    // 鼠标移入时
    graph.on('edge:mouseenter', ({ edge, cell }) => {
      // 展示删除按钮
      edge.addTools([
        {
          name: 'button-remove',
          args: {
            distance: -40,
            // 删除时回调
            onClick({ cell }) {
              graph.getCellById(cell.target.cell).removePort(cell.target.port);
            },
          },
        },
      ]);
    
      // 展示移动连接桩
      // 回调代码写在上面的 edge:connected 事件
      cell.addTools([
        'source-arrowhead',
        {
          name: 'target-arrowhead',
          args: {
            attrs: {
              fill: 'red',
            },
          },
        },
      ]);
    });
    
    // 鼠标移出时移除删除小工具
    graph.on('edge:mouseleave', ({ edge }) => {
      edge.removeTools('');
      // if (edge.hasTool('button-remove')) {
      //   edge.removeTool('button-remove');
      // }
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43