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.isFrozen = false;
14 this.nodeList = new Array();
15 this.edgeList = new Array();
16 this.selectedEdge = null;
19 window.addEventListener('keydown', function(event){
20 switch(event.keyCode){
22 that.moveViewRelative(-10, 0);
25 that.moveViewRelative(10, 0);
28 that.moveViewRelative(0, -10);
31 that.moveViewRelative(0, 10);
36 this.isMouseDown = false;
37 this.mouseDownPosition = new Point2D(0, 0);
38 this.lastMousePosition = new Point2D(0, 0);
39 this.canvas.onmousemove = function (e){
45 that.lastMousePosition = that.getMousePositionOnElement(e);
48 this.canvas.onmousedown = function (e){
53 that.lastMousePosition = that.getMousePositionOnElement(e);
54 that.mouseDownPosition = that.lastMousePosition;
55 var p = that.convertPointToGraphLayerFromCanvasLayerP(that.lastMousePosition);
56 //console.log(p.x + "," + p.y);
57 var node = that.getNodeAtPointP(p);
58 that.selectNode(node);
59 that.isMouseDown = true;
61 this.canvas.onmouseup = function (e){
62 that.isMouseDown = false;
65 MGCanvas.prototype = {
66 setGraph: function(gArray){
67 //gArray = [[Node1, Node2, Node3, ...], [[Node1, Node3], [Node3, Node2], ...]]
70 var n = function(identifier){ return that.nodeList.isIncluded(identifier, function(a, b){ return (a.identifier == b); }); };
72 for(var i = 0, iLen = p.length; i < iLen; i++){
73 this.nodeList.push(new MGNode(this, p[i]));
77 for(var i = 0, iLen = p.length; i < iLen; i++){
78 this.edgeList.push(new MGEdge(this, p[i][2], n(p[i][0]), n(p[i][1])));
81 bringToCenter: function(){
82 // 重心を求めて、それを表示オフセットに設定する
83 var g = new Point2D(0, 0);
86 for(var i = 0, iLen = p.length; i < iLen; i++){
87 g.x += p[i].position.x;
88 g.y += p[i].position.y;
93 this.positionOffset.x = -g.x;
94 this.positionOffset.y = -g.y;
97 this.context.scale(2, 2);
98 this.currentScale *= 2;
101 this.context.scale(0.5, 0.5);
102 this.currentScale *= 0.5;
104 moveViewRelative: function(x, y){
105 this.positionOffset.x += -x;
106 this.positionOffset.y += -y;
109 bringInScreen: function(){
111 var g = new Point2D(0, 0);
114 for(var i = 0, iLen = p.length; i < iLen; i++){
115 g.x += p[i].position.x;
116 g.y += p[i].position.y;
120 if( g.x < this.displayRect.origin.x / 2 ||
121 g.x > -this.displayRect.origin.x / 2 ||
122 g.y < this.displayRect.origin.y / 2 ||
123 g.y > -this.displayRect.origin.x / 2){
125 this.positionOffset.x = -g.x;
126 this.positionOffset.y = -g.y;
141 // View moving with mouse
143 if(this.isMouseDown){
144 this.moveViewRelative(
145 (this.mouseDownPosition.x - this.lastMousePosition.x) * 4 / this.tickPerSecond,
146 (this.mouseDownPosition.y - this.lastMousePosition.y) * 4 / this.tickPerSecond
156 for(var i = 0, iLen = p.length; i < iLen; i++){
157 nTemp = this.getVectorLength(p[i].vector);
164 n.ignoreEdgeRepulsion = 10;
172 for(var i = 0, iLen = p.length; i < iLen; i++){
173 this.nodeList[i].tick();
176 for(var i = 0, iLen = p.length; i < iLen; i++){
177 this.edgeList[i].tick();
184 dr = this.displayRect;
186 this.context.scale(1 / this.currentScale, 1 / this.currentScale);
187 this.context.clearRect(dr.origin.x, dr.origin.y, dr.size.x, dr.size.y);
188 this.context.scale(this.currentScale, this.currentScale);
190 this.context.translate(this.positionOffset.x, this.positionOffset.y);
193 for(var i = 0, iLen = p.length; i < iLen; i++){
194 this.nodeList[i].draw();
198 for(var i = 0, iLen = p.length; i < iLen; i++){
199 this.edgeList[i].draw();
202 this.context.translate(-this.positionOffset.x, -this.positionOffset.y);
205 getMousePositionOnElement: function(e){
206 // http://tmlife.net/programming/javascript/javascript-mouse-pos.html
208 var retv = new Object();
209 var rect = e.target.getBoundingClientRect();
210 retv.x = e.clientX - rect.left;
211 retv.y = e.clientY - rect.top;
214 fillRect: function(x, y, w, h){
215 var d = this.drawCoordinatesInInteger;
216 this.context.fillRect(d(x), d(y), d(w), d(h));
218 strokeRect: function(x, y, w, h){
219 var d = this.drawCoordinatesInInteger;
220 this.context.strokeRect(d(x), d(y), d(w), d(h));
222 drawCoordinatesInInteger: function(coordinateElement){
223 //from http://www.html5rocks.com/ja/tutorials/canvas/performance/
224 // With a bitwise or.
225 return ((0.5 + coordinateElement) | 0);
227 drawCircle: function(x, y, r){
228 var d = this.drawCoordinatesInInteger;
229 this.context.beginPath();
230 this.context.arc(d(x), d(y), d(r), 0, 2 * Math.PI);
231 this.context.closePath();
233 this.context.stroke();
235 drawLineP: function(p, q){
236 var d = this.drawCoordinatesInInteger;
237 this.context.beginPath();
238 this.context.moveTo(d(p.x), d(p.y));
239 this.context.lineTo(d(q.x), d(q.y));
240 this.context.closePath();
241 this.context.stroke();
243 drawText: function(text, x, y){
245 //前景をstrokeStyleで塗りつぶした文字列を描画する
248 var textsize = this.context.measureText(text);
249 this.context.fillRect(x, y, textsize.width, 20);
251 this.context.fillStyle = this.context.strokeStyle;
252 //fillText引数の座標は文字列の左下!
253 this.context.fillText(text, x, y + 20 - 1);
254 this.context.restore();
256 getVectorLengthP: function(p, q){
257 return this.getVectorLength(this.getVectorP(p, q));
259 getVectorLength: function(a){
260 return Math.sqrt(a.x * a.x + a.y * a.y);
262 getVectorP: function(p, q){
263 return new Point2D(q.x - p.x, q.y - p.y);
265 getUnitVectorP: function(p, q){
266 var e = this.getVectorP(p, q);
267 return this.getUnitVector(e);
269 getUnitVector: function(a){
270 var l = Math.sqrt(a.x * a.x + a.y * a.y);
275 getNormalUnitVectorSideOfP: function(a, b, p){
276 //直線ab上にない点pが存在する側へ向かう単位法線ベクトルを返す。
277 return this.getUnitVector(this.getNormalVectorSideOfP(a, b, p));
279 getNormalVectorSideOfP: function(a, b, p){
280 //直線ab上にない点pが存在する側へ向かう法線ベクトルを返す。
281 //pがab上にある場合は零ベクトルとなる。
282 var n = this.getVectorP(a, b);
288 i = this.getInnerVector2D(n, this.getVectorP(a, p));
290 //この法線ベクトルとapの向きが逆なので反転する。
299 getExteriorVector2D: function(a, b){
300 return a.x * b.y - a.y * b.x;
302 getInnerVector2D: function(a, b){
303 return a.x * b.x + a.y * b.y;
305 getDistanceDotAndLineP: function(p, a, b){
306 // http://www.sousakuba.com/Programming/gs_dot_line_distance.html
313 ab = this.getVectorP(a, b);
314 ap = this.getVectorP(a, p);
316 s = Math.abs(this.getExteriorVector2D(ab, ap));
317 l = this.getVectorLengthP(a, b);
320 s = this.getInnerVector2D(ap, ab);
322 //線分の範囲外なので端点aからの距離に変換
325 d = Math.sqrt(d * d + l * l);
326 } else if(s > l * l){
329 d = Math.sqrt(d * d + l * l);
333 initGraphicContext: function(newCanvas){
334 this.canvas = newCanvas;
335 this.context = this.canvas.getContext('2d');
336 this.context.fillStyle = "rgba(255,255,255,0.5)";
337 this.context.strokeStyle = "rgba(0, 0, 0, 1)";
338 this.context.font = "normal 20px sans-serif";
339 var w = this.canvas.width / 2;
340 var h = this.canvas.height / 2;
341 this.context.translate(w, h);
342 this.displayRect = new Rectangle(-w, -h, this.canvas.width, this.canvas.height);
343 this.currentScale = 1;
344 this.positionOffset = new Point2D(0, 0);
346 convertPointToGraphLayerFromCanvasLayerP: function(pCanvas){
347 var p = new Point2D(pCanvas.x, pCanvas.y);
349 p.x -= this.canvas.width / 2;
350 p.y -= this.canvas.height / 2;
352 p.x /= this.currentScale;
353 p.y /= this.currentScale;
356 p.x -= this.positionOffset.x;
357 p.y -= this.positionOffset.y;
361 getNodeAtPointP: function(p){
362 var r = new Rectangle(p.x - 10, p.y - 10, 20, 20);
364 var nl = this.nodeList;
365 for(var i = 0, iLen = nl.length; i < iLen; i++){
366 if(r.isIncludePointP(nl[i].position)){
372 selectNode: function(node){
373 if(this.selectedNode){
374 this.selectedNode.isSelected = false;
377 node.isSelected = true;
379 this.selectedNode = node;
381 setIdentifierForSelectedNode: function(str){
382 if(this.selectedNode){
383 this.selectedNode.identifier = str;
388 function MGNode(env, identifier){
390 this.identifier = identifier;
391 this.position = new Point2D(0, 0);
394 this.vector = new Point2D(Math.random() * 2 - 1, Math.random() * 2 - 1);
395 this.friction = (100 - 8) / 100;
396 this.repulsionLengthNode = 90;
397 this.repulsionLengthEdge = 90;
398 this.ignoreEdgeRepulsion = 0;
400 this.strokeStyle = "rgba(0, 0, 0, 1)";
401 this.isSelected = false;
406 this.env.context.strokeStyle = "rgba(255, 0, 0, 1)";
408 this.env.context.strokeStyle = this.strokeStyle;
410 this.env.drawCircle(this.position.x, this.position.y, this.size);
411 this.env.drawText(this.identifier.toString(), this.position.x + 10, this.position.y + 10);
418 this.position.x += this.vector.x;
419 this.position.y += this.vector.y;
420 this.vector.x *= this.friction;
421 this.vector.y *= this.friction;
423 if(!this.ignoreEdgeRepulsion){
425 //距離の近い点同士には斥力が働くとする。
426 p = this.env.nodeList;
427 for(var i = 0, iLen = p.length; i < iLen; i++){
428 var q = this.env.nodeList[i];
430 l = this.env.getVectorLengthP(this.position, q.position);
431 if(l < this.repulsionLengthNode && l != 0){
432 e = this.env.getUnitVectorP(q.position, this.position);
433 e.x *= this.repulsionLengthNode / l;
434 e.y *= this.repulsionLengthNode / l;
435 this.vector.x += e.x;
436 this.vector.y += e.y;
441 //自分を端点に含まないエッジとの距離が近い場合、そのエッジから遠ざかろうとする。
442 p = this.env.edgeList;
443 for(var i = 0, iLen = p.length; i < iLen; i++){
444 var q = this.env.edgeList[i];
445 if(q.node0 != this && q.node1 != this){
446 l = this.env.getDistanceDotAndLineP(this.position, q.node0.position, q.node1.position);
447 if(l < this.repulsionLengthEdge && l != 0){
451 e = this.env.getNormalUnitVectorSideOfP(q.node0.position, q.node1.position, this.position);
452 e.x *= this.repulsionLengthEdge / l;
453 e.y *= this.repulsionLengthEdge / l;
454 this.vector.x += e.x;
455 this.vector.y += e.y;
456 q.node0.vector.x -= e.x / 2;
457 q.node0.vector.y -= e.y / 2;
458 q.node1.vector.x -= e.x / 2;
459 q.node1.vector.y -= e.y / 2;
464 this.ignoreEdgeRepulsion--;
469 function MGEdge(env, identifier, node0, node1){
471 this.identifier = identifier;
474 this.freeLength = 250;
476 this.strokeStyle = "rgba(0, 0, 0, 1)";
477 this.isSelected = false;
482 this.env.context.strokeStyle = "rgba(255, 0, 0, 1)";
484 this.env.context.strokeStyle = this.strokeStyle;
486 if(this.node0 && this.node1){
487 this.env.drawLineP(this.node0.position, this.node1.position);
491 var l = this.env.getVectorLengthP(this.node0.position, this.node1.position);
493 if(l > this.freeLength){
494 e = this.env.getUnitVectorP(this.node0.position, this.node1.position);
495 e.x *= l / this.freeLength;
496 e.y *= l / this.freeLength;
497 this.node0.vector.x += e.x;
498 this.node0.vector.y += e.y;
499 this.node1.vector.x -= e.x;
500 this.node1.vector.y -= e.y;
505 function Point2D(x, y){
509 Point2D.prototype = {
513 function Rectangle(x, y, width, height){
514 this.origin = new Point2D(x,y);
515 this.size = new Point2D(width,height);
517 Rectangle.prototype = {
518 isIncludePointP: function(p){
519 return (this.origin.x <= p.x) && (p.x <= this.origin.x + this.size.x) &&
520 (this.origin.y <= p.y) && (p.y <= this.origin.y + this.size.y);