「AntV X6」从5个核心要素出发,快速上手AntV X6图可视化编排

1、背景

刚刚开始接触AntV X6的前端小伙伴,应该能立即感受到------使用它展开前端开发工作、跟我们平时的开发经验不太一样,因为它属于有别于我们日常前端开发的另一个领域------前端可视化。

我个人非常喜欢AntV X6------不仅因为它很酷,而且很有用、很好用,我们可以用它开发一些很酷、很有用的应用------我在公司内部做了几场关于AntV X6的技术分享,也使用AntV X6在公司实际开发过3个项目,在项目中实现的功能如下:

(1)逻辑可视化编排

这是公司内部一个前端低代码项目------包括页面可视化搭建,逻辑可视化编排,以及数据源配置等等。

我在这个项目里,使用AntV X6实现了逻辑可视化编排------其中包括逻辑可视化编排的Debug功能,对逻辑可视化编排的Debug功能实现感兴趣的同学,可以阅读我的另一篇文章《怎样给iMove开发一个Debug插件------可视化逻辑编排的Debug实现》

(2)流程可视化搭建

在这个项目里,我通过使用AntV X6实现了流程可视化搭建的功能原型。

同样是在这个项目里,因为需求方有一个自动布局的需求,我使用到了@antv/layout 这个关于图布局的包,同时对Dagre(图的层次布局算法)有了一些学习和研究,对这块感兴趣的同学,可以阅读我写的另一篇文章《Dagre算法简介以及在流程图自动布局中的应用》

(3)客户旅程时光轴

这是我最近正在做的一个项目,项目中的实际需求大概是这样的------

  1. 客户旅程是由旅程节点组成,一行展示固定个数(比如一行6个),超出则换行显示;
  2. 客户的某些旅程节点是可选的,对于没有走过的旅程节点和边,需要置灰显示;
  3. 客户旅程走过的节点,可以通过动画的方式进行展示。

在这个项目里,我通过使用AntV X6实现了客户旅程时光轴,目前正在实现客户旅程时光轴的可视化编排,对这块感兴趣的同学,可以阅读我写的另一篇文章《「AntV」怎样用SVG & X6制作客户旅程时光轴》

我个人非常喜欢AntV X6------它很酷,很有用,而且很好用,然而使用AntV X6也是有一些门槛------软件/工具是知识和经验凝结的产物,在开发上遇到的很多障碍、往往是相关知识、开发经验或者能力方面有欠缺,我希望在这篇文章中能够把我使用AntV X6的经验进行提炼、帮助大家跨过使用AntV X6的门槛。

2、数据:网状数据集

我们之前提到过,AntV X6属于有别于我们日常前端开发的另一个领域------前端可视化。而说到前端可视化,我们第一个想到的工具、可能就会是Echarts ------ 它是一个图表库,通过使用它提供的各个配置项、进行简单地组合使用,就能快速实现饼图、柱状图、折线图等等。

但是像Echarts这样的图表库,适合处理的是表格型数据集------而使用AntV X6,我们需要处理的是一个个的"节点"、以及节点之间的"边",这属于网状数据集,所以AntV X6属于有别于Echarts这种图表可视化的另一个可视化分类------"图可视化"。

举个例子,用Antv X6绘制一个Hello -> World,需要准备的数据如下:

typescript 复制代码
const data = {
  // 节点
  nodes: [
    {
      id: 'node1', // String,可选,节点的唯一标识
      x: 40,       // Number,必选,节点位置的 x 值
      y: 40,       // Number,必选,节点位置的 y 值
      width: 80,   // Number,可选,节点大小的 width 值
      height: 40,  // Number,可选,节点大小的 height 值
      label: 'Hello', // String,节点标签
    },
    {
      id: 'node2', // String,节点的唯一标识
      x: 200,      // Number,必选,节点位置的 x 值
      y: 40,      // Number,必选,节点位置的 y 值
      width: 80,   // Number,可选,节点大小的 width 值
      height: 40,  // Number,可选,节点大小的 height 值
      label: 'World', // String,节点标签
    },
  ],
  // 边
  edges: [
    {
      source: 'node1', // String,必须,起始节点 id
      target: 'node2', // String,必须,目标节点 id
    },
  ],
};

在上面的"Hello -> World"示例中------我们提供两个节点Hello和World,还提供了一条连接两个节点的边------以下是Hello -> World 示例实际查看效果

3、结构:5个核心要素

