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 window.addEventListener('keydown', function(event){
18 switch(event.keyCode){
20 that.moveViewRelative(-10, 0);
23 that.moveViewRelative(10, 0);
26 that.moveViewRelative(0, -10);
29 that.moveViewRelative(0, 10);
34 this.isMouseDown = false;
35 this.mouseDownPosition = new Point2D(0, 0);
36 this.lastMousePosition = new Point2D(0, 0);
37 this.canvas.onmousemove = function (e){
43 that.lastMousePosition = that.getMousePositionOnElement(e);
46 this.canvas.onmousedown = function (e){
51 that.lastMousePosition = that.getMousePositionOnElement(e);
52 that.mouseDownPosition = that.lastMousePosition;
53 var p = that.convertPointToGraphLayerFromCanvasLayerP(that.lastMousePosition);
54 console.log(p.x + "," + p.y);
55 var node = that.getNodeAtPointP(p);
57 node.isSelected = true;
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
155 for(var i = 0, iLen = p.length; i < iLen; i++){
156 nTemp = this.getVectorLength(p[i].vector);
163 n.ignoreEdgeRepulsion = 10;
171 for(var i = 0, iLen = p.length; i < iLen; i++){
172 this.nodeList[i].tick();
175 for(var i = 0, iLen = p.length; i < iLen; i++){
176 this.edgeList[i].tick();
182 dr = this.displayRect;
184 this.context.scale(1 / this.currentScale, 1 / this.currentScale);
185 this.context.clearRect(dr.origin.x, dr.origin.y, dr.size.x, dr.size.y);
186 this.context.scale(this.currentScale, this.currentScale);
188 this.context.translate(this.positionOffset.x, this.positionOffset.y);
191 for(var i = 0, iLen = p.length; i < iLen; i++){
192 this.nodeList[i].draw();
196 for(var i = 0, iLen = p.length; i < iLen; i++){
197 this.edgeList[i].draw();
200 this.context.translate(-this.positionOffset.x, -this.positionOffset.y);
203 getMousePositionOnElement: function(e){
204 // http://tmlife.net/programming/javascript/javascript-mouse-pos.html
206 var retv = new Object();
207 var rect = e.target.getBoundingClientRect();
208 retv.x = e.clientX - rect.left;
209 retv.y = e.clientY - rect.top;
212 fillRect: function(x, y, w, h){
213 var d = this.drawCoordinatesInInteger;
214 this.context.fillRect(d(x), d(y), d(w), d(h));
216 strokeRect: function(x, y, w, h){
217 var d = this.drawCoordinatesInInteger;
218 this.context.strokeRect(d(x), d(y), d(w), d(h));
220 drawCoordinatesInInteger: function(coordinateElement){
221 //from http://www.html5rocks.com/ja/tutorials/canvas/performance/
222 // With a bitwise or.
223 return ((0.5 + coordinateElement) | 0);
225 drawCircle: function(x, y, r){
226 var d = this.drawCoordinatesInInteger;
227 this.context.beginPath();
228 this.context.arc(d(x), d(y), d(r), 0, 2 * Math.PI);
229 this.context.closePath();
231 this.context.stroke();
233 drawLineP: function(p, q){
234 var d = this.drawCoordinatesInInteger;
235 this.context.beginPath();
236 this.context.moveTo(d(p.x), d(p.y));
237 this.context.lineTo(d(q.x), d(q.y));
238 this.context.closePath();
239 this.context.stroke();
241 drawText: function(text, x, y){
243 //前景をstrokeStyleで塗りつぶした文字列を描画する
246 var textsize = this.context.measureText(text);
247 this.context.fillRect(x, y, textsize.width, 20);
249 this.context.fillStyle = this.context.strokeStyle;
250 //fillText引数の座標は文字列の左下!
251 this.context.fillText(text, x, y + 20 - 1);
252 this.context.restore();
254 getVectorLengthP: function(p, q){
255 return this.getVectorLength(this.getVectorP(p, q));
257 getVectorLength: function(a){
258 return Math.sqrt(a.x * a.x + a.y * a.y);
260 getVectorP: function(p, q){
261 return new Point2D(q.x - p.x, q.y - p.y);
263 getUnitVectorP: function(p, q){
264 var e = this.getVectorP(p, q);
265 return this.getUnitVector(e);
267 getUnitVector: function(a){
268 var l = Math.sqrt(a.x * a.x + a.y * a.y);
273 getNormalUnitVectorSideOfP: function(a, b, p){
274 //直線ab上にない点pが存在する側へ向かう単位法線ベクトルを返す。
275 return this.getUnitVector(this.getNormalVectorSideOfP(a, b, p));
277 getNormalVectorSideOfP: function(a, b, p){
278 //直線ab上にない点pが存在する側へ向かう法線ベクトルを返す。
279 //pがab上にある場合は零ベクトルとなる。
280 var n = this.getVectorP(a, b);
286 i = this.getInnerVector2D(n, this.getVectorP(a, p));
288 //この法線ベクトルとapの向きが逆なので反転する。
297 getExteriorVector2D: function(a, b){
298 return a.x * b.y - a.y * b.x;
300 getInnerVector2D: function(a, b){
301 return a.x * b.x + a.y * b.y;
303 getDistanceDotAndLineP: function(p, a, b){
304 // http://www.sousakuba.com/Programming/gs_dot_line_distance.html
311 ab = this.getVectorP(a, b);
312 ap = this.getVectorP(a, p);
314 s = Math.abs(this.getExteriorVector2D(ab, ap));
315 l = this.getVectorLengthP(a, b);
318 s = this.getInnerVector2D(ap, ab);
320 //線分の範囲外なので端点aからの距離に変換
323 d = Math.sqrt(d * d + l * l);
324 } else if(s > l * l){
327 d = Math.sqrt(d * d + l * l);
331 initGraphicContext: function(newCanvas){
332 this.canvas = newCanvas;
333 this.context = this.canvas.getContext('2d');
334 this.context.fillStyle = "rgba(255,255,255,0.5)";
335 this.context.strokeStyle = "rgba(0, 0, 0, 1)";
336 this.context.font = "normal 20px sans-serif";
337 var w = this.canvas.width / 2;
338 var h = this.canvas.height / 2;
339 this.context.translate(w, h);
340 this.displayRect = new Rectangle(-w, -h, this.canvas.width, this.canvas.height);
341 this.currentScale = 1;
342 this.positionOffset = new Point2D(0, 0);
344 convertPointToGraphLayerFromCanvasLayerP: function(pCanvas){
345 var p = new Point2D(pCanvas.x, pCanvas.y);
347 p.x -= this.canvas.width / 2;
348 p.y -= this.canvas.height / 2;
350 p.x /= this.currentScale;
351 p.y /= this.currentScale;
354 p.x -= this.positionOffset.x;
355 p.y -= this.positionOffset.y;
359 getNodeAtPointP: function(p){
360 var r = new Rectangle(p.x - 10, p.y - 10, 20, 20);
362 var nl = this.nodeList;
363 for(var i = 0, iLen = nl.length; i < iLen; i++){
364 if(r.isIncludePointP(nl[i].position)){
372 function MGNode(env, identifier){
374 this.identifier = identifier;
375 this.position = new Point2D(0, 0);
378 this.vector = new Point2D(Math.random() * 2 - 1, Math.random() * 2 - 1);
379 this.friction = (100 - 8) / 100;
380 this.repulsionLengthNode = 90;
381 this.repulsionLengthEdge = 90;
382 this.ignoreEdgeRepulsion = 0;
383 this.strokeStyle = "rgba(0, 0, 0, 1)";
384 this.isSelected = false;
389 this.env.context.strokeStyle = "rgba(255, 0, 0, 1)";
391 this.env.context.strokeStyle = this.strokeStyle;
393 this.env.drawCircle(this.position.x, this.position.y, this.size);
394 this.env.drawText(this.identifier.toString(), this.position.x + 10, this.position.y + 10);
401 this.position.x += this.vector.x;
402 this.position.y += this.vector.y;
403 this.vector.x *= this.friction;
404 this.vector.y *= this.friction;
406 if(!this.ignoreEdgeRepulsion){
408 //距離の近い点同士には斥力が働くとする。
409 p = this.env.nodeList;
410 for(var i = 0, iLen = p.length; i < iLen; i++){
411 var q = this.env.nodeList[i];
413 l = this.env.getVectorLengthP(this.position, q.position);
414 if(l < this.repulsionLengthNode && l != 0){
415 e = this.env.getUnitVectorP(q.position, this.position);
416 e.x *= this.repulsionLengthNode / l;
417 e.y *= this.repulsionLengthNode / l;
418 this.vector.x += e.x;
419 this.vector.y += e.y;
424 //自分を端点に含まないエッジとの距離が近い場合、そのエッジから遠ざかろうとする。
425 p = this.env.edgeList;
426 for(var i = 0, iLen = p.length; i < iLen; i++){
427 var q = this.env.edgeList[i];
428 if(q.node0 != this && q.node1 != this){
429 l = this.env.getDistanceDotAndLineP(this.position, q.node0.position, q.node1.position);
430 if(l < this.repulsionLengthEdge && l != 0){
434 e = this.env.getNormalUnitVectorSideOfP(q.node0.position, q.node1.position, this.position);
435 e.x *= this.repulsionLengthEdge / l;
436 e.y *= this.repulsionLengthEdge / l;
437 this.vector.x += e.x;
438 this.vector.y += e.y;
439 q.node0.vector.x -= e.x / 2;
440 q.node0.vector.y -= e.y / 2;
441 q.node1.vector.x -= e.x / 2;
442 q.node1.vector.y -= e.y / 2;
447 this.ignoreEdgeRepulsion--;
452 function MGEdge(env, identifier, node0, node1){
454 this.identifier = identifier;
457 this.freeLength = 250;
461 if(this.node0 && this.node1){
462 this.env.drawLineP(this.node0.position, this.node1.position);
466 var l = this.env.getVectorLengthP(this.node0.position, this.node1.position);
468 if(l > this.freeLength){
469 e = this.env.getUnitVectorP(this.node0.position, this.node1.position);
470 e.x *= l / this.freeLength;
471 e.y *= l / this.freeLength;
472 this.node0.vector.x += e.x;
473 this.node0.vector.y += e.y;
474 this.node1.vector.x -= e.x;
475 this.node1.vector.y -= e.y;
480 function Point2D(x, y){
484 Point2D.prototype = {
488 function Rectangle(x, y, width, height){
489 this.origin = new Point2D(x,y);
490 this.size = new Point2D(width,height);
492 Rectangle.prototype = {
493 isIncludePointP: function(p){
494 return (this.origin.x <= p.x) && (p.x <= this.origin.x + this.size.x) &&
495 (this.origin.y <= p.y) && (p.y <= this.origin.y + this.size.y);