交互模式 Mode

本章以添加节点及在两个节点之间连线为例进行介绍 G6 中的交互。在阅读本章之前,需要先熟悉以下内容:

  • 选择“默认”按钮时,切换到 default 交互模式:拖拽节点时节点跟随鼠标移动;点击节点时选中该节点;
  • 选择“添加节点”按钮时,切换到 addNode 交互模式:点击空白区域在点击处增加一个节点;点击节点时选中该节点;
  • 选择“添加边”按钮时,切换到 addEdge 交互模式:依次点击两个节点将会在这两个节点之间添加一条边。

使用多个 mode 的原因 相同的鼠标操作,在不同场景下有不同的含义。例如:

  • 点击空白画布取消目前图上所有节点的选中状态、点击空白画布在响应位置添加节点,这两种需求都对应了用户点击画布空白处的操作;
  • 点击选中、点击两个节点添加边都涉及到了鼠标在节点上的点击操作。

为了区分这些操作的含义,我们使用交互模式 mode 划分不同的场景。

前提代码

下面 HTML 代码是本文的基础代码,后续功能将在这份代码中增量添加。下面代码定义了左上方的下拉菜单,以及后面将会用到图上的初始数据 data

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Interactively Add</title>
  6. </head>
  7. <body>
  8. <!-- 左上方的下拉菜单 -->
  9. <select id="selector">
  10. <option value="default">默认</option>
  11. <option value="addNode">添加节点</option>
  12. <option value="addEdge">添加边</option>
  13. </select>
  14. <div id="mountNode"></div>
  15. <script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.g6-3.1.0/build/g6.js"></script>
  16. <script>
  17. // 初始数据
  18. const data = {
  19. nodes: [{
  20. id: 'node1',
  21. x: 100,
  22. y: 200
  23. },{
  24. id: 'node2',
  25. x: 300,
  26. y: 200
  27. },{
  28. id: 'node3',
  29. x: 300,
  30. y: 300
  31. }],
  32. edges: [{
  33. id: 'edge1',
  34. target: 'node2',
  35. source: 'node1'
  36. }]
  37. };
  38. </script>
  39. </body>
  40. </html>

配置交互模式

下面代码实例化了图,并配置了交互模式的集合 modes,其中包括 default 默认交互模式、addNode 增加节点交互模式、addEdge 增加边交互模式。每种交互模式中都包含了各自的交互行为,其中 'drag-node'(拖拽节点) 和 'click-select'(点击选中) 是 G6 内置的交互行为,'click-add-node'(点击空白画布添加节点) 和 'click-add-edge'(点击两个节点添加边) 需要我们在后面进行自定义。

  1. // const data = ...
  2. const graph = new G6.Graph({
  3. container: 'mountNode',
  4. width: 500,
  5. height: 500,
  6. // 交互模式集合
  7. modes: {
  8. // 默认交互模式
  9. default: ['drag-node', 'click-select'],
  10. // 增加节点交互模式
  11. addNode: ['click-add-node', 'click-select'],
  12. // 增加边交互模式
  13. addEdge: ['click-add-edge', 'click-select'],
  14. },
  15. });
  16. graph.data(data);
  17. graph.render();
  18. // 监听左上角下拉菜单的变化,根据其变化切换图的交互模式
  19. document.getElementById('selector').addEventListener('change', e => {
  20. const value = e.target.value;
  21. // 切换交互模式
  22. graph.setMode(value);
  23. });

添加节点

在上面的实例中,当选中添加节点按钮时,会切换到添加节点的 Mode 上。实现在点击空白画布时,在点击位置添加节点的方式是通过 G6.registerBehavior 自定义名为 'click-add-node'(名字可以自由设定) 的 Behavior 实现的 。

  1. // 封装点击添加边的交互
  2. G6.registerBehavior('click-add-node', {
  3. // 设定该自定义行为需要监听的事件及其响应函数
  4. getEvents() {
  5. // 监听的事件为 cnavas:click,响应函数时 onClick
  6. return {
  7. 'canvas:click': 'onClick',
  8. };
  9. },
  10. // 点击事件
  11. onClick(ev) {
  12. const graph = this.graph;
  13. // 在图上新增一个节点
  14. const node = graph.addItem('node', {
  15. x: ev.x,
  16. y: ev.y,
  17. id: G6.Util.uniqueId(), // 生成唯一的 id
  18. });
  19. },
  20. });

添加边

在上面的案例中,当需要在两个节点之间连线时,要先切换到添加边的 Mode 上。下面代码自定义了名为 'click-add-edge'(名字可以自由设定)的 Behavior 实现两个节点之间连线。

  1. // 封装点击添加边的交互
  2. G6.registerBehavior('click-add-edge', {
  3. // 设定该自定义行为需要监听的事件及其响应函数
  4. getEvents() {
  5. return {
  6. 'node:click': 'onClick', // 监听事件 node:click,响应函数时 onClick
  7. mousemove: 'onMousemove', // 监听事件 mousemove,响应函数时 onMousemove
  8. 'edge:click': 'onEdgeClick', // 监听事件 edge:click,响应函数时 onEdgeClick
  9. };
  10. },
  11. // getEvents 中定义的 'node:click' 的响应函数
  12. onClick(ev) {
  13. const node = ev.item;
  14. const graph = this.graph;
  15. // 鼠标当前点击的节点的位置
  16. const point = { x: ev.x, y: ev.y };
  17. const model = node.getModel();
  18. if (this.addingEdge && this.edge) {
  19. graph.updateItem(this.edge, {
  20. target: model.id,
  21. });
  22. this.edge = null;
  23. this.addingEdge = false;
  24. } else {
  25. // 在图上新增一条边,结束点是鼠标当前点击的节点的位置
  26. this.edge = graph.addItem('edge', {
  27. source: model.id,
  28. target: point,
  29. });
  30. this.addingEdge = true;
  31. }
  32. },
  33. // getEvents 中定义的 mousemove 的响应函数
  34. onMousemove(ev) {
  35. // 鼠标的当前位置
  36. const point = { x: ev.x, y: ev.y };
  37. if (this.addingEdge && this.edge) {
  38. // 更新边的结束点位置为当前鼠标位置
  39. this.graph.updateItem(this.edge, {
  40. target: point,
  41. });
  42. }
  43. },
  44. // getEvents 中定义的 'edge:click' 的响应函数
  45. onEdgeClick(ev) {
  46. const currentEdge = ev.item;
  47. // 拖拽过程中,点击会点击到新增的边上
  48. if (this.addingEdge && this.edge == currentEdge) {
  49. graph.removeItem(this.edge);
  50. this.edge = null;
  51. this.addingEdge = false;
  52. }
  53. },
  54. });

完整代码

完整 demo 代码参见:动态添加元素