|
|
- ;(function(undefined) {
- 'use strict';
-
- var __instances = {};
-
- /**
- * This is the sigma instances constructor. One instance of sigma represent
- * one graph. It is possible to represent this grapĥ with several renderers
- * at the same time. By default, the default renderer (WebGL + Canvas
- * polyfill) will be used as the only renderer, with the container specified
- * in the configuration.
- *
- * @param {?*} conf The configuration of the instance. There are a lot of
- * different recognized forms to instantiate sigma, check
- * example files, documentation in this file and unit
- * tests to know more.
- * @return {sigma} The fresh new sigma instance.
- *
- * Instanciating sigma:
- * ********************
- * If no parameter is given to the constructor, the instance will be created
- * without any renderer or camera. It will just instantiate the graph, and
- * other modules will have to be instantiated through the public methods,
- * like "addRenderer" etc:
- *
- * > s0 = new sigma();
- * > s0.addRenderer({
- * > type: 'canvas',
- * > container: 'my-container-id'
- * > });
- *
- * In most of the cases, sigma will simply be used with the default renderer.
- * Then, since the only required parameter is the DOM container, there are
- * some simpler way to call the constructor. The four following calls do the
- * exact same things:
- *
- * > s1 = new sigma('my-container-id');
- * > s2 = new sigma(document.getElementById('my-container-id'));
- * > s3 = new sigma({
- * > container: document.getElementById('my-container-id')
- * > });
- * > s4 = new sigma({
- * > renderers: [{
- * > container: document.getElementById('my-container-id')
- * > }]
- * > });
- *
- * Recognized parameters:
- * **********************
- * Here is the exhaustive list of every accepted parameters, when calling the
- * constructor with to top level configuration object (fourth case in the
- * previous examples):
- *
- * {?string} id The id of the instance. It will be generated
- * automatically if not specified.
- * {?array} renderers An array containing objects describing renderers.
- * {?object} graph An object containing an array of nodes and an array
- * of edges, to avoid having to add them by hand later.
- * {?object} settings An object containing instance specific settings that
- * will override the default ones defined in the object
- * sigma.settings.
- */
- var sigma = function(conf) {
- // Local variables:
- // ****************
- var i,
- l,
- a,
- c,
- o,
- id;
-
- sigma.classes.dispatcher.extend(this);
-
- // Private attributes:
- // *******************
- var _self = this,
- _conf = conf || {};
-
- // Little shortcut:
- // ****************
- // The configuration is supposed to have a list of the configuration
- // objects for each renderer.
- // - If there are no configuration at all, then nothing is done.
- // - If there are no renderer list, the given configuration object will be
- // considered as describing the first and only renderer.
- // - If there are no renderer list nor "container" object, it will be
- // considered as the container itself (a DOM element).
- // - If the argument passed to sigma() is a string, it will be considered
- // as the ID of the DOM container.
- if (
- typeof _conf === 'string' ||
- _conf instanceof HTMLElement
- )
- _conf = {
- renderers: [_conf]
- };
- else if (Object.prototype.toString.call(_conf) === '[object Array]')
- _conf = {
- renderers: _conf
- };
-
- // Also check "renderer" and "container" keys:
- o = _conf.renderers || _conf.renderer || _conf.container;
- if (!_conf.renderers || _conf.renderers.length === 0)
- if (
- typeof o === 'string' ||
- o instanceof HTMLElement ||
- (typeof o === 'object' && 'container' in o)
- )
- _conf.renderers = [o];
-
- // Recense the instance:
- if (_conf.id) {
- if (__instances[_conf.id])
- throw 'sigma: Instance "' + _conf.id + '" already exists.';
- Object.defineProperty(this, 'id', {
- value: _conf.id
- });
- } else {
- id = 0;
- while (__instances[id])
- id++;
- Object.defineProperty(this, 'id', {
- value: '' + id
- });
- }
- __instances[this.id] = this;
-
- // Initialize settings function:
- this.settings = new sigma.classes.configurable(
- sigma.settings,
- _conf.settings || {}
- );
-
- // Initialize locked attributes:
- Object.defineProperty(this, 'graph', {
- value: new sigma.classes.graph(this.settings),
- configurable: true
- });
- Object.defineProperty(this, 'middlewares', {
- value: [],
- configurable: true
- });
- Object.defineProperty(this, 'cameras', {
- value: {},
- configurable: true
- });
- Object.defineProperty(this, 'renderers', {
- value: {},
- configurable: true
- });
- Object.defineProperty(this, 'renderersPerCamera', {
- value: {},
- configurable: true
- });
- Object.defineProperty(this, 'cameraFrames', {
- value: {},
- configurable: true
- });
- Object.defineProperty(this, 'camera', {
- get: function() {
- return this.cameras[0];
- }
- });
- Object.defineProperty(this, 'events', {
- value: [
- 'click',
- 'rightClick',
- 'clickStage',
- 'doubleClickStage',
- 'rightClickStage',
- 'clickNode',
- 'clickNodes',
- 'doubleClickNode',
- 'doubleClickNodes',
- 'rightClickNode',
- 'rightClickNodes',
- 'overNode',
- 'overNodes',
- 'outNode',
- 'outNodes',
- 'downNode',
- 'downNodes',
- 'upNode',
- 'upNodes'
- ],
- configurable: true
- });
-
- // Add a custom handler, to redispatch events from renderers:
- this._handler = (function(e) {
- var k,
- data = {};
-
- for (k in e.data)
- data[k] = e.data[k];
-
- data.renderer = e.target;
- this.dispatchEvent(e.type, data);
- }).bind(this);
-
- // Initialize renderers:
- a = _conf.renderers || [];
- for (i = 0, l = a.length; i < l; i++)
- this.addRenderer(a[i]);
-
- // Initialize middlewares:
- a = _conf.middlewares || [];
- for (i = 0, l = a.length; i < l; i++)
- this.middlewares.push(
- typeof a[i] === 'string' ?
- sigma.middlewares[a[i]] :
- a[i]
- );
-
- // Check if there is already a graph to fill in:
- if (typeof _conf.graph === 'object' && _conf.graph) {
- this.graph.read(_conf.graph);
-
- // If a graph is given to the to the instance, the "refresh" method is
- // directly called:
- this.refresh();
- }
-
- // Deal with resize:
- window.addEventListener('resize', function() {
- if (_self.settings)
- _self.refresh();
- });
- };
-
-
-
-
- /**
- * This methods will instantiate and reference a new camera. If no id is
- * specified, then an automatic id will be generated.
- *
- * @param {?string} id Eventually the camera id.
- * @return {sigma.classes.camera} The fresh new camera instance.
- */
- sigma.prototype.addCamera = function(id) {
- var self = this,
- camera;
-
- if (!arguments.length) {
- id = 0;
- while (this.cameras['' + id])
- id++;
- id = '' + id;
- }
-
- if (this.cameras[id])
- throw 'sigma.addCamera: The camera "' + id + '" already exists.';
-
- camera = new sigma.classes.camera(id, this.graph, this.settings);
- this.cameras[id] = camera;
-
- // Add a quadtree to the camera:
- camera.quadtree = new sigma.classes.quad();
-
- // Add an edgequadtree to the camera:
- if (sigma.classes.edgequad !== undefined) {
- camera.edgequadtree = new sigma.classes.edgequad();
- }
-
- camera.bind('coordinatesUpdated', function(e) {
- self.renderCamera(camera, camera.isAnimated);
- });
-
- this.renderersPerCamera[id] = [];
-
- return camera;
- };
-
- /**
- * This method kills a camera, and every renderer attached to it.
- *
- * @param {string|camera} v The camera to kill or its ID.
- * @return {sigma} Returns the instance.
- */
- sigma.prototype.killCamera = function(v) {
- v = typeof v === 'string' ? this.cameras[v] : v;
-
- if (!v)
- throw 'sigma.killCamera: The camera is undefined.';
-
- var i,
- l,
- a = this.renderersPerCamera[v.id];
-
- for (l = a.length, i = l - 1; i >= 0; i--)
- this.killRenderer(a[i]);
-
- delete this.renderersPerCamera[v.id];
- delete this.cameraFrames[v.id];
- delete this.cameras[v.id];
-
- if (v.kill)
- v.kill();
-
- return this;
- };
-
- /**
- * This methods will instantiate and reference a new renderer. The "type"
- * argument can be the constructor or its name in the "sigma.renderers"
- * package. If no type is specified, then "sigma.renderers.def" will be used.
- * If no id is specified, then an automatic id will be generated.
- *
- * @param {?object} options Eventually some options to give to the renderer
- * constructor.
- * @return {renderer} The fresh new renderer instance.
- *
- * Recognized parameters:
- * **********************
- * Here is the exhaustive list of every accepted parameters in the "options"
- * object:
- *
- * {?string} id Eventually the renderer id.
- * {?(function|string)} type Eventually the renderer constructor or its
- * name in the "sigma.renderers" package.
- * {?(camera|string)} camera Eventually the renderer camera or its
- * id.
- */
- sigma.prototype.addRenderer = function(options) {
- var id,
- fn,
- camera,
- renderer,
- o = options || {};
-
- // Polymorphism:
- if (typeof o === 'string')
- o = {
- container: document.getElementById(o)
- };
- else if (o instanceof HTMLElement)
- o = {
- container: o
- };
-
- // If the container still is a string, we get it by id
- if (typeof o.container === 'string')
- o.container = document.getElementById(o.container);
-
- // Reference the new renderer:
- if (!('id' in o)) {
- id = 0;
- while (this.renderers['' + id])
- id++;
- id = '' + id;
- } else
- id = o.id;
-
- if (this.renderers[id])
- throw 'sigma.addRenderer: The renderer "' + id + '" already exists.';
-
- // Find the good constructor:
- fn = typeof o.type === 'function' ? o.type : sigma.renderers[o.type];
- fn = fn || sigma.renderers.def;
-
- // Find the good camera:
- camera = 'camera' in o ?
- (
- o.camera instanceof sigma.classes.camera ?
- o.camera :
- this.cameras[o.camera] || this.addCamera(o.camera)
- ) :
- this.addCamera();
-
- if (this.cameras[camera.id] !== camera)
- throw 'sigma.addRenderer: The camera is not properly referenced.';
-
- // Instantiate:
- renderer = new fn(this.graph, camera, this.settings, o);
- this.renderers[id] = renderer;
- Object.defineProperty(renderer, 'id', {
- value: id
- });
-
- // Bind events:
- if (renderer.bind)
- renderer.bind(
- [
- 'click',
- 'rightClick',
- 'clickStage',
- 'doubleClickStage',
- 'rightClickStage',
- 'clickNode',
- 'clickNodes',
- 'clickEdge',
- 'clickEdges',
- 'doubleClickNode',
- 'doubleClickNodes',
- 'doubleClickEdge',
- 'doubleClickEdges',
- 'rightClickNode',
- 'rightClickNodes',
- 'rightClickEdge',
- 'rightClickEdges',
- 'overNode',
- 'overNodes',
- 'overEdge',
- 'overEdges',
- 'outNode',
- 'outNodes',
- 'outEdge',
- 'outEdges',
- 'downNode',
- 'downNodes',
- 'downEdge',
- 'downEdges',
- 'upNode',
- 'upNodes',
- 'upEdge',
- 'upEdges'
- ],
- this._handler
- );
-
- // Reference the renderer by its camera:
- this.renderersPerCamera[camera.id].push(renderer);
-
- return renderer;
- };
-
- /**
- * This method kills a renderer.
- *
- * @param {string|renderer} v The renderer to kill or its ID.
- * @return {sigma} Returns the instance.
- */
- sigma.prototype.killRenderer = function(v) {
- v = typeof v === 'string' ? this.renderers[v] : v;
-
- if (!v)
- throw 'sigma.killRenderer: The renderer is undefined.';
-
- var a = this.renderersPerCamera[v.camera.id],
- i = a.indexOf(v);
-
- if (i >= 0)
- a.splice(i, 1);
-
- if (v.kill)
- v.kill();
-
- delete this.renderers[v.id];
-
- return this;
- };
-
-
-
-
- /**
- * This method calls the "render" method of each renderer, with the same
- * arguments than the "render" method, but will also check if the renderer
- * has a "process" method, and call it if it exists.
- *
- * It is useful for quadtrees or WebGL processing, for instance.
- *
- * @param {?object} options Eventually some options to give to the refresh
- * method.
- * @return {sigma} Returns the instance itself.
- *
- * Recognized parameters:
- * **********************
- * Here is the exhaustive list of every accepted parameters in the "options"
- * object:
- *
- * {?boolean} skipIndexation A flag specifying wether or not the refresh
- * function should reindex the graph in the
- * quadtrees or not (default: false).
- */
- sigma.prototype.refresh = function(options) {
- var i,
- l,
- k,
- a,
- c,
- bounds,
- prefix = 0;
-
- options = options || {};
-
- // Call each middleware:
- a = this.middlewares || [];
- for (i = 0, l = a.length; i < l; i++)
- a[i].call(
- this,
- (i === 0) ? '' : 'tmp' + prefix + ':',
- (i === l - 1) ? 'ready:' : ('tmp' + (++prefix) + ':')
- );
-
- // Then, for each camera, call the "rescale" middleware, unless the
- // settings specify not to:
- for (k in this.cameras) {
- c = this.cameras[k];
- if (
- c.settings('autoRescale') &&
- this.renderersPerCamera[c.id] &&
- this.renderersPerCamera[c.id].length
- )
- sigma.middlewares.rescale.call(
- this,
- a.length ? 'ready:' : '',
- c.readPrefix,
- {
- width: this.renderersPerCamera[c.id][0].width,
- height: this.renderersPerCamera[c.id][0].height
- }
- );
- else
- sigma.middlewares.copy.call(
- this,
- a.length ? 'ready:' : '',
- c.readPrefix
- );
-
- if (!options.skipIndexation) {
- // Find graph boundaries:
- bounds = sigma.utils.getBoundaries(
- this.graph,
- c.readPrefix
- );
-
- // Refresh quadtree:
- c.quadtree.index(this.graph.nodes(), {
- prefix: c.readPrefix,
- bounds: {
- x: bounds.minX,
- y: bounds.minY,
- width: bounds.maxX - bounds.minX,
- height: bounds.maxY - bounds.minY
- }
- });
-
- // Refresh edgequadtree:
- if (
- c.edgequadtree !== undefined &&
- c.settings('drawEdges') &&
- c.settings('enableEdgeHovering')
- ) {
- c.edgequadtree.index(this.graph, {
- prefix: c.readPrefix,
- bounds: {
- x: bounds.minX,
- y: bounds.minY,
- width: bounds.maxX - bounds.minX,
- height: bounds.maxY - bounds.minY
- }
- });
- }
- }
- }
-
- // Call each renderer:
- a = Object.keys(this.renderers);
- for (i = 0, l = a.length; i < l; i++)
- if (this.renderers[a[i]].process) {
- if (this.settings('skipErrors'))
- try {
- this.renderers[a[i]].process();
- } catch (e) {
- console.log(
- 'Warning: The renderer "' + a[i] + '" crashed on ".process()"'
- );
- }
- else
- this.renderers[a[i]].process();
- }
-
- this.render();
-
- return this;
- };
-
- /**
- * This method calls the "render" method of each renderer.
- *
- * @return {sigma} Returns the instance itself.
- */
- sigma.prototype.render = function() {
- var i,
- l,
- a,
- prefix = 0;
-
- // Call each renderer:
- a = Object.keys(this.renderers);
- for (i = 0, l = a.length; i < l; i++)
- if (this.settings('skipErrors'))
- try {
- this.renderers[a[i]].render();
- } catch (e) {
- if (this.settings('verbose'))
- console.log(
- 'Warning: The renderer "' + a[i] + '" crashed on ".render()"'
- );
- }
- else
- this.renderers[a[i]].render();
-
- return this;
- };
-
- /**
- * This method calls the "render" method of each renderer that is bound to
- * the specified camera. To improve the performances, if this method is
- * called too often, the number of effective renderings is limitated to one
- * per frame, unless you are using the "force" flag.
- *
- * @param {sigma.classes.camera} camera The camera to render.
- * @param {?boolean} force If true, will render the camera
- * directly.
- * @return {sigma} Returns the instance itself.
- */
- sigma.prototype.renderCamera = function(camera, force) {
- var i,
- l,
- a,
- self = this;
-
- if (force) {
- a = this.renderersPerCamera[camera.id];
- for (i = 0, l = a.length; i < l; i++)
- if (this.settings('skipErrors'))
- try {
- a[i].render();
- } catch (e) {
- if (this.settings('verbose'))
- console.log(
- 'Warning: The renderer "' + a[i].id + '" crashed on ".render()"'
- );
- }
- else
- a[i].render();
- } else {
- if (!this.cameraFrames[camera.id]) {
- a = this.renderersPerCamera[camera.id];
- for (i = 0, l = a.length; i < l; i++)
- if (this.settings('skipErrors'))
- try {
- a[i].render();
- } catch (e) {
- if (this.settings('verbose'))
- console.log(
- 'Warning: The renderer "' +
- a[i].id +
- '" crashed on ".render()"'
- );
- }
- else
- a[i].render();
-
- this.cameraFrames[camera.id] = requestAnimationFrame(function() {
- delete self.cameraFrames[camera.id];
- });
- }
- }
-
- return this;
- };
-
- /**
- * This method calls the "kill" method of each module and destroys any
- * reference from the instance.
- */
- sigma.prototype.kill = function() {
- var k;
-
- // Dispatching event
- this.dispatchEvent('kill');
-
- // Kill graph:
- this.graph.kill();
-
- // Kill middlewares:
- delete this.middlewares;
-
- // Kill each renderer:
- for (k in this.renderers)
- this.killRenderer(this.renderers[k]);
-
- // Kill each camera:
- for (k in this.cameras)
- this.killCamera(this.cameras[k]);
-
- delete this.renderers;
- delete this.cameras;
-
- // Kill everything else:
- for (k in this)
- if (this.hasOwnProperty(k))
- delete this[k];
-
- delete __instances[this.id];
- };
-
-
-
-
- /**
- * Returns a clone of the instances object or a specific running instance.
- *
- * @param {?string} id Eventually an instance ID.
- * @return {object} The related instance or a clone of the instances
- * object.
- */
- sigma.instances = function(id) {
- return arguments.length ?
- __instances[id] :
- sigma.utils.extend({}, __instances);
- };
-
-
-
- /**
- * The current version of sigma:
- */
- sigma.version = '1.2.1';
-
-
-
-
- /**
- * EXPORT:
- * *******
- */
- if (typeof this.sigma !== 'undefined')
- throw 'An object called sigma is already in the global scope.';
-
- this.sigma = sigma;
-
- }).call(this);
|