antv x6使用笔记

2,528 阅读4分钟

1、初见antv X6

工作中总会遇到图可视化的需求,比如监控大屏展示。以往的需求比较简单,柱状图、折线图可以使用Echarts、antv G2快速实现。这次遇到的需求是实现拓扑图,数据关系层级比较复杂,找到了几种常见的图可视化库,如Echarts/G3/Antv,比较了下发现antv系列现在做的是越来越好了,而且维护频率很高,最终选用了antv X6(v1.34.6)。

2、需求梳理

官网说明:X6 是 AntV 旗下的图编辑引擎,提供了一系列开箱即用的交互组件和简单易用的节点定制能力,方便我们快速搭建 DAG 图、ER 图、流程图等应用。 x6可以实现图编辑,比如图元素的生成,通过拖拽绘制图形。

公司的需求不需要绘制能力,产品经理和设计想实现类似下面的关系图

image.png 这是一个基于elkjs展示的关系图,所以本文只着重介绍ELK图和x6混合的开发。

3、什么是elkjs

elkjs是一个开源的自动布局算法,elk全称 Eclipse Layout Kernel。Eclipse相信大家很都听过,其实elkjs就是基于Eclipse内核布局的算法,并可以应用于javaScript世界。 通过阅读文档,可以看到elkjs的用法和配置。下面讲解一下我使用的历程。

4、elkjs配置使用

安装:

npm install elkjs

这里我安装的是 "elkjs": "^0.8.2" 对了还要安装web-worker, 我安装的是 "web-worker": "^1.2.0"

使用

const ELK = require('elkjs')
const elk = new ELK()

const graph = {
  id: "root",
  layoutOptions: { 'elk.algorithm': 'layered' },
  children: [
    { id: "n1", width: 30, height: 30 },
    { id: "n2", width: 30, height: 30 },
    { id: "n3", width: 30, height: 30 }
  ],
  edges: [
    { id: "e1", sources: [ "n1" ], targets: [ "n2" ] },
    { id: "e2", sources: [ "n1" ], targets: [ "n3" ] }
  ]
}

elk.layout(graph)
   .then(console.log)
   .catch(console.error)

首先引用elkjs并且创建实例,然后定义x6中的图结构,包含id、children、edges,还有必不可少的layoutOptions: { 'elk.algorithm': 'layered' },表明布局设置是elkjs算法的分层布局,当然如果你想使用其他布局也可以设置,官方文档里配置 点击这里。 elk可以链式调用,与Promise使用方法一样,layout方法接收参数(graph, options),可以在这里找到:

image.png

5、X6基本使用

本文假设你已经会使用了基础的x6特性

1、注册节点 Graph.registerNode

首先X6默认存在内置节点和边,存在默认的特性和样式,但是默认的往往不符合我们的需要,这时我们需要自定义节点或边。 点击这里

//调用 Graph 的静态方法 registerNode 来注册节点,注册以后就可以像使用内置节点那样来使用节点
Graph.registerNode(name: string, cls: typeof Node, overwrite?: boolean)

比如我想要这样的节点效果:

image.png

节点和边有自定义样式、文本、链接桩、图片及支持鼠标事件,且不同类型节点表现形式不同。
这里放出示例代码,展示一小部分实现代码:

//注册node
Graph.registerNode(
  'elk-node', //定义名称
  {
    inherit: 'rect', //继承的基类
    markup: [ // 定义SVG片段
      {
        tagName: 'rect',
        selector: 'body'
      },
      {
        tagName: 'text',
        selector: 'type'
      },
      {
        tagName: 'text',
        selector: 'name'
      }
    ],
    attrs: {//定制样式
      body: {
        fill: '#DFE6F9',
        stroke: '#0048FF',
        strokeWidth: 1,
        cursor: 'pointer'
      },
      type: {
        refX: 0.5,
        refY: 0.3,
        fill: '#ACB9D9',
        fontFamily: 'MiSans',
        fontSize: 10,
        cursor: 'pointer'
        // textAnchor: 'end',
        // textDecoration: 'underline',
      },
      name: {
        refX: 0.5,
        refY: 0.6,
        fill: '#000000',
        fontFamily: 'MiSans',
        fontSize: 10,
        cursor: 'pointer'
        // fontWeight: '800',
        // textAnchor: 'end',
      }
    },
    ports: {
      groups: {
        //分组
        SEND: {
          position: {
            name: 'absolute'
          },
          markup: [
            {
              tagName: 'rect',
              selector: 'portBody'
            }
          ],
          attrs: {
            portBody: {
              magnet: 'passive',
              fill: '#0048FF',
              refWidth: '100%',
              refHeight: '100%'
            }
          }
        },
        RECEIVE: {
          position: {
            name: 'absolute'
          },
          markup: [
            {
              tagName: 'rect',
              selector: 'portBody'
            }
          ],
          attrs: {
            portBody: {
              magnet: 'passive',
              fill: '#0048FF',
              refWidth: '100%',
              refHeight: '100%'
            }
          }
        },
        ONLY_RECEIVE: {
          markup: {
            tagName: 'rect',
            selector: 'receiveBody'
          },
          position: {
            name: 'absolute'
          },
          attrs: {
            receiveBody: {
              magnet: 'passive', //端口是否连接线
              fill: portColor[1],
              refWidth: '100%',
              refHeight: '100%'
            }
          }
        },
        ONLY_SEND: {
          markup: {
            tagName: 'rect',
            selector: 'sendBody'
          },
          position: {
            name: 'absolute'
          },
          attrs: {
            sendBody: {
              magnet: 'passive', //端口是否连接线
              fill: portColor[0],
              refWidth: '100%',
              refHeight: '100%'
            }
          }
        }
      }
    }
  },
  true
)

markupmarkup 指定了渲染节点/边时使用的 SVG/HTML 片段,使用 JSON 格式描述
attrsattrs 选项定制节点样式,attrs 选项是一个复杂对象,该对象的 Key 是节点中 SVG 元素的选择器(Selector),对应的值是应用到该 SVG 元素的 SVG 属性值(如 fill 和 stroke)
ports groups:链接桩分组

2、自定义边

//注册edge
Graph.registerEdge(
  'elk-edge',
  {
    inherit: 'edge',
    attrs: {
      line: {
        stroke: '#ACB9D9',
        strokeWidth: 1, //边宽度随数量不同而不同
        targetMarker: {
          name: 'block',
          width: 4,
          height: 4
        }
      }
    }
  },
  true
)

3、鼠标事件

 //示例:边鼠标移入事件,设置边的样式及文本
  graph.on('edge:mouseenter', ({ e, edge, view }) => {
    // console.log(e, edge, view)
    const msg = edge.labels[0].attrs?.customMsg.text
    edge.attr('line/stroke', '#0048FF')
    edge.prop('labels/0', {
      attrs: {
        label: {
          text: msg
        }
      }
    })
  })

4、elkjs使用

elk
    .layout(graph)
    .then((res) => {
      addChildren(res.children || [])
      addEdges(res.edges || [])
      
      graph.resetCells(cells)
      graph.item.centerContent() // 居中显示
      graph.item.zoomTo(1) //默认1倍数,充满屏幕
    })
    .catch((err) => {
      console.log(err)
    })

6 总结

elkjs和X6的使用还需要多多去看官方文档,特别是要看elk的文档才能混合配置使用。有问题在评论区留言,互相交流~