图的交互 Behavior

G6 封装了一系列交互方法,方便用户直接使用。本文将为 Tutorial案例 增加简单的交互:hover 节点、点击节点、点击边、放缩画布、拖拽画布。本节目标效果如下: 图的交互 Behavior - 图1 图1 Tutorial案例的交互效果。

基本概念

交互行为 Behavior

G6 中的交互行为。G6 内置了一系列交互行为,用户可以直接使用。简单地理解,就是可以一键开启这些交互行为:

  • drag-canvas:拖拽画布;
  • zoom-canvas:缩放画布。

更多详见:交互行为 Behavior

交互管理 mode

mode 是 G6 交互行为的管理机制,一个 mode 是多种行为 Behavior 的组合,允许用户通过切换不同的模式进行交互行为的管理。由于该概念较为复杂,在本入门教程中,读者不需要对该机制深入理解。如有需求,参见 G6 中的 mode

交互状态 State

状态 State 是 G6 中的状态机制。用户可以为图中的元素(节点/边)设置不同的状态及不同状态下的样式。在状态发生变化时,G6 自动更新元素的样式。例如,可以为节点设置状态 'click'truefalse,并为节点设置 'click' 的样式为加粗节点边框。当 'click' 状态被切换为 true 时,节点的边框将会被加粗,'click' 状态被切换为 false 时,节点的样式恢复到默认。在下面的使用方法中,将会有具体例子。

使用方法

拖拽、缩放——内置的交互行为

在 G6 中使用内置 behavior 的方式非常简单,只需要在图实例化时配置 modes。拖拽和缩放属于 G6 内置交互行为,修改代码如下:

  1. const graph = new G6.Graph({
  2. // ... // 其他配置项
  3. modes: {
  4. default: ['drag-canvas', 'zoom-canvas', 'drag-node'], // 允许拖拽画布、放缩画布、拖拽节点
  5. },
  6. });

除了直接使用内置交互名称外,也可以为 behavior 配置参数,例如放缩画布的敏感度、最大/最小放缩程度等,具体用法参见 内置的交互 Behavior

上面代码中的 modes 定义了 G6 的模式,default 是默认的模式,还可以允许有其他的模式,比如:编辑模式 edit 等。不同的模式,用户能进行的行为可以不同,比如默认模式能拖拽画布,编辑模式不允许拖拽画布:

  1. // 举例解释不同模式
  2. modes: {
  3. default: ['drag-canvas'],
  4. edit: []
  5. }

更多关于模式、行为可以参考: 交互模型 Mode内置 Behavior

Hover、Click 改变样式——状态式交互

有时我们希望通过交互可以将元素样式变成特定样式,如我们看到的图 1 中,鼠标 hover 节点、点击节点、点击边时,样式发生了变化。这里涉及到了 G6 中 状态 State 的概念。简单地说,是否 hoverclick 、任何操作(可以是自己起的状态名),都可以称为一种状态(state)。用户可以自由设置不同状态下的元素样式。要达到交互更改元素样式,需要两步:

  • Step 1: 设置各状态下的元素样式;
  • Step 2: 监听事件并切换元素状态

设置各状态下的元素样式

在实例化图时,通过 nodeStateStylesedgeStateStyles 两个配置项可以配置元素在不同状态下的样式。为达到 Tutorial案例 中的效果:

  • 鼠标 hover 节点时,该节点颜色变浅;
  • 点击节点时,该节点边框加粗变黑;
  • 点击边时,该边变成蓝色。

下面代码设置了节点分别在 hoverclick 状态为 true 时的样式,边在 click 状态为 true 时的样式:

  1. const graph = new G6.Graph({
  2. // ... // 其他配置项
  3. // 节点不同状态下的样式集合
  4. nodeStateStyles: {
  5. // 鼠标 hover 上节点,即 hover 状态为 true 时的样式
  6. hover: {
  7. fill: 'lightsteelblue',
  8. },
  9. // 鼠标点击节点,即 click 状态为 true 时的样式
  10. click: {
  11. stroke: '#000',
  12. lineWidth: 3,
  13. },
  14. },
  15. // 节点不同状态下的样式集合
  16. edgeStateStyles: {
  17. // 鼠标点击边,即 click 状态为 true 时的样式
  18. click: {
  19. stroke: 'steelblue',
  20. },
  21. },
  22. });

监听事件并切换元素状态

G6 中所有元素监听都挂载在图实例上,如下代码中的 graph 对象是 G6.Graph 的实例,graph.on() 函数监听了某元素类型(node / edge)的某种事件(click / mouseenter / mouseleave / … 所有事件参见:Event API)。

  1. // 在图实例 graph 上监听
  2. graph.on('元素类型:事件名', e => {
  3. // do something
  4. });

