简介

G2可以通过点和边来实现树图、网络图等关系图,而节点的位置需要使用布局算法来确定,常见的布局算法分为:

  • 树的布局算法,有正交布局、径向布局、缩进布局、圆锥布局等
  • 图的布局算法,有力导布局、MDS 布局等

上面的布局算法在 D3 中都有介绍,本章仅介绍 G2 实现的布局算法和一些约定。

节点和链接

关系图一般使用节点-链接法绘制,使用节点表示对象,用线(或者边)表示关系。节点链接法的布局有几个原则:

  • 节点不能相互遮挡、边尽量避免交叉
  • 节点的不能移出边界,尽量提升空间利用率

绘制关系图

使用节点-链接法绘制关系图需要两份数据,节点的数据和边的数据。由于G2中一个View只能使用一个数据源,所以在 G2 中绘制关系图需要两个 View,所以在 G2 中绘制关系图的步骤如下:

  • 声明节点和边的数据
  • 创建图表
  • 绘制边,保证节点绘制在边上面
  • XXX绘制节点
  1. <div id="c1" class="chart-container"></div>
  1. var nodes = [// 节点信息:类别、ID,位置 x,y
  2. {id: '0',name: '开始',type: 'start',x: 50,y: 10},
  3. {id: '1',name: '步骤一',type: 'action',x: 50,y: 20},
  4. {id: '2',name: '步骤二',type: 'action',x: 50,y: 30},
  5. {id: '3',name: '条件',type: 'condition',x: 50,y: 40},
  6. {id: '4.1',name: '分步骤一',type: 'action',x: 40,y: 50},
  7. {id: '4.2',name: '分步骤二',type: 'action',x: 60,y: 50},
  8. {id: '5',name: '汇总',type: 'action',x: 50,y: 60},
  9. {id: '6',name: '结束',type: 'end',x: 50,y: 70}
  10. ];
  11. var edges = [
  12. {source: '0', target: '1'},
  13. {source: '1', target: '2'},
  14. {source: '2', target: '3'},
  15. {source: '3', target: '4.1'},
  16. {source: '3', target: '4.2'},
  17. {source: '4.1', target: '5'},
  18. {source: '4.2', target: '5'},
  19. {source: '5', target: '6'}
  20. ];
  21. var Stat = G2.Stat;
  22. var chart = new G2.Chart({
  23. id: 'c1',
  24. width: 800,
  25. height: 500,
  26. plotCfg: {
  27. margin: [0,0]
  28. }
  29. });
  30. var defs = {
  31. x: {min: 0,max:100},
  32. y: {min: 0, max:100},
  33. '..x': {min: 0,max:100},
  34. '..y': {min: 0,max:100}
  35. };
  36. // 首先绘制 edges,点要在边的上面
  37. // 创建单独的视图
  38. var edgeView = chart.createView();
  39. edgeView.source(edges, defs);
  40. edgeView.coord().reflect(); // 从上到下
  41. edgeView.axis(false);
  42. edgeView.tooltip(false);
  43. // Stat.link 方法会生成 ..x, ..y的字段类型,数值范围是 0-1
  44. edgeView.edge()
  45. .position(Stat.link('source*target',nodes))
  46. .color('#ccc');
  47. // 绘制节点
  48. var nodeView = chart.createView();
  49. nodeView.coord().reflect(); // 从上到下
  50. nodeView.axis(false);
  51. nodeView.source(nodes, defs);
  52. nodeView.point().position('x*y').color('steelblue')
  53. .label('name', {
  54. offset: 10
  55. })
  56. .tooltip('name');
  57. chart.render();

注意事项:

  • 由于节点和链接在两个视图上,所以需要统一两个视图的x,y的度量
  • 链接需要使用节点计算线(边)的起始、结束为止,所以需要统计函数 Stat.link
  • 节点中需要 x, y, id 字段,方便边的起始、结束点计算

布局算法的作用

在 G2 中只要我们知道节点的位置和对应的边就能绘制出关系图,在 G2 中布局算法不是必须的,只要能够计算出节点的位置即可,计算出来的节点需要满足:

  • 节点中需要 x, y, id 字段,方便边的起始、结束点计算
  • 计算出来的 x, y 的值都要分布在 [0 - 1] 的范围内

G2 实现的布局算法

G2 目前仅支持了树的正交布局,保证树的空间利用率最高

layout 布局 - 图1

正交布局的API

使用方式

