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