仔细观察上面的上面的"Hello -> World"示例,我们拆解来看、一个图本质上无外乎就是「两点一线」,以及在节点和连线上的文字标签。

根据我使用AntV X6进行实际项目的开发经验,一个图可视化编排应用大概是由以下5个核心要素组成的:

  • 节点(Node):图中那些一个个的,或圆形、或方型、或图标等等图形所代表的元素,被称为"节点",节点是图里面的最基础的元素;
  • 连线(Edge):连接两个节点的元素,被称为"连线",连线是 AntV X6 中非常重要的一部分,AntV X6 内置了很多实用的连线功能,也提供了优雅的扩展机制 ,这是相比于其他流程图框架占据绝对优势的地方;
  • 标签(Label):节点或者连线上的说明文字,被称为"标签";
  • 布局(Layout):让图中各个节点按照一定的规则进行排列,被称为"布局"。AntV X6提供了一个@antv/layout包------里面提供了包括grid布局、dagre布局、force布局等等布局算法供我们使用------我们往往会通过使用各个布局算法,将布局的结果作为我们节点的位置坐标;
  • 事件(Events):通过AntV X6内置事件系统,我们可以监听图内发生的任何事件。

接下来我们结合下面的客户旅程时光的案例,从这5个核心要素出发、进行一一讲解:

(1)节点(Node)

节点是图里面的最基础的元素。打开Chrome开发者工具,当我们查看第一个节点的元素时,我们可以在元素面板看到如下DOM结构:

一个节点是由一个<g>标签组成,<g>标签是SVG中的标签------AntV X6的底层绘制系统是基于SVG,SVG是指令式图形系统,可以通过类似HTML的XML标签进行图形绘制,上手难度较低,体验跟HTML开发的体验非常一致。

再往下看,我们可以看到,旅程图的圆形节点是由<circle>标签实现的------<circle>是SVG中的形状元素,其他形状元素包括:<rect><ellipse><line><polyline><polygon>以及<path>,可以分别用来画方形、椭圆、线、折线、多边形以及更复杂的图形。

在 SVG 中有一个特殊的<foreignObject>元素,在该元素中可以内嵌任何 XHTML 元素,所以我们可以借助该元素来渲染 HTML 元素和 React/Vue/Angular组件到需要位置------由于我们项目上主要是使用React,所以我们是用React组件进行节点定制------只要你会React/Vue/Angular组件开发,就能进行AntV X6的节点开发。

接下来我们从节点定义和节点样式两个方面讲述在AntV X6中节点的使用:

① 节点定义

首先来看下一个简单的矩形节点的基础配置:

typescript 复制代码
// 一个简单的矩形节点的基础配置
graph.addNode({
  shape: 'rect', // shape:定义节点的形状,X6 内置了 rect、circle、ellipse、polygon、polyline、image、html 等基础形状
  x: 100, // x/y:定义节点的左上角坐标
  y: 100,
  width: 80, // width/height:定义节点的尺寸
  height: 40,
  attrs: { // attrs: 可以将 attrs 看做 css 样式集合
    body: {
      stroke: 'red'
    }
  }
});
  • shape:定义节点的形状,X6 内置了 rect、circle、ellipse、polygon、polyline、image、html 等基础形状;
  • x/y:定义节点的左上角坐标;
  • width/height:定义节点的尺寸 ;
  • attrs:看到 attrs 大家可能比较奇怪,这是什么东西?其实可以将 attrs 看做 css 样式集合,其中 body 类似于 css 选择器,body 的值是被选中元素的属性。那 body 又是哪来的呢?这里就要说的另一个重要的配置项markup,markup 表示的是节点的 DOM 结构,内置的 rect 的默认 markup 为:
typescript 复制代码
[
  {
    tagName: 'rect',
    selector: 'body',
  },
  {
    tagName: 'text',
    selector: 'label',
  },
]

渲染完成后,实际生效的 DOM 为:

typescript 复制代码
<g data-cell-id="ca715562-8faf-4c88-a242-2b18d4ce47a6" data-shape="rect" class="x6-cell x6-node" transform="translate(100,100)">
  <rect fill="#ffffff" stroke="red" stroke-width="2" width="80" height="40"></rect>
  <text font-size="14" fill="#000000" text-anchor="middle" text-vertical-anchor="middle" font-family="Arial, helvetica, sans-serif" transform="matrix(1,0,0,1,40,20)"></text>
</g>