使用正交布局的步骤

  • 指定数据源
  • 使用布局算法计算树节点的位置,获取树节点之间的边
  • 分别创建边的视图和节点的视图,传入数据源
  • 渲染
  1. <div id="c2" class="chart-container"></div>
  1. // 指定数据源
  2. var data = [{
  3. name: 'root',
  4. children: [{
  5. name: 'a',
  6. children: [{
  7. name: 'a1'
  8. }, {
  9. name: 'a2'
  10. }]
  11. }, {
  12. name: 'b',
  13. children: [{
  14. name: 'b1',
  15. children: [{
  16. name: 'b11'
  17. }]
  18. }]
  19. }, {
  20. name: 'c'
  21. }]
  22. }];
  23. var layout = new G2.Layout.Tree({
  24. nodes: data
  25. });
  26. var nodes = layout.getNodes();
  27. var edges = layout.getEdges();
  28. var Stat = G2.Stat;
  29. var chart = new G2.Chart({
  30. id: 'c2',
  31. width: 800,
  32. height: 500,
  33. plotCfg: {
  34. margin: [20,0]
  35. }
  36. });
  37. var defs = {
  38. x: {min: 0,max:1},
  39. y: {min: 0, max:1}/*, 由于 ..x和..y的默认大小就是 0-1,所以可以不设置
  40. '..x': {min: 0,max:1},
  41. '..y': {min: 0,max:1}*/
  42. };
  43. // 首先绘制 edges,点要在边的上面
  44. // 创建单独的视图
  45. var edgeView = chart.createView();
  46. edgeView.source(edges, defs);
  47. edgeView.coord().reflect(); // 从上到下
  48. edgeView.axis(false);
  49. edgeView.tooltip(false);
  50. // Stat.link 方法会生成 ..x, ..y的字段类型,数值范围是 0-1
  51. edgeView.edge()
  52. .position(Stat.link('source*target',nodes))
  53. .color('#ccc');
  54. // 绘制节点
  55. var nodeView = chart.createView();
  56. nodeView.coord().reflect(); // 从上到下
  57. nodeView.axis(false);
  58. nodeView.source(nodes, defs);
  59. nodeView.point().position('x*y').color('steelblue')
  60. .label('name', {
  61. offset: 10
  62. })
  63. .tooltip('name');
  64. chart.render();

如何实现自己的布局算法

实现自己的布局算法非常容易只要满足以下条件:

  • 节点的返回值中存在 x, y 字段
  • 节点和边能够通过id 相互关联起来

使用自己布局算法的注意事项:

  • 统一边和节点视图的 x,y 度量的大小范围

示例

我们以最简单的随机布局来介绍如何实现自己的算法,步骤如下:

  • 设定数据源
  • 随机指定节点的位置
  • 在G2中使用布局算法
  1. <div id="c3" class="chart-container"></div>
  1. var data = [
  2. {id: '1', name: '1'},
  3. {id: '2', name: '2'},
  4. {id: '3', name: '3'},
  5. {id: '4', name: '4'},
  6. {id: '5', name: '5'},
  7. {id: '6', name: '6'}
  8. ];
  9. var edges = [
  10. {source: '0', target: '1'},
  11. {source: '1', target: '2'},
  12. {source: '2', target: '3'},
  13. {source: '3', target: '5'},
  14. {source: '3', target: '6'},
  15. {source: '4', target: '2'},
  16. {source: '5', target: '6'}
  17. ];
  18. // 自己的随机布局算法
  19. function layout(nodes) {
  20. var rst = [];
  21. nodes.forEach(function(node) {
  22. var obj = {};
  23. obj.id = node.id;
  24. obj.name = node.name;
  25. obj.x = Math.random();
  26. obj.y = Math.random(); // 使得 x,y随机的分布在0-1范围内
  27. rst.push(obj);
  28. });
  29. return rst;
  30. }
  31. // 调用布局算法
  32. var nodes = layout(data);
  33. var Stat = G2.Stat;
  34. var chart = new G2.Chart({
  35. id: 'c3',
  36. width: 800,
  37. height: 500,
  38. plotCfg: {
  39. margin: [20,20]
  40. }
  41. });
  42. var defs = {
  43. x: {min: 0,max:1},
  44. y: {min: 0, max:1}/*, 由于 ..x和..y的默认大小就是 0-1,所以可以不设置
  45. '..x': {min: 0,max:1},
  46. '..y': {min: 0,max:1}*/
  47. };
  48. // 首先绘制 edges,点要在边的上面
  49. // 创建单独的视图
  50. var edgeView = chart.createView();
  51. edgeView.source(edges, defs);
  52. edgeView.coord().reflect(); // 从上到下
  53. edgeView.axis(false);
  54. edgeView.tooltip(false);
  55. // Stat.link 方法会生成 ..x, ..y的字段类型,数值范围是 0-1
  56. edgeView.edge()
  57. .position(Stat.link('source*target',nodes))
  58. .color('#ccc');
  59. // 绘制节点
  60. var nodeView = chart.createView();
  61. nodeView.coord().reflect(); // 从上到下
  62. nodeView.axis(false);
  63. nodeView.source(nodes, defs);
  64. nodeView.point().position('x*y')
  65. .size(10)
  66. .label('name', {
  67. offset: 0
  68. })
  69. .tooltip('name');
  70. chart.render();

另外,在使用过程中可以从其他框架迁移相应的布局算法。