使用图布局 Layout

当数据中没有节点位置信息,或者数据中的位置信息不满足需求时,需要借助一些布局算法对图进行布局。G6 提供了 7 种一般图的布局和 4 种树图的布局:一般图:

  • Random Layout:随机布局;
  • Force Layout:经典力导向布局:

力导向布局:一个布局网络中,粒子与粒子之间具有引力和斥力,从初始的随机无序的布局不断演变,逐渐趋于平衡稳定的布局方式称之为力导向布局。适用于描述事物间关系,比如人物关系、计算机网络关系等。

  • Circular Layout:环形布局;
  • Radial Layout:辐射状布局;
  • MDS Layout:高维数据降维算法布局;
  • Fruchterman Layout:Fruchterman 布局,一种力导布局;
  • Dagre Layout:层次布局。

树图布局:

  • Dendrogram Layout:树状布局(叶子节点布局对齐到同一层);
  • CompactBox Layout:紧凑树布局;
  • Mindmap Layout:脑图布局;
  • Intended Layout:缩进布局。

各种布局方法的具体介绍及其配置参见 Layout API。本教程中,我们使用的是力导向布局 (Force Layout)。 使用图布局 Layout - 图1

取消自动适配画布

我们在之前的教程里面,为了能够将超出画布的图适配到视野中,在实例化图时使用了 fitView 配置项。这节开始我们将会去掉这个特性。因为复杂的布局系统会打破适配的规则,反而会造成更多的困扰。让我们将相关的适配代码变为注释:

  1. const graph = new G6.Graph({
  2. // ...
  3. // fitView: true,
  4. // fitViewPadding: [ 20, 40, 50, 20 ]
  5. });

默认布局

当实例化图时没有配置布局时:

  • 若数据中节点有位置信息(xy),则按照数据的位置信息进行绘制;
  • 若数据中节点没有位置信息,则默认使用 Random Layout 进行布局。

配置布局

G6 使用布局的方式非常简单,在图实例化的时候,加上 layout 配置即可。下面代码在实例化图时设置了布局方法为 type: 'force',即经典力导向图布局。并设置了参数 preventOverlap: true ,表示希望节点不重叠。力导向布局的更多配置项参见:Layout API

  1. const graph = new G6.Graph({
  2. ... // 其他配置项
  3. layout: { // Object,可选,布局的方法及其配置项,默认为 random 布局。
  4. type: 'force', // 指定为力导向布局
  5. preventOverlap: true, // 防止节点重叠
  6. // nodeSize: 30 // 节点大小,用于算法中防止节点重叠时的碰撞检测。由于已经在上一节的元素配置中设置了每个节点的 size 属性,则不需要在此设置 nodeSize。
  7. }
  8. });

结果如下: 使用图布局 Layout - 图2 如图所示,节点按照力导向布局自动平衡。但是图中的节点过于拥挤,边上的文字信息被挤占,无法看清。我们希望布局计算边的距离可以更长一些。G6 的力导向布局提供了 linkDistance 属性用来指定布局的时候边的距离长度:

  1. const graph = new G6.Graph({
  2. // ...
  3. layout: {
  4. type: 'force',
  5. preventOverlap: true,
  6. linkDistance: 100, // 指定边距离为100
  7. },
  8. });

结果如下: 使用图布局 Layout - 图3![image.png]

不同布局之间、相同布局不同参数允许动态切换和过渡,具体参见:布局切换

提示 布局将在调用 graph.render() 时执行计算。

完整代码

至此,完整代码如下:

  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. defaultNode: {
  16. size: 30,
  17. labelCfg: {
  18. style: {
  19. fill: '#fff'
  20. }
  21. }
  22. },
  23. defaultEdge: {
  24. labelCfg: {
  25. autoRotate: true
  26. }
  27. },
  28. layout: {
  29. type: 'force', // 设置布局算法为 force
  30. linkDistance: 100, // 设置边长为 100
  31. preventOverlap: true, // 设置防止重叠
  32. }
  33. });
  34. const main = async () => {
  35. const response = await fetch(
  36. 'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'
  37. );
  38. const remoteData = await response.json();
  39. const nodes = remoteData.nodes;
  40. const edges = remoteData.edges;
  41. nodes.forEach(node => {
  42. if (!node.style) {
  43. node.style = {};
  44. }
  45. node.style.lineWidth = 1;
  46. node.style.stroke = '#666';
  47. node.style.fill = 'steelblue';
  48. switch (node.class) {
  49. case 'c0': {
  50. node.shape = 'circle';
  51. break;
  52. }
  53. case 'c1': {
  54. node.shape = 'rect';
  55. node.size = [ 35, 20 ];
  56. break;
  57. }
  58. case 'c2': {
  59. node.shape = 'ellipse';
  60. node.size = [ 35, 20 ];
  61. break;
  62. }
  63. }
  64. });
  65. edges.forEach(edge => {
  66. if (!edge.style) {
  67. edge.style = {};
  68. }
  69. edge.style.lineWidth = edge.weight;
  70. edge.style.opacity = 0.6;
  71. edge.style.stroke = 'grey';
  72. });
  73. graph.data(remoteData);
  74. graph.render();
  75. };
  76. main();
  77. </script>
  78. </body>
  79. </html>

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