② 节点样式

适用于表达节点的视觉样式的有大小、颜色、描边、形状、图标、角标 这么几个视觉参数:

  • 大小:通过width/height来定义节点的尺寸;
  • 颜色:通过attrs中的fill来定义填充颜色;
  • 描边:通过attrs中的stroke来定义描边颜色;
  • 形状:通过shape来定义节点形状,X6 内置了 rect、circle、ellipse、polygon、polyline、image、html等基础形状,另外也支持React、Vue组件;
  • 图标:同上;
  • 角标:节点右上角的圆形徽标。

(2)连线(Edge)

接下来我们查看一下连线的DOM元素,我们可以看到连线是由许多<path>标签实现的------<path>标签是SVG中最强大的标签,前面的<rect><circle><ellipse><line><polyline>以及<polygon>等等都可以通过<path>来实现,使用<path>可以实现更复杂的图形。

AntV X6 中连线分两种形式,代码生成的和用户手动拖拽而成。

① 代码生成

我们可以通过以下代码建立连线:

typescript 复制代码
// source 或 target 是坐标点
graph.addEdge({
  source: [0, 0],
  target: [100, 100]
})

// source 或 target 是节点对象
graph.addEdge({
  source: sourceNode,
  target: targetNode,
})

// source 或 target 是节点 ID
graph.addEdge({
  source: 'sourceId',
  target: 'targetId',
})

// source 或 target 是连接桩
graph.addEdge({
  source: { cell: 'cellId1', port: 'portId1' },
  target: { cell: 'cellId2', port: 'portId2' }
})

② 手动拖拽

如果想通过手动操作来创建连线,需要有两个条件:

  1. 需要从具有 magnet: true 属性的元素上才能手动拖拽出连线;
  2. 需要在全局 connecting 配置中自定义 createEdge 方法。
typescript 复制代码
import { Graph, Shape } from '@antv/x6'

const graph = new Graph({
  connecting: {
    createEdge() {
      return new Shape.Edge()
    },
  },
})
graph.addNode({
  shape: 'rect',
  x: 100,
  y: 100,
  width: 80,
  height: 40,
  attrs: {
    body: {
      stroke: 'red',
      magnet: true
    }
  }
})
graph.addNode({
  shape: 'rect',
  x: 400,
  y: 100,
  width: 80,
  height: 40,
  attrs: {
    body: {
      stroke: 'red',
      magnet: true
    }
  }
})

我们经常需要自己来定义连线的样式,其实连线和图形都是由 markup 和 attrs 来定义的,默认连线的 markup 为:

typescript 复制代码
[
  {
    tagName: 'path',
    selector: 'wrap',
    groupSelector: 'lines',
    attrs: {
      fill: 'none',
      cursor: 'pointer',
      stroke: 'transparent',
      strokeLinecap: 'round',
    },
  },
  {
    tagName: 'path',
    selector: 'line',
    groupSelector: 'lines',
    attrs: {
      fill: 'none',
      pointerEvents: 'none',
    },
  },
]

其中 line 为实际展示的连线,wrap 是为了方便响应交互的占位元素。

(3)标签(Label)

我们可以看到标签是由一个<text>文本元素实现的。

这里需要注意的是,SVG中的<text>标签文本内容超出设定的长度之后不会自动换行,不过AntV X6提供了textWrap这个扩展属性实现了文本内容自动换行,比如以下是我在客户旅程时光轴节点上的标签设置:

typescript 复制代码
JourneyNode.config({
  shape: 'journey-node',
  component: (node: any) => {
    return <NodeView node={node} />;
  },
  width: NODE_WIDTH,
  height: NODE_WIDTH,
  attrs: {
    label: {
      refX: 0.5,
      refY: '100%',
      refY2: 20,
      fill: '#333',
      fontSize: 13,
      textAnchor: 'middle',
      textVerticalAnchor: 'middle',
      textWrap: {
        width: 80,
        height: 60,
        ellipsis: false,
        breakWord: true,
      },
    },
  },
});

(4)布局(Layout)

AntV X6提供了一个@antv/layout包------里面提供了包括grid布局、dagre布局、force布局等等布局算法供我们使用------我们往往会通过使用各个布局算法,将布局的结果作为我们节点的位置坐标。

