3 //まず、最も多数のエッジを持つノードを探す。
6 function MGCanvas(canvasDOMObj){
9 this.initGraphicContext(canvasDOMObj);
10 this.tickPerSecond = 30;
12 this.tickTimer = window.setInterval(function(){ that.tick(); }, 1000/this.tickPerSecond);
13 this.nodeList = new Array();
14 this.edgeList = new Array();
17 this.canvas.onmousemove = function (e){
22 var loc = that.getMousePositionOnElement(e);
24 //console.log("x:" + loc.x);
25 //console.log("y:" + loc.y);
28 MGCanvas.prototype = {
29 setGraph: function(gArray){
30 //gArray = [[Node1, Node2, Node3, ...], [[Node1, Node3], [Node3, Node2], ...]]
33 var n = function(identifier){ return that.nodeList.isIncluded(identifier, function(a, b){ return (a.identifier == b); }); };
35 for(var i = 0, iLen = p.length; i < iLen; i++){
36 this.nodeList.push(new MGNode(this, p[i]));
40 for(var i = 0, iLen = p.length; i < iLen; i++){
41 this.edgeList.push(new MGEdge(this, p[i][2], n(p[i][0]), n(p[i][1])));
44 bringToCenter: function(){
45 var g = new Point2D(0, 0);
48 for(var i = 0, iLen = p.length; i < iLen; i++){
49 g.x += p[i].position.x;
50 g.y += p[i].position.y;
54 for(var i = 0, iLen = p.length; i < iLen; i++){
55 p[i].position.x -= g.x;
56 p[i].position.y -= g.y;
59 bringInScreen: function(){
61 var g = new Point2D(0, 0);
64 for(var i = 0, iLen = p.length; i < iLen; i++){
65 g.x += p[i].position.x;
66 g.y += p[i].position.y;
70 if( g.x < this.displayRect.origin.x / 2 ||
71 g.x > -this.displayRect.origin.x / 2 ||
72 g.y < this.displayRect.origin.y / 2 ||
73 g.y > -this.displayRect.origin.x / 2){
74 for(var i = 0, iLen = p.length; i < iLen; i++){
75 p[i].position.x -= g.x;
76 p[i].position.y -= g.y;
89 //console.log(this.tickCount);
90 if(this.tickCount % 30 == 0){
99 for(var i = 0, iLen = p.length; i < iLen; i++){
100 nTemp = this.getVectorLength(p[i].vector);
107 n.ignoreEdgeRepulsion = 10;
115 for(var i = 0, iLen = p.length; i < iLen; i++){
116 this.nodeList[i].tick();
119 for(var i = 0, iLen = p.length; i < iLen; i++){
120 this.edgeList[i].tick();
126 dr = this.displayRect;
127 this.context.clearRect(dr.origin.x, dr.origin.y, dr.size.x, dr.size.y);
129 for(var i = 0, iLen = p.length; i < iLen; i++){
130 this.nodeList[i].draw();
133 for(var i = 0, iLen = p.length; i < iLen; i++){
134 this.edgeList[i].draw();
137 getMousePositionOnElement: function(e){
138 // http://tmlife.net/programming/javascript/javascript-mouse-pos.html
140 var retv = new Object();
141 var rect = e.target.getBoundingClientRect();
142 retv.x = e.clientX - rect.left;
143 retv.y = e.clientY - rect.top;
146 fillRect: function(x, y, w, h){
147 var d = this.drawCoordinatesInInteger;
148 this.context.fillRect(d(x), d(y), d(w), d(h));
150 strokeRect: function(x, y, w, h){
151 var d = this.drawCoordinatesInInteger;
152 this.context.strokeRect(d(x), d(y), d(w), d(h));
154 drawCoordinatesInInteger: function(coordinateElement){
155 //from http://www.html5rocks.com/ja/tutorials/canvas/performance/
156 // With a bitwise or.
157 return ((0.5 + coordinateElement) | 0);
159 drawCircle: function(x, y, r){
160 var d = this.drawCoordinatesInInteger;
161 this.context.beginPath();
162 this.context.arc(d(x), d(y), d(r), 0, 2 * Math.PI);
163 this.context.closePath();
165 this.context.stroke();
167 drawLineP: function(p, q){
168 var d = this.drawCoordinatesInInteger;
169 this.context.beginPath();
170 this.context.moveTo(d(p.x), d(p.y));
171 this.context.lineTo(d(q.x), d(q.y));
172 this.context.closePath();
173 this.context.stroke();
175 drawText: function(text, x, y){
177 //前景をstrokeStyleで塗りつぶした文字列を描画する
180 var textsize = this.context.measureText(text);
181 this.context.fillRect(x, y, textsize.width, 20);
183 this.context.fillStyle = this.context.strokeStyle;
184 //fillText引数の座標は文字列の左下!
185 this.context.fillText(text, x, y + 20 - 1);
186 this.context.restore();
188 getVectorLengthP: function(p, q){
189 return this.getVectorLength(this.getVectorP(p, q));
191 getVectorLength: function(a){
192 return Math.sqrt(a.x * a.x + a.y * a.y);
194 getVectorP: function(p, q){
195 return new Point2D(q.x - p.x, q.y - p.y);
197 getUnitVectorP: function(p, q){
198 var e = this.getVectorP(p, q);
199 return this.getUnitVector(e);
201 getUnitVector: function(a){
202 var l = Math.sqrt(a.x * a.x + a.y * a.y);
207 getNormalUnitVectorSideOfP: function(a, b, p){
208 //直線ab上にない点pが存在する側へ向かう単位法線ベクトルを返す。
209 return this.getUnitVector(this.getNormalVectorSideOfP(a, b, p));
211 getNormalVectorSideOfP: function(a, b, p){
212 //直線ab上にない点pが存在する側へ向かう法線ベクトルを返す。
213 //pがab上にある場合は零ベクトルとなる。
214 var n = this.getVectorP(a, b);
220 i = this.getInnerVector2D(n, this.getVectorP(a, p));
222 //この法線ベクトルとapの向きが逆なので反転する。
231 getExteriorVector2D: function(a, b){
232 return a.x * b.y - a.y * b.x;
234 getInnerVector2D: function(a, b){
235 return a.x * b.x + a.y * b.y;
237 getDistanceDotAndLineP: function(p, a, b){
238 // http://www.sousakuba.com/Programming/gs_dot_line_distance.html
245 ab = this.getVectorP(a, b);
246 ap = this.getVectorP(a, p);
248 s = Math.abs(this.getExteriorVector2D(ab, ap));
249 l = this.getVectorLengthP(a, b);
252 s = this.getInnerVector2D(ap, ab);
254 //線分の範囲外なので端点aからの距離に変換
257 d = Math.sqrt(d * d + l * l);
258 } else if(s > l * l){
261 d = Math.sqrt(d * d + l * l);
265 initGraphicContext: function(newCanvas){
266 this.canvas = newCanvas;
267 this.context = this.canvas.getContext('2d');
268 this.context.fillStyle = "rgba(255,255,255,0.5)";
269 this.context.strokeStyle = "rgba(0, 0, 0, 1)";
270 this.context.font = "normal 20px sans-serif";
271 var w = this.canvas.width / 2;
272 var h = this.canvas.height / 2;
273 this.context.translate(w, h);
274 this.displayRect = new Rectangle(-w, -h, this.canvas.width, this.canvas.height);
278 function MGNode(env, identifier){
280 this.identifier = identifier;
281 this.position = new Point2D(0, 0);
284 this.vector = new Point2D(Math.random() * 2 - 1, Math.random() * 2 - 1);
285 this.friction = (100 - 8) / 100;
286 this.repulsionLengthNode = 90;
287 this.repulsionLengthEdge = 90;
288 this.ignoreEdgeRepulsion = 0;
292 this.env.drawCircle(this.position.x, this.position.y, this.size);
293 this.env.drawText(this.identifier.toString(), this.position.x + 10, this.position.y + 10);
300 this.position.x += this.vector.x;
301 this.position.y += this.vector.y;
302 this.vector.x *= this.friction;
303 this.vector.y *= this.friction;
305 if(!this.ignoreEdgeRepulsion){
307 //距離の近い点同士には斥力が働くとする。
308 p = this.env.nodeList;
309 for(var i = 0, iLen = p.length; i < iLen; i++){
310 var q = this.env.nodeList[i];
312 l = this.env.getVectorLengthP(this.position, q.position);
313 if(l < this.repulsionLengthNode && l != 0){
314 e = this.env.getUnitVectorP(q.position, this.position);
315 e.x *= this.repulsionLengthNode / l;
316 e.y *= this.repulsionLengthNode / l;
317 this.vector.x += e.x;
318 this.vector.y += e.y;
323 //自分を端点に含まないエッジとの距離が近い場合、そのエッジから遠ざかろうとする。
324 p = this.env.edgeList;
325 for(var i = 0, iLen = p.length; i < iLen; i++){
326 var q = this.env.edgeList[i];
327 if(q.node0 != this && q.node1 != this){
328 l = this.env.getDistanceDotAndLineP(this.position, q.node0.position, q.node1.position);
329 if(l < this.repulsionLengthEdge && l != 0){
333 e = this.env.getNormalUnitVectorSideOfP(q.node0.position, q.node1.position, this.position);
334 e.x *= this.repulsionLengthEdge / l;
335 e.y *= this.repulsionLengthEdge / l;
336 this.vector.x += e.x;
337 this.vector.y += e.y;
338 q.node0.vector.x -= e.x / 2;
339 q.node0.vector.y -= e.y / 2;
340 q.node1.vector.x -= e.x / 2;
341 q.node1.vector.y -= e.y / 2;
346 this.ignoreEdgeRepulsion--;
351 function MGEdge(env, identifier, node0, node1){
353 this.identifier = identifier;
356 this.freeLength = 250;
360 if(this.node0 && this.node1){
361 this.env.drawLineP(this.node0.position, this.node1.position);
365 var l = this.env.getVectorLengthP(this.node0.position, this.node1.position);
367 if(l > this.freeLength){
368 e = this.env.getUnitVectorP(this.node0.position, this.node1.position);
369 e.x *= l / this.freeLength;
370 e.y *= l / this.freeLength;
371 this.node0.vector.x += e.x;
372 this.node0.vector.y += e.y;
373 this.node1.vector.x -= e.x;
374 this.node1.vector.y -= e.y;
379 function Point2D(x, y){
383 Point2D.prototype = {
387 function Rectangle(x, y, width, height){
388 this.origin = new Point2D(x,y);
389 this.size = new Point2D(width,height);
391 Rectangle.prototype = {