### 前言
定位browser 的 chart, ? VML,SVG, HTML5 Canvas使用的方式各不一樣。
如果使用現有的js ?library ([各種實現js 圖表的library匯總與比較](http://blog.csdn.net/oscar999/article/details/8536727)) , 調用的API方式也肯定不同。
舉個例子: draw2d 使用addFigure 和 setPosition 都可以設置圖的位置。
混在特定技術或是特定library 里去layout , 很明顯不是一個明智之舉。
切分開來, layout 的功能對于任何的圖形繪制都適用。就是本章所討論的了。
### 實現思想
其實實現思想很簡單,維護一個JS 的object(Graph)。 在這個Object 里記錄節點,邊的信息;?
節點包含有如下信息:
? ? id ?-- 表示符
? ? x ?-- 橫坐標
? ? y ?-- 縱坐標
? ?shape -- 繪制的圖
這樣的話, 在繪制一個圖節點之前, 先要在這個Graph 維護這個圖節點的一些信息。
### Graph 示例
~~~
var Graph = function() {
this.nodeSet = {};
this.nodes = [];
this.edges = [];
this.adjacency = {};
this.nextNodeId = 0;
this.nextEdgeId = 0;
this.eventListeners = [];
};
var Node = function(id, data) {
this.id = id;
this.data = typeof(data) !== 'undefined' ? data : {};
};
var Edge = function(id, source, target, data) {
this.id = id;
this.source = source;
this.target = target;
this.data = typeof(data) !== 'undefined' ? data : {};
};
Graph.prototype.addNode = function(node) {
if (typeof(this.nodeSet[node.id]) === 'undefined') {
this.nodes.push(node);
}
this.nodeSet[node.id] = node;
this.notify();
return node;
};
Graph.prototype.addEdge = function(edge) {
var exists = false;
this.edges.forEach(function(e) {
if (edge.id === e.id) { exists = true; }
});
if (!exists) {
this.edges.push(edge);
}
if (typeof(this.adjacency[edge.source.id]) === 'undefined') {
this.adjacency[edge.source.id] = {};
}
if (typeof(this.adjacency[edge.source.id][edge.target.id]) === 'undefined') {
this.adjacency[edge.source.id][edge.target.id] = [];
}
exists = false;
this.adjacency[edge.source.id][edge.target.id].forEach(function(e) {
if (edge.id === e.id) { exists = true; }
});
if (!exists) {
this.adjacency[edge.source.id][edge.target.id].push(edge);
}
this.notify();
return edge;
};
Graph.prototype.newNode = function(data) {
var node = new Node(this.nextNodeId++, data);
this.addNode(node);
return node;
};
Graph.prototype.newEdge = function(source, target, data) {
var edge = new Edge(this.nextEdgeId++, source, target, data);
this.addEdge(edge);
return edge;
};
// find the edges from node1 to node2
Graph.prototype.getEdges = function(node1, node2) {
if (typeof(this.adjacency[node1.id]) !== 'undefined'
&& typeof(this.adjacency[node1.id][node2.id]) !== 'undefined') {
return this.adjacency[node1.id][node2.id];
}
return [];
};
// remove a node and it's associated edges from the graph
Graph.prototype.removeNode = function(node) {
if (typeof(this.nodeSet[node.id]) !== 'undefined') {
delete this.nodeSet[node.id];
}
for (var i = this.nodes.length - 1; i >= 0; i--) {
if (this.nodes[i].id === node.id) {
this.nodes.splice(i, 1);
}
}
this.detachNode(node);
};
// removes edges associated with a given node
Graph.prototype.detachNode = function(node) {
var tmpEdges = this.edges.slice();
tmpEdges.forEach(function(e) {
if (e.source.id === node.id || e.target.id === node.id) {
this.removeEdge(e);
}
}, this);
this.notify();
};
// remove a node and it's associated edges from the graph
Graph.prototype.removeEdge = function(edge) {
for (var i = this.edges.length - 1; i >= 0; i--) {
if (this.edges[i].id === edge.id) {
this.edges.splice(i, 1);
}
}
for (var x in this.adjacency) {
for (var y in this.adjacency[x]) {
var edges = this.adjacency[x][y];
for (var j=edges.length - 1; j>=0; j--) {
if (this.adjacency[x][y][j].id === edge.id) {
this.adjacency[x][y].splice(j, 1);
}
}
}
}
this.notify();
};
/* Merge a list of nodes and edges into the current graph. eg.
var o = {
nodes: [
{id: 123, data: {type: 'user', userid: 123, displayname: 'aaa'}},
{id: 234, data: {type: 'user', userid: 234, displayname: 'bbb'}}
],
edges: [
{from: 0, to: 1, type: 'submitted_design', directed: true, data: {weight: }}
]
}
*/
Graph.prototype.merge = function(data) {
var nodes = [];
data.nodes.forEach(function(n) {
nodes.push(this.addNode(new Node(n.id, n.data)));
}, this);
data.edges.forEach(function(e) {
var from = nodes[e.from];
var to = nodes[e.to];
var id = (e.directed)
? (id = e.type + "-" + from.id + "-" + to.id)
: (from.id < to.id) // normalise id for non-directed edges
? e.type + "-" + from.id + "-" + to.id
: e.type + "-" + to.id + "-" + from.id;
var edge = this.addEdge(new Edge(id, from, to, e.data));
edge.data.type = e.type;
}, this);
};
Graph.prototype.filterNodes = function(fn) {
var tmpNodes = this.nodes.slice();
tmpNodes.forEach(function(n) {
if (!fn(n)) {
this.removeNode(n);
}
}, this);
};
Graph.prototype.filterEdges = function(fn) {
var tmpEdges = this.edges.slice();
tmpEdges.forEach(function(e) {
if (!fn(e)) {
this.removeEdge(e);
}
}, this);
};
Graph.prototype.addGraphListener = function(obj) {
this.eventListeners.push(obj);
};
Graph.prototype.notify = function() {
this.eventListeners.forEach(function(obj){
obj.graphChanged();
});
};
~~~
- 前言
- [Web Chart系列之一]Web端圖形繪制SVG,VML, HTML5 Canvas 技術比較
- [Web Chart系列之二] 各種實現js 圖表的library匯總與比較
- [Web Chart系列之三] 圖形布局-Layout
- [Web Chart系列之四] 圖形布局-Layout 之js設計實現
- [Web Chart系列之一(續)]Web端圖形繪制SVG,VML, HTML5 Canvas 簡單實例
- [Web Chart系列之五] 1. 實戰draw2d 之總體介紹
- [Web Chart系列之五] 2. 實戰draw2d 之Label 放大,縮小的問題(raphael的text類似問題)
- [Web Chart系列之五] 3. 實戰draw2d 之圖形填充色(純色 or 漸變)
- [Web Chart系列之五] 4. 實戰draw2d(Raphael)之取消Chrome中Label Text 全部選中
- [Web Chart系列之六] canvas Chart 導出圖文件
- [Web Chart系列之七] 物理動畫效果(如撕扯效果)
- [Web Chart系列之五] 5. 實戰draw2d之figure tooltip 實現
- Web圖形開發方案選型,SVG/VML/Flash/Applet優劣比較
- [Web Chart系列之五] 6. 實戰draw2d之ConnectionRouter