现在,我们通过下面代码,为 Tutorial案例 增加点和边上的监听事件,并在监听函数里使用 graph.setItemState() 改变元素的状态:

  1. // 鼠标进入节点
  2. graph.on('node:mouseenter', e => {
  3. const nodeItem = e.item; // 获取鼠标进入的节点元素对象
  4. graph.setItemState(nodeItem, 'hover', true); // 设置当前节点的 hover 状态为 true
  5. });
  6. // 鼠标离开节点
  7. graph.on('node:mouseleave', e => {
  8. const nodeItem = e.item; // 获取鼠标离开的节点元素对象
  9. graph.setItemState(nodeItem, 'hover', false); // 设置当前节点的 hover 状态为 false
  10. });
  11. // 点击节点
  12. graph.on('node:click', e => {
  13. // 先将所有当前是 click 状态的节点置为非 click 状态
  14. const clickNodes = graph.findAllByState('node', 'click');
  15. clickNodes.forEach(cn => {
  16. graph.setItemState(cn, 'click', false);
  17. });
  18. const nodeItem = e.item; // 获取被点击的节点元素对象
  19. graph.setItemState(nodeItem, 'click', true); // 设置当前节点的 click 状态为 true
  20. });
  21. // 点击边
  22. graph.on('edge:click', e => {
  23. // 先将所有当前是 click 状态的边置为非 click 状态
  24. const clickEdges = graph.findAllByState('edge', 'click');
  25. clickEdges.forEach(ce => {
  26. graph.setItemState(ce, 'click', false);
  27. });
  28. const edgeItem = e.item; // 获取被点击的边元素对象
  29. graph.setItemState(edgeItem, 'click', true); // 设置当前边的 click 状态为 true
  30. });
  31. });

完整代码

至此,完整代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Tutorial Demo</title>
  6. </head>
  7. <body>
  8. <div id="mountNode"></div>
  9. <script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.g6-3.1.0/build/g6.js"></script>
  10. <script>
  11. const graph = new G6.Graph({
  12. container: 'mountNode',
  13. width: 800,
  14. height: 600,
  15. // 节点默认配置
  16. defaultNode: {
  17. labelCfg: {
  18. style: {
  19. fill: '#fff'
  20. }
  21. }
  22. },
  23. // 边默认配置
  24. defaultEdge: {
  25. labelCfg: {
  26. autoRotate: true
  27. }
  28. },
  29. // 节点在各状态下的样式
  30. nodeStateStyles: {
  31. // hover 状态为 true 时的样式
  32. hover: {
  33. fill: 'lightsteelblue'
  34. },
  35. // click 状态为 true 时的样式
  36. click: {
  37. stroke: '#000',
  38. lineWidth: 3
  39. }
  40. },
  41. // 边在各状态下的样式
  42. edgeStateStyles: {
  43. // click 状态为 true 时的样式
  44. click: {
  45. stroke: 'steelblue'
  46. }
  47. },
  48. // 布局
  49. layout: {
  50. type: 'force',
  51. linkDistance: 100,
  52. preventOverlap: true,
  53. nodeStrength: -30,
  54. edgeStrength: 0.1
  55. },
  56. // 内置交互
  57. modes: {
  58. default: [ 'drag-canvas', 'zoom-canvas', 'drag-node' ]
  59. },
  60. });
  61. const main = async () => {
  62. const response = await fetch(
  63. 'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'
  64. );
  65. const remoteData = await response.json();
  66. const nodes = remoteData.nodes;
  67. const edges = remoteData.edges;
  68. nodes.forEach(node => {
  69. if (!node.style) {
  70. node.style = {};
  71. }
  72. node.style.lineWidth = 1;
  73. node.style.stroke = '#666';
  74. node.style.fill = 'steelblue';
  75. switch (node.class) {
  76. case 'c0': {
  77. node.shape = 'circle';
  78. node.size = 30;
  79. break;
  80. }
  81. case 'c1': {
  82. node.shape = 'rect';
  83. node.size = [ 35, 20 ];
  84. break;
  85. }
  86. case 'c2': {
  87. node.shape = 'ellipse';
  88. node.size = [ 35, 20 ];
  89. break;
  90. }
  91. }
  92. });
  93. edges.forEach(edge => {
  94. if (!edge.style) {
  95. edge.style = {};
  96. }
  97. edge.style.lineWidth = edge.weight;
  98. edge.style.opacity = 0.6;
  99. edge.style.stroke = 'grey';
  100. });
  101. graph.data(remoteData);
  102. graph.render();
  103. // 监听鼠标进入节点
  104. graph.on('node:mouseenter', e => {
  105. const nodeItem = e.item;
  106. // 设置目标节点的 hover 状态 为 true
  107. graph.setItemState(nodeItem, 'hover', true);
  108. });
  109. // 监听鼠标离开节点
  110. graph.on('node:mouseleave', e => {
  111. const nodeItem = e.item;
  112. // 设置目标节点的 hover 状态 false
  113. graph.setItemState(nodeItem, 'hover', false);
  114. });
  115. // 监听鼠标点击节点
  116. graph.on('node:click', e => {
  117. // 先将所有当前有 click 状态的节点的 click 状态置为 false
  118. const clickNodes = graph.findAllByState('node', 'click');
  119. clickNodes.forEach(cn => {
  120. graph.setItemState(cn, 'click', false);
  121. });
  122. const nodeItem = e.item;
  123. // 设置目标节点的 click 状态 为 true
  124. graph.setItemState(nodeItem, 'click', true);
  125. });
  126. // 监听鼠标点击节点
  127. graph.on('edge:click', e => {
  128. // 先将所有当前有 click 状态的边的 click 状态置为 false
  129. const clickEdges = graph.findAllByState('edge', 'click');
  130. clickEdges.forEach(ce => {
  131. graph.setItemState(ce, 'click', false);
  132. });
  133. const edgeItem = e.item;
  134. // 设置目标边的 click 状态 为 true
  135. graph.setItemState(edgeItem, 'click', true);
  136. });
  137. };
  138. main();
  139. </script>
  140. </body>
  141. </html>

⚠️注意 若需更换数据,请替换 'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json&#39; 为新的数据文件地址。