You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
488 lines
13 KiB
488 lines
13 KiB
/**
|
|
* Graph data structure
|
|
*
|
|
* @module echarts/data/Graph
|
|
* @author Yi Shen(https://www.github.com/pissang)
|
|
*/
|
|
define(function(require) {
|
|
|
|
var util = require('zrender/tool/util');
|
|
|
|
'use strict';
|
|
|
|
/**
|
|
* @alias module:echarts/data/Graph
|
|
* @constructor
|
|
* @param {boolean} directed
|
|
*/
|
|
var Graph = function(directed) {
|
|
/**
|
|
* 是否是有向图
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
this._directed = directed || false;
|
|
|
|
/**
|
|
* @type {Array}
|
|
*/
|
|
this.nodes = [];
|
|
this.edges = [];
|
|
|
|
this._nodesMap = {};
|
|
this._edgesMap = {};
|
|
};
|
|
|
|
/**
|
|
* 是否是有向图
|
|
* @return {boolean}
|
|
*/
|
|
Graph.prototype.isDirected = function () {
|
|
return this._directed;
|
|
};
|
|
|
|
/**
|
|
* 添加一个新的节点
|
|
* @param {string} id 节点名称
|
|
* @param {*} [data] 存储的数据
|
|
*/
|
|
Graph.prototype.addNode = function (id, data) {
|
|
if (this._nodesMap[id]) {
|
|
return this._nodesMap[id];
|
|
}
|
|
|
|
var node = new Graph.Node(id, data);
|
|
|
|
this.nodes.push(node);
|
|
|
|
this._nodesMap[id] = node;
|
|
return node;
|
|
};
|
|
|
|
/**
|
|
* 获取节点
|
|
* @param {string} id
|
|
* @return {module:echarts/data/Graph~Node}
|
|
*/
|
|
Graph.prototype.getNodeById = function (id) {
|
|
return this._nodesMap[id];
|
|
};
|
|
|
|
/**
|
|
* 添加边
|
|
* @param {string|module:echarts/data/Graph~Node} n1
|
|
* @param {string|module:echarts/data/Graph~Node} n2
|
|
* @param {*} data
|
|
* @return {module:echarts/data/Graph~Edge}
|
|
*/
|
|
Graph.prototype.addEdge = function (n1, n2, data) {
|
|
if (typeof(n1) == 'string') {
|
|
n1 = this._nodesMap[n1];
|
|
}
|
|
if (typeof(n2) == 'string') {
|
|
n2 = this._nodesMap[n2];
|
|
}
|
|
if (!n1 || !n2) {
|
|
return;
|
|
}
|
|
|
|
var key = n1.id + '-' + n2.id;
|
|
if (this._edgesMap[key]) {
|
|
return this._edgesMap[key];
|
|
}
|
|
|
|
var edge = new Graph.Edge(n1, n2, data);
|
|
|
|
if (this._directed) {
|
|
n1.outEdges.push(edge);
|
|
n2.inEdges.push(edge);
|
|
}
|
|
n1.edges.push(edge);
|
|
if (n1 !== n2) {
|
|
n2.edges.push(edge);
|
|
}
|
|
|
|
this.edges.push(edge);
|
|
this._edgesMap[key] = edge;
|
|
|
|
return edge;
|
|
};
|
|
|
|
/**
|
|
* 移除边
|
|
* @param {module:echarts/data/Graph~Edge} edge
|
|
*/
|
|
Graph.prototype.removeEdge = function (edge) {
|
|
var n1 = edge.node1;
|
|
var n2 = edge.node2;
|
|
var key = n1.id + '-' + n2.id;
|
|
if (this._directed) {
|
|
n1.outEdges.splice(util.indexOf(n1.outEdges, edge), 1);
|
|
n2.inEdges.splice(util.indexOf(n2.inEdges, edge), 1);
|
|
}
|
|
n1.edges.splice(util.indexOf(n1.edges, edge), 1);
|
|
if (n1 !== n2) {
|
|
n2.edges.splice(util.indexOf(n2.edges, edge), 1);
|
|
}
|
|
|
|
delete this._edgesMap[key];
|
|
this.edges.splice(util.indexOf(this.edges, edge), 1);
|
|
};
|
|
|
|
/**
|
|
* 获取边
|
|
* @param {module:echarts/data/Graph~Node|string} n1
|
|
* @param {module:echarts/data/Graph~Node|string} n2
|
|
* @return {module:echarts/data/Graph~Edge}
|
|
*/
|
|
Graph.prototype.getEdge = function (n1, n2) {
|
|
if (typeof(n1) !== 'string') {
|
|
n1 = n1.id;
|
|
}
|
|
if (typeof(n2) !== 'string') {
|
|
n2 = n2.id;
|
|
}
|
|
|
|
if (this._directed) {
|
|
return this._edgesMap[n1 + '-' + n2];
|
|
} else {
|
|
return this._edgesMap[n1 + '-' + n2]
|
|
|| this._edgesMap[n2 + '-' + n1];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 移除节点(及其邻接边)
|
|
* @param {module:echarts/data/Graph~Node|string} node
|
|
*/
|
|
Graph.prototype.removeNode = function (node) {
|
|
if (typeof(node) === 'string') {
|
|
node = this._nodesMap[node];
|
|
if (!node) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
delete this._nodesMap[node.id];
|
|
this.nodes.splice(util.indexOf(this.nodes, node), 1);
|
|
|
|
for (var i = 0; i < this.edges.length;) {
|
|
var edge = this.edges[i];
|
|
if (edge.node1 === node || edge.node2 === node) {
|
|
this.removeEdge(edge);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 遍历并且过滤指定的节点
|
|
* @param {Function} cb
|
|
* @param {*} [context]
|
|
*/
|
|
Graph.prototype.filterNode = function (cb, context) {
|
|
var len = this.nodes.length;
|
|
for (var i = 0; i < len;) {
|
|
if (cb.call(context, this.nodes[i], i)) {
|
|
i++;
|
|
} else {
|
|
this.removeNode(this.nodes[i]);
|
|
len--;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 遍历并且过滤指定的边
|
|
* @param {Function} cb
|
|
* @param {*} [context]
|
|
*/
|
|
Graph.prototype.filterEdge = function (cb, context) {
|
|
var len = this.edges.length;
|
|
for (var i = 0; i < len;) {
|
|
if (cb.call(context, this.edges[i], i)) {
|
|
i++;
|
|
} else {
|
|
this.removeEdge(this.edges[i]);
|
|
len--;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 线性遍历所有节点
|
|
* @param {Function} cb
|
|
* @param {*} [context]
|
|
*/
|
|
Graph.prototype.eachNode = function (cb, context) {
|
|
var len = this.nodes.length;
|
|
for (var i = 0; i < len; i++) {
|
|
if (this.nodes[i]) { // 可能在遍历过程中存在节点删除
|
|
cb.call(context, this.nodes[i], i);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 线性遍历所有边
|
|
* @param {Function} cb
|
|
* @param {*} [context]
|
|
*/
|
|
Graph.prototype.eachEdge = function (cb, context) {
|
|
var len = this.edges.length;
|
|
for (var i = 0; i < len; i++) {
|
|
if (this.edges[i]) { // 可能在遍历过程中存在边删除
|
|
cb.call(context, this.edges[i], i);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 清空图
|
|
*/
|
|
Graph.prototype.clear = function() {
|
|
this.nodes.length = 0;
|
|
this.edges.length = 0;
|
|
|
|
this._nodesMap = {};
|
|
this._edgesMap = {};
|
|
};
|
|
|
|
/**
|
|
* 广度优先遍历
|
|
* @param {Function} cb
|
|
* @param {module:echarts/data/Graph~Node} startNode 遍历起始节点
|
|
* @param {string} [direction=none] none, in, out 指定遍历边
|
|
* @param {*} [context] 回调函数调用context
|
|
*/
|
|
Graph.prototype.breadthFirstTraverse = function (
|
|
cb, startNode, direction, context
|
|
) {
|
|
if (typeof(startNode) === 'string') {
|
|
startNode = this._nodesMap[startNode];
|
|
}
|
|
if (!startNode) {
|
|
return;
|
|
}
|
|
|
|
var edgeType = 'edges';
|
|
if (direction === 'out') {
|
|
edgeType = 'outEdges';
|
|
} else if (direction === 'in') {
|
|
edgeType = 'inEdges';
|
|
}
|
|
|
|
for (var i = 0; i < this.nodes.length; i++) {
|
|
this.nodes[i].__visited = false;
|
|
}
|
|
|
|
if (cb.call(context, startNode, null)) {
|
|
return;
|
|
}
|
|
|
|
var queue = [startNode];
|
|
while (queue.length) {
|
|
var currentNode = queue.shift();
|
|
var edges = currentNode[edgeType];
|
|
|
|
for (var i = 0; i < edges.length; i++) {
|
|
var e = edges[i];
|
|
var otherNode = e.node1 === currentNode
|
|
? e.node2 : e.node1;
|
|
if (!otherNode.__visited) {
|
|
if (cb.call(otherNode, otherNode, currentNode)) {
|
|
// Stop traversing
|
|
return;
|
|
}
|
|
queue.push(otherNode);
|
|
otherNode.__visited = true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 复制图
|
|
*/
|
|
Graph.prototype.clone = function () {
|
|
var graph = new Graph(this._directed);
|
|
for (var i = 0; i < this.nodes.length; i++) {
|
|
graph.addNode(this.nodes[i].id, this.nodes[i].data);
|
|
}
|
|
for (var i = 0; i < this.edges.length; i++) {
|
|
var e = this.edges[i];
|
|
graph.addEdge(e.node1.id, e.node2.id, e.data);
|
|
}
|
|
return graph;
|
|
};
|
|
|
|
/**
|
|
* 图节点
|
|
* @alias module:echarts/data/Graph~Node
|
|
* @param {string} id
|
|
* @param {*} [data]
|
|
*/
|
|
var Node = function(id, data) {
|
|
/**
|
|
* 节点名称
|
|
* @type {string}
|
|
*/
|
|
this.id = id;
|
|
/**
|
|
* 节点存储的数据
|
|
* @type {*}
|
|
*/
|
|
this.data = data || null;
|
|
/**
|
|
* 入边,只在有向图上有效
|
|
* @type {Array.<module:echarts/data/Graph~Edge>}
|
|
*/
|
|
this.inEdges = [];
|
|
/**
|
|
* 出边,只在有向图上有效
|
|
* @type {Array.<module:echarts/data/Graph~Edge>}
|
|
*/
|
|
this.outEdges = [];
|
|
/**
|
|
* 邻接边
|
|
* @type {Array.<module:echarts/data/Graph~Edge>}
|
|
*/
|
|
this.edges = [];
|
|
};
|
|
|
|
/**
|
|
* 度
|
|
* @return {number}
|
|
*/
|
|
Node.prototype.degree = function() {
|
|
return this.edges.length;
|
|
};
|
|
|
|
/**
|
|
* 入度,只在有向图上有效
|
|
* @return {number}
|
|
*/
|
|
Node.prototype.inDegree = function() {
|
|
return this.inEdges.length;
|
|
};
|
|
|
|
/**
|
|
* 出度,只在有向图上有效
|
|
* @return {number}
|
|
*/
|
|
Node.prototype.outDegree = function() {
|
|
return this.outEdges.length;
|
|
};
|
|
|
|
/**
|
|
* 图边
|
|
* @alias module:echarts/data/Graph~Edge
|
|
* @param {module:echarts/data/Graph~Node} node1
|
|
* @param {module:echarts/data/Graph~Node} node2
|
|
* @param {extra} data
|
|
*/
|
|
var Edge = function(node1, node2, data) {
|
|
/**
|
|
* 节点1,如果是有向图则为源节点
|
|
* @type {module:echarts/data/Graph~Node}
|
|
*/
|
|
this.node1 = node1;
|
|
/**
|
|
* 节点2,如果是有向图则为目标节点
|
|
* @type {module:echarts/data/Graph~Node}
|
|
*/
|
|
this.node2 = node2;
|
|
|
|
/**
|
|
* 边存储的数据
|
|
* @type {*}
|
|
*/
|
|
this.data = data || null;
|
|
};
|
|
|
|
Graph.Node = Node;
|
|
Graph.Edge = Edge;
|
|
|
|
/**
|
|
* 从邻接矩阵生成
|
|
* ```
|
|
* TARGET
|
|
* -1--2--3--4--5-
|
|
* 1| x x x x x
|
|
* 2| x x x x x
|
|
* 3| x x x x x SOURCE
|
|
* 4| x x x x x
|
|
* 5| x x x x x
|
|
* ```
|
|
* 节点的行列总和会被写到`node.data.value`
|
|
* 对于有向图会计算每一行的和写到`node.data.outValue`,
|
|
* 计算每一列的和写到`node.data.inValue`。
|
|
* 边的权重会被然后写到`edge.data.weight`。
|
|
*
|
|
* @method module:echarts/data/Graph.fromMatrix
|
|
* @param {Array.<Object>} nodesData 节点信息,必须有`id`属性, 会保存到`node.data`中
|
|
* @param {Array} matrix 邻接矩阵
|
|
* @param {boolean} directed 是否是有向图
|
|
* @return {module:echarts/data/Graph}
|
|
*/
|
|
Graph.fromMatrix = function(nodesData, matrix, directed) {
|
|
if (
|
|
!matrix || !matrix.length
|
|
|| (matrix[0].length !== matrix.length)
|
|
|| (nodesData.length !== matrix.length)
|
|
) {
|
|
// Not a valid data
|
|
return;
|
|
}
|
|
|
|
var size = matrix.length;
|
|
var graph = new Graph(directed);
|
|
|
|
for (var i = 0; i < size; i++) {
|
|
var node = graph.addNode(nodesData[i].id, nodesData[i]);
|
|
// TODO
|
|
// node.data已经有value的情况
|
|
node.data.value = 0;
|
|
if (directed) {
|
|
node.data.outValue = node.data.inValue = 0;
|
|
}
|
|
}
|
|
for (var i = 0; i < size; i++) {
|
|
for (var j = 0; j < size; j++) {
|
|
var item = matrix[i][j];
|
|
if (directed) {
|
|
graph.nodes[i].data.outValue += item;
|
|
graph.nodes[j].data.inValue += item;
|
|
}
|
|
graph.nodes[i].data.value += item;
|
|
graph.nodes[j].data.value += item;
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < size; i++) {
|
|
for (var j = i; j < size; j++) {
|
|
var item = matrix[i][j];
|
|
if (item === 0) {
|
|
continue;
|
|
}
|
|
var n1 = graph.nodes[i];
|
|
var n2 = graph.nodes[j];
|
|
var edge = graph.addEdge(n1, n2, {});
|
|
edge.data.weight = item;
|
|
if (i !== j) {
|
|
if (directed && matrix[j][i]) {
|
|
var inEdge = graph.addEdge(n2, n1, {});
|
|
inEdge.data.weight = matrix[j][i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// console.log(graph.edges.map(function (e) {return e.node1.id + '-' + e.node2.id;}))
|
|
|
|
return graph;
|
|
};
|
|
|
|
return Graph;
|
|
});
|