0e8e1407e5f9de1f59759e238f452305054936b6
[myslice.git] / plugins / senslabmap / static / js / map.js
1 var Senslab = {
2   normalize: function(node) {
3     var info;
4     
5     if (node.component_name) { // wsn430-11.devlille.iot-lab.info
6       info = node.component_name.split(".");
7     } /*else if (node.hrn) { // iotlab.a8-11\.devgrenoble\.iot-lab\.info
8       var info = node.hrn.split("\\.");
9       info[0] = info[0].split(".")[1];
10     }*/
11
12     if (info && info[2] == "iot-lab" && info[3] == "info") {
13       node.arch = info[0].split("-")[0];
14       node.id = info[0].split("-")[1];
15       node.site = info[1];
16       node.normalized = true;
17     }
18   },
19   notify: function(node) {
20     console.log("[Notify] node " + node.id + " is " + node.boot_state);
21   },
22   createMaps: function($container, sites, nodes) {
23     var maps = {};
24     $.each(sites, function(i, site) {
25       var $div = $("<div />").appendTo($container);
26       maps[site] = new Senslab.Map($div);
27       maps[site].addNodes(nodes[site]);
28     });
29   }
30 };
31
32 Senslab.Map = function() {
33   var colors = {
34     "Alive": 0x7FFF00,
35     "Busy": 0x9943BE,
36     "Suspected": 0xFF3030,
37     "Selected": 0x0099CC
38   };
39
40   var archs = [
41     "wsn430",
42     "m3",
43     "a8"
44   ];
45   
46   function Map($container, options) {
47     this.width  = 600;
48     this.height = 400;
49     
50     this.distance = 50;
51     this.phi = -100;
52     this.theta = 0;
53     this.onRot = false;
54     
55     this.pointerDetectRay = new THREE.Raycaster();
56     this.pointerDetectRay.ray.direction.set(0, -1, 0);
57     this.projector = new THREE.Projector();
58     this.mouse2D = new THREE.Vector3(0, 0, 0);
59     
60     this.renderer = new THREE.CanvasRenderer();
61     this.renderer.setSize(this.width, this.height);
62     
63     this.camera = new THREE.PerspectiveCamera(75, this.width / this.height, 1, 10000);
64     
65     this.scene = new THREE.Scene();
66     this.scene.add(this.camera);
67     
68     this.updatePosition();
69     
70     var self = this;
71     
72     this.$nodeInputs = {};
73     
74     $.each(archs, function(i, arch) {
75       self.$nodeInputs[arch] = $("<input type='text' placeholder='" + arch + "'>")
76       .appendTo($container)
77       .change(function() {
78         self.updateSelected(arch, expand($(this).val()));
79         self.update();
80       });
81     });
82     
83     var $canvas = $(this.renderer.domElement)
84     .mousemove(function(e) {
85       self.mouse2D.x =  ((e.pageX - $canvas.offset().left) / $canvas.width()) * 2 - 1;
86       self.mouse2D.y = -((e.pageY - $canvas.offset().top) / $canvas.height()) * 2 + 1;
87       
88       if (self.onRot) {
89         self.theta -= e.pageX - self.mouse2D.pageX;
90         self.phi += e.pageY - self.mouse2D.pageY;
91         if (self.phi > 180)
92           self.phi = 180;
93         if (self.phi < -180)
94           self.phi = -180;
95         
96         self.mouse2D.pageX = e.pageX;
97         self.mouse2D.pageY = e.pageY;
98         
99         self.updatePosition();
100         self.update();
101       }
102     }).mousedown(function(e) {
103       e.preventDefault();
104       switch (e.which) {
105         case 1:
106           self.pointerDetectRay = self.projector.pickingRay(self.mouse2D.clone(), self.camera);
107           var intersects = self.pointerDetectRay.intersectObjects(self.scene.children);
108           if (intersects.length > 0) {
109             var particle = intersects[0].object;
110             if (particle.data.boot_state != "Suspected") {
111               setState(particle, particle.data.boot_state == "Alive" ? "Selected" : "Alive");
112               var $nodeInput = self.$nodeInputs[particle.data.arch];
113               $nodeInput.val(factorize(self.getNodesId(particle.data.arch)));
114               self.update();
115             }
116           }
117           break;
118         case 3:
119           self.mouse2D.pageX = e.pageX;
120           self.mouse2D.pageY = e.pageY;
121           self.onRot = true;
122           break;
123       }
124     }).mouseup(function(e) {
125       e.preventDefault();
126       switch (e.which) {
127         case 3:
128           self.onRot = false;
129           break;
130       }
131     }).mouseleave(function(e) {
132       self.onRot = false;
133     }).mousewheel(function(e, delta) {
134       e.preventDefault();
135       self.distance += delta * 5;
136       self.updatePosition();
137       self.update();
138     });
139     
140     $container.append($canvas);
141   }
142   
143   Map.prototype.getNodesId = function(arch) {
144     var particles = this.scene.children;
145     var nodes = [];
146     for (var i = 0; i < particles.length; ++i) {
147       if (particles[i].data && particles[i].data.arch == arch && particles[i].data.boot_state == "Selected") {
148         nodes.push(particles[i].id);
149       }
150     }
151     return nodes;
152   }
153   
154   Map.prototype.addNodes = function(nodes) {
155     var center = getCenter(nodes);
156
157     nodes.sort(function(a, b) {
158       return a.id - b.id;
159     });
160     
161     for(var i = 0; i < nodes.length; ++i) {
162       var material = new THREE.ParticleCanvasMaterial({program: circle});
163       var particle = new THREE.Particle(material);
164       particle.data = nodes[i];
165       particle.id = parseInt(nodes[i].id);
166       particle.position.x = (nodes[i].x - center.x) * 10;
167       particle.position.y = (nodes[i].y - center.y) * 10;
168       particle.position.z = (nodes[i].z - center.z) * 10;
169       particle.scale.x = particle.scale.y = 1;
170       setColor(particle)
171       this.scene.add(particle);
172     }
173     this.update();
174   };
175   
176   Map.prototype.updateSelected = function(arch, selected) {
177     var particles = this.scene.children;
178     for (var i = 0; i < particles.length; ++i) {
179       if (particles[i].data && particles[i].data.arch == arch && particles[i].data.boot_state != "Suspected") {
180         var particle = particles[i];
181         var state = $.inArray(particle.id, selected) == -1 ? "Alive" : "Selected";
182         if (particle.data.boot_state != state) {
183           setState(particle, state);
184         }
185       }
186     }
187   }
188   
189   Map.prototype.updatePosition = function() {
190     this.camera.position.x = this.distance
191       * Math.sin(this.theta * Math.PI / 360)
192       * Math.cos(this.phi * Math.PI / 360);
193     this.camera.position.y = this.distance * Math.sin(this.phi * Math.PI / 360);
194     this.camera.position.z = this.distance
195       * Math.cos(this.theta * Math.PI / 360)
196       * Math.cos(this.phi * Math.PI / 360);
197     this.camera.lookAt(this.scene.position);
198     this.camera.updateMatrix();
199   };
200   
201   Map.prototype.update = function() {
202     this.renderer.render(this.scene, this.camera);
203   };
204
205   function setState(particle, state) {
206     particle.data.boot_state = state;
207     setColor(particle);
208     Senslab.notify(particle.data);
209   }
210
211   function setColor(particle) {
212     var color = colors[particle.data.boot_state] || colors["Selected"];
213     particle.material.color.setHex(color);
214   }
215
216   function getCenter(nodes) {
217     var xmin = 0, ymin = 0, zmin = 0;
218     var xmax = 0, ymax = 0, zmax = 0;
219     
220     for (var i = 0; i < nodes.length; ++i) {
221       if (nodes[i].x > xmax) xmax = nodes[i].x;
222       if (nodes[i].x < xmin) xmin = nodes[i].x;
223       if (nodes[i].y > ymax) ymax = nodes[i].y;
224       if (nodes[i].y < ymin) ymin = nodes[i].y;
225       if (nodes[i].z > zmax) zmax = nodes[i].z;
226       if (nodes[i].z < zmin) zmin = nodes[i].z;
227     }
228     return {x: (xmax + xmin) / 2, y: (ymax + ymin) / 2, z: (zmax + zmin) / 2};
229   }
230
231   function factorize(nodes) {
232     var factorized = [];
233     var prev = 0;
234     var intervalStart = 0;
235     
236     for (var i = 0; i < nodes.length; ++i) {
237       if (intervalStart) {
238         if (nodes[i] == prev + 1) {
239           prev++;
240         } else {
241           factorized.push(intervalStart + "-" + prev);
242           intervalStart = 0;
243           prev = nodes[i];
244         }
245       } else {
246         if (nodes[i] == prev + 1) {
247           intervalStart = prev;
248           prev++;
249         } else {
250           prev && factorized.push(prev);
251           prev = nodes[i];
252         }
253       }
254     }
255     factorized.push(intervalStart ? intervalStart + "-" + prev : prev);
256     return factorized.join(",");
257   }
258   
259   function expand(input) {
260     var factorized = input.split(",");
261     var expanded = [];
262     for (var i = 0; i < factorized.length; ++i) {
263       var d = factorized[i].split("-");
264       if (d.length == 2) {
265         for (var j = parseInt(d[0]); j < parseInt(d[1]) + 1; j++) {
266           expanded.push(j);
267         }
268       } else {
269         expanded.push(parseInt(factorized[i]));
270       }
271     }
272     
273     expanded.sort(function(a, b) {
274       return a - b;
275     });
276     
277     for (var i = 1; i < expanded.length; i++) {
278       if (expanded[i] == expanded[i - 1]) {
279         expanded.splice(i--, 1);
280       }
281     }
282     return expanded;
283   }
284
285   function circle(context) {
286     context.beginPath();
287     context.arc(0, 0, 1, 0, Math.PI * 2, true);
288     context.closePath();
289     context.fill();
290   };
291   
292   return Map;
293 }();