7 // Adding Array helpers, if not present yet:
9 if (!Array.prototype.some) {
10 Array.prototype.some = function(fun /*, thisp*/) {
11 var len = this.length;
12 if (typeof fun != 'function') {
13 throw new TypeError();
16 var thisp = arguments[1];
17 for (var i = 0; i < len; i++) {
19 fun.call(thisp, this[i], i, this)) {
28 if (!Array.prototype.forEach) {
29 Array.prototype.forEach = function(fun /*, thisp*/) {
30 var len = this.length;
31 if (typeof fun != 'function') {
32 throw new TypeError();
35 var thisp = arguments[1];
36 for (var i = 0; i < len; i++) {
38 fun.call(thisp, this[i], i, this);
44 if (!Array.prototype.map) {
45 Array.prototype.map = function(fun /*, thisp*/) {
46 var len = this.length;
47 if (typeof fun != 'function') {
48 throw new TypeError();
51 var res = new Array(len);
52 var thisp = arguments[1];
53 for (var i = 0; i < len; i++) {
55 res[i] = fun.call(thisp, this[i], i, this);
63 if (!Array.prototype.filter) {
64 Array.prototype.filter = function(fun /*, thisp*/) {
65 var len = this.length;
66 if (typeof fun != 'function')
67 throw new TypeError();
69 var res = new Array();
70 var thisp = arguments[1];
71 for (var i = 0; i < len; i++) {
73 var val = this[i]; // in case fun mutates this
74 if (fun.call(thisp, val, i, this)) {
85 Object.keys = (function() {
86 var hasOwnProperty = Object.prototype.hasOwnProperty,
87 hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
94 'propertyIsEnumerable',
97 dontEnumsLength = dontEnums.length;
99 return function(obj) {
100 if (typeof obj !== 'object' &&
101 typeof obj !== 'function' ||
104 throw new TypeError('Object.keys called on non-object');
109 for (var prop in obj) {
110 if (hasOwnProperty.call(obj, prop)) result.push(prop);
113 if (hasDontEnumBug) {
114 for (var i = 0; i < dontEnumsLength; i++) {
115 if (hasOwnProperty.call(obj, dontEnums[i])) {
116 result.push(dontEnums[i]);
127 * sigma.js custom event dispatcher class.
129 * @this {sigma.classes.EventDispatcher}
131 sigma.classes.EventDispatcher = function() {
133 * An object containing all the different handlers bound to one or many
134 * events, indexed by these events.
136 * @type {Object.<string,Object>}
141 * Represents "this", without the well-known scope issue.
143 * @type {sigma.classes.EventDispatcher}
148 * Will execute the handler the next (and only the next) time that the
149 * indicated event (or the indicated events) will be triggered.
150 * @param {string} events The name of the event (or the events
151 * separated by spaces).
152 * @param {function(Object)} handler The handler to bind.
153 * @return {sigma.classes.EventDispatcher} Returns itself.
155 function one(events, handler) {
156 if (!handler || !events) {
160 var eArray = ((typeof events) == 'string') ? events.split(' ') : events;
162 eArray.forEach(function(event) {
177 * Will execute the handler everytime that the indicated event (or the
178 * indicated events) will be triggered.
179 * @param {string} events The name of the event (or the events
180 * separated by spaces).
181 * @param {function(Object)} handler The handler to bind.
182 * @return {sigma.classes.EventDispatcher} Returns itself.
184 function bind(events, handler) {
185 if (!handler || !events) {
189 var eArray = ((typeof events) == 'string') ? events.split(' ') : events;
191 eArray.forEach(function(event) {
206 * Unbinds the handler from a specified event (or specified events).
207 * @param {?string} events The name of the event (or the events
208 * separated by spaces). If undefined,
209 * then all handlers are unbound.
210 * @param {?function(Object)} handler The handler to unbind. If undefined,
211 * each handler bound to the event or the
212 * events will be unbound.
213 * @return {sigma.classes.EventDispatcher} Returns itself.
215 function unbind(events, handler) {
220 var eArray = typeof events == 'string' ? events.split(' ') : events;
223 eArray.forEach(function(event) {
225 _h[event] = _h[event].filter(function(e) {
226 return e['h'] != handler;
230 if (_h[event] && _h[event].length == 0) {
235 eArray.forEach(function(event) {
244 * Executes each handler bound to the event
245 * @param {string} type The type of the event.
246 * @param {?Object} content The content of the event (optional).
247 * @return {sigma.classes.EventDispatcher} Returns itself.
249 function dispatch(type, content) {
251 _h[type].forEach(function(e) {
259 _h[type] = _h[type].filter(function(e) {
267 /* PUBLIC INTERFACE: */
270 this.unbind = unbind;
271 this.dispatch = dispatch;
275 * A jQuery like properties management class. It works like jQuery .css()
276 * method: You can call it with juste one string to get the corresponding
277 * property, with a string and anything else to set the corresponding property,
278 * or directly with an object, and then each pair string / object (or any type)
279 * will be set in the properties.
281 * @this {sigma.classes.Cascade}
283 sigma.classes.Cascade = function() {
285 * This instance properties.
292 * The method to use to set/get any property of this instance.
293 * @param {(string|Object)} a1 If it is a string and if a2 is undefined,
294 * then it will return the corresponding
296 * If it is a string and if a2 is set, then it
297 * will set a2 as the property corresponding to
298 * a1, and return this.
299 * If it is an object, then each pair string /
300 * object (or any other type) will be set as a
302 * @param {*?} a2 The new property corresponding to a1 if a1 is
304 * @return {(*|sigma.classes.Cascade)} Returns itself or the corresponding
307 this.config = function(a1, a2) {
308 if (typeof a1 == 'string' && a2 == undefined) {
311 var o = (typeof a1 == 'object' && a2 == undefined) ? a1 : {};
312 if (typeof a1 == 'string') {
317 if (this.p[k] != undefined) {
327 // Define local shortcut:
330 // Define local package:
334 sigma.init = function(dom) {
335 var inst = new Sigma(dom, (++id).toString());
336 sigma.instances[id] = new SigmaPublic(inst);
337 return sigma.instances[id];
341 * The graph data model used in sigma.js.
343 * @extends sigma.classes.Cascade
344 * @extends sigma.classes.EventDispatcher
348 sigma.classes.Cascade.call(this);
349 sigma.classes.EventDispatcher.call(this);
352 * Represents "this", without the well-known scope issue.
359 * The different parameters that determine how the nodes and edges should be
360 * translated and rescaled.
369 // - 'inside' (default)
371 scalingMode: 'inside',
377 * Contains the borders of the graph. These are useful to avoid the user to
378 * drag the graph out of the canvas.
384 * Inserts a node in the graph.
385 * @param {string} id The node's ID.
386 * @param {object} params An object containing the different parameters
388 * @return {Graph} Returns itself.
390 function addNode(id, params) {
391 if (self.nodesIndex[id]) {
392 throw new Error('Node "' + id + '" already exists.');
395 params = params || {};
410 'label': id.toString(),
412 // Custom attributes :
416 for (var k in params) {
436 n['attr'][k] = params[k];
441 self.nodesIndex[id.toString()] = n;
447 * Generates the clone of a node, to make it easier to be exported.
449 * @param {Object} node The node to clone.
450 * @return {Object} The clone of the node.
452 function cloneNode(node) {
456 'size': node['size'],
457 'degree': node['degree'],
458 'inDegree': node['inDegree'],
459 'outDegree': node['outDegree'],
460 'displayX': node['displayX'],
461 'displayY': node['displayY'],
462 'displaySize': node['displaySize'],
463 'label': node['label'],
465 'color': node['color'],
466 'fixed': node['fixed'],
467 'active': node['active'],
468 'hidden': node['hidden'],
469 'forceLabel': node['forceLabel'],
475 * Checks the clone of a node, and inserts its values when possible. For
476 * example, it is possible to modify the size or the color of a node, but it
477 * is not possible to modify its display values or its id.
479 * @param {Object} node The original node.
480 * @param {Object} copy The clone.
481 * @return {Graph} Returns itself.
483 function checkNode(node, copy) {
484 for (var k in copy) {
508 node[k] = (copy[k] || '').toString();
511 node['attr'][k] = copy[k];
519 * Deletes one or several nodes from the graph, and the related edges.
520 * @param {(string|Array.<string>)} v A string ID, or an Array of several
522 * @return {Graph} Returns itself.
524 function dropNode(v) {
525 var a = (v instanceof Array ? v : [v]) || [];
526 var nodesIdsToRemove = {};
528 // Create hash to make lookups faster
529 a.forEach(function(id) {
530 if (self.nodesIndex[id]) {
531 nodesIdsToRemove[id] = true;
533 sigma.log('Node "' + id + '" does not exist.');
537 var indexesToRemove = [];
538 self.nodes.forEach(function(n, i) {
539 if (n['id'] in nodesIdsToRemove) {
540 // Add to front, so we have a reverse-sorted list
541 indexesToRemove.unshift(i);
542 // No edges means we are done
543 if (n['degree'] == 0) {
544 delete nodesIdsToRemove[n['id']];
549 indexesToRemove.forEach(function(index) {
550 self.nodes.splice(index, 1);
553 self.edges = self.edges.filter(function(e) {
554 if (e['source']['id'] in nodesIdsToRemove) {
555 delete self.edgesIndex[e['id']];
556 e['target']['degree']--;
557 e['target']['inDegree']--;
559 }else if (e['target']['id'] in nodesIdsToRemove) {
560 delete self.edgesIndex[e['id']];
561 e['source']['degree']--;
562 e['source']['outDegree']--;
572 * Inserts an edge in the graph.
573 * @param {string} id The edge ID.
574 * @param {string} source The ID of the edge source.
575 * @param {string} target The ID of the edge target.
576 * @param {object} params An object containing the different parameters
578 * @return {Graph} Returns itself.
580 function addEdge(id, source, target, params) {
581 if (self.edgesIndex[id]) {
582 throw new Error('Edge "' + id + '" already exists.');
585 if (!self.nodesIndex[source]) {
586 var s = 'Edge\'s source "' + source + '" does not exist yet.';
590 if (!self.nodesIndex[target]) {
591 var s = 'Edge\'s target "' + target + '" does not exist yet.';
595 params = params || {};
597 'source': self.nodesIndex[source],
598 'target': self.nodesIndex[target],
602 'label': id.toString(),
608 e['source']['degree']++;
609 e['source']['outDegree']++;
610 e['target']['degree']++;
611 e['target']['inDegree']++;
613 for (var k in params) {
627 e[k] = params[k].toString();
630 e[k] = params[k].toString();
636 e['attr'][k] = params[k];
641 self.edgesIndex[id.toString()] = e;
647 * Generates the clone of a edge, to make it easier to be exported.
649 * @param {Object} edge The edge to clone.
650 * @return {Object} The clone of the edge.
652 function cloneEdge(edge) {
654 'source': edge['source']['id'],
655 'target': edge['target']['id'],
656 'size': edge['size'],
657 'type': edge['type'],
658 'weight': edge['weight'],
659 'displaySize': edge['displaySize'],
660 'label': edge['label'],
661 'hidden': edge['hidden'],
663 'attr': edge['attr'],
664 'color': edge['color']
669 * Checks the clone of an edge, and inserts its values when possible. For
670 * example, it is possible to modify the label or the type of an edge, but it
671 * is not possible to modify its display values or its id.
673 * @param {Object} edge The original edge.
674 * @param {Object} copy The clone.
675 * @return {Graph} Returns itself.
677 function checkEdge(edge, copy) {
678 for (var k in copy) {
689 edge[k] = self.nodesIndex[k] || edge[k];
697 edge[k] = (copy[k] || '').toString();
700 edge['attr'][k] = copy[k];
708 * Deletes one or several edges from the graph.
709 * @param {(string|Array.<string>)} v A string ID, or an Array of several
711 * @return {Graph} Returns itself.
713 function dropEdge(v) {
714 var a = (v instanceof Array ? v : [v]) || [];
716 a.forEach(function(id) {
717 if (self.edgesIndex[id]) {
718 self.edgesIndex[id]['source']['degree']--;
719 self.edgesIndex[id]['source']['outDegree']--;
720 self.edgesIndex[id]['target']['degree']--;
721 self.edgesIndex[id]['target']['inDegree']--;
724 self.edges.some(function(n, i) {
732 index != null && self.edges.splice(index, 1);
733 delete self.edgesIndex[id];
735 sigma.log('Edge "' + id + '" does not exist.');
743 * Deletes every nodes and edges from the graph.
744 * @return {Graph} Returns itself.
748 self.nodesIndex = {};
750 self.edgesIndex = {};
756 * Computes the display x, y and size of each node, relatively to the
757 * original values and the borders determined in the parameters, such as
758 * each node is in the described area.
759 * @param {number} w The area width (actually the width of the DOM
761 * @param {number} h The area height (actually the height of the
763 * @param {boolean} parseNodes Indicates if the nodes have to be parsed.
764 * @param {boolean} parseEdges Indicates if the edges have to be parsed.
765 * @return {Graph} Returns itself.
767 function rescale(w, h, parseNodes, parseEdges) {
768 var weightMax = 0, sizeMax = 0;
770 parseNodes && self.nodes.forEach(function(node) {
771 sizeMax = Math.max(node['size'], sizeMax);
774 parseEdges && self.edges.forEach(function(edge) {
775 weightMax = Math.max(edge['size'], weightMax);
778 sizeMax = sizeMax || 1;
779 weightMax = weightMax || 1;
781 // Recenter the nodes:
782 var xMin, xMax, yMin, yMax;
783 parseNodes && self.nodes.forEach(function(node) {
784 xMax = Math.max(node['x'], xMax || node['x']);
785 xMin = Math.min(node['x'], xMin || node['x']);
786 yMax = Math.max(node['y'], yMax || node['y']);
787 yMin = Math.min(node['y'], yMin || node['y']);
790 // First, we compute the scaling ratio, without considering the sizes
791 // of the nodes : Each node will have its center in the canvas, but might
792 // be partially out of it.
793 var scale = self.p.scalingMode == 'outside' ?
794 Math.max(w / Math.max(xMax - xMin, 1),
795 h / Math.max(yMax - yMin, 1)) :
796 Math.min(w / Math.max(xMax - xMin, 1),
797 h / Math.max(yMax - yMin, 1));
799 // Then, we correct that scaling ratio considering a margin, which is
800 // basically the size of the biggest node.
801 // This has to be done as a correction since to compare the size of the
802 // biggest node to the X and Y values, we have to first get an
803 // approximation of the scaling ratio.
804 var margin = (self.p.maxNodeSize || sizeMax) / scale;
810 scale = self.p.scalingMode == 'outside' ?
811 Math.max(w / Math.max(xMax - xMin, 1),
812 h / Math.max(yMax - yMin, 1)) :
813 Math.min(w / Math.max(xMax - xMin, 1),
814 h / Math.max(yMax - yMin, 1));
816 // Size homothetic parameters:
818 if (!self.p.maxNodeSize && !self.p.minNodeSize) {
821 }else if (self.p.maxNodeSize == self.p.minNodeSize) {
823 b = self.p.maxNodeSize;
825 a = (self.p.maxNodeSize - self.p.minNodeSize) / sizeMax;
826 b = self.p.minNodeSize;
830 if (!self.p.maxEdgeSize && !self.p.minEdgeSize) {
833 }else if (self.p.maxEdgeSize == self.p.minEdgeSize) {
835 d = self.p.minEdgeSize;
837 c = (self.p.maxEdgeSize - self.p.minEdgeSize) / weightMax;
838 d = self.p.minEdgeSize;
841 // Rescale the nodes:
842 parseNodes && self.nodes.forEach(function(node) {
843 node['displaySize'] = node['size'] * a + b;
845 if (!node['fixed']) {
846 node['displayX'] = (node['x'] - (xMax + xMin) / 2) * scale + w / 2;
847 node['displayY'] = (node['y'] - (yMax + yMin) / 2) * scale + h / 2;
851 parseEdges && self.edges.forEach(function(edge) {
852 edge['displaySize'] = edge['size'] * c + d;
859 * Translates the display values of the nodes and edges relatively to the
860 * scene position and zoom ratio.
861 * @param {number} sceneX The x position of the scene.
862 * @param {number} sceneY The y position of the scene.
863 * @param {number} ratio The zoom ratio of the scene.
864 * @param {boolean} parseNodes Indicates if the nodes have to be parsed.
865 * @param {boolean} parseEdges Indicates if the edges have to be parsed.
866 * @return {Graph} Returns itself.
868 function translate(sceneX, sceneY, ratio, parseNodes, parseEdges) {
869 var sizeRatio = Math.pow(ratio, self.p.nodesPowRatio);
870 parseNodes && self.nodes.forEach(function(node) {
871 if (!node['fixed']) {
872 node['displayX'] = node['displayX'] * ratio + sceneX;
873 node['displayY'] = node['displayY'] * ratio + sceneY;
876 node['displaySize'] = node['displaySize'] * sizeRatio;
879 sizeRatio = Math.pow(ratio, self.p.edgesPowRatio);
880 parseEdges && self.edges.forEach(function(edge) {
881 edge['displaySize'] = edge['displaySize'] * sizeRatio;
888 * Determines the borders of the graph as it will be drawn. It is used to
889 * avoid the user to drag the graph out of the canvas.
891 function setBorders() {
894 self.nodes.forEach(function(node) {
895 self.borders.minX = Math.min(
896 self.borders.minX == undefined ?
897 node['displayX'] - node['displaySize'] :
899 node['displayX'] - node['displaySize']
902 self.borders.maxX = Math.max(
903 self.borders.maxX == undefined ?
904 node['displayX'] + node['displaySize'] :
906 node['displayX'] + node['displaySize']
909 self.borders.minY = Math.min(
910 self.borders.minY == undefined ?
911 node['displayY'] - node['displaySize'] :
913 node['displayY'] - node['displaySize']
916 self.borders.maxY = Math.max(
917 self.borders.maxY == undefined ?
918 node['displayY'] - node['displaySize'] :
920 node['displayY'] - node['displaySize']
926 * Checks which nodes are under the (mX, mY) points, representing the mouse
928 * @param {number} mX The mouse X position.
929 * @param {number} mY The mouse Y position.
930 * @return {Graph} Returns itself.
932 function checkHover(mX, mY) {
933 var dX, dY, s, over = [], out = [];
934 self.nodes.forEach(function(node) {
935 if (node['hidden']) {
936 node['hover'] = false;
940 dX = Math.abs(node['displayX'] - mX);
941 dY = Math.abs(node['displayY'] - mY);
942 s = node['displaySize'];
944 var oldH = node['hover'];
945 var newH = dX < s && dY < s && Math.sqrt(dX * dX + dY * dY) < s;
948 node['hover'] = false;
950 } else if (newH && !oldH) {
951 node['hover'] = true;
956 over.length && self.dispatch('overnodes', over);
957 out.length && self.dispatch('outnodes', out);
963 * Applies a function to a clone of each node (or indicated nodes), and then
964 * tries to apply the modifications made on the clones to the original nodes.
965 * @param {function(Object)} fun The function to execute.
966 * @param {?Array.<string>} ids An Array of node IDs (optional).
967 * @return {Graph} Returns itself.
969 function iterNodes(fun, ids) {
970 var a = ids ? ids.map(function(id) {
971 return self.nodesIndex[id];
974 var aCopies = a.map(cloneNode);
975 aCopies.forEach(fun);
977 a.forEach(function(n, i) {
978 checkNode(n, aCopies[i]);
985 * Applies a function to a clone of each edge (or indicated edges), and then
986 * tries to apply the modifications made on the clones to the original edges.
987 * @param {function(Object)} fun The function to execute.
988 * @param {?Array.<string>} ids An Array of edge IDs (optional).
989 * @return {Graph} Returns itself.
991 function iterEdges(fun, ids) {
992 var a = ids ? ids.map(function(id) {
993 return self.edgesIndex[id];
996 var aCopies = a.map(cloneEdge);
997 aCopies.forEach(fun);
999 a.forEach(function(e, i) {
1000 checkEdge(e, aCopies[i]);
1007 * Returns a specific node clone or an array of specified node clones.
1008 * @param {(string|Array.<string>)} ids The ID or an array of node IDs.
1009 * @return {(Object|Array.<Object>)} The clone or the array of clones.
1011 function getNodes(ids) {
1012 var a = ((ids instanceof Array ? ids : [ids]) || []).map(function(id) {
1013 return cloneNode(self.nodesIndex[id]);
1016 return (ids instanceof Array ? a : a[0]);
1020 * Returns a specific edge clone or an array of specified edge clones.
1021 * @param {(string|Array.<string>)} ids The ID or an array of edge IDs.
1022 * @return {(Object|Array.<Object>)} The clone or the array of clones.
1024 function getEdges(ids) {
1025 var a = ((ids instanceof Array ? ids : [ids]) || []).map(function(id) {
1026 return cloneEdge(self.edgesIndex[id]);
1029 return (ids instanceof Array ? a : a[0]);
1034 this.addNode = addNode;
1035 this.addEdge = addEdge;
1036 this.dropNode = dropNode;
1037 this.dropEdge = dropEdge;
1039 this.iterEdges = iterEdges;
1040 this.iterNodes = iterNodes;
1042 this.getEdges = getEdges;
1043 this.getNodes = getNodes;
1046 this.rescale = rescale;
1047 this.translate = translate;
1048 this.setBorders = setBorders;
1049 this.checkHover = checkHover;
1053 * Sigma is the main class. It represents the core of any instance id sigma.js.
1054 * It is private and can be initialized only from inside sigma.js. To see its
1055 * public interface, see {@link SigmaPublic}.
1056 * It owns its own {@link Graph}, {@link MouseCaptor}, {@link Plotter}
1057 * and {@link Monitor}.
1059 * @extends sigma.classes.Cascade
1060 * @extends sigma.classes.EventDispatcher
1061 * @param {element} root The DOM root of this instance (a div, for example).
1062 * @param {string} id The ID of this instance.
1065 function Sigma(root, id) {
1066 sigma.classes.Cascade.call(this);
1067 sigma.classes.EventDispatcher.call(this);
1070 * Represents "this", without the well-known scope issue.
1077 * The ID of the instance.
1080 this.id = id.toString();
1083 * The different parameters that define how this instance should work.
1084 * @see sigma.classes.Cascade
1095 drawHoverNodes: true,
1096 drawActiveNodes: true
1100 * The root DOM element of this instance, containing every other elements.
1103 this.domRoot = root;
1106 * The width of this instance - initially, the root's width.
1109 this.width = this.domRoot.offsetWidth;
1112 * The height of this instance - initially, the root's height.
1115 this.height = this.domRoot.offsetHeight;
1118 * The graph of this instance - initiallyempty.
1121 this.graph = new Graph();
1124 * An object referencing every DOM elements used by this instance.
1127 this.domElements = {};
1129 initDOM('edges', 'canvas');
1130 initDOM('nodes', 'canvas');
1131 initDOM('labels', 'canvas');
1132 initDOM('hover', 'canvas');
1133 initDOM('monitor', 'div');
1134 initDOM('mouse', 'canvas');
1137 * The class dedicated to manage the drawing process of the graph of the
1141 this.plotter = new Plotter(
1142 this.domElements.nodes.getContext('2d'),
1143 this.domElements.edges.getContext('2d'),
1144 this.domElements.labels.getContext('2d'),
1145 this.domElements.hover.getContext('2d'),
1152 * The class dedicated to monitor different probes about the running
1153 * processes or the data, such as the number of nodes or edges, or how
1154 * many times the graph is drawn per second.
1157 this.monitor = new Monitor(
1159 this.domElements.monitor
1163 * The class dedicated to manage the different mouse events.
1164 * @type {MouseCaptor}
1166 this.mousecaptor = new MouseCaptor(
1167 this.domElements.mouse,
1171 // Interaction listeners:
1172 this.mousecaptor.bind('drag interpolate', function(e) {
1174 self.p.auto ? 2 : self.p.drawNodes,
1175 self.p.auto ? 0 : self.p.drawEdges,
1176 self.p.auto ? 2 : self.p.drawLabels,
1179 }).bind('stopdrag stopinterpolate', function(e) {
1181 self.p.auto ? 2 : self.p.drawNodes,
1182 self.p.auto ? 1 : self.p.drawEdges,
1183 self.p.auto ? 2 : self.p.drawLabels,
1186 }).bind('mousedown mouseup', function(e) {
1187 var targeted = self.graph.nodes.filter(function(n) {
1188 return !!n['hover'];
1189 }).map(function(n) {
1194 e['type'] == 'mousedown' ?
1199 if (targeted.length) {
1201 e['type'] == 'mousedown' ?
1207 }).bind('move', function() {
1208 self.domElements.hover.getContext('2d').clearRect(
1211 self.domElements.hover.width,
1212 self.domElements.hover.height
1219 sigma.chronos.bind('startgenerators', function() {
1220 if (sigma.chronos.getGeneratorsIDs().some(function(id) {
1221 return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
1224 self.p.auto ? 2 : self.p.drawNodes,
1225 self.p.auto ? 0 : self.p.drawEdges,
1226 self.p.auto ? 2 : self.p.drawLabels
1229 }).bind('stopgenerators', function() {
1234 * Resizes the element, and redraws the graph with the last settings.
1235 * @param {?number} w The new width (if undefined, it will use the root
1237 * @param {?number} h The new height (if undefined, it will use the root
1239 * @return {Sigma} Returns itself.
1241 function resize(w, h) {
1242 var oldW = self.width, oldH = self.height;
1244 if (w != undefined && h != undefined) {
1248 self.width = self.domRoot.offsetWidth;
1249 self.height = self.domRoot.offsetHeight;
1252 if (oldW != self.width || oldH != self.height) {
1253 for (var k in self.domElements) {
1254 self.domElements[k].setAttribute('width', self.width + 'px');
1255 self.domElements[k].setAttribute('height', self.height + 'px');
1258 self.plotter.resize(self.width, self.height);
1271 * Kills every drawing task currently running. Basically, it stops this
1272 * instance's drawing process.
1273 * @return {Sigma} Returns itself.
1275 function clearSchedule() {
1276 sigma.chronos.removeTask(
1277 'node_' + self.id, 2
1279 'edge_' + self.id, 2
1281 'label_' + self.id, 2
1287 * Initialize a DOM element, that will be stores by this instance, to make
1288 * automatic these elements resizing.
1290 * @param {string} id The element's ID.
1291 * @param {string} type The element's nodeName (Example : canvas, div, ...).
1292 * @return {Sigma} Returns itself.
1294 function initDOM(id, type) {
1295 self.domElements[id] = document.createElement(type);
1296 self.domElements[id].style.position = 'absolute';
1297 self.domElements[id].setAttribute('id', 'sigma_' + id + '_' + self.id);
1298 self.domElements[id].setAttribute('class', 'sigma_' + id + '_' + type);
1299 self.domElements[id].setAttribute('width', self.width + 'px');
1300 self.domElements[id].setAttribute('height', self.height + 'px');
1302 self.domRoot.appendChild(self.domElements[id]);
1307 * Starts the graph drawing process. The three first parameters indicate
1308 * how the different layers have to be drawn:
1309 * . -1: The layer is not drawn, but it is not erased.
1310 * . 0: The layer is not drawn.
1311 * . 1: The layer is drawn progressively.
1312 * . 2: The layer is drawn directly.
1313 * @param {?number} nodes Determines if and how the nodes must be drawn.
1314 * @param {?number} edges Determines if and how the edges must be drawn.
1315 * @param {?number} labels Determines if and how the labels must be drawn.
1316 * @param {?boolean} safe If true, nothing will happen if any generator
1317 * affiliated to this instance is currently running
1318 * (an iterative layout, for example).
1319 * @return {Sigma} Returns itself.
1321 function draw(nodes, edges, labels, safe) {
1322 if (safe && sigma.chronos.getGeneratorsIDs().some(function(id) {
1323 return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
1328 var n = (nodes == undefined) ? self.p.drawNodes : nodes;
1329 var e = (edges == undefined) ? self.p.drawEdges : edges;
1330 var l = (labels == undefined) ? self.p.drawLabels : labels;
1338 self.p.lastNodes = n;
1339 self.p.lastEdges = e;
1340 self.p.lastLabels = l;
1353 self.mousecaptor.checkBorders(
1359 self.graph.translate(
1360 self.mousecaptor.stageX,
1361 self.mousecaptor.stageY,
1362 self.mousecaptor.ratio,
1372 for (var k in self.domElements) {
1374 self.domElements[k].nodeName.toLowerCase() == 'canvas' &&
1375 (params[k] == undefined || params[k] >= 0)
1377 self.domElements[k].getContext('2d').clearRect(
1380 self.domElements[k].width,
1381 self.domElements[k].height
1386 self.plotter.currentEdgeIndex = 0;
1387 self.plotter.currentNodeIndex = 0;
1388 self.plotter.currentLabelIndex = 0;
1390 var previous = null;
1395 while (self.plotter.task_drawNode()) {}
1397 sigma.chronos.addTask(
1398 self.plotter.task_drawNode,
1404 previous = 'node_' + self.id;
1410 while (self.plotter.task_drawLabel()) {}
1413 sigma.chronos.queueTask(
1414 self.plotter.task_drawLabel,
1419 sigma.chronos.addTask(
1420 self.plotter.task_drawLabel,
1427 previous = 'label_' + self.id;
1433 while (self.plotter.task_drawEdge()) {}
1436 sigma.chronos.queueTask(
1437 self.plotter.task_drawEdge,
1442 sigma.chronos.addTask(
1443 self.plotter.task_drawEdge,
1450 previous = 'edge_' + self.id;
1460 start && sigma.chronos.runTasks();
1465 * Draws the hover and active nodes labels.
1466 * @return {Sigma} Returns itself.
1468 function refresh() {
1469 self.domElements.hover.getContext('2d').clearRect(
1472 self.domElements.hover.width,
1473 self.domElements.hover.height
1483 * Draws the hover nodes labels. This method is applied directly, and does
1484 * not use the pseudo-asynchronous tasks process.
1485 * @return {Sigma} Returns itself.
1487 function drawHover() {
1488 if (self.p.drawHoverNodes) {
1489 self.graph.checkHover(
1490 self.mousecaptor.mouseX,
1491 self.mousecaptor.mouseY
1494 self.graph.nodes.forEach(function(node) {
1495 if (node.hover && !node.active) {
1496 self.plotter.drawHoverNode(node);
1505 * Draws the active nodes labels. This method is applied directly, and does
1506 * not use the pseudo-asynchronous tasks process.
1507 * @return {Sigma} Returns itself.
1509 function drawActive() {
1510 if (self.p.drawActiveNodes) {
1511 self.graph.nodes.forEach(function(node) {
1513 self.plotter.drawActiveNode(node);
1522 for (var i = 0; i < local.plugins.length; i++) {
1523 local.plugins[i](this);
1527 this.resize = resize;
1528 this.refresh = refresh;
1529 this.drawHover = drawHover;
1530 this.drawActive = drawActive;
1531 this.clearSchedule = clearSchedule;
1533 window.addEventListener('resize', function() {
1538 function SigmaPublic(sigmaInstance) {
1539 var s = sigmaInstance;
1541 sigma.classes.EventDispatcher.call(this);
1543 this._core = sigmaInstance;
1545 this.kill = function() {
1549 this.getID = function() {
1554 this.configProperties = function(a1, a2) {
1555 var res = s.config(a1, a2);
1556 return res == s ? self : res;
1559 this.drawingProperties = function(a1, a2) {
1560 var res = s.plotter.config(a1, a2);
1561 return res == s.plotter ? self : res;
1564 this.mouseProperties = function(a1, a2) {
1565 var res = s.mousecaptor.config(a1, a2);
1566 return res == s.mousecaptor ? self : res;
1569 this.graphProperties = function(a1, a2) {
1570 var res = s.graph.config(a1, a2);
1571 return res == s.graph ? self : res;
1574 this.getMouse = function() {
1576 mouseX: s.mousecaptor.mouseX,
1577 mouseY: s.mousecaptor.mouseY,
1578 down: s.mousecaptor.isMouseDown
1583 this.position = function(stageX, stageY, ratio) {
1584 if (arguments.length == 0) {
1586 stageX: s.mousecaptor.stageX,
1587 stageY: s.mousecaptor.stageY,
1588 ratio: s.mousecaptor.ratio
1591 s.mousecaptor.stageX = stageX != undefined ?
1593 s.mousecaptor.stageX;
1594 s.mousecaptor.stageY = stageY != undefined ?
1596 s.mousecaptor.stageY;
1597 s.mousecaptor.ratio = ratio != undefined ?
1599 s.mousecaptor.ratio;
1605 this.goTo = function(stageX, stageY, ratio) {
1606 s.mousecaptor.interpolate(stageX, stageY, ratio);
1610 this.zoomTo = function(x, y, ratio) {
1612 Math.max(s.mousecaptor.config('minRatio'), ratio),
1613 s.mousecaptor.config('maxRatio')
1615 if (ratio == s.mousecaptor.ratio) {
1616 s.mousecaptor.interpolate(
1617 x - s.width / 2 + s.mousecaptor.stageX,
1618 y - s.height / 2 + s.mousecaptor.stageY
1621 s.mousecaptor.interpolate(
1622 (ratio * x - s.mousecaptor.ratio * s.width/2) /
1623 (ratio - s.mousecaptor.ratio),
1624 (ratio * y - s.mousecaptor.ratio * s.height/2) /
1625 (ratio - s.mousecaptor.ratio),
1632 this.resize = function(w, h) {
1637 this.draw = function(nodes, edges, labels, safe) {
1638 s.draw(nodes, edges, labels, safe);
1642 this.refresh = function() {
1648 this.addGenerator = function(id, task, condition) {
1649 sigma.chronos.addGenerator(id + '_ext_' + s.id, task, condition);
1653 this.removeGenerator = function(id) {
1654 sigma.chronos.removeGenerator(id + '_ext_' + s.id);
1659 this.addNode = function(id, params) {
1660 s.graph.addNode(id, params);
1664 this.addEdge = function(id, source, target, params) {
1665 s.graph.addEdge(id, source, target, params);
1669 this.dropNode = function(v) {
1670 s.graph.dropNode(v);
1674 this.dropEdge = function(v) {
1675 s.graph.dropEdge(v);
1679 this.pushGraph = function(object, safe) {
1680 object.nodes && object.nodes.forEach(function(node) {
1681 node['id'] && (!safe || !s.graph.nodesIndex[node['id']]) &&
1682 self.addNode(node['id'], node);
1686 object.edges && object.edges.forEach(function(edge) {
1687 validID = edge['source'] && edge['target'] && edge['id'];
1689 (!safe || !s.graph.edgesIndex[edge['id']]) &&
1701 this.emptyGraph = function() {
1706 this.getNodesCount = function() {
1707 return s.graph.nodes.length;
1710 this.getEdgesCount = function() {
1711 return s.graph.edges.length;
1714 this.iterNodes = function(fun, ids) {
1715 s.graph.iterNodes(fun, ids);
1719 this.iterEdges = function(fun, ids) {
1720 s.graph.iterEdges(fun, ids);
1724 this.getNodes = function(ids) {
1725 return s.graph.getNodes(ids);
1728 this.getEdges = function(ids) {
1729 return s.graph.getEdges(ids);
1733 this.activateMonitoring = function() {
1734 return s.monitor.activate();
1737 this.desactivateMonitoring = function() {
1738 return s.monitor.desactivate();
1742 s.bind('downnodes upnodes downgraph upgraph', function(e) {
1743 self.dispatch(e.type, e.content);
1746 s.graph.bind('overnodes outnodes', function(e) {
1747 self.dispatch(e.type, e.content);
1752 * This class listen to all the different mouse events, to normalize them and
1753 * dispatch action events instead (from "startinterpolate" to "isdragging",
1756 * @extends sigma.classes.Cascade
1757 * @extends sigma.classes.EventDispatcher
1758 * @param {element} dom The DOM element to bind the handlers on.
1759 * @this {MouseCaptor}
1761 function MouseCaptor(dom) {
1762 sigma.classes.Cascade.call(this);
1763 sigma.classes.EventDispatcher.call(this);
1766 * Represents "this", without the well-known scope issue.
1768 * @type {MouseCaptor}
1773 * The DOM element to bind the handlers on.
1779 * The different parameters that define how this instance should work.
1780 * @see sigma.classes.Cascade
1790 directZooming: false,
1805 var targetRatio = 1;
1806 var targetStageX = 0;
1807 var targetStageY = 0;
1810 var lastStageX2 = 0;
1812 var lastStageY2 = 0;
1815 var isZooming = false;
1824 this.isMouseDown = false;
1827 * Extract the local X position from a mouse event.
1829 * @param {event} e A mouse event.
1830 * @return {number} The local X value of the mouse.
1833 return e.offsetX != undefined && e.offsetX ||
1834 e.layerX != undefined && e.layerX ||
1835 e.clientX != undefined && e.clientX;
1839 * Extract the local Y position from a mouse event.
1841 * @param {event} e A mouse event.
1842 * @return {number} The local Y value of the mouse.
1845 return e.offsetY != undefined && e.offsetY ||
1846 e.layerY != undefined && e.layerY ||
1847 e.clientY != undefined && e.clientY;
1851 * Extract the wheel delta from a mouse event.
1853 * @param {event} e A mouse event.
1854 * @return {number} The wheel delta of the mouse.
1856 function getDelta(e) {
1857 return e.wheelDelta != undefined && e.wheelDelta ||
1858 e.detail != undefined && -e.detail;
1862 * The handler listening to the 'move' mouse event. It will set the mouseX
1863 * and mouseY values as the mouse position values, prevent the default event,
1864 * and dispatch a 'move' event.
1866 * @param {event} event A 'move' mouse event.
1868 function moveHandler(event) {
1869 oldMouseX = self.mouseX;
1870 oldMouseY = self.mouseY;
1872 self.mouseX = getX(event);
1873 self.mouseY = getY(event);
1875 self.isMouseDown && drag(event);
1876 self.dispatch('move');
1878 if (event.preventDefault) {
1879 event.preventDefault();
1881 event.returnValue = false;
1886 * The handler listening to the 'up' mouse event. It will set the isMouseDown
1887 * value as false, dispatch a 'mouseup' event, and trigger stopDrag().
1889 * @param {event} event A 'up' mouse event.
1891 function upHandler(event) {
1892 if (self.p.mouseEnabled && self.isMouseDown) {
1893 self.isMouseDown = false;
1894 self.dispatch('mouseup');
1897 if (event.preventDefault) {
1898 event.preventDefault();
1900 event.returnValue = false;
1906 * The handler listening to the 'down' mouse event. It will set the
1907 * isMouseDown value as true, dispatch a 'mousedown' event, and trigger
1910 * @param {event} event A 'down' mouse event.
1912 function downHandler(event) {
1913 if (self.p.mouseEnabled) {
1914 self.isMouseDown = true;
1915 oldMouseX = self.mouseX;
1916 oldMouseY = self.mouseY;
1918 self.dispatch('mousedown');
1922 if (event.preventDefault) {
1923 event.preventDefault();
1925 event.returnValue = false;
1931 * The handler listening to the 'wheel' mouse event. It will trigger
1932 * {@link startInterpolate} with the event delta as parameter.
1934 * @param {event} event A 'wheel' mouse event.
1936 function wheelHandler(event) {
1937 if (self.p.mouseEnabled) {
1941 self.ratio * (getDelta(event) > 0 ?
1942 self.p.zoomMultiply :
1943 1 / self.p.zoomMultiply)
1946 if (self.p['blockScroll']) {
1947 if (event.preventDefault) {
1948 event.preventDefault();
1950 event.returnValue = false;
1957 * Will start computing the scene X and Y, until {@link stopDrag} is
1960 function startDrag() {
1961 oldStageX = self.stageX;
1962 oldStageY = self.stageY;
1963 startX = self.mouseX;
1964 startY = self.mouseY;
1966 lastStageX = self.stageX;
1967 lastStageX2 = self.stageX;
1968 lastStageY = self.stageY;
1969 lastStageY2 = self.stageY;
1971 self.dispatch('startdrag');
1975 * Stops computing the scene position.
1977 function stopDrag() {
1978 if (oldStageX != self.stageX || oldStageY != self.stageY) {
1980 self.stageX + self.p.inertia * (self.stageX - lastStageX2),
1981 self.stageY + self.p.inertia * (self.stageY - lastStageY2)
1987 * Computes the position of the scene, relatively to the mouse position, and
1988 * dispatches a "drag" event.
1991 var newStageX = self.mouseX - startX + oldStageX;
1992 var newStageY = self.mouseY - startY + oldStageY;
1994 if (newStageX != self.stageX || newStageY != self.stageY) {
1995 lastStageX2 = lastStageX;
1996 lastStageY2 = lastStageY;
1998 lastStageX = newStageX;
1999 lastStageY = newStageY;
2001 self.stageX = newStageX;
2002 self.stageY = newStageY;
2003 self.dispatch('drag');
2008 * Will start computing the scene zoom ratio, until {@link stopInterpolate} is
2010 * @param {number} x The new stage X.
2011 * @param {number} y The new stage Y.
2012 * @param {number} ratio The new zoom ratio.
2014 function startInterpolate(x, y, ratio) {
2015 if (self.isMouseDown) {
2019 window.clearInterval(self.interpolationID);
2020 isZooming = ratio != undefined;
2022 oldStageX = self.stageX;
2025 oldStageY = self.stageY;
2028 oldRatio = self.ratio;
2029 targetRatio = ratio || self.ratio;
2030 targetRatio = Math.min(
2031 Math.max(targetRatio, self.p.minRatio),
2036 self.p.directZooming ?
2037 1 - (isZooming ? self.p.zoomDelta : self.p.dragDelta) :
2041 self.ratio != targetRatio ||
2042 self.stageX != targetStageX ||
2043 self.stageY != targetStageY
2046 self.interpolationID = window.setInterval(interpolate, 50);
2047 self.dispatch('startinterpolate');
2052 * Stops the move interpolation.
2054 function stopInterpolate() {
2055 var oldRatio = self.ratio;
2058 self.ratio = targetRatio;
2059 self.stageX = targetStageX +
2060 (self.stageX - targetStageX) *
2063 self.stageY = targetStageY +
2064 (self.stageY - targetStageY) *
2068 self.stageX = targetStageX;
2069 self.stageY = targetStageY;
2072 self.dispatch('stopinterpolate');
2076 * Computes the interpolate ratio and the position of the scene, relatively
2077 * to the last mouse event delta received, and dispatches a "interpolate"
2080 function interpolate() {
2081 progress += (isZooming ? self.p.zoomDelta : self.p.dragDelta);
2082 progress = Math.min(progress, 1);
2084 var k = sigma.easing.quadratic.easeout(progress);
2085 var oldRatio = self.ratio;
2087 self.ratio = oldRatio * (1 - k) + targetRatio * k;
2090 self.stageX = targetStageX +
2091 (self.stageX - targetStageX) *
2095 self.stageY = targetStageY +
2096 (self.stageY - targetStageY) *
2100 self.stageX = oldStageX * (1 - k) + targetStageX * k;
2101 self.stageY = oldStageY * (1 - k) + targetStageY * k;
2104 self.dispatch('interpolate');
2105 if (progress >= 1) {
2106 window.clearInterval(self.interpolationID);
2112 * Checks that there is always a part of the graph that is displayed, to
2113 * avoid the user to drag the graph out of the stage.
2114 * @param {Object} b An object containing the borders of the graph.
2115 * @param {number} width The width of the stage.
2116 * @param {number} height The height of the stage.
2117 * @return {MouseCaptor} Returns itself.
2119 function checkBorders(b, width, height) {
2120 // TODO : Find the good formula
2121 /*if (!isNaN(b.minX) && !isNaN(b.maxX)) {
2122 self.stageX = Math.min(
2123 self.stageX = Math.max(
2125 (b.minX - width) * self.ratio +
2126 self.p.marginRatio*(b.maxX - b.minX)
2128 (b.maxX - width) * self.ratio +
2130 self.p.marginRatio*(b.maxX - b.minX)
2134 if (!isNaN(b.minY) && !isNaN(b.maxY)) {
2135 self.stageY = Math.min(
2136 self.stageY = Math.max(
2138 (b.minY - height) * self.ratio +
2139 self.p.marginRatio*(b.maxY - b.minY)
2141 (b.maxY - height) * self.ratio +
2143 self.p.marginRatio*(b.maxY - b.minY)
2151 dom.addEventListener('DOMMouseScroll', wheelHandler, true);
2152 dom.addEventListener('mousewheel', wheelHandler, true);
2153 dom.addEventListener('mousemove', moveHandler, true);
2154 dom.addEventListener('mousedown', downHandler, true);
2155 document.addEventListener('mouseup', upHandler, true);
2157 this.checkBorders = checkBorders;
2158 this.interpolate = startInterpolate;
2162 * This class draws the graph on the different canvas DOM elements. It just
2163 * contains all the different methods to draw the graph, synchronously or
2164 * pseudo-asynchronously.
2166 * @param {CanvasRenderingContext2D} nodesCtx Context dedicated to draw nodes.
2167 * @param {CanvasRenderingContext2D} edgesCtx Context dedicated to draw edges.
2168 * @param {CanvasRenderingContext2D} labelsCtx Context dedicated to draw
2170 * @param {CanvasRenderingContext2D} hoverCtx Context dedicated to draw hover
2172 * @param {Graph} graph A reference to the graph to
2174 * @param {number} w The width of the DOM root
2176 * @param {number} h The width of the DOM root
2178 * @extends sigma.classes.Cascade
2181 function Plotter(nodesCtx, edgesCtx, labelsCtx, hoverCtx, graph, w, h) {
2182 sigma.classes.Cascade.call(this);
2185 * Represents "this", without the well-known scope issue.
2192 * The different parameters that define how this instance should work.
2193 * @see sigma.classes.Cascade
2202 // - default (then defaultLabelColor
2203 // will be used instead)
2204 labelColor: 'default',
2205 defaultLabelColor: '#000',
2206 // Label hover background color:
2208 // - default (then defaultHoverLabelBGColor
2209 // will be used instead)
2210 labelHoverBGColor: 'default',
2211 defaultHoverLabelBGColor: '#fff',
2212 // Label hover shadow:
2213 labelHoverShadow: true,
2214 labelHoverShadowColor: '#000',
2215 // Label hover color:
2217 // - default (then defaultLabelHoverColor
2218 // will be used instead)
2219 labelHoverColor: 'default',
2220 defaultLabelHoverColor: '#000',
2221 // Label active background color:
2223 // - default (then defaultActiveLabelBGColor
2224 // will be used instead)
2225 labelActiveBGColor: 'default',
2226 defaultActiveLabelBGColor: '#fff',
2227 // Label active shadow:
2228 labelActiveShadow: true,
2229 labelActiveShadowColor: '#000',
2230 // Label active color:
2232 // - default (then defaultLabelActiveColor
2233 // will be used instead)
2234 labelActiveColor: 'default',
2235 defaultLabelActiveColor: '#000',
2243 defaultLabelSize: 12, // for fixed display only
2244 labelSizeRatio: 2, // for proportional display only
2251 activeFontStyle: '',
2258 // - default (then defaultEdgeColor or edge['color']
2259 // will be used instead)
2260 edgeColor: 'source',
2261 defaultEdgeColor: '#aaa',
2262 defaultEdgeType: 'line',
2266 defaultNodeColor: '#aaa',
2268 // Node hover color:
2270 // - default (then defaultNodeHoverColor
2271 // will be used instead)
2272 nodeHoverColor: 'node',
2273 defaultNodeHoverColor: '#fff',
2275 // Node active color:
2277 // - default (then defaultNodeActiveColor
2278 // will be used instead)
2279 nodeActiveColor: 'node',
2280 defaultNodeActiveColor: '#fff',
2281 // Node border color:
2283 // - default (then defaultNodeBorderColor
2284 // will be used instead)
2286 nodeBorderColor: 'node',
2287 defaultNodeBorderColor: '#fff',
2297 * The canvas context dedicated to draw the nodes.
2298 * @type {CanvasRenderingContext2D}
2300 var nodesCtx = nodesCtx;
2303 * The canvas context dedicated to draw the edges.
2304 * @type {CanvasRenderingContext2D}
2306 var edgesCtx = edgesCtx;
2309 * The canvas context dedicated to draw the labels.
2310 * @type {CanvasRenderingContext2D}
2312 var labelsCtx = labelsCtx;
2315 * The canvas context dedicated to draw the hover nodes.
2316 * @type {CanvasRenderingContext2D}
2318 var hoverCtx = hoverCtx;
2321 * A reference to the graph to draw.
2327 * The width of the stage to draw on.
2333 * The height of the stage to draw on.
2339 * The index of the next edge to draw.
2342 this.currentEdgeIndex = 0;
2345 * The index of the next node to draw.
2348 this.currentNodeIndex = 0;
2351 * The index of the next label to draw.
2354 this.currentLabelIndex = 0;
2357 * An atomic function to drawn the N next edges, with N as edgesSpeed.
2358 * The counter is {@link this.currentEdgeIndex}.
2359 * This function has been designed to work with {@link sigma.chronos}, that
2360 * will insert frames at the middle of the calls, to make the edges drawing
2361 * process fluid for the user.
2362 * @see sigma.chronos
2363 * @return {boolean} Returns true if all the edges are drawn and false else.
2365 function task_drawEdge() {
2366 var c = graph.edges.length;
2369 while (i++< self.p.edgesSpeed && self.currentEdgeIndex < c) {
2370 e = graph.edges[self.currentEdgeIndex];
2376 (!self.isOnScreen(s) && !self.isOnScreen(t))) {
2377 self.currentEdgeIndex++;
2379 drawEdge(graph.edges[self.currentEdgeIndex++]);
2383 return self.currentEdgeIndex < c;
2387 * An atomic function to drawn the N next nodes, with N as nodesSpeed.
2388 * The counter is {@link this.currentEdgeIndex}.
2389 * This function has been designed to work with {@link sigma.chronos}, that
2390 * will insert frames at the middle of the calls, to make the nodes drawing
2391 * process fluid for the user.
2392 * @see sigma.chronos
2393 * @return {boolean} Returns true if all the nodes are drawn and false else.
2395 function task_drawNode() {
2396 var c = graph.nodes.length;
2399 while (i++< self.p.nodesSpeed && self.currentNodeIndex < c) {
2400 if (!self.isOnScreen(graph.nodes[self.currentNodeIndex])) {
2401 self.currentNodeIndex++;
2403 drawNode(graph.nodes[self.currentNodeIndex++]);
2407 return self.currentNodeIndex < c;
2411 * An atomic function to drawn the N next labels, with N as labelsSpeed.
2412 * The counter is {@link this.currentEdgeIndex}.
2413 * This function has been designed to work with {@link sigma.chronos}, that
2414 * will insert frames at the middle of the calls, to make the labels drawing
2415 * process fluid for the user.
2416 * @see sigma.chronos
2417 * @return {boolean} Returns true if all the labels are drawn and false else.
2419 function task_drawLabel() {
2420 var c = graph.nodes.length;
2423 while (i++< self.p.labelsSpeed && self.currentLabelIndex < c) {
2424 if (!self.isOnScreen(graph.nodes[self.currentLabelIndex])) {
2425 self.currentLabelIndex++;
2427 drawLabel(graph.nodes[self.currentLabelIndex++]);
2431 return self.currentLabelIndex < c;
2435 * Draws one node to the corresponding canvas.
2436 * @param {Object} node The node to draw.
2437 * @return {Plotter} Returns itself.
2439 function drawNode(node) {
2440 var size = Math.round(node['displaySize'] * 10) / 10;
2443 ctx.fillStyle = node['color'];
2445 ctx.arc(node['displayX'],
2455 node['hover'] && drawHoverNode(node);
2460 * Draws one edge to the corresponding canvas.
2461 * @param {Object} edge The edge to draw.
2462 * @return {Plotter} Returns itself.
2464 function drawEdge(edge) {
2465 var x1 = edge['source']['displayX'];
2466 var y1 = edge['source']['displayY'];
2467 var x2 = edge['target']['displayX'];
2468 var y2 = edge['target']['displayY'];
2469 var color = edge['color'];
2472 switch (self.p.edgeColor) {
2474 color = edge['source']['color'] ||
2475 self.p.defaultNodeColor;
2478 color = edge['target']['color'] ||
2479 self.p.defaultNodeColor;
2482 color = self.p.defaultEdgeColor;
2489 switch (edge['type'] || self.p.defaultEdgeType) {
2491 ctx.strokeStyle = color;
2492 ctx.lineWidth = edge['displaySize'] / 3;
2495 ctx.quadraticCurveTo((x1 + x2) / 2 + (y2 - y1) / 4,
2496 (y1 + y2) / 2 + (x1 - x2) / 4,
2503 ctx.strokeStyle = color;
2504 ctx.lineWidth = edge['displaySize'] / 3;
2517 * Draws one label to the corresponding canvas.
2518 * @param {Object} node The label to draw.
2519 * @return {Plotter} Returns itself.
2521 function drawLabel(node) {
2522 var ctx = labelsCtx;
2524 if (node['displaySize'] >= self.p.labelThreshold || node['forceLabel']) {
2525 var fontSize = self.p.labelSize == 'fixed' ?
2526 self.p.defaultLabelSize :
2527 self.p.labelSizeRatio * node['displaySize'];
2529 ctx.font = self.p.fontStyle + fontSize + 'px ' + self.p.font;
2531 ctx.fillStyle = self.p.labelColor == 'node' ?
2532 (node['color'] || self.p.defaultNodeColor) :
2533 self.p.defaultLabelColor;
2536 Math.round(node['displayX'] + node['displaySize'] * 1.5),
2537 Math.round(node['displayY'] + fontSize / 2 - 3)
2545 * Draws one hover node to the corresponding canvas.
2546 * @param {Object} node The hover node to draw.
2547 * @return {Plotter} Returns itself.
2549 function drawHoverNode(node) {
2552 var fontSize = self.p.labelSize == 'fixed' ?
2553 self.p.defaultLabelSize :
2554 self.p.labelSizeRatio * node['displaySize'];
2556 ctx.font = (self.p.hoverFontStyle || self.p.fontStyle || '') + ' ' +
2558 (self.p.hoverFont || self.p.font || '');
2560 ctx.fillStyle = self.p.labelHoverBGColor == 'node' ?
2561 (node['color'] || self.p.defaultNodeColor) :
2562 self.p.defaultHoverLabelBGColor;
2564 // Label background:
2567 if (self.p.labelHoverShadow) {
2568 ctx.shadowOffsetX = 0;
2569 ctx.shadowOffsetY = 0;
2571 ctx.shadowColor = self.p.labelHoverShadowColor;
2574 sigma.tools.drawRoundRect(
2576 Math.round(node['displayX'] - fontSize / 2 - 2),
2577 Math.round(node['displayY'] - fontSize / 2 - 2),
2578 Math.round(ctx.measureText(node['label']).width +
2579 node['displaySize'] * 1.5 +
2581 Math.round(fontSize + 4),
2582 Math.round(fontSize / 2 + 2),
2588 ctx.shadowOffsetX = 0;
2589 ctx.shadowOffsetY = 0;
2594 ctx.fillStyle = self.p.nodeBorderColor == 'node' ?
2595 (node['color'] || self.p.defaultNodeColor) :
2596 self.p.defaultNodeBorderColor;
2597 ctx.arc(Math.round(node['displayX']),
2598 Math.round(node['displayY']),
2599 node['displaySize'] + self.p.borderSize,
2608 ctx.fillStyle = self.p.nodeHoverColor == 'node' ?
2609 (node['color'] || self.p.defaultNodeColor) :
2610 self.p.defaultNodeHoverColor;
2611 ctx.arc(Math.round(node['displayX']),
2612 Math.round(node['displayY']),
2613 node['displaySize'],
2622 ctx.fillStyle = self.p.labelHoverColor == 'node' ?
2623 (node['color'] || self.p.defaultNodeColor) :
2624 self.p.defaultLabelHoverColor;
2627 Math.round(node['displayX'] + node['displaySize'] * 1.5),
2628 Math.round(node['displayY'] + fontSize / 2 - 3)
2635 * Draws one active node to the corresponding canvas.
2636 * @param {Object} node The active node to draw.
2637 * @return {Plotter} Returns itself.
2639 function drawActiveNode(node) {
2642 if (!isOnScreen(node)) {
2646 var fontSize = self.p.labelSize == 'fixed' ?
2647 self.p.defaultLabelSize :
2648 self.p.labelSizeRatio * node['displaySize'];
2650 ctx.font = (self.p.activeFontStyle || self.p.fontStyle || '') + ' ' +
2652 (self.p.activeFont || self.p.font || '');
2654 ctx.fillStyle = self.p.labelHoverBGColor == 'node' ?
2655 (node['color'] || self.p.defaultNodeColor) :
2656 self.p.defaultActiveLabelBGColor;
2658 // Label background:
2661 if (self.p.labelActiveShadow) {
2662 ctx.shadowOffsetX = 0;
2663 ctx.shadowOffsetY = 0;
2665 ctx.shadowColor = self.p.labelActiveShadowColor;
2668 sigma.tools.drawRoundRect(
2670 Math.round(node['displayX'] - fontSize / 2 - 2),
2671 Math.round(node['displayY'] - fontSize / 2 - 2),
2672 Math.round(ctx.measureText(node['label']).width +
2673 node['displaySize'] * 1.5 +
2675 Math.round(fontSize + 4),
2676 Math.round(fontSize / 2 + 2),
2682 ctx.shadowOffsetX = 0;
2683 ctx.shadowOffsetY = 0;
2688 ctx.fillStyle = self.p.nodeBorderColor == 'node' ?
2689 (node['color'] || self.p.defaultNodeColor) :
2690 self.p.defaultNodeBorderColor;
2691 ctx.arc(Math.round(node['displayX']),
2692 Math.round(node['displayY']),
2693 node['displaySize'] + self.p.borderSize,
2702 ctx.fillStyle = self.p.nodeActiveColor == 'node' ?
2703 (node['color'] || self.p.defaultNodeColor) :
2704 self.p.defaultNodeActiveColor;
2705 ctx.arc(Math.round(node['displayX']),
2706 Math.round(node['displayY']),
2707 node['displaySize'],
2716 ctx.fillStyle = self.p.labelActiveColor == 'node' ?
2717 (node['color'] || self.p.defaultNodeColor) :
2718 self.p.defaultLabelActiveColor;
2721 Math.round(node['displayX'] + node['displaySize'] * 1.5),
2722 Math.round(node['displayY'] + fontSize / 2 - 3)
2729 * Determines if a node is on the screen or not. The limits here are
2730 * bigger than the actual screen, to avoid seeing labels disappear during
2731 * the graph manipulation.
2732 * @param {Object} node The node to check if it is on or out the screen.
2733 * @return {boolean} Returns false if the node is hidden or not on the screen
2736 function isOnScreen(node) {
2737 if (isNaN(node['x']) || isNaN(node['y'])) {
2738 throw (new Error('A node\'s coordinate is not a ' +
2739 'number (id: ' + node['id'] + ')')
2743 return !node['hidden'] &&
2744 (node['displayX'] + node['displaySize'] > -width / 3) &&
2745 (node['displayX'] - node['displaySize'] < width * 4 / 3) &&
2746 (node['displayY'] + node['displaySize'] > -height / 3) &&
2747 (node['displayY'] - node['displaySize'] < height * 4 / 3);
2751 * Resizes this instance.
2752 * @param {number} w The new width.
2753 * @param {number} h The new height.
2754 * @return {Plotter} Returns itself.
2756 function resize(w, h) {
2763 this.task_drawLabel = task_drawLabel;
2764 this.task_drawEdge = task_drawEdge;
2765 this.task_drawNode = task_drawNode;
2766 this.drawActiveNode = drawActiveNode;
2767 this.drawHoverNode = drawHoverNode;
2768 this.isOnScreen = isOnScreen;
2769 this.resize = resize;
2773 * A class to monitor some local / global probes directly on an instance,
2774 * inside a div DOM element.
2775 * It executes different methods (called "probes") regularly, and displays
2776 * the results on the element.
2778 * @extends sigma.classes.Cascade
2779 * @param {Sigma} instance The instance to monitor.
2780 * @param {element} dom The div DOM element to draw write on.
2783 function Monitor(instance, dom) {
2784 sigma.classes.Cascade.call(this);
2787 * Represents "this", without the well-known scope issue.
2794 * {@link Sigma} instance owning this Monitor instance.
2797 this.instance = instance;
2800 * Determines if the monitoring is activated or not.
2803 this.monitoring = false;
2806 * The different parameters that define how this instance should work. It
2807 * also contains the different probes.
2808 * @see sigma.classes.Cascade
2815 'Time (ms)': sigma.chronos.getExecutionTime,
2816 'Queue': sigma.chronos.getQueuedTasksCount,
2817 'Tasks': sigma.chronos.getTasksCount,
2818 'FPS': sigma.chronos.getFPS
2821 'Nodes count': function() { return self.instance.graph.nodes.length; },
2822 'Edges count': function() { return self.instance.graph.edges.length; }
2827 * Activates the monitoring: Some texts describing some values about sigma.js
2828 * or the owning {@link Sigma} instance will appear over the graph, but
2829 * beneath the mouse sensible DOM element.
2830 * @return {Monitor} Returns itself.
2832 function activate() {
2833 if (!self.monitoring) {
2834 self.monitoring = window.setInterval(routine, 1000 / self.p.fps);
2841 * Desactivates the monitoring: Will disappear, and stop computing the
2843 * @return {Monitor} Returns itself.
2845 function desactivate() {
2846 if (self.monitoring) {
2847 window.clearInterval(self.monitoring);
2848 self.monitoring = null;
2850 self.p.dom.innerHTML = '';
2857 * The private method dedicated to compute the different values to observe.
2859 * @return {Monitor} Returns itself.
2861 function routine() {
2864 s += '<p>GLOBAL :</p>';
2865 for (var k in self.p.globalProbes) {
2866 s += '<p>' + k + ' : ' + self.p.globalProbes[k]() + '</p>';
2869 s += '<br><p>LOCAL :</p>';
2870 for (var k in self.p.localProbes) {
2871 s += '<p>' + k + ' : ' + self.p.localProbes[k]() + '</p>';
2874 self.p.dom.innerHTML = s;
2879 this.activate = activate;
2880 this.desactivate = desactivate;
2884 * Add a function to the prototype of SigmaPublic, but with access to the
2885 * Sigma class properties.
2886 * @param {string} pluginName [description].
2887 * @param {function} caller [description].
2888 * @param {function(Sigma)} launcher [description].
2890 sigma.addPlugin = function(pluginName, caller, launcher) {
2891 SigmaPublic.prototype[pluginName] = caller;
2892 local.plugins.push(launcher);
2894 sigma.tools.drawRoundRect = function(ctx, x, y, w, h, ellipse, corners) {
2895 var e = ellipse ? ellipse : 0;
2896 var c = corners ? corners : [];
2897 c = ((typeof c) == 'string') ? c.split(' ') : c;
2899 var tl = e && (c.indexOf('topleft') >= 0 ||
2900 c.indexOf('top') >= 0 ||
2901 c.indexOf('left') >= 0);
2902 var tr = e && (c.indexOf('topright') >= 0 ||
2903 c.indexOf('top') >= 0 ||
2904 c.indexOf('right') >= 0);
2905 var bl = e && (c.indexOf('bottomleft') >= 0 ||
2906 c.indexOf('bottom') >= 0 ||
2907 c.indexOf('left') >= 0);
2908 var br = e && (c.indexOf('bottomright') >= 0 ||
2909 c.indexOf('bottom') >= 0 ||
2910 c.indexOf('right') >= 0);
2912 ctx.moveTo(x, y + e);
2915 ctx.arcTo(x, y, x + e, y, e);
2921 ctx.lineTo(x + w - e, y);
2922 ctx.arcTo(x + w, y, x + w, y + e, e);
2924 ctx.lineTo(x + w, y);
2928 ctx.lineTo(x + w, y + h - e);
2929 ctx.arcTo(x + w, y + h, x + w - e, y + h, e);
2931 ctx.lineTo(x + w, y + h);
2935 ctx.lineTo(x + e, y + h);
2936 ctx.arcTo(x, y + h, x, y + h - e, e);
2938 ctx.lineTo(x, y + h);
2941 ctx.lineTo(x, y + e);
2944 sigma.tools.getRGB = function(s, asArray) {
2952 if (s.length >= 3) {
2953 if (s.charAt(0) == '#') {
2954 var l = s.length - 1;
2957 'r': parseInt(s.charAt(1) + s.charAt(2), 16),
2958 'g': parseInt(s.charAt(3) + s.charAt(4), 16),
2959 'b': parseInt(s.charAt(5) + s.charAt(5), 16)
2963 'r': parseInt(s.charAt(1) + s.charAt(1), 16),
2964 'g': parseInt(s.charAt(2) + s.charAt(2), 16),
2965 'b': parseInt(s.charAt(3) + s.charAt(3), 16)
2982 sigma.tools.rgbToHex = function(R, G, B) {
2983 return sigma.tools.toHex(R) + sigma.tools.toHex(G) + sigma.tools.toHex(B);
2986 sigma.tools.toHex = function(n) {
2987 n = parseInt(n, 10);
2992 n = Math.max(0, Math.min(n, 255));
2993 return '0123456789ABCDEF'.charAt((n - n % 16) / 16) +
2994 '0123456789ABCDEF'.charAt(n % 16);
3002 sigma.easing.linear.easenone = function(k) {
3006 sigma.easing.quadratic.easein = function(k) {
3010 sigma.easing.quadratic.easeout = function(k) {
3011 return - k * (k - 2);
3014 sigma.easing.quadratic.easeinout = function(k) {
3015 if ((k *= 2) < 1) return 0.5 * k * k;
3016 return - 0.5 * (--k * (k - 2) - 1);
3020 * sigma.chronos manages frames insertion to simulate asynchronous computing.
3021 * It has been designed to make possible to execute heavy computing tasks
3022 * for the browser, without freezing it.
3024 * @extends sigma.classes.Cascade
3025 * @extends sigma.classes.EventDispatcher
3026 * @this {sigma.chronos}
3028 sigma.chronos = new (function() {
3029 sigma.classes.EventDispatcher.call(this);
3032 * Represents "this", without the well-known scope issue.
3034 * @type {sigma.chronos}
3039 * Indicates whether any task is actively running or not.
3043 var isRunning = false;
3046 * Indicates the FPS "goal", that will define the theoretical
3054 * Stores the last computed FPS value (FPS is computed only when any
3062 * The number of frames inserted since the last start.
3066 var framesCount = 0;
3069 * The theoretical frame time.
3073 var frameTime = 1000 / fpsReq;
3076 * The theoretical frame length, minus the last measured delay.
3080 var correctedFrameTime = frameTime;
3083 * The measured length of the last frame.
3087 var effectiveTime = 0;
3090 * The time passed since the last runTasks action.
3094 var currentTime = 0;
3097 * The time when the last frame was inserted.
3104 * The difference between the theoretical frame length and the
3105 * last measured frame length.
3112 * The container of all active generators.
3114 * @type {Object.<string, Object>}
3116 var generators = {};
3119 * The array of all the referenced and active tasks.
3121 * @type {Array.<Object>}
3126 * The array of all the referenced and queued tasks.
3128 * @type {Array.<Object>}
3130 var queuedTasks = [];
3133 * The index of the next task to execute.
3141 * Inserts a frame before executing the callback.
3142 * @param {function()} callback The callback to execute after having
3143 * inserted the frame.
3144 * @return {sigma.chronos} Returns itself.
3146 function insertFrame(callback) {
3147 window.setTimeout(callback, 0);
3152 * The local method that executes routine, and inserts frames when needed.
3153 * It dispatches a "frameinserted" event after having inserted any frame,
3154 * and an "insertframe" event before.
3157 function frameInserter() {
3158 self.dispatch('frameinserted');
3159 while (isRunning && tasks.length && routine()) {}
3161 if (!isRunning || !tasks.length) {
3164 startTime = (new Date()).getTime();
3166 delay = effectiveTime - frameTime;
3167 correctedFrameTime = frameTime - delay;
3169 self.dispatch('insertframe');
3170 insertFrame(frameInserter);
3175 * The local method that executes the tasks, and compares the current frame
3176 * length to the ideal frame length.
3178 * @return {boolean} Returns false if the current frame should be ended,
3181 function routine() {
3182 taskIndex = taskIndex % tasks.length;
3184 if (!tasks[taskIndex].task()) {
3185 var n = tasks[taskIndex].taskName;
3187 queuedTasks = queuedTasks.filter(function(e) {
3188 (e.taskParent == n) && tasks.push({
3189 taskName: e.taskName,
3192 return e.taskParent != n;
3195 self.dispatch('killed', tasks.splice(taskIndex--, 1)[0]);
3199 effectiveTime = (new Date()).getTime() - startTime;
3200 return effectiveTime <= correctedFrameTime;
3204 * Starts tasks execution.
3205 * @return {sigma.chronos} Returns itself.
3207 function runTasks() {
3212 startTime = (new Date()).getTime();
3213 currentTime = startTime;
3215 self.dispatch('start');
3216 self.dispatch('insertframe');
3217 insertFrame(frameInserter);
3222 * Stops tasks execution, and dispatch a "stop" event.
3223 * @return {sigma.chronos} Returns itself.
3225 function stopTasks() {
3226 self.dispatch('stop');
3232 * A task is a function that will be executed continuously while it returns
3233 * true. As soon as it return false, the task will be removed.
3234 * If several tasks are present, they will be executed in parallele.
3235 * This method will add the task to this execution process.
3236 * @param {function(): boolean} task The task to add.
3237 * @param {string} name The name of the worker, used for
3238 * managing the different tasks.
3239 * @param {boolean} autostart If true, sigma.chronos will start
3240 * automatically if it is not working
3242 * @return {sigma.chronos} Returns itself.
3244 function addTask(task, name, autostart) {
3245 if (typeof task != 'function') {
3246 throw new Error('Task "' + name + '" is not a function');
3254 isRunning = !!(isRunning || (autostart && runTasks()) || true);
3259 * Will add a task that will be start to be executed as soon as a task
3260 * named as the parent will be removed.
3261 * @param {function(): boolean} task The task to add.
3262 * @param {string} name The name of the worker, used for
3263 * managing the different tasks.
3264 * @param {string} parent The name of the parent task.
3265 * @return {sigma.chronos} Returns itself.
3267 function queueTask(task, name, parent) {
3268 if (typeof task != 'function') {
3269 throw new Error('Task "' + name + '" is not a function');
3272 if (!tasks.concat(queuedTasks).some(function(e) {
3273 return e.taskName == parent;
3276 'Parent task "' + parent + '" of "' + name + '" is not attached.'
3291 * @param {string} v If v is undefined, then every tasks will
3292 * be removed. If not, each task named v will
3294 * @param {number} queueStatus Determines the queued tasks behaviour. If 0,
3295 * then nothing will happen. If 1, the tasks
3296 * queued to any removed task will be triggered.
3297 * If 2, the tasks queued to any removed task
3298 * will be removed as well.
3299 * @return {sigma.chronos} Returns itself.
3301 function removeTask(v, queueStatus) {
3302 if (v == undefined) {
3304 if (queueStatus == 1) {
3306 }else if (queueStatus == 2) {
3307 tasks = queuedTasks;
3312 var n = (typeof v == 'string') ? v : '';
3313 tasks = tasks.filter(function(e) {
3314 if ((typeof v == 'string') ? e.taskName == v : e.task == v) {
3321 if (queueStatus > 0) {
3322 queuedTasks = queuedTasks.filter(function(e) {
3323 if (queueStatus == 1 && e.taskParent == n) {
3326 return e.taskParent != n;
3331 isRunning = !!(!tasks.length || (stopTasks() && false));
3336 * A generator is a pair task/condition. The task will be executed
3337 * while it returns true.
3338 * When it returns false, the condition will be tested. If
3339 * the condition returns true, the task will be executed
3340 * again at the next process iteration. If not, the generator
3342 * If several generators are present, they will be executed one
3343 * by one: When the first stops, the second will start, etc. When
3344 * they are all ended, then the conditions will be tested to know
3345 * which generators have to be started again.
3346 * @param {string} id The generators ID.
3347 * @param {function(): boolean} task The generator's task.
3348 * @param {function(): boolean} condition The generator's condition.
3349 * @return {sigma.chronos} Returns itself.
3351 function addGenerator(id, task, condition) {
3352 if (generators[id] != undefined) {
3358 condition: condition
3361 getGeneratorsCount(true) == 0 && startGenerators();
3366 * Removes a generator. It means that the task will continue being eecuted
3367 * until it returns false, but then the
3368 * condition will not be tested.
3369 * @param {string} id The generator's ID.
3370 * @return {sigma.chronos} Returns itself.
3372 function removeGenerator(id) {
3373 if (generators[id]) {
3374 generators[id].on = false;
3375 generators[id].del = true;
3381 * Returns the number of generators.
3383 * @param {boolean} running If true, returns the number of active
3384 * generators instead.
3385 * @return {sigma.chronos} Returns itself.
3387 function getGeneratorsCount(running) {
3389 Object.keys(generators).filter(function(id) {
3390 return !!generators[id].on;
3392 Object.keys(generators).length;
3396 * Returns the array of the generators IDs.
3397 * @return {array.<string>} The array of IDs.
3399 function getGeneratorsIDs() {
3400 return Object.keys(generators);
3404 * startGenerators is the method that manages which generator
3405 * is the next to start when another one stops. It will dispatch
3406 * a "stopgenerators" event if there is no more generator to start,
3407 * and a "startgenerators" event else.
3408 * @return {sigma.chronos} Returns itself.
3410 function startGenerators() {
3411 if (!Object.keys(generators).length) {
3412 self.dispatch('stopgenerators');
3414 self.dispatch('startgenerators');
3416 self.unbind('killed', onTaskEnded);
3417 insertFrame(function() {
3418 for (var k in generators) {
3419 generators[k].on = true;
3428 self.bind('killed', onTaskEnded).runTasks();
3435 * A callback triggered everytime the task of a generator stops, that will
3436 * test the related generator's condition, and see if there is still any
3437 * generator to start.
3439 * @param {Object} e The sigma.chronos "killed" event.
3441 function onTaskEnded(e) {
3442 if (generators[e['content'].taskName] != undefined) {
3443 if (generators[e['content'].taskName].del ||
3444 !generators[e['content'].taskName].condition()) {
3445 delete generators[e['content'].taskName];
3447 generators[e['content'].taskName].on = false;
3450 if (getGeneratorsCount(true) == 0) {
3457 * Either set or returns the fpsReq property. This property determines
3458 * the number of frames that should be inserted per second.
3459 * @param {?number} v The frequency asked.
3460 * @return {(Chronos|number)} Returns the frequency if v is undefined, and
3463 function frequency(v) {
3464 if (v != undefined) {
3465 fpsReq = Math.abs(1 * v);
3466 frameTime = 1000 / fpsReq;
3475 * Returns the actual average number of frames that are inserted per
3477 * @return {number} The actual average FPS.
3484 ((new Date()).getTime() - currentTime) *
3493 * Returns the number of tasks.
3494 * @return {number} The number of tasks.
3496 function getTasksCount() {
3497 return tasks.length;
3501 * Returns the number of queued tasks.
3502 * @return {number} The number of queued tasks.
3504 function getQueuedTasksCount() {
3505 return queuedTasks.length;
3509 * Returns how long sigma.chronos has active tasks running
3510 * without interuption for, in ms.
3511 * @return {number} The time chronos is running without interuption for.
3513 function getExecutionTime() {
3514 return startTime - currentTime;
3517 this.frequency = frequency;
3519 this.runTasks = runTasks;
3520 this.stopTasks = stopTasks;
3521 this.insertFrame = insertFrame;
3523 this.addTask = addTask;
3524 this.queueTask = queueTask;
3525 this.removeTask = removeTask;
3527 this.addGenerator = addGenerator;
3528 this.removeGenerator = removeGenerator;
3529 this.startGenerators = startGenerators;
3530 this.getGeneratorsIDs = getGeneratorsIDs;
3532 this.getFPS = getFPS;
3533 this.getTasksCount = getTasksCount;
3534 this.getQueuedTasksCount = getQueuedTasksCount;
3535 this.getExecutionTime = getExecutionTime;
3540 sigma.debugMode = 0;
3542 sigma.log = function() {
3543 if (sigma.debugMode == 1) {
3544 for (var k in arguments) {
3545 console.log(arguments[k]);
3547 }else if (sigma.debugMode > 1) {
3548 for (var k in arguments) {
3549 throw new Error(arguments[k]);
3556 sigma.publicPrototype = SigmaPublic.prototype;