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