前言

本文在通读文档、了解G6之后食用更美味…

G6是什么?

官方原文如下介绍:

G6 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等图可视化的基础能力。旨在让关系变得透明,简单。让用户获得关系数据的 Insight。

上面提到的 “基础能力” 需要划重点,因为这句话可以说贯彻了 G6 的本质。

在我的理解下如果解释的再纯粹一点,那么 G6 是一个命名空间,它提供面向图形编程、赋予开发者手动组装图形 & 自定义图形交互的能力,自己动手丰衣足食,这些正反映了官方所述的基础能力。

现在,围绕流程可视化的主题我们从 概念上 去使用 G6 实现一个作业流程监控可视化的产品。这款产品的技能点涉及到:

  • 自定义作业节点
  • 节点拥有不同状态的样式
  • 节点响应事件
  • 拖拽生成边,将两个节点连起来
  • 删除节点、边
  • 获取图数据

自定义节点

  1. api

    1. /**
    2. * @desc 自定义节点 api
    3. * @param {String} nodeName - 节点名称,全局唯一
    4. * @param {Object} options - 节点配置项
    5. * @param {String} extendName - 继承的内置节点名称
    6. */
    7. G6.registerNode(nodeName, options, extendName)
  2. 节点 ( 还有边 )是图的最小单位,节点又是通过图形( shape )组装而成的,我们可以如此理解:

  • 如果说节点是化学分子,那么shape就是原子;
  • 如果说节点是汽车,那么shape就是组装汽车的轮胎、玻璃、座椅等零件
  1. 实现一个自定义的节点渲染到画布上必须满足:
  • 自定义节点,并给这个节点全局唯一的名字;
  • 通过数据渲染图时,节点的 type 指定为 (1) 中的名称。
  1. // 自定义作业节点
  2. G6.registerNode('job-node',
  3. {
  4. draw (cfg, group) {
  5. const keyShape = group.addShape()
  6. return keyShape // 每个节点都有一个keyShape, 所以这一步是必须的
  7. }
  8. },
  9. 'single-node'
  10. )
  11. // 通过在数据中指定节点type,来渲染自定义的作业节点
  12. // 配置渲染的节点类型还有全局配置、函数配置等其他方式,请自行查阅
  13. const data = {
  14. nodes: [
  15. {
  16. id: 1,
  17. type: 'job-node'
  18. }
  19. ]
  20. }
  21. G6.read(data)

节点配置状态样式

在实际使用 G6 的开发过程中,有两种不同的场景需要配置状态样式:

  1. 业务数据字段的属性值不同,绘制的节点样式也不一样,比如节点是禁用还是可用状态;
  2. 不同的交互,节点绘制的效果不同,比如 鼠标hover 和 鼠标点击选中节点;
  1. // 第一种,根据数据绘制节点
  2. // 1.1 准备数据
  3. const data = {
  4. nodes: [
  5. {
  6. id: 1,
  7. type: 'job-node',
  8. name: '巡检监控'
  9. disabled: true
  10. },
  11. {
  12. id: 2,
  13. type: 'job-node',
  14. name: '核心数仓'
  15. disabled: false
  16. }
  17. ]
  18. }
  19. // 1.2 注册自定义节点时,根据业务数据动态绘制
  20. G6.registerNode('job-node',
  21. {
  22. draw (cfg, group) {
  23. // 在绘制过程中业务数据属性会传递到cfg,所以在这里我们能拿到数据中的name/disabled等属性
  24. const { name, disabled } = cfg
  25. const keyShape = group.addShape()
  26. // group.addShape() 方法返回图形实例
  27. // 根据业务数据属性动态修改图形的样式
  28. keyShape.attr({
  29. cursor: disabled ? 'not-allowed' : 'default' // 如果禁用(diabled 为 true),悬浮时会显示禁用的手势
  30. })
  31. return keyShape
  32. }
  33. },
  34. 'single-node'
  35. )
  36. // 第二种,根据交互配置样式
  37. // 2.1 注册节点时,配置状态样式
  38. G6.registerNode('job-node',
  39. {
  40. options: {
  41. stateStyles: {
  42. hover: {
  43. fill: '#ccc'
  44. },
  45. select: {
  46. fill: 'red'
  47. }
  48. }
  49. }
  50. draw (cfg, group) {
  51. const keyShape = group.addShape()
  52. return keyShape
  53. }
  54. },
  55. // 继承内置节点是必要的,内部节点封装处理了节点的 `options.stateStyles` 配置在不同状态下 keyshape 和 子shape 的样式
  56. // 也就意味着不用我们去复写 `setState` 以及它内部处理节点各shape的样式
  57. 'single-node'
  58. )
  59. // 2.2 交互过程中给节点添加状态
  60. // 鼠标划入节点,节点背景置灰
  61. graph.on('node:mouseenter', evt => {
  62. const node = evt.item
  63. graph.setItemState(node, 'hover', true)
  64. })
  65. // 鼠标划出节点,节点背景置为默认
  66. graph.on('node:mouseout', evt => {
  67. const node = evt.item
  68. graph.setItemState(node, 'hover', false)
  69. })
  70. // 点击节点,节点背景变红
  71. graph.on('node:click', evt => {
  72. const node = evt.item
  73. graph.setItemState(node, 'select', true)
  74. })

