简介

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

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

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

节点和链接

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

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

绘制关系图

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

  1. 声明节点和边的数据
  2. 创建图表
  3. 绘制边,保证节点绘制在边上面
  4. XXX绘制节点
<div id="c1" class="chart-container"></div>

var nodes = [// 节点信息:类别、ID,位置 x,y
  {id: '0',name: '开始',type: 'start',x: 50,y: 10},
  {id: '1',name: '步骤一',type: 'action',x: 50,y: 20},
  {id: '2',name: '步骤二',type: 'action',x: 50,y: 30},
  {id: '3',name: '条件',type: 'condition',x: 50,y: 40},
  {id: '4.1',name: '分步骤一',type: 'action',x: 40,y: 50},
  {id: '4.2',name: '分步骤二',type: 'action',x: 60,y: 50},
  {id: '5',name: '汇总',type: 'action',x: 50,y: 60},
  {id: '6',name: '结束',type: 'end',x: 50,y: 70}
];
var edges = [
  {source: '0', target: '1'},
  {source: '1', target: '2'},
  {source: '2', target: '3'},
  {source: '3', target: '4.1'},
  {source: '3', target: '4.2'},
  {source: '4.1', target: '5'},
  {source: '4.2', target: '5'},
  {source: '5', target: '6'}
];

var Stat = G2.Stat;
var chart = new G2.Chart({
  id: 'c1',
  width: 800,
  height: 500,
  plotCfg: {
    margin: [0,0]
  }
});

var defs = {
  x: {min: 0,max:100},
  y: {min: 0, max:100},
  '..x': {min: 0,max:100},
  '..y': {min: 0,max:100}
};

// 首先绘制 edges,点要在边的上面
// 创建单独的视图
var edgeView = chart.createView();
edgeView.source(edges, defs);
edgeView.coord().reflect(); // 从上到下
edgeView.axis(false);
edgeView.tooltip(false);
// Stat.link 方法会生成 ..x, ..y的字段类型,数值范围是 0-1
edgeView.edge()
  .position(Stat.link('source*target',nodes))
  .color('#ccc');

// 绘制节点
var nodeView = chart.createView();
nodeView.coord().reflect(); // 从上到下
nodeView.axis(false);
nodeView.source(nodes, defs);
nodeView.point().position('x*y').color('steelblue')
  .label('name', {
  offset: 10
})
  .tooltip('name');
chart.render();

注意事项:

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

布局算法的作用

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

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

G2 实现的布局算法

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

正交布局的API

使用方式

使用正交布局的步骤

  1. 指定数据源
  2. 使用布局算法计算树节点的位置,获取树节点之间的边
  3. 分别创建边的视图和节点的视图,传入数据源
  4. 渲染
<div id="c2" class="chart-container"></div>
// 指定数据源
var data = [{
  name: 'root',
  children: [{
    name: 'a',
    children: [{
      name: 'a1'
    }, {
      name: 'a2'
    }]
  }, {
    name: 'b',
    children: [{
      name: 'b1',
      children: [{
        name: 'b11'
      }]
    }]
  }, {
    name: 'c'
  }]
}];
var layout = new G2.Layout.Tree({
  nodes: data
});

var nodes = layout.getNodes();
var edges = layout.getEdges();

var Stat = G2.Stat;
var chart = new G2.Chart({
  id: 'c2',
  width: 800,
  height: 500,
  plotCfg: {
    margin: [20,0]
  }
});

var defs = {
  x: {min: 0,max:1},
  y: {min: 0, max:1}/*, 由于 ..x和..y的默认大小就是 0-1,所以可以不设置
  '..x': {min: 0,max:1},
  '..y': {min: 0,max:1}*/
};

// 首先绘制 edges,点要在边的上面
// 创建单独的视图
var edgeView = chart.createView();
edgeView.source(edges, defs);
edgeView.coord().reflect(); // 从上到下
edgeView.axis(false);
edgeView.tooltip(false);
// Stat.link 方法会生成 ..x, ..y的字段类型,数值范围是 0-1
edgeView.edge()
  .position(Stat.link('source*target',nodes))
  .color('#ccc');

// 绘制节点
var nodeView = chart.createView();
nodeView.coord().reflect(); // 从上到下
nodeView.axis(false);
nodeView.source(nodes, defs);
nodeView.point().position('x*y').color('steelblue')
  .label('name', {
  offset: 10
})
  .tooltip('name');
chart.render();

如何实现自己的布局算法

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

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

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

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

示例

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

  1. 设定数据源
  2. 随机指定节点的位置
  3. 在G2中使用布局算法
<div id="c3" class="chart-container"></div>
var data = [
  {id: '1', name: '1'},
  {id: '2', name: '2'},
  {id: '3', name: '3'},
  {id: '4', name: '4'},
  {id: '5', name: '5'},
  {id: '6', name: '6'}
];

var edges = [
  {source: '0', target: '1'},
  {source: '1', target: '2'},
  {source: '2', target: '3'},
  {source: '3', target: '5'},
  {source: '3', target: '6'},
  {source: '4', target: '2'},
  {source: '5', target: '6'}
];

// 自己的随机布局算法
function layout(nodes) {
  var rst = [];

  nodes.forEach(function(node) {
    var obj = {};
    obj.id = node.id;
    obj.name = node.name;
    obj.x = Math.random();
    obj.y = Math.random(); // 使得 x,y随机的分布在0-1范围内
    rst.push(obj);
  });

  return rst;
}

// 调用布局算法
var nodes = layout(data);

var Stat = G2.Stat;
var chart = new G2.Chart({
  id: 'c3',
  width: 800,
  height: 500,
  plotCfg: {
    margin: [20,20]
  }
});
var defs = {
  x: {min: 0,max:1},
  y: {min: 0, max:1}/*, 由于 ..x和..y的默认大小就是 0-1,所以可以不设置
  '..x': {min: 0,max:1},
  '..y': {min: 0,max:1}*/
};

// 首先绘制 edges,点要在边的上面
// 创建单独的视图
var edgeView = chart.createView();
edgeView.source(edges, defs);
edgeView.coord().reflect(); // 从上到下
edgeView.axis(false);
edgeView.tooltip(false);
// Stat.link 方法会生成 ..x, ..y的字段类型,数值范围是 0-1
edgeView.edge()
  .position(Stat.link('source*target',nodes))
  .color('#ccc');

// 绘制节点
var nodeView = chart.createView();
nodeView.coord().reflect(); // 从上到下
nodeView.axis(false);
nodeView.source(nodes, defs);
nodeView.point().position('x*y')
  .size(10)
  .label('name', {
    offset: 0
  })
  .tooltip('name');

chart.render();

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