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

相关推荐
SomeB1oody9 分钟前
【Rust自学】6.3. 控制流运算符-match
开发语言·前端·rust
m0_7482567836 分钟前
【Django自学】Django入门:如何使用django开发一个web项目(非常详细)
前端·django·sqlite
林小白的日常1 小时前
uniapp中wx.getFuzzyLocation报错如何解决
前端·javascript·uni-app
傻小胖1 小时前
React 脚手架配置代理完整指南
前端·react.js·前端框架
EterNity_TiMe_1 小时前
【论文复现】农作物病害分类(Web端实现)
前端·人工智能·python·机器学习·分类·数据挖掘
余生H2 小时前
深入理解HTML页面加载解析和渲染过程(一)
前端·html·渲染
吴敬悦2 小时前
领导:按规范提交代码conventionalcommit
前端·程序员·前端工程化
ganlanA2 小时前
uniapp+vue 前端防多次点击表单,防误触多次请求方法。
前端·vue.js·uni-app
卓大胖_2 小时前
Next.js 新手容易犯的错误 _ 性能优化与安全实践(6)
前端·javascript·安全
m0_748246352 小时前
Spring Web MVC:功能端点(Functional Endpoints)
前端·spring·mvc