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