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();
16 window.addEventListener('keydown', function(event){
17 switch(event.keyCode){
19 that.moveViewRelative(-10, 0);
22 that.moveViewRelative(10, 0);
25 that.moveViewRelative(0, -10);
28 that.moveViewRelative(0, 10);
33 MGCanvas.prototype = {
34 setGraph: function(gArray){
35 //gArray = [[Node1, Node2, Node3, ...], [[Node1, Node3], [Node3, Node2], ...]]
38 var n = function(identifier){ return that.nodeList.isIncluded(identifier, function(a, b){ return (a.identifier == b); }); };
40 for(var i = 0, iLen = p.length; i < iLen; i++){
41 this.nodeList.push(new MGNode(this, p[i]));
45 for(var i = 0, iLen = p.length; i < iLen; i++){
46 this.edgeList.push(new MGEdge(this, p[i][2], n(p[i][0]), n(p[i][1])));
49 bringToCenter: function(){
50 // 重心を求めて、それを表示オフセットに設定する
51 var g = new Point2D(0, 0);
54 for(var i = 0, iLen = p.length; i < iLen; i++){
55 g.x += p[i].position.x;
56 g.y += p[i].position.y;
61 this.positionOffset.x = -g.x;
62 this.positionOffset.y = -g.y;
65 this.context.scale(2, 2);
66 this.currentScale *= 2;
69 this.context.scale(0.5, 0.5);
70 this.currentScale *= 0.5;
72 moveViewRelative: function(x, y){
73 this.positionOffset.x += -x;
74 this.positionOffset.y += -y;
77 bringInScreen: function(){
79 var g = new Point2D(0, 0);
82 for(var i = 0, iLen = p.length; i < iLen; i++){
83 g.x += p[i].position.x;
84 g.y += p[i].position.y;
88 if( g.x < this.displayRect.origin.x / 2 ||
89 g.x > -this.displayRect.origin.x / 2 ||
90 g.y < this.displayRect.origin.y / 2 ||
91 g.y > -this.displayRect.origin.x / 2){
93 this.positionOffset.x = -g.x;
94 this.positionOffset.y = -g.y;
113 for(var i = 0, iLen = p.length; i < iLen; i++){
114 nTemp = this.getVectorLength(p[i].vector);
121 n.ignoreEdgeRepulsion = 10;
129 for(var i = 0, iLen = p.length; i < iLen; i++){
130 this.nodeList[i].tick();
133 for(var i = 0, iLen = p.length; i < iLen; i++){
134 this.edgeList[i].tick();
140 dr = this.displayRect;
142 this.context.scale(1 / this.currentScale, 1 / this.currentScale);
143 this.context.clearRect(dr.origin.x, dr.origin.y, dr.size.x, dr.size.y);
144 this.context.scale(this.currentScale, this.currentScale);
146 this.context.translate(this.positionOffset.x, this.positionOffset.y);
149 for(var i = 0, iLen = p.length; i < iLen; i++){
150 this.nodeList[i].draw();
154 for(var i = 0, iLen = p.length; i < iLen; i++){
155 this.edgeList[i].draw();
158 this.context.translate(-this.positionOffset.x, -this.positionOffset.y);
161 getMousePositionOnElement: function(e){
162 // http://tmlife.net/programming/javascript/javascript-mouse-pos.html
164 var retv = new Object();
165 var rect = e.target.getBoundingClientRect();
166 retv.x = e.clientX - rect.left;
167 retv.y = e.clientY - rect.top;
170 fillRect: function(x, y, w, h){
171 var d = this.drawCoordinatesInInteger;
172 this.context.fillRect(d(x), d(y), d(w), d(h));
174 strokeRect: function(x, y, w, h){
175 var d = this.drawCoordinatesInInteger;
176 this.context.strokeRect(d(x), d(y), d(w), d(h));
178 drawCoordinatesInInteger: function(coordinateElement){
179 //from http://www.html5rocks.com/ja/tutorials/canvas/performance/
180 // With a bitwise or.
181 return ((0.5 + coordinateElement) | 0);
183 drawCircle: function(x, y, r){
184 var d = this.drawCoordinatesInInteger;
185 this.context.beginPath();
186 this.context.arc(d(x), d(y), d(r), 0, 2 * Math.PI);
187 this.context.closePath();
189 this.context.stroke();
191 drawLineP: function(p, q){
192 var d = this.drawCoordinatesInInteger;
193 this.context.beginPath();
194 this.context.moveTo(d(p.x), d(p.y));
195 this.context.lineTo(d(q.x), d(q.y));
196 this.context.closePath();
197 this.context.stroke();
199 drawText: function(text, x, y){
201 //前景をstrokeStyleで塗りつぶした文字列を描画する
204 var textsize = this.context.measureText(text);
205 this.context.fillRect(x, y, textsize.width, 20);
207 this.context.fillStyle = this.context.strokeStyle;
208 //fillText引数の座標は文字列の左下!
209 this.context.fillText(text, x, y + 20 - 1);
210 this.context.restore();
212 getVectorLengthP: function(p, q){
213 return this.getVectorLength(this.getVectorP(p, q));
215 getVectorLength: function(a){
216 return Math.sqrt(a.x * a.x + a.y * a.y);
218 getVectorP: function(p, q){
219 return new Point2D(q.x - p.x, q.y - p.y);
221 getUnitVectorP: function(p, q){
222 var e = this.getVectorP(p, q);
223 return this.getUnitVector(e);
225 getUnitVector: function(a){
226 var l = Math.sqrt(a.x * a.x + a.y * a.y);
231 getNormalUnitVectorSideOfP: function(a, b, p){
232 //直線ab上にない点pが存在する側へ向かう単位法線ベクトルを返す。
233 return this.getUnitVector(this.getNormalVectorSideOfP(a, b, p));
235 getNormalVectorSideOfP: function(a, b, p){
236 //直線ab上にない点pが存在する側へ向かう法線ベクトルを返す。
237 //pがab上にある場合は零ベクトルとなる。
238 var n = this.getVectorP(a, b);
244 i = this.getInnerVector2D(n, this.getVectorP(a, p));
246 //この法線ベクトルとapの向きが逆なので反転する。
255 getExteriorVector2D: function(a, b){
256 return a.x * b.y - a.y * b.x;
258 getInnerVector2D: function(a, b){
259 return a.x * b.x + a.y * b.y;
261 getDistanceDotAndLineP: function(p, a, b){
262 // http://www.sousakuba.com/Programming/gs_dot_line_distance.html
269 ab = this.getVectorP(a, b);
270 ap = this.getVectorP(a, p);
272 s = Math.abs(this.getExteriorVector2D(ab, ap));
273 l = this.getVectorLengthP(a, b);
276 s = this.getInnerVector2D(ap, ab);
278 //線分の範囲外なので端点aからの距離に変換
281 d = Math.sqrt(d * d + l * l);
282 } else if(s > l * l){
285 d = Math.sqrt(d * d + l * l);
289 initGraphicContext: function(newCanvas){
290 this.canvas = newCanvas;
291 this.context = this.canvas.getContext('2d');
292 this.context.fillStyle = "rgba(255,255,255,0.5)";
293 this.context.strokeStyle = "rgba(0, 0, 0, 1)";
294 this.context.font = "normal 20px sans-serif";
295 var w = this.canvas.width / 2;
296 var h = this.canvas.height / 2;
297 this.context.translate(w, h);
298 this.displayRect = new Rectangle(-w, -h, this.canvas.width, this.canvas.height);
299 this.currentScale = 1;
300 this.positionOffset = new Point2D(0, 0);
304 function MGNode(env, identifier){
306 this.identifier = identifier;
307 this.position = new Point2D(0, 0);
310 this.vector = new Point2D(Math.random() * 2 - 1, Math.random() * 2 - 1);
311 this.friction = (100 - 8) / 100;
312 this.repulsionLengthNode = 90;
313 this.repulsionLengthEdge = 90;
314 this.ignoreEdgeRepulsion = 0;
318 this.env.drawCircle(this.position.x, this.position.y, this.size);
319 this.env.drawText(this.identifier.toString(), this.position.x + 10, this.position.y + 10);
326 this.position.x += this.vector.x;
327 this.position.y += this.vector.y;
328 this.vector.x *= this.friction;
329 this.vector.y *= this.friction;
331 if(!this.ignoreEdgeRepulsion){
333 //距離の近い点同士には斥力が働くとする。
334 p = this.env.nodeList;
335 for(var i = 0, iLen = p.length; i < iLen; i++){
336 var q = this.env.nodeList[i];
338 l = this.env.getVectorLengthP(this.position, q.position);
339 if(l < this.repulsionLengthNode && l != 0){
340 e = this.env.getUnitVectorP(q.position, this.position);
341 e.x *= this.repulsionLengthNode / l;
342 e.y *= this.repulsionLengthNode / l;
343 this.vector.x += e.x;
344 this.vector.y += e.y;
349 //自分を端点に含まないエッジとの距離が近い場合、そのエッジから遠ざかろうとする。
350 p = this.env.edgeList;
351 for(var i = 0, iLen = p.length; i < iLen; i++){
352 var q = this.env.edgeList[i];
353 if(q.node0 != this && q.node1 != this){
354 l = this.env.getDistanceDotAndLineP(this.position, q.node0.position, q.node1.position);
355 if(l < this.repulsionLengthEdge && l != 0){
359 e = this.env.getNormalUnitVectorSideOfP(q.node0.position, q.node1.position, this.position);
360 e.x *= this.repulsionLengthEdge / l;
361 e.y *= this.repulsionLengthEdge / l;
362 this.vector.x += e.x;
363 this.vector.y += e.y;
364 q.node0.vector.x -= e.x / 2;
365 q.node0.vector.y -= e.y / 2;
366 q.node1.vector.x -= e.x / 2;
367 q.node1.vector.y -= e.y / 2;
372 this.ignoreEdgeRepulsion--;
377 function MGEdge(env, identifier, node0, node1){
379 this.identifier = identifier;
382 this.freeLength = 250;
386 if(this.node0 && this.node1){
387 this.env.drawLineP(this.node0.position, this.node1.position);
391 var l = this.env.getVectorLengthP(this.node0.position, this.node1.position);
393 if(l > this.freeLength){
394 e = this.env.getUnitVectorP(this.node0.position, this.node1.position);
395 e.x *= l / this.freeLength;
396 e.y *= l / this.freeLength;
397 this.node0.vector.x += e.x;
398 this.node0.vector.y += e.y;
399 this.node1.vector.x -= e.x;
400 this.node1.vector.y -= e.y;
405 function Point2D(x, y){
409 Point2D.prototype = {
413 function Rectangle(x, y, width, height){
414 this.origin = new Point2D(x,y);
415 this.size = new Point2D(width,height);
417 Rectangle.prototype = {