圖形布局-Layout 之js設計實現

來源:互聯網
上載者:User
前言

定位browser 的 chart,   VML,SVG, HTML5 Canvas使用的方式各不一樣。

如果使用現有的js  library (各種實現js 圖表的library匯總與比較) , 調用的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 node2Graph.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 graphGraph.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 nodeGraph.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 graphGraph.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();});};
前言

定位browser 的 chart,   VML,SVG, HTML5 Canvas使用的方式各不一樣。

如果使用現有的js  library (各種實現js 圖表的library匯總與比較) , 調用的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 node2Graph.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 graphGraph.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 nodeGraph.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 graphGraph.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();});};

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.