「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进行项目开发过程中翻阅了大量资料,以下是推荐大家进行延伸阅读的参考资料:

相关推荐
酷爱码15 分钟前
css中的 vertical-align与line-height作用详解
前端·css
沐土Arvin29 分钟前
深入理解 requestIdleCallback:浏览器空闲时段的性能优化利器
开发语言·前端·javascript·设计模式·html
专注VB编程开发20年31 分钟前
VB.NET关于接口实现与简化设计的分析,封装其他类
java·前端·数据库
小妖66640 分钟前
css 中 content: “\e6d0“ 怎么变成图标的?
前端·css
L耀早睡1 小时前
mapreduce打包运行
大数据·前端·spark·mapreduce
HouGISer2 小时前
副业小程序YUERGS,从开发到变现
前端·小程序
outstanding木槿2 小时前
react中安装依赖时的问题 【集合】
前端·javascript·react.js·node.js
霸王蟹2 小时前
React中useState中更新是同步的还是异步的?
前端·javascript·笔记·学习·react.js·前端框架
霸王蟹2 小时前
React Hooks 必须在组件最顶层调用的原因解析
前端·javascript·笔记·学习·react.js
专注VB编程开发20年3 小时前
asp.net IHttpHandler 对分块传输编码的支持,IIs web服务器后端技术
服务器·前端·asp.net