OSDN Git Service

c91093bb386f8ecd1b32345d9dbd466c1284a475
[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         
16         var that = this;
17         this.canvas.onmousemove = function (e){
18                 if(!e){
19                         //for IE
20                         e = window.event;
21                 }
22                 var loc = that.getMousePositionOnElement(e);
23                 // 出力テスト
24                 //console.log("x:" + loc.x);
25                 //console.log("y:" + loc.y);
26         };
27 }
28 MGCanvas.prototype = {
29         setGraph: function(gArray){
30                 //gArray = [[Node1, Node2, Node3, ...], [[Node1, Node3], [Node3, Node2], ...]]
31                 var that = this;
32                 var p = gArray[0];
33                 var n = function(identifier){ return that.nodeList.isIncluded(identifier, function(a, b){ return (a.identifier == b); }); };
34                 
35                 for(var i = 0, iLen = p.length; i < iLen; i++){
36                         this.nodeList.push(new MGNode(this, p[i]));
37                 }
38                 
39                 p = gArray[1];
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])));
42                 }
43         },
44         bringToCenter: function(){
45                 var g = new Point2D(0, 0);
46                 var p;
47                 p = this.nodeList;
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;
51                 }
52                 g.x /= p.length;
53                 g.y /= p.length;
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;
57                 }
58         },
59         bringInScreen: function(){
60                 //大きく外れていないときには動かさない
61                 var g = new Point2D(0, 0);
62                 var p;
63                 p = this.nodeList;
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;
67                 }
68                 g.x /= p.length;
69                 g.y /= p.length;
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;
77                         }
78                 }
79         },
80         tick: function(){
81                 var p;
82                 var t;
83                 var dr;
84                 var n = null;
85                 var nMax = 0;
86                 var nTemp;
87                 
88                 this.tickCount++;
89                 //console.log(this.tickCount);
90                 if(this.tickCount % 30 == 0){
91                         this.bringInScreen();
92                 }
93                 
94                 //
95                 // Check
96                 //
97                 
98                 p = this.nodeList;
99                 for(var i = 0, iLen = p.length; i < iLen; i++){
100                         nTemp = this.getVectorLength(p[i].vector);
101                         if(nMax < nTemp){
102                                 n = p[i];
103                                 nMax = nTemp;
104                         }
105                 }
106                 if(n){
107                         n.ignoreEdgeRepulsion = 10;
108                 }
109                 
110                 
111                 //
112                 // Move
113                 //
114                 p = this.nodeList;
115                 for(var i = 0, iLen = p.length; i < iLen; i++){
116                         this.nodeList[i].tick();
117                 }
118                 p = this.edgeList;
119                 for(var i = 0, iLen = p.length; i < iLen; i++){
120                         this.edgeList[i].tick();
121                 }
122                 
123                 //
124                 // Refresh
125                 //
126                 dr = this.displayRect;
127                 this.context.clearRect(dr.origin.x, dr.origin.y, dr.size.x, dr.size.y);
128                 p = this.nodeList;
129                 for(var i = 0, iLen = p.length; i < iLen; i++){
130                         this.nodeList[i].draw();
131                 }
132                 p = this.edgeList;
133                 for(var i = 0, iLen = p.length; i < iLen; i++){
134                         this.edgeList[i].draw();
135                 }
136         },
137         getMousePositionOnElement: function(e){
138                 // http://tmlife.net/programming/javascript/javascript-mouse-pos.html
139                 // retv:
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;
144                 return retv;
145         },
146         fillRect: function(x, y, w, h){
147                 var d = this.drawCoordinatesInInteger;
148                 this.context.fillRect(d(x), d(y), d(w), d(h));
149         },
150         strokeRect: function(x, y, w, h){
151                 var d = this.drawCoordinatesInInteger;
152                 this.context.strokeRect(d(x), d(y), d(w), d(h));
153         },
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);
158         },
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();
164                 this.context.fill();
165                 this.context.stroke();
166         },
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();
174         },
175         drawText: function(text, x, y){
176                 //背景をfillStyle
177                 //前景をstrokeStyleで塗りつぶした文字列を描画する
178                 //塗りつぶし高さは20px固定
179                 //座標は文字列の左上の座標となる
180                 var textsize = this.context.measureText(text);
181                 this.context.fillRect(x, y, textsize.width, 20);
182                 this.context.save();
183                 this.context.fillStyle = this.context.strokeStyle;
184                 //fillText引数の座標は文字列の左下!
185                 this.context.fillText(text, x, y + 20 - 1);
186                 this.context.restore();
187         },
188         getVectorLengthP: function(p, q){
189                 return this.getVectorLength(this.getVectorP(p, q));
190         },
191         getVectorLength: function(a){
192                 return Math.sqrt(a.x * a.x + a.y * a.y);
193         },
194         getVectorP: function(p, q){
195                 return new Point2D(q.x - p.x, q.y - p.y);
196         },
197         getUnitVectorP: function(p, q){
198                 var e = this.getVectorP(p, q);
199                 return this.getUnitVector(e);
200         },
201         getUnitVector: function(a){
202                 var l = Math.sqrt(a.x * a.x + a.y * a.y);
203                 a.x /= l;
204                 a.y /= l;
205                 return a;
206         },
207         getNormalUnitVectorSideOfP: function(a, b, p){
208                 //直線ab上にない点pが存在する側へ向かう単位法線ベクトルを返す。
209                 return this.getUnitVector(this.getNormalVectorSideOfP(a, b, p));
210         },
211         getNormalVectorSideOfP: function(a, b, p){
212                 //直線ab上にない点pが存在する側へ向かう法線ベクトルを返す。
213                 //pがab上にある場合は零ベクトルとなる。
214                 var n = this.getVectorP(a, b);
215                 var t = n.x;
216                 var i;
217                 n.x = -n.y;
218                 n.y = t;
219                 
220                 i = this.getInnerVector2D(n, this.getVectorP(a, p));
221                 if(i < 0){
222                         //この法線ベクトルとapの向きが逆なので反転する。
223                         n.x = -n.x;
224                         n.y = -n.y;
225                 } else if(i == 0){
226                         n.x = 0;
227                         n.y = 0;
228                 }
229                 return n;
230         },
231         getExteriorVector2D: function(a, b){
232                 return a.x * b.y - a.y * b.x;
233         },
234         getInnerVector2D: function(a, b){
235                 return a.x * b.x + a.y * b.y;
236         },
237         getDistanceDotAndLineP: function(p, a, b){
238                 // http://www.sousakuba.com/Programming/gs_dot_line_distance.html
239                 var ab;
240                 var ap;
241                 var s;
242                 var l;
243                 var d;
244                 
245                 ab = this.getVectorP(a, b);
246                 ap = this.getVectorP(a, p);
247                 
248                 s = Math.abs(this.getExteriorVector2D(ab, ap));
249                 l = this.getVectorLengthP(a, b);
250                 d = (s / l);
251                 
252                 s = this.getInnerVector2D(ap, ab);
253                 if(s < 0){
254                         //線分の範囲外なので端点aからの距離に変換
255                         //端点から垂線の足までの距離
256                         l = - (s / l);
257                         d = Math.sqrt(d * d + l * l);
258                 } else if(s > l * l){
259                         //同様に端点bからの距離に変換
260                         l = s / l;
261                         d = Math.sqrt(d * d + l * l);
262                 }
263                 return d;
264         },
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);
275         },
276 }
277
278 function MGNode(env, identifier){
279         this.env = env;
280         this.identifier = identifier;
281         this.position = new Point2D(0, 0);
282         this.size = 10;
283         //ランダムな初期ベクトルをもつ。
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;
289 }
290 MGNode.prototype = {
291         draw: function(){
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);
294         },
295         tick: function(){
296                 var e;
297                 var p;
298                 var l;
299                 var q;
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;
304                 
305                 if(!this.ignoreEdgeRepulsion){
306                         //node
307                         //距離の近い点同士には斥力が働くとする。
308                         p = this.env.nodeList;
309                         for(var i = 0, iLen = p.length; i < iLen; i++){
310                                 var q = this.env.nodeList[i];
311                                 if(q != this){
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;
319                                         }
320                                 }
321                         }
322                         //edge
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){
330                                                 if(l < 1){
331                                                         l = 1;
332                                                 }
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;
342                                         }
343                                 }
344                         }
345                 } else{
346                         this.ignoreEdgeRepulsion--;
347                 }
348         },
349 }
350
351 function MGEdge(env, identifier, node0, node1){
352         this.env = env;
353         this.identifier = identifier;
354         this.node0 = node0;
355         this.node1 = node1;
356         this.freeLength = 250;
357 }
358 MGEdge.prototype = {
359         draw: function(){
360                 if(this.node0 && this.node1){
361                         this.env.drawLineP(this.node0.position, this.node1.position);
362                 }
363         },
364         tick: function(){
365                 var l = this.env.getVectorLengthP(this.node0.position, this.node1.position);
366                 var e;
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;
375                 }
376         },
377 }
378
379 function Point2D(x, y){
380         this.x = x;
381         this.y = y;
382 }
383 Point2D.prototype = {
384         
385 }
386
387 function Rectangle(x, y, width, height){
388         this.origin = new Point2D(x,y);
389         this.size = new Point2D(width,height);
390 }
391 Rectangle.prototype = {
392         
393 }
394