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