在一篇名称为《可视化图布局算法浅析》的文章中总结了图可视化场景下常用的布局算法------

  • 几何布局:grid(网格布局算法),circle(环形布局算法),concentric(同心圆布局算法),radial(辐射状布局算法),avsdf(邻接点最小度优先算法,Adjacent Vertex with Smallest Degree First);
  • 层级布局:dagre(有向无环图树布局算法,Directed Acyclic Graph and Trees),breadthfirst(广度优先布局算法),elk(Eclipse布局算法,Eclipse Layout Kernel),klay(K层布局算法,K Lay);
  • 力导布局:fcose(最快复合弹簧内置布局算法,Fast Compound Spring Embedder),cola(约束布局,Constraint-based Layout),cise(环形弹簧内置布局算法,Circular Spring Embedder),elk2(Eclipse布局算法,Eclipse Layout Kernel),euler(欧拉布局算法),spread(扩展布局算法),fruchterman(Fruchterman-Reingold布局算法),combo(混合布局算法);
  • 其他布局:mds(高维数据降维布局算法,Multi Dimensional Scaling),random(随机布局算法)。

流程图应用是一种层次布局,而Dagre布局算法是"层次布局"的一个成熟算法实现,X6的@antv/layout布局包中就有Dagre布局的实现,接下来我们重点介绍一下:

在 Dagre算法中定义了几个基本概念:

(1)rankDir:图的延展方向,分为由上到下(tb)、由下到上(bt)、由左到右(lr)、由右到左(rl)四种。

(2)rank:沿着图的延展方向划分的层级,每个顶点都存在于某个层级上,一个层级上可能有多个顶点。

(3)level:在每个 rank 中针对每一个节点划分的级。不同 rank 中的 level 互不影响。

Dagre布局的使用很简单,实现如下------

typescript 复制代码
const dagreLayout = new DagreLayout({
    type: 'dagre',
    rankdir: 'LR',
    // align: 'UR', // 居中对齐
    ranksep: 36,
    nodesep: 20,
});
dagreLayout.updateCfg({
    begin: begin,
    ranker: 'longest-path', // 'tight-tree' 'longest-path' 'network-simplex'
});
let dagreModel = dagreLayout.layout(data as any);
graph.fromJSON(dagreModel);

(5)事件(Events)

通过AntV X6内置事件系统,我们可以监听图内发生的任何事件------包括鼠标交互事件,节点/连线的增/删/改事件,以及自定义事件等等。

比如上面的例子所示,在节点通过拖拽添加到画布中时,我们可以通过监听新增节点的添加事件(即node:added),实现流程图的自动连线和自动布局,代码大致如下所示:

typescript 复制代码
flowGraph.on('node:added', (args) => {
    // 1. 实现自动连线
    const oldTarget = edge.getTargetNode() || undefined;
    const oldPort = edge.getTargetPortId() || 'left';
    edge.setTarget(args.node, { port: 'left' });
    flowGraph.addEdge({
        source: args.node,
        sourcePort: 'right',
        target: oldTarget,
        targetPort: oldPort,
    });
    
    // 2. 实现自动布局
    const dagreLayout: DagreLayout = new DagreLayout({
      begin: [40, 40],
      type: 'dagre',
      rankdir,
      align,
      nodeSize,
      ranksep,
      nodesep,
      controlPoints,
    });
    dagreLayout.updateCfg({
      // ranker: 'tight-tree', // 'tight-tree' 'longest-path' 'network-simplex'
      // nodeOrder,
      // preset: {
      //   nodes: model.nodes.filter((node: any) => node._order !== undefined),
      // },
      ...cfg,
    });
    const model = flowGraph.toJSON();
    const { nodes: newNodes } = dagreLayout.layout({
      // @ts-ignore
      nodes: model.cells.filter((cell) => cell.shape !== 'edge'), // @ts-ignore
      edges: model.cells.filter((cell) => cell.shape === 'edge'),
    });
    newNodes?.forEach((node: any) => {
      const cell: Node | undefined = flowGraph.getCellById(node.id) as
        | Node
        | undefined;
      if (cell) {
        cell.position(node.x, node.y);
      }
    });
  
    flowGraph.cleanSelection();
    flowGraph.select(args.cell);
});

4、参考资料

我在实际使用AntV X6进行项目开发过程中翻阅了大量资料,以下是推荐大家进行延伸阅读的参考资料:

相关推荐
Apifox5 分钟前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿32 分钟前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼1 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下1 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox1 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞1 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行1 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758101 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周2 小时前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队2 小时前
Vue自定义指令最佳实践教程
前端·vue.js