OSDN Git Service

MGCanvasの表示画面移動を実装
[chnosproject/AI004.git] / mgcanvas / mgcanvas.js
1 //文字列をキーとする。
2
3 //まず、最も多数のエッジを持つノードを探す。
4 //それを起点にして、エッジをたどる。
5
6 function MGCanvas(canvasDOMObj){
7         var that = this;
8
9         this.initGraphicContext(canvasDOMObj);
10         this.tickPerSecond = 30;
11         this.tickCount = 0;
12         this.tickTimer = window.setInterval(function(){ that.tick(); }, 1000 / this.tickPerSecond);
13         this.nodeList = new Array();
14         this.edgeList = new Array();
15         var that = this;
16         window.addEventListener('keydown', function(event){
17                 switch(event.keyCode){
18                         case 37:        //左カーソル
19                                 that.moveViewRelative(-10, 0);
20                                 break;
21                         case 39:        //右カーソル
22                                 that.moveViewRelative(10, 0);
23                                 break;
24                         case 38:        //上カーソル
25                                 that.moveViewRelative(0, -10);
26                                 break;
27                         case 40:        //下カーソル
28                                 that.moveViewRelative(0, 10);
29                                 break;
30                 }
31         }, true);
32 }
33 MGCanvas.prototype = {
34         setGraph: function(gArray){
35                 //gArray = [[Node1, Node2, Node3, ...], [[Node1, Node3], [Node3, Node2], ...]]
36                 var that = this;
37                 var p = gArray[0];
38                 var n = function(identifier){ return that.nodeList.isIncluded(identifier, function(a, b){ return (a.identifier == b); }); };
39                 
40                 for(var i = 0, iLen = p.length; i < iLen; i++){
41                         this.nodeList.push(new MGNode(this, p[i]));
42                 }
43                 
44                 p = gArray[1];
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])));
47                 }
48         },
49         bringToCenter: function(){
50                 // 重心を求めて、それを表示オフセットに設定する
51                 var g = new Point2D(0, 0);
52                 var p;
53                 p = this.nodeList;
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;
57                 }
58                 g.x /= p.length;
59                 g.y /= p.length;
60                 
61                 this.positionOffset.x = -g.x;
62                 this.positionOffset.y = -g.y;
63         },
64         zoomIn: function(){
65                 this.context.scale(2, 2);
66                 this.currentScale *= 2;
67         },
68         zoomOut: function(){
69                 this.context.scale(0.5, 0.5);
70                 this.currentScale *= 0.5;
71         },
72         moveViewRelative: function(x, y){
73                 this.positionOffset.x += -x;
74                 this.positionOffset.y += -y;
75         },
76         /*
77         bringInScreen: function(){
78                 //大きく外れていないときには動かさない
79                 var g = new Point2D(0, 0);
80                 var p;
81                 p = this.nodeList;
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;
85                 }
86                 g.x /= p.length;
87                 g.y /= p.length;
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){
92                         
93                         this.positionOffset.x = -g.x;
94                         this.positionOffset.y = -g.y;
95                 }
96         },
97         */
98         tick: function(){
99                 var p;
100                 var t;
101                 var dr;
102                 var n = null;
103                 var nMax = 0;
104                 var nTemp;
105                 
106                 this.tickCount++;
107                 
108                 //
109                 // Check
110                 //
111                 
112                 p = this.nodeList;
113                 for(var i = 0, iLen = p.length; i < iLen; i++){
114                         nTemp = this.getVectorLength(p[i].vector);
115                         if(nMax < nTemp){
116                                 n = p[i];
117                                 nMax = nTemp;
118                         }
119                 }
120                 if(n){
121                         n.ignoreEdgeRepulsion = 10;
122                 }
123                 
124                 
125                 //
126                 // Move
127                 //
128                 p = this.nodeList;
129                 for(var i = 0, iLen = p.length; i < iLen; i++){
130                         this.nodeList[i].tick();
131                 }
132                 p = this.edgeList;
133                 for(var i = 0, iLen = p.length; i < iLen; i++){
134                         this.edgeList[i].tick();
135                 }
136                 
137                 //
138                 // Refresh
139                 //
140                 dr = this.displayRect;
141                 
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);
145                 
146                 this.context.translate(this.positionOffset.x, this.positionOffset.y);
147                 
148                 p = this.nodeList;
149                 for(var i = 0, iLen = p.length; i < iLen; i++){
150                         this.nodeList[i].draw();
151                 }
152                 
153                 p = this.edgeList;
154                 for(var i = 0, iLen = p.length; i < iLen; i++){
155                         this.edgeList[i].draw();
156                 }
157                 
158                 this.context.translate(-this.positionOffset.x, -this.positionOffset.y);
159                 
160         },
161         getMousePositionOnElement: function(e){
162                 // http://tmlife.net/programming/javascript/javascript-mouse-pos.html
163                 // retv:
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;
168                 return retv;
169         },
170         fillRect: function(x, y, w, h){
171                 var d = this.drawCoordinatesInInteger;
172                 this.context.fillRect(d(x), d(y), d(w), d(h));
173         },
174         strokeRect: function(x, y, w, h){
175                 var d = this.drawCoordinatesInInteger;
176                 this.context.strokeRect(d(x), d(y), d(w), d(h));
177         },
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);
182         },
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();
188                 this.context.fill();
189                 this.context.stroke();
190         },
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();
198         },
199         drawText: function(text, x, y){
200                 //背景をfillStyle
201                 //前景をstrokeStyleで塗りつぶした文字列を描画する
202                 //塗りつぶし高さは20px固定
203                 //座標は文字列の左上の座標となる
204                 var textsize = this.context.measureText(text);
205                 this.context.fillRect(x, y, textsize.width, 20);
206                 this.context.save();
207                 this.context.fillStyle = this.context.strokeStyle;
208                 //fillText引数の座標は文字列の左下!
209                 this.context.fillText(text, x, y + 20 - 1);
210                 this.context.restore();
211         },
212         getVectorLengthP: function(p, q){
213                 return this.getVectorLength(this.getVectorP(p, q));
214         },
215         getVectorLength: function(a){
216                 return Math.sqrt(a.x * a.x + a.y * a.y);
217         },
218         getVectorP: function(p, q){
219                 return new Point2D(q.x - p.x, q.y - p.y);
220         },
221         getUnitVectorP: function(p, q){
222                 var e = this.getVectorP(p, q);
223                 return this.getUnitVector(e);
224         },
225         getUnitVector: function(a){
226                 var l = Math.sqrt(a.x * a.x + a.y * a.y);
227                 a.x /= l;
228                 a.y /= l;
229                 return a;
230         },
231         getNormalUnitVectorSideOfP: function(a, b, p){
232                 //直線ab上にない点pが存在する側へ向かう単位法線ベクトルを返す。
233                 return this.getUnitVector(this.getNormalVectorSideOfP(a, b, p));
234         },
235         getNormalVectorSideOfP: function(a, b, p){
236                 //直線ab上にない点pが存在する側へ向かう法線ベクトルを返す。
237                 //pがab上にある場合は零ベクトルとなる。
238                 var n = this.getVectorP(a, b);
239                 var t = n.x;
240                 var i;
241                 n.x = -n.y;
242                 n.y = t;
243                 
244                 i = this.getInnerVector2D(n, this.getVectorP(a, p));
245                 if(i < 0){
246                         //この法線ベクトルとapの向きが逆なので反転する。
247                         n.x = -n.x;
248                         n.y = -n.y;
249                 } else if(i == 0){
250                         n.x = 0;
251                         n.y = 0;
252                 }
253                 return n;
254         },
255         getExteriorVector2D: function(a, b){
256                 return a.x * b.y - a.y * b.x;
257         },
258         getInnerVector2D: function(a, b){
259                 return a.x * b.x + a.y * b.y;
260         },
261         getDistanceDotAndLineP: function(p, a, b){
262                 // http://www.sousakuba.com/Programming/gs_dot_line_distance.html
263                 var ab;
264                 var ap;
265                 var s;
266                 var l;
267                 var d;
268                 
269                 ab = this.getVectorP(a, b);
270                 ap = this.getVectorP(a, p);
271                 
272                 s = Math.abs(this.getExteriorVector2D(ab, ap));
273                 l = this.getVectorLengthP(a, b);
274                 d = (s / l);
275                 
276                 s = this.getInnerVector2D(ap, ab);
277                 if(s < 0){
278                         //線分の範囲外なので端点aからの距離に変換
279                         //端点から垂線の足までの距離
280                         l = - (s / l);
281                         d = Math.sqrt(d * d + l * l);
282                 } else if(s > l * l){
283                         //同様に端点bからの距離に変換
284                         l = s / l;
285                         d = Math.sqrt(d * d + l * l);
286                 }
287                 return d;
288         },
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);
301         },
302 }
303
304 function MGNode(env, identifier){
305         this.env = env;
306         this.identifier = identifier;
307         this.position = new Point2D(0, 0);
308         this.size = 10;
309         //ランダムな初期ベクトルをもつ。
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;
315 }
316 MGNode.prototype = {
317         draw: function(){
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);
320         },
321         tick: function(){
322                 var e;
323                 var p;
324                 var l;
325                 var q;
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;
330                 
331                 if(!this.ignoreEdgeRepulsion){
332                         //node
333                         //距離の近い点同士には斥力が働くとする。
334                         p = this.env.nodeList;
335                         for(var i = 0, iLen = p.length; i < iLen; i++){
336                                 var q = this.env.nodeList[i];
337                                 if(q != this){
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;
345                                         }
346                                 }
347                         }
348                         //edge
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){
356                                                 if(l < 1){
357                                                         l = 1;
358                                                 }
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;
368                                         }
369                                 }
370                         }
371                 } else{
372                         this.ignoreEdgeRepulsion--;
373                 }
374         },
375 }
376
377 function MGEdge(env, identifier, node0, node1){
378         this.env = env;
379         this.identifier = identifier;
380         this.node0 = node0;
381         this.node1 = node1;
382         this.freeLength = 250;
383 }
384 MGEdge.prototype = {
385         draw: function(){
386                 if(this.node0 && this.node1){
387                         this.env.drawLineP(this.node0.position, this.node1.position);
388                 }
389         },
390         tick: function(){
391                 var l = this.env.getVectorLengthP(this.node0.position, this.node1.position);
392                 var e;
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;
401                 }
402         },
403 }
404
405 function Point2D(x, y){
406         this.x = x;
407         this.y = y;
408 }
409 Point2D.prototype = {
410         
411 }
412
413 function Rectangle(x, y, width, height){
414         this.origin = new Point2D(x,y);
415         this.size = new Point2D(width,height);
416 }
417 Rectangle.prototype = {
418         
419 }
420