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