Personal blog written from scratch using Node.js, Bootstrap, and MySQL. https://jrtechs.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

739 lines
20 KiB

;(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);