节点响应事件

G6 提供监听画布、节点、边、组等多维度的事件注册机制,我们可以根据实际场景在不同的维度注册事件。

另外在 G6 中,事件响应的最小单元是节点 or 边,如果想针对节点内的某个图形做事件响应,可以通过监听节点/边的事件,然后进一步通过事件对象以及节点/边的相关api去判断。

比如只有点击节点的 close-icon-shape 后,才能从画布中删除该节点。

  1. G6.registerNode('job-node',
  2. {
  3. draw (cfg, group) {
  4. const iconShape = group.addShape('image', {
  5. attrs: {
  6. url: closeIcon
  7. },
  8. // 这里name属性定义是必须的,后续可以通过事件对象拿到此处定义的图形名称
  9. name: 'close-icon-shape'
  10. })
  11. const keyShape = group.addShape()
  12. return keyShape
  13. }
  14. },
  15. 'single-node'
  16. )
  17. const data = {...}
  18. const graph = new G6.Graph({...})
  19. graph.on('node:click', evt => {
  20. // 节点
  21. const item = evt.item
  22. // shape图形
  23. const target = evt.target
  24. // 通过get('name')方法获取图形定义时的名称
  25. if (target.get('name') === 'close-icon-shape') {
  26. // 只有点击 close-icon-shape 图形时,才会触发
  27. graph.removeItem(item)
  28. }
  29. })

拖拽生成边,将两个节点连起来

拖拽生成边由一系列事件组合而成,也可以说是一组动作,因此注册某个事件响应很难达到我们想要的效果、甚至会影响到别的交互。

所以G6提供了我们将一组动作封装到一起的能力,这里就涉及到了G6的 交互模式自定义交互

通过交互模式的切换,我们就可以让图中元素对于同一个事件做出不同的响应。

例如,存在 default 和 edit 两种 mode(交互模式):

  • default 模式中包含点击选中节点行为和拖拽画布行为;
  • edit 模式中包含点击节点弹出编辑框行为和拖拽节点行为。

具体的效果可以参见G6图表示例中的 切换模式增加点和边

获取图数据

经过上述步骤,我们流程图基本实现了:

  • 节点的绘制和生成
  • 作业节点之间的流程依赖
  • 新增、删除节点

最后需要获取图数据并将其传给服务端将图数据持久化,在G6图中数据的获取也是相当简单,只需要通过下面的方法即可:

  1. console.log(graph.save()) // graph 是 G6.Graph 的实例
  2. // 上面的方法会打印出如下格式的对象
  3. {
  4. nodes: [...],
  5. edges: [...],
  6. // 还有group/combo等组数据...
  7. }

最后的最后

想当初在接触G6前,我用 canvas 画布对象整个 “爱心” 都能开心一会儿。

学习了G6后,对可视化的产品需求我有了相当大的自信,这一路非常感谢 G6 团队的大佬们在小弟刚上路时的指引和解惑,在此必须点名感谢几位老哥:十吾、羽然、聚则!也感谢前阵子晚上做梦都在画路径的自己。爱你们!😚

最后为各位看官奉上我当初学习G6做的笔记:

  • 自定义节点时的Shape图形:
    • 所有图形相对于节点中心绘制(节点中心的坐标相对于画布,由group所在矩阵控制)。x / y 是指定 相对于节点中心 的位置的距离。尺寸属性用:width / height / r / rx / ry
  • 自定义节点继承内置节点:
    • single-node:内部封装处理了节点的 options.stateStyles 配置在不同状态下 keyshape 和 子shape 的样式,也就意味着不用我们去复写 setState 以及它内部处理节点各shape的样式
  • 画布的事件参数属性:
  1. graph.on('click', evt => {
  2. console.log('输出节点', evt.item); // null<点击画布> | Node<点击节点> | Edge<点击边>
  3. console.log('输出父类', evt.parent); // null<点击画布> | Group<图形分组,点击了节点或者边>
  4. console.log('输出图形', evt.target); // Text | Path | Rect ....
  5. })
  • groupshape 是图形层面上的概念,就类似 svg 的g标签和其他图形标签circle之类的;item 不是图形上的概念,作为画布上节点、边的实例,它包含可视化相关API的操作方法以及group的代理

image-20200617185233275.png