| ;(function(undefined) { | |
|   'use strict'; | |
| 
 | |
|   if (typeof sigma === 'undefined') | |
|     throw 'sigma is not declared'; | |
| 
 | |
|   sigma.utils.pkg('sigma.classes'); | |
| 
 | |
|   /** | |
|    * The camera constructor. It just initializes its attributes and methods. | |
|    * | |
|    * @param  {string}       id       The id. | |
|    * @param  {sigma.classes.graph}  graph    The graph. | |
|    * @param  {configurable} settings The settings function. | |
|    * @param  {?object}      options  Eventually some overriding options. | |
|    * @return {camera}                Returns the fresh new camera instance. | |
|    */ | |
|   sigma.classes.camera = function(id, graph, settings, options) { | |
|     sigma.classes.dispatcher.extend(this); | |
| 
 | |
|     Object.defineProperty(this, 'graph', { | |
|       value: graph | |
|     }); | |
|     Object.defineProperty(this, 'id', { | |
|       value: id | |
|     }); | |
|     Object.defineProperty(this, 'readPrefix', { | |
|       value: 'read_cam' + id + ':' | |
|     }); | |
|     Object.defineProperty(this, 'prefix', { | |
|       value: 'cam' + id + ':' | |
|     }); | |
| 
 | |
|     this.x = 0; | |
|     this.y = 0; | |
|     this.ratio = 1; | |
|     this.angle = 0; | |
|     this.isAnimated = false; | |
|     this.settings = (typeof options === 'object' && options) ? | |
|       settings.embedObject(options) : | |
|       settings; | |
|   }; | |
| 
 | |
|   /** | |
|    * Updates the camera position. | |
|    * | |
|    * @param  {object} coordinates The new coordinates object. | |
|    * @return {camera}             Returns the camera. | |
|    */ | |
|   sigma.classes.camera.prototype.goTo = function(coordinates) { | |
|     if (!this.settings('enableCamera')) | |
|       return this; | |
| 
 | |
|     var i, | |
|         l, | |
|         c = coordinates || {}, | |
|         keys = ['x', 'y', 'ratio', 'angle']; | |
| 
 | |
|     for (i = 0, l = keys.length; i < l; i++) | |
|       if (c[keys[i]] !== undefined) { | |
|         if (typeof c[keys[i]] === 'number' && !isNaN(c[keys[i]])) | |
|           this[keys[i]] = c[keys[i]]; | |
|         else | |
|           throw 'Value for "' + keys[i] + '" is not a number.'; | |
|       } | |
| 
 | |
|     this.dispatchEvent('coordinatesUpdated'); | |
|     return this; | |
|   }; | |
| 
 | |
|   /** | |
|    * This method takes a graph and computes for each node and edges its | |
|    * coordinates relatively to the center of the camera. Basically, it will | |
|    * compute the coordinates that will be used by the graphic renderers. | |
|    * | |
|    * Since it should be possible to use different cameras and different | |
|    * renderers, it is possible to specify a prefix to put before the new | |
|    * coordinates (to get something like "node.camera1_x") | |
|    * | |
|    * @param  {?string} read    The prefix of the coordinates to read. | |
|    * @param  {?string} write   The prefix of the coordinates to write. | |
|    * @param  {?object} options Eventually an object of options. Those can be: | |
|    *                           - A restricted nodes array. | |
|    *                           - A restricted edges array. | |
|    *                           - A width. | |
|    *                           - A height. | |
|    * @return {camera}        Returns the camera. | |
|    */ | |
|   sigma.classes.camera.prototype.applyView = function(read, write, options) { | |
|     options = options || {}; | |
|     write = write !== undefined ? write : this.prefix; | |
|     read = read !== undefined ? read : this.readPrefix; | |
| 
 | |
|     var nodes = options.nodes || this.graph.nodes(), | |
|         edges = options.edges || this.graph.edges(); | |
| 
 | |
|     var i, | |
|         l, | |
|         node, | |
|         relCos = Math.cos(this.angle) / this.ratio, | |
|         relSin = Math.sin(this.angle) / this.ratio, | |
|         nodeRatio = Math.pow(this.ratio, this.settings('nodesPowRatio')), | |
|         edgeRatio = Math.pow(this.ratio, this.settings('edgesPowRatio')), | |
|         xOffset = (options.width || 0) / 2 - this.x * relCos - this.y * relSin, | |
|         yOffset = (options.height || 0) / 2 - this.y * relCos + this.x * relSin; | |
| 
 | |
|     for (i = 0, l = nodes.length; i < l; i++) { | |
|       node = nodes[i]; | |
|       node[write + 'x'] = | |
|         (node[read + 'x'] || 0) * relCos + | |
|         (node[read + 'y'] || 0) * relSin + | |
|         xOffset; | |
|       node[write + 'y'] = | |
|         (node[read + 'y'] || 0) * relCos - | |
|         (node[read + 'x'] || 0) * relSin + | |
|         yOffset; | |
|       node[write + 'size'] = | |
|         (node[read + 'size'] || 0) / | |
|         nodeRatio; | |
|     } | |
| 
 | |
|     for (i = 0, l = edges.length; i < l; i++) { | |
|       edges[i][write + 'size'] = | |
|         (edges[i][read + 'size'] || 0) / | |
|         edgeRatio; | |
|     } | |
| 
 | |
|     return this; | |
|   }; | |
| 
 | |
|   /** | |
|    * This function converts the coordinates of a point from the frame of the | |
|    * camera to the frame of the graph. | |
|    * | |
|    * @param  {number} x The X coordinate of the point in the frame of the | |
|    *                    camera. | |
|    * @param  {number} y The Y coordinate of the point in the frame of the | |
|    *                    camera. | |
|    * @return {object}   The point coordinates in the frame of the graph. | |
|    */ | |
|   sigma.classes.camera.prototype.graphPosition = function(x, y, vector) { | |
|     var X = 0, | |
|         Y = 0, | |
|         cos = Math.cos(this.angle), | |
|         sin = Math.sin(this.angle); | |
| 
 | |
|     // Revert the origin differential vector: | |
|     if (!vector) { | |
|       X = - (this.x * cos + this.y * sin) / this.ratio; | |
|       Y = - (this.y * cos - this.x * sin) / this.ratio; | |
|     } | |
| 
 | |
|     return { | |
|       x: (x * cos + y * sin) / this.ratio + X, | |
|       y: (y * cos - x * sin) / this.ratio + Y | |
|     }; | |
|   }; | |
| 
 | |
|   /** | |
|    * This function converts the coordinates of a point from the frame of the | |
|    * graph to the frame of the camera. | |
|    * | |
|    * @param  {number} x The X coordinate of the point in the frame of the | |
|    *                    graph. | |
|    * @param  {number} y The Y coordinate of the point in the frame of the | |
|    *                    graph. | |
|    * @return {object}   The point coordinates in the frame of the camera. | |
|    */ | |
|   sigma.classes.camera.prototype.cameraPosition = function(x, y, vector) { | |
|     var X = 0, | |
|         Y = 0, | |
|         cos = Math.cos(this.angle), | |
|         sin = Math.sin(this.angle); | |
| 
 | |
|     // Revert the origin differential vector: | |
|     if (!vector) { | |
|       X = - (this.x * cos + this.y * sin) / this.ratio; | |
|       Y = - (this.y * cos - this.x * sin) / this.ratio; | |
|     } | |
| 
 | |
|     return { | |
|       x: ((x - X) * cos - (y - Y) * sin) * this.ratio, | |
|       y: ((y - Y) * cos + (x - X) * sin) * this.ratio | |
|     }; | |
|   }; | |
| 
 | |
|   /** | |
|    * This method returns the transformation matrix of the camera. This is | |
|    * especially useful to apply the camera view directly in shaders, in case of | |
|    * WebGL rendering. | |
|    * | |
|    * @return {array} The transformation matrix. | |
|    */ | |
|   sigma.classes.camera.prototype.getMatrix = function() { | |
|     var scale = sigma.utils.matrices.scale(1 / this.ratio), | |
|         rotation = sigma.utils.matrices.rotation(this.angle), | |
|         translation = sigma.utils.matrices.translation(-this.x, -this.y), | |
|         matrix = sigma.utils.matrices.multiply( | |
|           translation, | |
|           sigma.utils.matrices.multiply( | |
|             rotation, | |
|             scale | |
|           ) | |
|         ); | |
| 
 | |
|     return matrix; | |
|   }; | |
| 
 | |
|   /** | |
|    * Taking a width and a height as parameters, this method returns the | |
|    * coordinates of the rectangle representing the camera on screen, in the | |
|    * graph's referentiel. | |
|    * | |
|    * To keep displaying labels of nodes going out of the screen, the method | |
|    * keeps a margin around the screen in the returned rectangle. | |
|    * | |
|    * @param  {number} width  The width of the screen. | |
|    * @param  {number} height The height of the screen. | |
|    * @return {object}        The rectangle as x1, y1, x2 and y2, representing | |
|    *                         two opposite points. | |
|    */ | |
|   sigma.classes.camera.prototype.getRectangle = function(width, height) { | |
|     var widthVect = this.cameraPosition(width, 0, true), | |
|         heightVect = this.cameraPosition(0, height, true), | |
|         centerVect = this.cameraPosition(width / 2, height / 2, true), | |
|         marginX = this.cameraPosition(width / 4, 0, true).x, | |
|         marginY = this.cameraPosition(0, height / 4, true).y; | |
| 
 | |
|     return { | |
|       x1: this.x - centerVect.x - marginX, | |
|       y1: this.y - centerVect.y - marginY, | |
|       x2: this.x - centerVect.x + marginX + widthVect.x, | |
|       y2: this.y - centerVect.y - marginY + widthVect.y, | |
|       height: Math.sqrt( | |
|         Math.pow(heightVect.x, 2) + | |
|         Math.pow(heightVect.y + 2 * marginY, 2) | |
|       ) | |
|     }; | |
|   }; | |
| }).call(this);
 |