Add Google Analytics
Add Google Analytics


Former-commit-id: c64f37b2545054d1482676e5f04dd933bcd024b9

<?php <?php
   
include_once("../include/common.inc.php"); include_once("../include/common.inc.php");
   
$format = "csv"; $format = "csv";
//$format = "json"; //$format = "json";
if (isset($_REQUEST['format'])) $format = $_REQUEST['format']; if (isset($_REQUEST['format'])) $format = $_REQUEST['format'];
   
setlocale(LC_CTYPE, 'C'); setlocale(LC_CTYPE, 'C');
if ($format == "csv") { if ($format == "csv") {
$headers = Array("name"); $headers = Array("name");
} else { } else {
$headers = Array(); $headers = Array();
} }
   
$db = $server->get_db('disclosr-agencies'); $db = $server->get_db('disclosr-agencies');
try { try {
$rows = $db->get_view("app", "all", null, true)->rows; $rows = $db->get_view("app", "all", null, true)->rows;
   
$dataValues = Array(); $dataValues = Array();
foreach ($rows as $row) { foreach ($rows as $row) {
if (isset($row->value->statistics->employees)) { if (isset($row->value->statistics->employees)) {
   
$headers = array_unique(array_merge($headers, array_keys(object_to_array($row->value->statistics->employees)))); $headers = array_unique(array_merge($headers, array_keys(object_to_array($row->value->statistics->employees))));
   
} }
} }
} catch (SetteeRestClientException $e) { } catch (SetteeRestClientException $e) {
setteErrorHandler($e); setteErrorHandler($e);
} }
   
$fp = fopen('php://output', 'w'); $fp = fopen('php://output', 'w');
if ($fp && $db) { if ($fp && $db) {
if ($format == "csv") { if ($format == "csv") {
header('Content-Type: text/csv; charset=utf-8'); header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="export.employeestats.' . date("c") . '.csv"'); header('Content-Disposition: attachment; filename="export.employeestats.' . date("c") . '.csv"');
} }
header('Pragma: no-cache'); header('Pragma: no-cache');
header('Expires: 0'); header('Expires: 0');
if ($format == "csv") { if ($format == "csv") {
fputcsv($fp, $headers); fputcsv($fp, $headers);
} else if ($format == "json") { } else if ($format == "json") {
echo '{ echo '{
"labels" : ["' . implode('","', $headers) . '"],'.PHP_EOL; "labels" : ["' . implode('","', $headers) . '"],'.PHP_EOL;
} }
try { try {
$agencies = $db->get_view("app", "all", null, true)->rows; $agencies = $db->get_view("app", "all", null, true)->rows;
//print_r($agencies); //print_r($agencies);
$first = true; $first = true;
if ($format == "json") { if ($format == "json") {
echo '"data" : ['.PHP_EOL; echo '"data" : ['.PHP_EOL;
} }
foreach ($agencies as $agency) { foreach ($agencies as $agency) {
   
if (isset($agency->value->statistics->employees)) { if (isset($agency->value->statistics->employees)) {
$row = Array(); $row = Array();
$agencyEmployeesArray = object_to_array($agency->value->statistics->employees); $agencyEmployeesArray = object_to_array($agency->value->statistics->employees);
foreach ($headers as $i => $fieldName) { foreach ($headers as $i => $fieldName) {
if (isset($agencyEmployeesArray[$fieldName])) { if (isset($agencyEmployeesArray[$fieldName])) {
$row[] = '['.$i.','.$agencyEmployeesArray[$fieldName]["value"].']'; $row[] = '['.$i.','.$agencyEmployeesArray[$fieldName]["value"].']';
} else { } else {
$row[] = '['.$i.',0]'; $row[] = '['.$i.',0]';
} }
} }
if ($format == "csv") { if ($format == "csv") {
fputcsv($fp, array_values($row)); fputcsv($fp, array_values($row));
} else if ($format == "json") { } else if ($format == "json") {
if (!$first) echo ","; if (!$first) echo ",";
echo '{"data" : [' . implode(",", array_values($row)) . '], "label": "'.$agency->value->name.'", "lines" : { "show" : true }, "points" : { "show" : true }}'.PHP_EOL; echo '{"data" : [' . implode(",", array_values($row)) . '], "label": "'.$agency->value->name.'", "lines" : { "show" : true }, "points" : { "show" : true }}'.PHP_EOL;
$first = false; $first = false;
} }
} }
} }
if ($format == "json") { if ($format == "json") {
echo '] echo ']
}'.PHP_EOL; }'.PHP_EOL;
} }
} catch (SetteeRestClientException $e) { } catch (SetteeRestClientException $e) {
setteErrorHandler($e); setteErrorHandler($e);
} }
   
die; die;
} }
?> ?>
   
  google-site-verification: google676a414ad086cefb.html
 
<?php <?php
   
date_default_timezone_set("Australia/Sydney"); date_default_timezone_set("Australia/Sydney");
   
$basePath = ""; $basePath = "";
if (strstr($_SERVER['PHP_SELF'], "alaveteli/") if (strstr($_SERVER['PHP_SELF'], "alaveteli/")
|| strstr($_SERVER['PHP_SELF'], "admin/") || strstr($_SERVER['PHP_SELF'], "admin/")
|| strstr($_SERVER['PHP_SELF'], "lib/") || strstr($_SERVER['PHP_SELF'], "lib/")
|| strstr($_SERVER['PHP_SELF'], "include/")) || strstr($_SERVER['PHP_SELF'], "include/"))
$basePath = "../"; $basePath = "../";
   
include_once ('couchdb.inc.php'); include_once ('couchdb.inc.php');
include_once ('template.inc.php'); include_once ('template.inc.php');
require_once $basePath.'lib/Requests/library/Requests.php'; require_once $basePath.'lib/Requests/library/Requests.php';
   
Requests::register_autoloader(); Requests::register_autoloader();
# Convert a stdClass to an Array. http://www.php.net/manual/en/language.types.object.php#102735 # Convert a stdClass to an Array. http://www.php.net/manual/en/language.types.object.php#102735
   
function object_to_array(stdClass $Class) { function object_to_array(stdClass $Class) {
# Typecast to (array) automatically converts stdClass -> array. # Typecast to (array) automatically converts stdClass -> array.
$Class = (array) $Class; $Class = (array) $Class;
   
# Iterate through the former properties looking for any stdClass properties. # Iterate through the former properties looking for any stdClass properties.
# Recursively apply (array). # Recursively apply (array).
foreach ($Class as $key => $value) { foreach ($Class as $key => $value) {
if (is_object($value) && get_class($value) === 'stdClass') { if (is_object($value) && get_class($value) === 'stdClass') {
$Class[$key] = object_to_array($value); $Class[$key] = object_to_array($value);
} }
} }
return $Class; return $Class;
} }
   
# Convert an Array to stdClass. http://www.php.net/manual/en/language.types.object.php#102735 # Convert an Array to stdClass. http://www.php.net/manual/en/language.types.object.php#102735
   
function array_to_object(array $array) { function array_to_object(array $array) {
# Iterate through our array looking for array values. # Iterate through our array looking for array values.
# If found recurvisely call itself. # If found recurvisely call itself.
foreach ($array as $key => $value) { foreach ($array as $key => $value) {
if (is_array($value)) { if (is_array($value)) {
$array[$key] = array_to_object($value); $array[$key] = array_to_object($value);
} }
} }
   
# Typecast to (object) will automatically convert array -> stdClass # Typecast to (object) will automatically convert array -> stdClass
return (object) $array; return (object) $array;
} }
   
function dept_to_portfolio($deptName) { function dept_to_portfolio($deptName) {
return trim(str_replace("Department of", "", str_replace("Department of the", "Department of", $deptName))); return trim(str_replace("Department of", "", str_replace("Department of the", "Department of", $deptName)));
} }
function phrase_to_tag ($phrase) { function phrase_to_tag ($phrase) {
return str_replace(" ","_",str_replace("'","",str_replace(",","",strtolower($phrase)))); return str_replace(" ","_",str_replace("'","",str_replace(",","",strtolower($phrase))));
} }
  function local_url() {
  return "http://" . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['PHP_SELF']), '/\\') . "/";
  }
function GetDomain($url) function GetDomain($url)
{ {
$nowww = ereg_replace('www\.','',$url); $nowww = ereg_replace('www\.','',$url);
$domain = parse_url($nowww); $domain = parse_url($nowww);
if(!empty($domain["host"])) if(!empty($domain["host"]))
{ {
return $domain["host"]; return $domain["host"];
} else } else
{ {
return $domain["path"]; return $domain["path"];
} }
} }
   
<?php <?php
   
include $basePath . "schemas/schemas.inc.php"; include $basePath . "schemas/schemas.inc.php";
   
require ($basePath . 'couchdb/settee/src/settee.php'); require ($basePath . 'couchdb/settee/src/settee.php');
   
function createDocumentsDesignDoc() { function createDocumentsDesignDoc() {
/* "views": { /* "views": {
"web_server": { "web_server": {
"map": "function(doc) {\n emit(doc.web_server, 1);\n}", "map": "function(doc) {\n emit(doc.web_server, 1);\n}",
"reduce": "function (key, values, rereduce) {\n return sum(values);\n}" "reduce": "function (key, values, rereduce) {\n return sum(values);\n}"
}, },
"byAgency": { "byAgency": {
"map": "function(doc) {\n emit(doc.agencyID, 1);\n}", "map": "function(doc) {\n emit(doc.agencyID, 1);\n}",
"reduce": "function (key, values, rereduce) {\n return sum(values);\n}" "reduce": "function (key, values, rereduce) {\n return sum(values);\n}"
}, },
"byURL": { "byURL": {
"map": "function(doc) {\n emit(doc.url, doc);\n}" "map": "function(doc) {\n emit(doc.url, doc);\n}"
}, },
"agency": { "agency": {
"map": "function(doc) {\n emit(doc.agencyID, doc);\n}" "map": "function(doc) {\n emit(doc.agencyID, doc);\n}"
}, },
"byWebServer": { "byWebServer": {
"map": "function(doc) {\n emit(doc.web_server, doc);\n}" "map": "function(doc) {\n emit(doc.web_server, doc);\n}"
}, },
"getValidationRequired": { "getValidationRequired": {
"map": "function(doc) {\nif (doc.mime_type == \"text/html\" \n&& typeof(doc.validation) == \"undefined\") {\n emit(doc._id, doc._attachments);\n}\n}" "map": "function(doc) {\nif (doc.mime_type == \"text/html\" \n&& typeof(doc.validation) == \"undefined\") {\n emit(doc._id, doc._attachments);\n}\n}"
} }
} */ } */
} }
   
function createAgencyDesignDoc() { function createAgencyDesignDoc() {
global $db; global $db;
$obj = new stdClass(); $obj = new stdClass();
$obj->_id = "_design/" . urlencode("app"); $obj->_id = "_design/" . urlencode("app");
$obj->language = "javascript"; $obj->language = "javascript";
$obj->views->all->map = "function(doc) { emit(doc._id, doc); };"; $obj->views->all->map = "function(doc) { emit(doc._id, doc); };";
$obj->views->byABN->map = "function(doc) { emit(doc.abn, doc); };"; $obj->views->byABN->map = "function(doc) { emit(doc.abn, doc); };";
$obj->views->byCanonicalName->map = "function(doc) { $obj->views->byCanonicalName->map = "function(doc) {
if (doc.parentOrg || doc.orgType == 'FMA-DepartmentOfState') { if (doc.parentOrg || doc.orgType == 'FMA-DepartmentOfState') {
emit(doc.name, doc); emit(doc.name, doc);
} }
};"; };";
$obj->views->byDeptStateName->map = "function(doc) { $obj->views->byDeptStateName->map = "function(doc) {
if (doc.orgType == 'FMA-DepartmentOfState') { if (doc.orgType == 'FMA-DepartmentOfState') {
emit(doc.name, doc._id); emit(doc.name, doc._id);
} }
};"; };";
$obj->views->parentOrgs->map = "function(doc) { $obj->views->parentOrgs->map = "function(doc) {
if (doc.parentOrg) { if (doc.parentOrg) {
emit(doc._id, doc.parentOrg); emit(doc._id, doc.parentOrg);
} }
};"; };";
$obj->views->byName->map = 'function(doc) { $obj->views->byName->map = 'function(doc) {
if (typeof(doc["status"]) == "undefined" || doc["status"] != "suspended") { if (typeof(doc["status"]) == "undefined" || doc["status"] != "suspended") {
emit(doc.name, doc._id); emit(doc.name, doc._id);
if (typeof(doc.shortName) != "undefined" && doc.shortName != doc.name) { if (typeof(doc.shortName) != "undefined" && doc.shortName != doc.name) {
emit(doc.shortName, doc._id); emit(doc.shortName, doc._id);
} }
for (name in doc.otherNames) { for (name in doc.otherNames) {
if (doc.otherNames[name] != "" && doc.otherNames[name] != doc.name) { if (doc.otherNames[name] != "" && doc.otherNames[name] != doc.name) {
emit(doc.otherNames[name], doc._id); emit(doc.otherNames[name], doc._id);
} }
} }
for (name in doc.foiBodies) { for (name in doc.foiBodies) {
if (doc.foiBodies[name] != "" && doc.foiBodies[name] != doc.name) { if (doc.foiBodies[name] != "" && doc.foiBodies[name] != doc.name) {
emit(doc.foiBodies[name], doc._id); emit(doc.foiBodies[name], doc._id);
} }
} }
} }
};'; };';
   
$obj->views->foiEmails->map = "function(doc) { $obj->views->foiEmails->map = "function(doc) {
emit(doc._id, doc.foiEmail); emit(doc._id, doc.foiEmail);
};"; };";
   
$obj->views->byLastModified->map = "function(doc) { emit(doc.metadata.lastModified, doc); }"; $obj->views->byLastModified->map = "function(doc) { emit(doc.metadata.lastModified, doc); }";
$obj->views->getActive->map = 'function(doc) { if (doc.status == "active") { emit(doc._id, doc); } };'; $obj->views->getActive->map = 'function(doc) { if (doc.status == "active") { emit(doc._id, doc); } };';
$obj->views->getSuspended->map = 'function(doc) { if (doc.status == "suspended") { emit(doc._id, doc); } };'; $obj->views->getSuspended->map = 'function(doc) { if (doc.status == "suspended") { emit(doc._id, doc); } };';
$obj->views->getScrapeRequired->map = "function(doc) { $obj->views->getScrapeRequired->map = "function(doc) {
   
var lastScrape = Date.parse(doc.metadata.lastScraped); var lastScrape = Date.parse(doc.metadata.lastScraped);
   
var today = new Date(); var today = new Date();
   
if (!lastScrape || lastScrape.getTime() + 1000 != today.getTime()) { if (!lastScrape || lastScrape.getTime() + 1000 != today.getTime()) {
emit(doc._id, doc); emit(doc._id, doc);
} }
   
};"; };";
$obj->views->showNamesABNs->map = "function(doc) { emit(doc._id, {name: doc.name, abn: doc.abn}); };"; $obj->views->showNamesABNs->map = "function(doc) { emit(doc._id, {name: doc.name, abn: doc.abn}); };";
$obj->views->getConflicts->map = "function(doc) { $obj->views->getConflicts->map = "function(doc) {
if (doc._conflicts) { if (doc._conflicts) {
emit(null, [doc._rev].concat(doc._conflicts)); emit(null, [doc._rev].concat(doc._conflicts));
} }
}"; }";
// http://stackoverflow.com/questions/646628/javascript-startswith // http://stackoverflow.com/questions/646628/javascript-startswith
$obj->views->score->map = 'if(!String.prototype.startsWith){ $obj->views->score->map = 'if(!String.prototype.startsWith){
String.prototype.startsWith = function (str) { String.prototype.startsWith = function (str) {
return !this.indexOf(str); return !this.indexOf(str);
} }
} }
   
function(doc) { function(doc) {
count = 0; count = 0;
if (doc["status"] != "suspended") { if (doc["status"] != "suspended") {
for(var propName in doc) { for(var propName in doc) {
if(typeof(doc[propName]) != "undefined" && doc[propName] != "") { if(typeof(doc[propName]) != "undefined" && doc[propName] != "") {
count++; count++;
} }
} }
portfolio = doc.parentOrg; portfolio = doc.parentOrg;
if (doc.orgType == "FMA-DepartmentOfState") { if (doc.orgType == "FMA-DepartmentOfState") {
portfolio = doc._id; portfolio = doc._id;
} }
if (doc.orgType == "Court-Commonwealth" || doc.orgType == "FMA-DepartmentOfParliament") { if (doc.orgType == "Court-Commonwealth" || doc.orgType == "FMA-DepartmentOfParliament") {
portfolio = doc.orgType; portfolio = doc.orgType;
} }
emit(count+doc._id, {id:doc._id, name: doc.name, score:count, orgType: doc.orgType, portfolio:portfolio}); emit(count+doc._id, {id:doc._id, name: doc.name, score:count, orgType: doc.orgType, portfolio:portfolio});
} }
}'; }';
$obj->views->scoreHas->map = 'if(!String.prototype.startsWith){ $obj->views->scoreHas->map = 'if(!String.prototype.startsWith){
String.prototype.startsWith = function (str) { String.prototype.startsWith = function (str) {
return !this.indexOf(str); return !this.indexOf(str);
} }
} }
if(!String.prototype.endsWith){ if(!String.prototype.endsWith){
String.prototype.endsWith = function(suffix) { String.prototype.endsWith = function(suffix) {
    return this.indexOf(suffix, this.length - suffix.length) !== -1;     return this.indexOf(suffix, this.length - suffix.length) !== -1;
}; };
} }
function(doc) { function(doc) {
if (typeof(doc["status"]) == "undefined" || doc["status"] != "suspended") { if (typeof(doc["status"]) == "undefined" || doc["status"] != "suspended") {
for(var propName in doc) { for(var propName in doc) {
if(typeof(doc[propName]) != "undefined" && (propName.startsWith("has") || propName.endsWith("URL"))) { if(typeof(doc[propName]) != "undefined" && (propName.startsWith("has") || propName.endsWith("URL"))) {
emit(propName, 1); emit(propName, 1);
} }
} }
emit("total", 1); emit("total", 1);
} }
}'; }';
$obj->views->scoreHas->reduce = 'function (key, values, rereduce) { $obj->views->scoreHas->reduce = 'function (key, values, rereduce) {
return sum(values); return sum(values);
}'; }';
$obj->views->fieldNames->map = ' $obj->views->fieldNames->map = '
function(doc) { function(doc) {
for(var propName in doc) { for(var propName in doc) {
emit(propName, doc._id); emit(propName, doc._id);
} }
}'; }';
$obj->views->fieldNames->reduce = 'function (key, values, rereduce) { $obj->views->fieldNames->reduce = 'function (key, values, rereduce) {
return values.length; return values.length;
}'; }';
// allow safe updates (even if slightly slower due to extra: rev-detection check). // allow safe updates (even if slightly slower due to extra: rev-detection check).
return $db->save($obj, true); return $db->save($obj, true);
} }
   
if (php_uname('n') == "vanille") { if (php_uname('n') == "vanille") {
$serverAddr = 'http://192.168.178.21:5984/'; $serverAddr = 'http://192.168.178.21:5984/';
} else } else
if (php_uname('n') == "KYUUBEY") { if (php_uname('n') == "KYUUBEY") {
   
$serverAddr = 'http://127.0.0.1:5984/'; $serverAddr = 'http://192.168.1.148:5984/';
} else { } else {
$serverAddr = 'http://127.0.0.1:5984/'; $serverAddr = 'http://127.0.0.1:5984/';
} }
$server = new SetteeServer($serverAddr); $server = new SetteeServer($serverAddr);
   
function setteErrorHandler($e) { function setteErrorHandler($e) {
echo $e->getMessage() . "<br>" . PHP_EOL; echo $e->getMessage() . "<br>" . PHP_EOL;
} }
   
<?php <?php
   
function include_header() { function include_header() {
global $basePath; global $basePath;
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
   
<!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ --> <!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]--> <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]--> <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]--> <!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!--[if gt IE 8]><!--> <html lang="en"> <!--<![endif]--> <!--[if gt IE 8]><!--> <html lang="en"> <!--<![endif]-->
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
   
<!-- Set the viewport width to device width for mobile --> <!-- Set the viewport width to device width for mobile -->
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
   
<title>Disclosr</title> <title>Disclosr</title>
   
<!-- Included CSS Files --> <!-- Included CSS Files -->
<link rel="stylesheet" href="<?php echo $basePath ?>stylesheets/foundation.css"> <link rel="stylesheet" href="<?php echo $basePath ?>stylesheets/foundation.css">
<link rel="stylesheet" href="<?php echo $basePath ?>stylesheets/app.css"> <link rel="stylesheet" href="<?php echo $basePath ?>stylesheets/app.css">
   
<!--[if lt IE 9]> <!--[if lt IE 9]>
<link rel="stylesheet" href="<?php echo $basePath ?>stylesheets/ie.css"> <link rel="stylesheet" href="<?php echo $basePath ?>stylesheets/ie.css">
<![endif]--> <![endif]-->
   
   
<!-- IE Fix for HTML5 Tags --> <!-- IE Fix for HTML5 Tags -->
<!--[if lt IE 9]> <!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]--> <![endif]-->
   
</head> </head>
<body xmlns:schema="http://schema.org/" xmlns:foaf="http://xmlns.com/foaf/0.1/"> <body xmlns:schema="http://schema.org/" xmlns:foaf="http://xmlns.com/foaf/0.1/">
   
<!-- navBar --> <!-- navBar -->
<div id="navbar" class="container"> <div id="navbar" class="container">
<div class="row"> <div class="row">
<div class="four columns"> <div class="four columns">
<h1><a href="/">Disclosr</a></h1> <h1><a href="/">Disclosr</a></h1>
</div> </div>
<div class="eight columns hide-on-phones"> <div class="eight columns hide-on-phones">
<strong class="right"> <strong class="right">
<a href="getAgency.php">Agencies</a> <a href="getAgency.php">Agencies</a>
<a href="about.php">About/FAQ</a> <a href="about.php">About/FAQ</a>
</strong> </strong>
</div> </div>
</div> </div>
</div> </div>
<!-- /navBar --> <!-- /navBar -->
   
<!-- container --> <!-- container -->
<div class="container"> <div class="container">
<?php } <?php }
   
function include_footer() { function include_footer() {
global $basePath; global $basePath;
?> ?>
</div> </div>
<!-- container --> <!-- container -->
   
   
   
   
<!-- Included JS Files --> <!-- Included JS Files -->
<script src="<?php echo $basePath; ?>javascripts/foundation.js"></script> <script src="<?php echo $basePath; ?>javascripts/foundation.js"></script>
<script src="<?php echo $basePath; ?>javascripts/app.js"></script> <script src="<?php echo $basePath; ?>javascripts/app.js"></script>
<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script> <script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="javascripts/flotr2/flotr2.js"></script> <script type="text/javascript" src="javascripts/flotr2/flotr2.js"></script>
  <?php
  if (strpos($_SERVER['SERVER_NAME'], ".gs")) {
  ?>
  <script type="text/javascript">
   
  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-12341040-2']);
  _gaq.push(['_trackPageview']);
   
  (function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();
   
  </script>
   
</body> </body>
</html> </html>
   
<?php } <?php }
  }
   
   
   
   
directory:a/javascripts/sigma -> directory:b/javascripts/sigma
   
/* sigmajs.org - an open-source light-weight JavaScript graph drawing library - Version: 0.1 - Author: Alexis Jacomy - License: MIT */ // Define packages:
var sigma={tools:{},classes:{},instances:{}}; var sigma = {};
(function(){if(!Array.prototype.some)Array.prototype.some=function(i,n){var g=this.length;if("function"!=typeof i)throw new TypeError;for(var j=0;j<g;j++)if(j in this&&i.call(n,this[j],j,this))return!0;return!1};if(!Array.prototype.forEach)Array.prototype.forEach=function(i,n){var g=this.length;if("function"!=typeof i)throw new TypeError;for(var j=0;j<g;j++)j in this&&i.call(n,this[j],j,this)};if(!Array.prototype.map)Array.prototype.map=function(i,n){var g=this.length;if("function"!=typeof i)throw new TypeError; sigma.tools = {};
for(var j=Array(g),m=0;m<g;m++)m in this&&(j[m]=i.call(n,this[m],m,this));return j};if(!Array.prototype.filter)Array.prototype.filter=function(i,n){var g=this.length;if("function"!=typeof i)throw new TypeError;for(var j=[],m=0;m<g;m++)if(m in this){var t=this[m];i.call(n,t,m,this)&&j.push(t)}return j};if(!Object.keys)Object.keys=function(){var i=Object.prototype.hasOwnProperty,n=!{toString:null}.propertyIsEnumerable("toString"),g="toString,toLocaleString,valueOf,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,constructor".split(","), sigma.classes = {};
j=g.length;return function(m){if("object"!==typeof m&&"function"!==typeof m||null===m)throw new TypeError("Object.keys called on non-object");var t=[],p;for(p in m)i.call(m,p)&&t.push(p);if(n)for(p=0;p<j;p++)i.call(m,g[p])&&t.push(g[p]);return t}}()})();sigma.classes.Cascade=function(){this.p={};this.config=function(i,n){if("string"==typeof i&&void 0==n)return this.p[i];var g="object"==typeof i&&void 0==n?i:{};"string"==typeof i&&(g[i]=n);for(var j in g)void 0!=this.p[j]&&(this.p[j]=g[j]);return this}}; sigma.instances = {};
sigma.classes.EventDispatcher=function(){var i={},n=this;this.one=function(g,j){if(!j||!g)return n;("string"==typeof g?g.split(" "):g).forEach(function(g){i[g]||(i[g]=[]);i[g].push({h:j,one:!0})});return n};this.bind=function(g,j){if(!j||!g)return n;("string"==typeof g?g.split(" "):g).forEach(function(g){i[g]||(i[g]=[]);i[g].push({h:j,one:!1})});return n};this.unbind=function(g,j){g||(i={});var m="string"==typeof g?g.split(" "):g;j?m.forEach(function(g){i[g]&&(i[g]=i[g].filter(function(g){return g.h!=  
j}));i[g]&&0==i[g].length&&delete i[g]}):m.forEach(function(g){delete i[g]});return n};this.dispatch=function(g,j){i[g]&&(i[g].forEach(function(i){i.h({type:g,content:j,target:n})}),i[g]=i[g].filter(function(g){return!g.one}));return n}}; // Adding Array helpers, if not present yet:
(function(){var i;function n(){function b(a){return{x:a.x,y:a.y,size:a.size,degree:a.degree,displayX:a.displayX,displayY:a.displayY,displaySize:a.displaySize,label:a.label,id:a.id,color:a.color,fixed:a.fixed,active:a.active,hidden:a.hidden,attr:a.attr}}function h(a){return{source:a.source.id,target:a.target.id,size:a.size,type:a.type,weight:a.weight,displaySize:a.displaySize,label:a.label,id:a.id,attr:a.attr,color:a.color}}function e(){d.nodes=[];d.nodesIndex={};d.edges=[];d.edgesIndex={};return d} (function() {
sigma.classes.Cascade.call(this);sigma.classes.EventDispatcher.call(this);var d=this;this.p={minNodeSize:0,maxNodeSize:0,minEdgeSize:0,maxEdgeSize:0,scalingMode:"inside",nodesPowRatio:0.5,edgesPowRatio:0};this.borders={};e();this.addNode=function(a,b){if(d.nodesIndex[a])throw Error('Node "'+a+'" already exists.');var b=b||{},c={x:0,y:0,size:1,degree:0,fixed:!1,active:!1,hidden:!1,label:a.toString(),id:a.toString(),attr:{}},e;for(e in b)switch(e){case "id":break;case "x":case "y":case "size":c[e]= if (!Array.prototype.some) {
+b[e];break;case "fixed":case "active":case "hidden":c[e]=!!b[e];break;case "color":case "label":c[e]=b[e];break;default:c.attr[e]=b[e]}d.nodes.push(c);d.nodesIndex[a.toString()]=c;return d};this.addEdge=function(a,b,c,e){if(d.edgesIndex[a])throw Error('Edge "'+a+'" already exists.');if(!d.nodesIndex[b])throw Error("Edge's source \""+b+'" does not exist yet.');if(!d.nodesIndex[c])throw Error("Edge's target \""+c+'" does not exist yet.');e=e||{};b={source:d.nodesIndex[b],target:d.nodesIndex[c],size:1, Array.prototype.some = function(fun /*, thisp*/) {
weight:1,displaySize:0.5,label:a.toString(),id:a.toString(),attr:{}};b.source.degree++;b.target.degree++;for(var k in e)switch(k){case "id":case "source":case "target":break;case "size":b[k]=+e[k];break;case "color":b[k]=e[k].toString();break;case "type":b[k]=e[k].toString();break;case "label":b[k]=e[k];break;default:b.attr[k]=e[k]}d.edges.push(b);d.edgesIndex[a.toString()]=b;return d};this.dropNode=function(a){((a instanceof Array?a:[a])||[]).forEach(function(a){if(d.nodesIndex[a]){var c=null;d.nodes.some(function(b, var len = this.length;
d){return b.id==a?(c=d,!0):!1});null!=c&&d.nodes.splice(c,1);delete d.nodesIndex[a];d.edges=d.edges.filter(function(c){return c.source.id==a||c.target.id==a?(delete d.edgesIndex[c.id],!1):!0})}else sigma.log('Node "'+a+'" does not exist.')});return d};this.dropEdge=function(a){((a instanceof Array?a:[a])||[]).forEach(function(a){if(d.edgesIndex[a]){var c=null;d.edges.some(function(b,d){return b.id==a?(c=d,!0):!1});null!=c&&d.edges.splice(c,1);delete d.edgesIndex[a]}else sigma.log('Edge "'+a+'" does not exist.')}); if (typeof fun != 'function') {
return d};this.iterEdges=function(a,b){var c=b?b.map(function(a){return d.edgesIndex[a]}):d.edges,e=c.map(h);e.forEach(a);c.forEach(function(a,c){var b=e[c],l;for(l in b)switch(l){case "id":case "weight":case "displaySize":break;case "size":a[l]=+b[l];break;case "source":case "target":a[l]=d.nodesIndex[l]||a[l];break;case "color":case "label":case "type":a[l]=(b[l]||"").toString();break;default:a.attr[l]=b[l]}});return d};this.iterNodes=function(a,e){var c=e?e.map(function(a){return d.nodesIndex[a]}): throw new TypeError();
d.nodes,h=c.map(b);h.forEach(a);c.forEach(function(a,c){var b=h[c],d;for(d in b)switch(d){case "id":case "attr":case "degree":case "displayX":case "displayY":case "displaySize":break;case "x":case "y":case "size":a[d]=+b[d];break;case "fixed":case "active":case "hidden":a[d]=!!b[d];break;case "color":case "label":a[d]=b[d].toString();break;default:a.attr[d]=b[d]}});return d};this.getEdges=function(a){var b=((a instanceof Array?a:[a])||[]).map(function(a){return h(d.edgesIndex[a])});return a instanceof }
Array?b:b[0]};this.getNodes=function(a){var e=((a instanceof Array?a:[a])||[]).map(function(a){return b(d.nodesIndex[a])});return a instanceof Array?e:e[0]};this.empty=e;this.rescale=function(a,b,c,e){var k=0,f=0;c&&d.nodes.forEach(function(a){f=Math.max(a.size,f)});e&&d.edges.forEach(function(a){k=Math.max(a.size,k)});var f=f||1,k=k||1,h,g,s,q;c&&d.nodes.forEach(function(a){g=Math.max(a.x,g||a.x);h=Math.min(a.x,h||a.x);q=Math.max(a.y,q||a.y);s=Math.min(a.y,s||a.y)});var z="outside"==d.p.scalingMode?  
Math.max(a/Math.max(g-h,1),b/Math.max(q-s,1)):Math.min(a/Math.max(g-h,1),b/Math.max(q-s,1)),i,j;!d.p.maxNodeSize&&!d.p.minNodeSize?(i=1,j=0):d.p.maxNodeSize==d.p.minNodeSize?(i=0,j=d.p.maxNodeSize):(i=(d.p.maxNodeSize-d.p.minNodeSize)/f,j=d.p.minNodeSize);var m,w;!d.p.maxEdgeSize&&!d.p.minEdgeSize?(m=1,w=0):(m=d.p.maxEdgeSize==d.p.minEdgeSize?0:(d.p.maxEdgeSize-d.p.minEdgeSize)/k,w=d.p.minEdgeSize);c&&d.nodes.forEach(function(c){c.displaySize=c.size*i+j;if(!c.fixed)c.displayX=(c.x-(g+h)/2)*z+a/2, var thisp = arguments[1];
c.displayY=(c.y-(q+s)/2)*z+b/2});e&&d.edges.forEach(function(a){a.displaySize=a.size*m+w});return d};this.translate=function(a,b,c,e,k){var f=Math.pow(c,d.p.nodesPowRatio);e&&d.nodes.forEach(function(d){if(!d.fixed)d.displayX=d.displayX*c+a,d.displayY=d.displayY*c+b;d.displaySize*=f});f=Math.pow(c,d.p.edgesPowRatio);k&&d.edges.forEach(function(a){a.displaySize*=f});return d};this.setBorders=function(){d.borders={};d.nodes.forEach(function(a){d.borders.minX=Math.min(void 0==d.borders.minX?a.displayX- for (var i = 0; i < len; i++) {
a.displaySize:d.borders.minX,a.displayX-a.displaySize);d.borders.maxX=Math.max(void 0==d.borders.maxX?a.displayX+a.displaySize:d.borders.maxX,a.displayX+a.displaySize);d.borders.minY=Math.min(void 0==d.borders.minY?a.displayY-a.displaySize:d.borders.minY,a.displayY-a.displaySize);d.borders.maxY=Math.max(void 0==d.borders.maxY?a.displayY-a.displaySize:d.borders.maxY,a.displayY-a.displaySize)})};this.checkHover=function(a,b){var c,e,k,f=[],h=[];d.nodes.forEach(function(d){if(d.hidden)d.hover=!1;else{c= if (i in this &&
Math.abs(d.displayX-a);e=Math.abs(d.displayY-b);k=d.displaySize;var s=d.hover,q=c<k&&e<k&&Math.sqrt(c*c+e*e)<k;if(s&&!q)d.hover=!1,h.push(d.id);else if(q&&!s)d.hover=!0,f.push(d.id)}});f.length&&d.dispatch("overnodes",f);h.length&&d.dispatch("outnodes",h);return d}}function g(b,h){function e(){var a;a="<p>GLOBAL :</p>";for(var b in d.p.globalProbes)a+="<p>"+b+" : "+d.p.globalProbes[b]()+"</p>";a+="<br><p>LOCAL :</p>";for(b in d.p.localProbes)a+="<p>"+b+" : "+d.p.localProbes[b]()+"</p>";d.p.dom.innerHTML= fun.call(thisp, this[i], i, this)) {
a;return d}sigma.classes.Cascade.call(this);var d=this;this.instance=b;this.monitoring=!1;this.p={fps:40,dom:h,globalProbes:{"Time (ms)":sigma.chronos.getExecutionTime,Queue:sigma.chronos.getQueuedTasksCount,Tasks:sigma.chronos.getTasksCount,FPS:sigma.chronos.getFPS},localProbes:{"Nodes count":function(){return d.instance.graph.nodes.length},"Edges count":function(){return d.instance.graph.edges.length}}};this.activate=function(){if(!d.monitoring)d.monitoring=window.setInterval(e,1E3/d.p.fps);return d}; return true;
this.desactivate=function(){if(d.monitoring)window.clearInterval(d.monitoring),d.monitoring=null,d.p.dom.innerHTML="";return d}}function j(b){function h(b){if(a.p.mouseEnabled&&(e(a.mouseX,a.mouseY,a.ratio*(0<(void 0!=b.wheelDelta&&b.wheelDelta||void 0!=b.detail&&-b.detail)?a.p.zoomMultiply:1/a.p.zoomMultiply)),a.p.blockScroll))b.preventDefault?b.preventDefault():b.returnValue=!1}function e(b,c,e){if(!a.isMouseDown&&(window.clearInterval(a.interpolationID),n=void 0!=e,i=a.stageX,j=b,k=a.stageY,l= }
c,f=e||a.ratio,f=Math.min(Math.max(f,a.p.minRatio),a.p.maxRatio),u=a.p.directZooming?1-(n?a.p.zoomDelta:a.p.dragDelta):0,a.ratio!=f||a.stageX!=j||a.stageY!=l))d(),a.interpolationID=window.setInterval(d,50),a.dispatch("startinterpolate")}function d(){u+=n?a.p.zoomDelta:a.p.dragDelta;u=Math.min(u,1);var b=sigma.easing.quadratic.easeout(u),c=a.ratio;a.ratio=c*(1-b)+f*b;n?(a.stageX=j+(a.stageX-j)*a.ratio/c,a.stageY=l+(a.stageY-l)*a.ratio/c):(a.stageX=i*(1-b)+j*b,a.stageY=k*(1-b)+l*b);a.dispatch("interpolate"); }
if(1<=u)window.clearInterval(a.interpolationID),b=a.ratio,n?(a.ratio=f,a.stageX=j+(a.stageX-j)*a.ratio/b,a.stageY=l+(a.stageY-l)*a.ratio/b):(a.stageX=j,a.stageY=l),a.dispatch("stopinterpolate")}sigma.classes.Cascade.call(this);sigma.classes.EventDispatcher.call(this);var a=this;this.p={minRatio:1,maxRatio:32,marginRatio:1,zoomDelta:0.1,dragDelta:0.3,zoomMultiply:2,directZooming:!1,blockScroll:!0,inertia:1.1,mouseEnabled:!0};var g=0,c=0,i=0,k=0,f=1,j=0,l=0,s=0,q=0,z=0,m=0,u=0,n=!1;this.stageY=this.stageX=  
0;this.ratio=1;this.mouseY=this.mouseX=0;this.isMouseDown=!1;b.addEventListener("DOMMouseScroll",h,!0);b.addEventListener("mousewheel",h,!0);b.addEventListener("mousemove",function(b){a.mouseX=void 0!=b.offsetX&&b.offsetX||void 0!=b.layerX&&b.layerX||void 0!=b.clientX&&b.clientX;a.mouseY=void 0!=b.offsetY&&b.offsetY||void 0!=b.layerY&&b.layerY||void 0!=b.clientY&&b.clientY;if(a.isMouseDown){var d=a.mouseX-g+i,f=a.mouseY-c+k;if(d!=a.stageX||f!=a.stageY)q=s,m=z,s=d,z=f,a.stageX=d,a.stageY=f,a.dispatch("drag")}a.dispatch("move"); return false;
b.preventDefault?b.preventDefault():b.returnValue=!1},!0);b.addEventListener("mousedown",function(b){if(a.p.mouseEnabled)a.isMouseDown=!0,a.dispatch("mousedown"),i=a.stageX,k=a.stageY,g=a.mouseX,c=a.mouseY,q=s=a.stageX,m=z=a.stageY,a.dispatch("startdrag"),b.preventDefault?b.preventDefault():b.returnValue=!1},!0);document.addEventListener("mouseup",function(b){if(a.p.mouseEnabled&&a.isMouseDown)a.isMouseDown=!1,a.dispatch("mouseup"),(i!=a.stageX||k!=a.stageY)&&e(a.stageX+a.p.inertia*(a.stageX-q),a.stageY+ };
a.p.inertia*(a.stageY-m)),b.preventDefault?b.preventDefault():b.returnValue=!1},!0);this.checkBorders=function(){return a};this.interpolate=e}function m(b,h,e,d,a,g,c){function i(a){var b=d,c="fixed"==f.p.labelSize?f.p.defaultLabelSize:f.p.labelSizeRatio*a.displaySize;b.font=(f.p.hoverFontStyle||f.p.fontStyle||"")+" "+c+"px "+(f.p.hoverFont||f.p.font||"");b.fillStyle="node"==f.p.labelHoverBGColor?a.color||f.p.defaultNodeColor:f.p.defaultHoverLabelBGColor;b.beginPath();if(f.p.labelHoverShadow)b.shadowOffsetX= }
0,b.shadowOffsetY=0,b.shadowBlur=4,b.shadowColor=f.p.labelHoverShadowColor;sigma.tools.drawRoundRect(b,Math.round(a.displayX-c/2-2),Math.round(a.displayY-c/2-2),Math.round(b.measureText(a.label).width+1.5*a.displaySize+c/2+4),Math.round(c+4),Math.round(c/2+2),"left");b.closePath();b.fill();b.shadowOffsetX=0;b.shadowOffsetY=0;b.shadowBlur=0;b.beginPath();b.fillStyle="node"==f.p.nodeBorderColor?a.color||f.p.defaultNodeColor:f.p.defaultNodeBorderColor;b.arc(Math.round(a.displayX),Math.round(a.displayY),  
a.displaySize+f.p.borderSize,0,2*Math.PI,!0);b.closePath();b.fill();b.beginPath();b.fillStyle="node"==f.p.nodeHoverColor?a.color||f.p.defaultNodeColor:f.p.defaultNodeHoverColor;b.arc(Math.round(a.displayX),Math.round(a.displayY),a.displaySize,0,2*Math.PI,!0);b.closePath();b.fill();b.fillStyle="node"==f.p.labelHoverColor?a.color||f.p.defaultNodeColor:f.p.defaultLabelHoverColor;b.fillText(a.label,Math.round(a.displayX+1.5*a.displaySize),Math.round(a.displayY+c/2-3));return f}function k(a){if(isNaN(a.x)|| if (!Array.prototype.forEach) {
isNaN(a.y))throw Error("A node's coordinate is not a number (id: "+a.id+")");return!a.hidden&&a.displayX+a.displaySize>-j/3&&a.displayX-a.displaySize<4*j/3&&a.displayY+a.displaySize>-l/3&&a.displayY-a.displaySize<4*l/3}sigma.classes.Cascade.call(this);var f=this;this.p={labelColor:"default",defaultLabelColor:"#000",labelHoverBGColor:"default",defaultHoverLabelBGColor:"#fff",labelHoverShadow:!0,labelHoverShadowColor:"#000",labelHoverColor:"default",defaultLabelHoverColor:"#000",labelActiveBGColor:"default", Array.prototype.forEach = function(fun /*, thisp*/) {
defaultActiveLabelBGColor:"#fff",labelActiveShadow:!0,labelActiveShadowColor:"#000",labelActiveColor:"default",defaultLabelActiveColor:"#000",labelSize:"fixed",defaultLabelSize:12,labelSizeRatio:2,labelThreshold:6,font:"Arial",hoverFont:"",activeFont:"",fontStyle:"",hoverFontStyle:"",activeFontStyle:"",edgeColor:"source",defaultEdgeColor:"#aaa",defaultEdgeType:"line",defaultNodeColor:"#aaa",nodeHoverColor:"node",defaultNodeHoverColor:"#fff",nodeActiveColor:"node",defaultNodeActiveColor:"#fff",borderSize:0, var len = this.length;
nodeBorderColor:"node",defaultNodeBorderColor:"#fff",edgesSpeed:200,nodesSpeed:200,labelsSpeed:200};var j=g,l=c;this.currentLabelIndex=this.currentNodeIndex=this.currentEdgeIndex=0;this.task_drawLabel=function(){for(var b=a.nodes.length,c=0;c++<f.p.labelsSpeed&&f.currentLabelIndex<b;)if(f.isOnScreen(a.nodes[f.currentLabelIndex])){var d=a.nodes[f.currentLabelIndex++],h=e;if(d.displaySize>=f.p.labelThreshold){var k="fixed"==f.p.labelSize?f.p.defaultLabelSize:f.p.labelSizeRatio*d.displaySize;h.font= if (typeof fun != 'function') {
f.p.fontStyle+k+"px "+f.p.font;h.fillStyle="node"==f.p.labelColor?d.color||f.p.defaultNodeColor:f.p.defaultLabelColor;h.fillText(d.label,Math.round(d.displayX+1.5*d.displaySize),Math.round(d.displayY+k/2-3))}}else f.currentLabelIndex++;return f.currentLabelIndex<b};this.task_drawEdge=function(){for(var b=a.edges.length,c,d,e=0;e++<f.p.edgesSpeed&&f.currentEdgeIndex<b;)if(c=a.edges[f.currentEdgeIndex].source,d=a.edges[f.currentEdgeIndex].target,c.hidden||d.hidden||!f.isOnScreen(c)&&!f.isOnScreen(d))f.currentEdgeIndex++; throw new TypeError();
else{c=a.edges[f.currentEdgeIndex++];d=c.source.displayX;var k=c.source.displayY,g=c.target.displayX,i=c.target.displayY,j=c.color;if(!j)switch(f.p.edgeColor){case "source":j=c.source.color||f.p.defaultNodeColor;break;case "target":j=c.target.color||f.p.defaultNodeColor;break;default:j=f.p.defaultEdgeColor}var l=h;switch(c.type||f.p.defaultEdgeType){case "curve":l.strokeStyle=j;l.lineWidth=c.displaySize/3;l.beginPath();l.moveTo(d,k);l.quadraticCurveTo((d+g)/2+(i-k)/4,(k+i)/2+(d-g)/4,g,i);l.stroke(); }
break;default:l.strokeStyle=j,l.lineWidth=c.displaySize/3,l.beginPath(),l.moveTo(d,k),l.lineTo(g,i),l.stroke()}}return f.currentEdgeIndex<b};this.task_drawNode=function(){for(var c=a.nodes.length,d=0;d++<f.p.nodesSpeed&&f.currentNodeIndex<c;)if(f.isOnScreen(a.nodes[f.currentNodeIndex])){var e=a.nodes[f.currentNodeIndex++],k=Math.round(10*e.displaySize)/10,h=b;h.fillStyle=e.color;h.beginPath();h.arc(e.displayX,e.displayY,k,0,2*Math.PI,!0);h.closePath();h.fill();e.hover&&i(e)}else f.currentNodeIndex++;  
return f.currentNodeIndex<c};this.drawActiveNode=function(a){var b=d;if(!k(a))return f;var c="fixed"==f.p.labelSize?f.p.defaultLabelSize:f.p.labelSizeRatio*a.displaySize;b.font=(f.p.activeFontStyle||f.p.fontStyle||"")+" "+c+"px "+(f.p.activeFont||f.p.font||"");b.fillStyle="node"==f.p.labelHoverBGColor?a.color||f.p.defaultNodeColor:f.p.defaultActiveLabelBGColor;b.beginPath();if(f.p.labelActiveShadow)b.shadowOffsetX=0,b.shadowOffsetY=0,b.shadowBlur=4,b.shadowColor=f.p.labelActiveShadowColor;sigma.tools.drawRoundRect(b, var thisp = arguments[1];
Math.round(a.displayX-c/2-2),Math.round(a.displayY-c/2-2),Math.round(b.measureText(a.label).width+1.5*a.displaySize+c/2+4),Math.round(c+4),Math.round(c/2+2),"left");b.closePath();b.fill();b.shadowOffsetX=0;b.shadowOffsetY=0;b.shadowBlur=0;b.beginPath();b.fillStyle="node"==f.p.nodeBorderColor?a.color||f.p.defaultNodeColor:f.p.defaultNodeBorderColor;b.arc(Math.round(a.displayX),Math.round(a.displayY),a.displaySize+f.p.borderSize,0,2*Math.PI,!0);b.closePath();b.fill();b.beginPath();b.fillStyle="node"== for (var i = 0; i < len; i++) {
f.p.nodeActiveColor?a.color||f.p.defaultNodeColor:f.p.defaultNodeActiveColor;b.arc(Math.round(a.displayX),Math.round(a.displayY),a.displaySize,0,2*Math.PI,!0);b.closePath();b.fill();b.fillStyle="node"==f.p.labelActiveColor?a.color||f.p.defaultNodeColor:f.p.defaultLabelActiveColor;b.fillText(a.label,Math.round(a.displayX+1.5*a.displaySize),Math.round(a.displayY+c/2-3));return f};this.drawHoverNode=i;this.isOnScreen=k;this.resize=function(a,b){j=a;l=b;return f}}function t(b,h){function e(){sigma.chronos.removeTask("node_"+ if (i in this) {
c.id,2).removeTask("edge_"+c.id,2).removeTask("label_"+c.id,2).stopTasks();return c}function d(a,b){c.domElements[a]=document.createElement(b);c.domElements[a].style.position="absolute";c.domElements[a].setAttribute("id","sigma_"+a+"_"+c.id);c.domElements[a].setAttribute("class","sigma_"+a+"_"+b);c.domElements[a].setAttribute("width",c.width+"px");c.domElements[a].setAttribute("height",c.height+"px");c.domRoot.appendChild(c.domElements[a]);return c}function a(){c.p.drawHoverNodes&&(c.graph.checkHover(c.mousecaptor.mouseX, fun.call(thisp, this[i], i, this);
c.mousecaptor.mouseY),c.graph.nodes.forEach(function(a){a.hover&&!a.active&&c.plotter.drawHoverNode(a)}));return c}function A(){c.p.drawActiveNodes&&c.graph.nodes.forEach(function(a){a.active&&c.plotter.drawActiveNode(a)});return c}sigma.classes.Cascade.call(this);sigma.classes.EventDispatcher.call(this);var c=this;this.id=h.toString();this.p={auto:!0,drawNodes:2,drawEdges:1,drawLabels:2,lastNodes:2,lastEdges:0,lastLabels:2,drawHoverNodes:!0,drawActiveNodes:!0};this.domRoot=b;this.width=this.domRoot.offsetWidth; }
this.height=this.domRoot.offsetHeight;this.graph=new n;this.domElements={};d("edges","canvas");d("nodes","canvas");d("labels","canvas");d("hover","canvas");d("monitor","div");d("mouse","canvas");this.plotter=new m(this.domElements.nodes.getContext("2d"),this.domElements.edges.getContext("2d"),this.domElements.labels.getContext("2d"),this.domElements.hover.getContext("2d"),this.graph,this.width,this.height);this.monitor=new g(this,this.domElements.monitor);this.mousecaptor=new j(this.domElements.mouse, }
this.id);this.mousecaptor.bind("drag interpolate",function(){c.draw(c.p.auto?2:c.p.drawNodes,c.p.auto?0:c.p.drawEdges,c.p.auto?2:c.p.drawLabels,!0)}).bind("stopdrag stopinterpolate",function(){c.draw(c.p.auto?2:c.p.drawNodes,c.p.auto?1:c.p.drawEdges,c.p.auto?2:c.p.drawLabels,!0)}).bind("mousedown mouseup",function(a){var b=c.graph.nodes.filter(function(a){return!!a.hover}).map(function(a){return a.id});c.dispatch("mousedown"==a.type?"downgraph":"upgraph");b.length&&c.dispatch("mousedown"==a.type? };
"downnodes":"upnodes",b)}).bind("move",function(){c.domElements.hover.getContext("2d").clearRect(0,0,c.domElements.hover.width,c.domElements.hover.height);a();A()});sigma.chronos.bind("startgenerators",function(){sigma.chronos.getGeneratorsIDs().some(function(a){return!!a.match(RegExp("_ext_"+c.id+"$",""))})&&c.draw(c.p.auto?2:c.p.drawNodes,c.p.auto?0:c.p.drawEdges,c.p.auto?2:c.p.drawLabels)}).bind("stopgenerators",function(){c.draw()});for(var B=0;B<i.length;B++)i[B](this);this.draw=function(a,b, }
d,h){if(h&&sigma.chronos.getGeneratorsIDs().some(function(a){return!!a.match(RegExp("_ext_"+c.id+"$",""))}))return c;a=void 0==a?c.p.drawNodes:a;b=void 0==b?c.p.drawEdges:b;d=void 0==d?c.p.drawLabels:d;h={nodes:a,edges:b,labels:d};c.p.lastNodes=a;c.p.lastEdges=b;c.p.lastLabels=d;e();c.graph.rescale(c.width,c.height,0<a,0<b).setBorders();c.mousecaptor.checkBorders(c.graph.borders,c.width,c.height);c.graph.translate(c.mousecaptor.stageX,c.mousecaptor.stageY,c.mousecaptor.ratio,0<a,0<b);c.dispatch("graphscaled");  
for(var g in c.domElements)"canvas"==c.domElements[g].nodeName.toLowerCase()&&(void 0==h[g]||0<=h[g])&&c.domElements[g].getContext("2d").clearRect(0,0,c.domElements[g].width,c.domElements[g].height);c.plotter.currentEdgeIndex=0;c.plotter.currentNodeIndex=0;c.plotter.currentLabelIndex=0;g=null;h=!1;if(a)if(1<a)for(;c.plotter.task_drawNode(););else sigma.chronos.addTask(c.plotter.task_drawNode,"node_"+c.id,!1),h=!0,g="node_"+c.id;if(d)if(1<d)for(;c.plotter.task_drawLabel(););else g?sigma.chronos.queueTask(c.plotter.task_drawLabel, if (!Array.prototype.map) {
"label_"+c.id,g):sigma.chronos.addTask(c.plotter.task_drawLabel,"label_"+c.id,!1),h=!0,g="label_"+c.id;if(b)if(1<b)for(;c.plotter.task_drawEdge(););else g?sigma.chronos.queueTask(c.plotter.task_drawEdge,"edge_"+c.id,g):sigma.chronos.addTask(c.plotter.task_drawEdge,"edge_"+c.id,!1),h=!0,g="edge_"+c.id;c.dispatch("draw");c.refresh();h&&sigma.chronos.runTasks();return c};this.resize=function(a,b){var d=c.width,e=c.height;void 0!=a&&void 0!=b?(c.width=a,c.height=b):(c.width=c.domRoot.offsetWidth,c.height= Array.prototype.map = function(fun /*, thisp*/) {
c.domRoot.offsetHeight);if(d!=c.width||e!=c.height){for(var h in c.domElements)c.domElements[h].setAttribute("width",c.width+"px"),c.domElements[h].setAttribute("height",c.height+"px");c.plotter.resize(c.width,c.height);c.draw(c.p.lastNodes,c.p.lastEdges,c.p.lastLabels,!0)}return c};this.refresh=function(){c.domElements.hover.getContext("2d").clearRect(0,0,c.domElements.hover.width,c.domElements.hover.height);a();A();return c};this.drawHover=a;this.drawActive=A;this.clearSchedule=e;window.addEventListener("resize", var len = this.length;
function(){c.resize()})}function p(b){var h=this;sigma.classes.EventDispatcher.call(this);this._core=b;this.kill=function(){};this.getID=function(){return b.id};this.configProperties=function(e,d){var a=b.config(e,d);return a==b?h:a};this.drawingProperties=function(e,d){var a=b.plotter.config(e,d);return a==b.plotter?h:a};this.mouseProperties=function(e,d){var a=b.mousecaptor.config(e,d);return a==b.mousecaptor?h:a};this.graphProperties=function(e,d){var a=b.graph.config(e,d);return a==b.graph?h: if (typeof fun != 'function') {
a};this.getMouse=function(){return{mouseX:b.mousecaptor.mouseX,mouseY:b.mousecaptor.mouseY,down:b.mousecaptor.isMouseDown}};this.position=function(e,d,a){if(0==arguments.length)return{stageX:b.mousecaptor.stageX,stageY:b.mousecaptor.stageY,ratio:b.mousecaptor.ratio};b.mousecaptor.stageX=void 0!=e?e:b.mousecaptor.stageX;b.mousecaptor.stageY=void 0!=d?d:b.mousecaptor.stageY;b.mousecaptor.ratio=void 0!=a?a:b.mousecaptor.ratio;return h};this.goTo=function(e,d,a){b.mousecaptor.interpolate(e,d,a);return h}; throw new TypeError();
this.zoomTo=function(e,d,a){a=Math.min(Math.max(b.mousecaptor.config("minRatio"),a),b.mousecaptor.config("maxRatio"));a==b.mousecaptor.ratio?b.mousecaptor.interpolate(e-b.width/2+b.mousecaptor.stageX,d-b.height/2+b.mousecaptor.stageY):b.mousecaptor.interpolate((a*e-b.mousecaptor.ratio*b.width/2)/(a-b.mousecaptor.ratio),(a*d-b.mousecaptor.ratio*b.height/2)/(a-b.mousecaptor.ratio),a);return h};this.resize=function(e,d){b.resize(e,d);return h};this.draw=function(e,d,a,g){b.draw(e,d,a,g);return h};this.refresh= }
function(){b.refresh();return h};this.addGenerator=function(e,d,a){sigma.chronos.addGenerator(e+"_ext_"+b.id,d,a);return h};this.removeGenerator=function(e){sigma.chronos.removeGenerator(e+"_ext_"+b.id);return h};this.addNode=function(e,d){b.graph.addNode(e,d);return h};this.addEdge=function(e,d,a,g){b.graph.addEdge(e,d,a,g);return h};this.dropNode=function(e){b.graph.dropNode(e);return h};this.dropEdge=function(e){b.graph.dropEdge(e);return h};this.pushGraph=function(e,d){e.nodes&&e.nodes.forEach(function(a){a.id&&  
(!d||!b.graph.nodesIndex[a.id])&&h.addNode(a.id,a)});e.edges&&e.edges.forEach(function(a){(validID=a.source&&a.target&&a.id)&&(!d||!b.graph.edgesIndex[a.id])&&h.addNode(a.id,a.source,a.target,a)});return h};this.emptyGraph=function(){b.graph.empty();return h};this.getNodesCount=function(){return b.graph.nodes.length};this.getEdgesCount=function(){return b.graph.edges.length};this.iterNodes=function(e,d){b.graph.iterNodes(e,d);return h};this.iterEdges=function(e,d){b.graph.iterEdges(e,d);return h}; var res = new Array(len);
this.getNodes=function(e){return b.graph.getNodes(e)};this.getEdges=function(e){return b.graph.getEdges(e)};this.activateMonitoring=function(){return b.monitor.activate()};this.desactivateMonitoring=function(){return b.monitor.desactivate()};b.bind("downnodes upnodes downgraph upgraph",function(b){h.dispatch(b.type,b.content)});b.graph.bind("overnodes outnodes",function(b){h.dispatch(b.type,b.content)})}var x=0;i=void 0;i=[];sigma.init=function(b){b=new t(b,(++x).toString());sigma.instances[x]=new p(b); var thisp = arguments[1];
return sigma.instances[x]};sigma.addPlugin=function(b,h,e){p.prototype[b]=h;i.push(e)};sigma.chronos=new function(){function b(a){window.setTimeout(a,0);return f}function h(){for(f.dispatch("frameinserted");m&&r.length&&e(););!m||!r.length?a():(w=(new Date).getTime(),q++,C=u-p,t=p-C,f.dispatch("insertframe"),b(h))}function e(){y%=r.length;if(!r[y].task()){var a=r[y].taskName;v=v.filter(function(b){b.taskParent==a&&r.push({taskName:b.taskName,task:b.task});return b.taskParent!=a});f.dispatch("killed", for (var i = 0; i < len; i++) {
r.splice(y--,1)[0])}y++;u=(new Date).getTime()-w;return u<=t}function d(){m=!0;q=y=0;x=w=(new Date).getTime();f.dispatch("start");f.dispatch("insertframe");b(h);return f}function a(){f.dispatch("stop");m=!1;return f}function g(a,b,c){if("function"!=typeof a)throw Error('Task "'+b+'" is not a function');r.push({taskName:b,task:a});m=!(!m&&!(c&&d()||1));return f}function c(a){return a?Object.keys(o).filter(function(a){return!!o[a].on}).length:Object.keys(o).length}function i(){Object.keys(o).length? if (i in this) {
(f.dispatch("startgenerators"),f.unbind("killed",j),b(function(){for(var a in o)o[a].on=!0,g(o[a].task,a,!1)}),f.bind("killed",j).runTasks()):f.dispatch("stopgenerators");return f}function j(a){if(void 0!=o[a.content.taskName])o[a.content.taskName].del||!o[a.content.taskName].condition()?delete o[a.content.taskName]:o[a.content.taskName].on=!1,0==c(!0)&&i()}sigma.classes.EventDispatcher.call(this);var f=this,m=!1,l=80,n=0,q=0,p=1E3/l,t=p,u=0,x=0,w=0,C=0,o={},r=[],v=[],y=0;this.frequency=function(a){return void 0!= res[i] = fun.call(thisp, this[i], i, this);
a?(l=Math.abs(1*a),p=1E3/l,q=0,f):l};this.runTasks=d;this.stopTasks=a;this.insertFrame=b;this.addTask=g;this.queueTask=function(a,b,c){if("function"!=typeof a)throw Error('Task "'+b+'" is not a function');if(!r.concat(v).some(function(a){return a.taskName==c}))throw Error('Parent task "'+c+'" of "'+b+'" is not attached.');v.push({taskParent:c,taskName:b,task:a});return f};this.removeTask=function(b,c){if(void 0==b)r=[],1==c?v=[]:2==c&&(r=v,v=[]),a();else{var d="string"==typeof b?b:"";r=r.filter(function(a){return("string"== }
typeof b?a.taskName==b:a.task==b)?(d=a.taskName,!1):!0});0<c&&(v=v.filter(function(a){1==c&&a.taskParent==d&&r.push(a);return a.taskParent!=d}))}m=!(r.length&&(!a()||1));return f};this.addGenerator=function(a,b,d){if(void 0!=o[a])return f;o[a]={task:b,condition:d};0==c(!0)&&i();return f};this.removeGenerator=function(a){if(o[a])o[a].on=!1,o[a].del=!0;return f};this.startGenerators=i;this.getGeneratorsIDs=function(){return Object.keys(o)};this.getFPS=function(){m&&(n=Math.round(1E4*(q/((new Date).getTime()- }
x)))/10);return n};this.getTasksCount=function(){return r.length};this.getQueuedTasksCount=function(){return v.length};this.getExecutionTime=function(){return w-x};return this};sigma.debugMode=0;sigma.log=function(){if(1==sigma.debugMode)for(var b in arguments)console.log(arguments[b]);else if(1<sigma.debugMode)for(b in arguments)throw Error(arguments[b]);return sigma};sigma.easing={linear:{},quadratic:{}};sigma.easing.linear.easenone=function(b){return b};sigma.easing.quadratic.easein=function(b){return b*  
b};sigma.easing.quadratic.easeout=function(b){return-b*(b-2)};sigma.easing.quadratic.easeinout=function(b){return 1>(b*=2)?0.5*b*b:-0.5*(--b*(b-2)-1)};sigma.tools.drawRoundRect=function(b,h,e,d,a,g,c){var g=g?g:0,i=c?c:[],i="string"==typeof i?i.split(" "):i,c=g&&(0<=i.indexOf("topleft")||0<=i.indexOf("top")||0<=i.indexOf("left")),j=g&&(0<=i.indexOf("topright")||0<=i.indexOf("top")||0<=i.indexOf("right")),f=g&&(0<=i.indexOf("bottomleft")||0<=i.indexOf("bottom")||0<=i.indexOf("left")),i=g&&(0<=i.indexOf("bottomright")|| return res;
0<=i.indexOf("bottom")||0<=i.indexOf("right"));b.moveTo(h,e+g);c?b.arcTo(h,e,h+g,e,g):b.lineTo(h,e);j?(b.lineTo(h+d-g,e),b.arcTo(h+d,e,h+d,e+g,g)):b.lineTo(h+d,e);i?(b.lineTo(h+d,e+a-g),b.arcTo(h+d,e+a,h+d-g,e+a,g)):b.lineTo(h+d,e+a);f?(b.lineTo(h+g,e+a),b.arcTo(h,e+a,h,e+a-g,g)):b.lineTo(h,e+a);b.lineTo(h,e+g)};sigma.tools.getRGB=function(b,g){var b=b.toString(),e={r:0,g:0,b:0};if(3<=b.length&&"#"==b.charAt(0)){var d=b.length-1;6==d?e={r:parseInt(b.charAt(1)+b.charAt(2),16),g:parseInt(b.charAt(3)+ };
b.charAt(4),16),b:parseInt(b.charAt(5)+b.charAt(5),16)}:3==d&&(e={r:parseInt(b.charAt(1)+b.charAt(1),16),g:parseInt(b.charAt(2)+b.charAt(2),16),b:parseInt(b.charAt(3)+b.charAt(3),16)})}g&&(e=[e.r,e.g,e.b]);return e};sigma.tools.rgbToHex=function(b,g,e){return sigma.tools.toHex(b)+sigma.tools.toHex(g)+sigma.tools.toHex(e)};sigma.tools.toHex=function(b){b=parseInt(b,10);if(isNaN(b))return"00";b=Math.max(0,Math.min(b,255));return"0123456789ABCDEF".charAt((b-b%16)/16)+"0123456789ABCDEF".charAt(b%16)}; }
sigma.publicPrototype=p.prototype})();  
  if (!Array.prototype.filter) {
  Array.prototype.filter = function(fun /*, thisp*/) {
  var len = this.length;
  if (typeof fun != 'function')
  throw new TypeError();
   
  var res = new Array();
  var thisp = arguments[1];
  for (var i = 0; i < len; i++) {
  if (i in this) {
  var val = this[i]; // in case fun mutates this
  if (fun.call(thisp, val, i, this)) {
  res.push(val);
  }
  }
  }
   
  return res;
  };
  }
   
  if (!Object.keys) {
  Object.keys = (function() {
  var hasOwnProperty = Object.prototype.hasOwnProperty,
  hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
  dontEnums = [
  'toString',
  'toLocaleString',
  'valueOf',
  'hasOwnProperty',
  'isPrototypeOf',
  'propertyIsEnumerable',
  'constructor'
  ],
  dontEnumsLength = dontEnums.length;
   
  return function(obj) {
  if (typeof obj !== 'object' &&
  typeof obj !== 'function' ||
  obj === null
  ) {
  throw new TypeError('Object.keys called on non-object');
  }
   
  var result = [];
   
  for (var prop in obj) {
  if (hasOwnProperty.call(obj, prop)) result.push(prop);
  }
   
  if (hasDontEnumBug) {
  for (var i = 0; i < dontEnumsLength; i++) {
  if (hasOwnProperty.call(obj, dontEnums[i])) {
  result.push(dontEnums[i]);
  }
  }
  }
  return result;
  }
  })();
  }
  })();
   
  /**
  * A jQuery like properties management class. It works like jQuery .css()
  * method: You can call it with juste one string to get the corresponding
  * property, with a string and anything else to set the corresponding property,
  * or directly with an object, and then each pair string / object (or any type)
  * will be set in the properties.
  * @constructor
  * @this {sigma.classes.Cascade}
  */
  sigma.classes.Cascade = function() {
  /**
  * This instance properties.
  * @protected
  * @type {Object}
  */
  this.p = {};
   
  /**
  * The method to use to set/get any property of this instance.
  * @param {(string|Object)} a1 If it is a string and if a2 is undefined,
  * then it will return the corresponding
  * property.
  * If it is a string and if a2 is set, then it
  * will set a2 as the property corresponding to
  * a1, and return this.
  * If it is an object, then each pair string /
  * object (or any other type) will be set as a
  * property.
  * @param {*?} a2 The new property corresponding to a1 if a1 is
  * a string.
  * @return {(*|sigma.classes.Cascade)} Returns itself or the corresponding
  * property.
  */
  this.config = function(a1, a2) {
  if (typeof a1 == 'string' && a2 == undefined) {
  return this.p[a1];
  } else {
  var o = (typeof a1 == 'object' && a2 == undefined) ? a1 : {};
  if (typeof a1 == 'string') {
  o[a1] = a2;
  }
   
  for (var k in o) {
  if (this.p[k] != undefined) {
  this.p[k] = o[k];
  }
  }
  return this;
  }
  };
  };
   
  /**
  * sigma.js custom event dispatcher class.
  * @constructor
  * @this {sigma.classes.EventDispatcher}
  */
  sigma.classes.EventDispatcher = function() {
  /**
  * An object containing all the different handlers bound to one or many
  * events, indexed by these events.
  * @private
  * @type {Object.<string,Object>}
  */
  var _h = {};
   
  /**
  * Represents "this", without the well-known scope issue.
  * @private
  * @type {sigma.classes.EventDispatcher}
  */
  var _self = this;
   
  /**
  * Will execute the handler the next (and only the next) time that the
  * indicated event (or the indicated events) will be triggered.
  * @param {string} events The name of the event (or the events
  * separated by spaces).
  * @param {function(Object)} handler The handler to bind.
  * @return {sigma.classes.EventDispatcher} Returns itself.
  */
  function one(events, handler) {
  if (!handler || !events) {
  return _self;
  }
   
  var eArray = ((typeof events) == 'string') ? events.split(' ') : events;
   
  eArray.forEach(function(event) {
  if (!_h[event]) {
  _h[event] = [];
  }
   
  _h[event].push({
  'h': handler,
  'one': true
  });
  });
   
  return _self;
  }
   
  /**
  * Will execute the handler everytime that the indicated event (or the
  * indicated events) will be triggered.
  * @param {string} events The name of the event (or the events
  * separated by spaces).
  * @param {function(Object)} handler The handler to bind.
  * @return {sigma.classes.EventDispatcher} Returns itself.
  */
  function bind(events, handler) {
  if (!handler || !events) {
  return _self;
  }
   
  var eArray = ((typeof events) == 'string') ? events.split(' ') : events;
   
  eArray.forEach(function(event) {
  if (!_h[event]) {
  _h[event] = [];
  }
   
  _h[event].push({
  'h': handler,
  'one': false
  });
  });
   
  return _self;
  }
   
  /**
  * Unbinds the handler from a specified event (or specified events).
  * @param {?string} events The name of the event (or the events
  * separated by spaces). If undefined,
  * then all handlers are unbound.
  * @param {?function(Object)} handler The handler to unbind. If undefined,
  * each handler bound to the event or the
  * events will be unbound.
  * @return {sigma.classes.EventDispatcher} Returns itself.
  */
  function unbind(events, handler) {
  if (!events) {
  _h = {};
  }
   
  var eArray = typeof events == 'string' ? events.split(' ') : events;
   
  if (handler) {
  eArray.forEach(function(event) {
  if (_h[event]) {
  _h[event] = _h[event].filter(function(e) {
  return e['h'] != handler;
  });
  }
   
  if (_h[event] && _h[event].length == 0) {
  delete _h[event];
  }
  });
  }else {
  eArray.forEach(function(event) {
  delete _h[event];
  });
  }
   
  return _self;
  }
   
  /**
  * Executes each handler bound to the event
  * @param {string} type The type of the event.
  * @param {?Object} content The content of the event (optional).
  * @return {sigma.classes.EventDispatcher} Returns itself.
  */
  function dispatch(type, content) {
  if (_h[type]) {
  _h[type].forEach(function(e) {
  e['h']({
  'type': type,
  'content': content,
  'target': _self
  });
  });
   
  _h[type] = _h[type].filter(function(e) {
  return !e['one'];
  });
  }
   
  return _self;
  }
   
  /* PUBLIC INTERFACE: */
  this.one = one;
  this.bind = bind;
  this.unbind = unbind;
  this.dispatch = dispatch;
  };
   
  (function() {
  // Define local shortcut:
  var id = 0;
   
  // Define local package:
  var local = {};
  local.plugins = [];
   
  sigma.init = function(dom) {
  var inst = new Sigma(dom, (++id).toString());
  sigma.instances[id] = new SigmaPublic(inst);
  return sigma.instances[id];
  };
   
  /**
  * This class listen to all the different mouse events, to normalize them and
  * dispatch action events instead (from "startinterpolate" to "isdragging",
  * etc).
  * @constructor
  * @extends sigma.classes.Cascade
  * @extends sigma.classes.EventDispatcher
  * @param {element} dom The DOM element to bind the handlers on.
  * @this {MouseCaptor}
  */
  function MouseCaptor(dom) {
  sigma.classes.Cascade.call(this);
  sigma.classes.EventDispatcher.call(this);
   
  /**
  * Represents "this", without the well-known scope issue.
  * @private
  * @type {MouseCaptor}
  */
  var self = this;
   
  /**
  * The DOM element to bind the handlers on.
  * @type {element}
  */
  var dom = dom;
   
  /**
  * The different parameters that define how this instance should work.
  * @see sigma.classes.Cascade
  * @type {Object}
  */
  this.p = {
  minRatio: 1,
  maxRatio: 32,
  marginRatio: 1,
  zoomDelta: 0.1,
  dragDelta: 0.3,
  zoomMultiply: 2,
  directZooming: false,
  blockScroll: true,
  inertia: 1.1,
  mouseEnabled: true
  };
   
  var oldMouseX = 0;
  var oldMouseY = 0;
  var startX = 0;
  var startY = 0;
   
  var oldStageX = 0;
  var oldStageY = 0;
  var oldRatio = 1;
   
  var targetRatio = 1;
  var targetStageX = 0;
  var targetStageY = 0;
   
  var lastStageX = 0;
  var lastStageX2 = 0;
  var lastStageY = 0;
  var lastStageY2 = 0;
   
  var progress = 0;
  var isZooming = false;
   
  this.stageX = 0;
  this.stageY = 0;
  this.ratio = 1;
   
  this.mouseX = 0;
  this.mouseY = 0;
   
  this.isMouseDown = false;
   
  /**
  * Extract the local X position from a mouse event.
  * @private
  * @param {event} e A mouse event.
  * @return {number} The local X value of the mouse.
  */
  function getX(e) {
  return e.offsetX != undefined && e.offsetX ||
  e.layerX != undefined && e.layerX ||
  e.clientX != undefined && e.clientX;
  };
   
  /**
  * Extract the local Y position from a mouse event.
  * @private
  * @param {event} e A mouse event.
  * @return {number} The local Y value of the mouse.
  */
  function getY(e) {
  return e.offsetY != undefined && e.offsetY ||
  e.layerY != undefined && e.layerY ||
  e.clientY != undefined && e.clientY;
  };
   
  /**
  * Extract the wheel delta from a mouse event.
  * @private
  * @param {event} e A mouse event.
  * @return {number} The wheel delta of the mouse.
  */
  function getDelta(e) {
  return e.wheelDelta != undefined && e.wheelDelta ||
  e.detail != undefined && -e.detail;
  };
   
  /**
  * The handler listening to the 'move' mouse event. It will set the mouseX
  * and mouseY values as the mouse position values, prevent the default event,
  * and dispatch a 'move' event.
  * @private
  * @param {event} event A 'move' mouse event.
  */
  function moveHandler(event) {
  oldMouseX = self.mouseX;
  oldMouseY = self.mouseY;
   
  self.mouseX = getX(event);
  self.mouseY = getY(event);
   
  self.isMouseDown && drag(event);
  self.dispatch('move');
   
  if (event.preventDefault) {
  event.preventDefault();
  } else {
  event.returnValue = false;
  }
  };
   
  /**
  * The handler listening to the 'up' mouse event. It will set the isMouseDown
  * value as false, dispatch a 'mouseup' event, and trigger stopDrag().
  * @private
  * @param {event} event A 'up' mouse event.
  */
  function upHandler(event) {
  if (self.p.mouseEnabled && self.isMouseDown) {
  self.isMouseDown = false;
  self.dispatch('mouseup');
  stopDrag();
   
  if (event.preventDefault) {
  event.preventDefault();
  } else {
  event.returnValue = false;
  }
  }
  };
   
  /**
  * The handler listening to the 'down' mouse event. It will set the
  * isMouseDown value as true, dispatch a 'mousedown' event, and trigger
  * startDrag().
  * @private
  * @param {event} event A 'down' mouse event.
  */
  function downHandler(event) {
  if (self.p.mouseEnabled) {
  self.isMouseDown = true;
  oldMouseX = self.mouseX;
  oldMouseY = self.mouseY;
   
  self.dispatch('mousedown');
   
  startDrag();
   
  if (event.preventDefault) {
  event.preventDefault();
  } else {
  event.returnValue = false;
  }
  }
  };
   
  /**
  * The handler listening to the 'wheel' mouse event. It will trigger
  * {@link startInterpolate} with the event delta as parameter.
  * @private
  * @param {event} event A 'wheel' mouse event.
  */
  function wheelHandler(event) {
  if (self.p.mouseEnabled) {
  startInterpolate(
  self.mouseX,
  self.mouseY,
  self.ratio * (getDelta(event) > 0 ?
  self.p.zoomMultiply :
  1 / self.p.zoomMultiply)
  );
   
  if (self.p['blockScroll']) {
  if (event.preventDefault) {
  event.preventDefault();
  } else {
  event.returnValue = false;
  }
  }
  }
  };
   
  /**
  * Will start computing the scene X and Y, until {@link stopDrag} is
  * triggered.
  */
  function startDrag() {
  oldStageX = self.stageX;
  oldStageY = self.stageY;
  startX = self.mouseX;
  startY = self.mouseY;
   
  lastStageX = self.stageX;
  lastStageX2 = self.stageX;
  lastStageY = self.stageY;
  lastStageY2 = self.stageY;
   
  self.dispatch('startdrag');
  };
   
  /**
  * Stops computing the scene position.
  */
  function stopDrag() {
  if (oldStageX != self.stageX || oldStageY != self.stageY) {
  startInterpolate(
  self.stageX + self.p.inertia * (self.stageX - lastStageX2),
  self.stageY + self.p.inertia * (self.stageY - lastStageY2)
  );
  }
  };
   
  /**
  * Computes the position of the scene, relatively to the mouse position, and
  * dispatches a "drag" event.
  */
  function drag() {
  var newStageX = self.mouseX - startX + oldStageX;
  var newStageY = self.mouseY - startY + oldStageY;
   
  if (newStageX != self.stageX || newStageY != self.stageY) {
  lastStageX2 = lastStageX;
  lastStageY2 = lastStageY;
   
  lastStageX = newStageX;
  lastStageY = newStageY;
   
  self.stageX = newStageX;
  self.stageY = newStageY;
  self.dispatch('drag');
  }
  };
   
  /**
  * Will start computing the scene zoom ratio, until {@link stopInterpolate} is
  * triggered.
  * @param {number} x The new stage X.
  * @param {number} y The new stage Y.
  * @param {number} ratio The new zoom ratio.
  */
  function startInterpolate(x, y, ratio) {
  if (self.isMouseDown) {
  return;
  }
   
  window.clearInterval(self.interpolationID);
  isZooming = ratio != undefined;
   
  oldStageX = self.stageX;
  targetStageX = x;
   
  oldStageY = self.stageY;
  targetStageY = y;
   
  oldRatio = self.ratio;
  targetRatio = ratio || self.ratio;
  targetRatio = Math.min(
  Math.max(targetRatio, self.p.minRatio),
  self.p.maxRatio
  );
   
  progress =
  self.p.directZooming ?
  1 - (isZooming ? self.p.zoomDelta : self.p.dragDelta) :
  0;
   
  if (
  self.ratio != targetRatio ||
  self.stageX != targetStageX ||
  self.stageY != targetStageY
  ) {
  interpolate();
  self.interpolationID = window.setInterval(interpolate, 50);
  self.dispatch('startinterpolate');
  }
  };
   
  /**
  * Stops the move interpolation.
  */
  function stopInterpolate() {
  var oldRatio = self.ratio;
   
  if (isZooming) {
  self.ratio = targetRatio;
  self.stageX = targetStageX +
  (self.stageX - targetStageX) *
  self.ratio /
  oldRatio;
  self.stageY = targetStageY +
  (self.stageY - targetStageY) *
  self.ratio /
  oldRatio;
  }else {
  self.stageX = targetStageX;
  self.stageY = targetStageY;
  }
   
  self.dispatch('stopinterpolate');
  };
   
  /**
  * Computes the interpolate ratio and the position of the scene, relatively
  * to the last mouse event delta received, and dispatches a "interpolate"
  * event.
  */
  function interpolate() {
  progress += (isZooming ? self.p.zoomDelta : self.p.dragDelta);
  progress = Math.min(progress, 1);
   
  var k = sigma.easing.quadratic.easeout(progress);
  var oldRatio = self.ratio;
   
  self.ratio = oldRatio * (1 - k) + targetRatio * k;
   
  if (isZooming) {
  self.stageX = targetStageX +
  (self.stageX - targetStageX) *
  self.ratio /
  oldRatio;
   
  self.stageY = targetStageY +
  (self.stageY - targetStageY) *
  self.ratio /
  oldRatio;
  } else {
  self.stageX = oldStageX * (1 - k) + targetStageX * k;
  self.stageY = oldStageY * (1 - k) + targetStageY * k;
  }
   
  self.dispatch('interpolate');
  if (progress >= 1) {
  window.clearInterval(self.interpolationID);
  stopInterpolate();
  }
  };
   
  /**
  * Checks that there is always a part of the graph that is displayed, to
  * avoid the user to drag the graph out of the stage.
  * @param {Object} b An object containing the borders of the graph.
  * @param {number} width The width of the stage.
  * @param {number} height The height of the stage.
  * @return {MouseCaptor} Returns itself.
  */
  function checkBorders(b, width, height) {
  // TODO : Find the good formula
  /*if (!isNaN(b.minX) && !isNaN(b.maxX)) {
  self.stageX = Math.min(
  self.stageX = Math.max(
  self.stageX,
  (b.minX - width) * self.ratio +
  self.p.marginRatio*(b.maxX - b.minX)
  ),
  (b.maxX - width) * self.ratio +
  width -
  self.p.marginRatio*(b.maxX - b.minX)
  );
  }
   
  if (!isNaN(b.minY) && !isNaN(b.maxY)) {
  self.stageY = Math.min(
  self.stageY = Math.max(
  self.stageY,
  (b.minY - height) * self.ratio +
  self.p.marginRatio*(b.maxY - b.minY)
  ),
  (b.maxY - height) * self.ratio +
  height -
  self.p.marginRatio*(b.maxY - b.minY)
  );
  }*/
   
  return self;
  };
   
  // ADD CALLBACKS
  dom.addEventListener('DOMMouseScroll', wheelHandler, true);
  dom.addEventListener('mousewheel', wheelHandler, true);
  dom.addEventListener('mousemove', moveHandler, true);
  dom.addEventListener('mousedown', downHandler, true);
  document.addEventListener('mouseup', upHandler, true);
   
  this.checkBorders = checkBorders;
  this.interpolate = startInterpolate;
  }
   
  /**
  * A class to monitor some local / global probes directly on an instance,
  * inside a div DOM element.
  * It executes different methods (called "probes") regularly, and displays
  * the results on the element.
  * @constructor
  * @extends sigma.classes.Cascade
  * @param {Sigma} instance The instance to monitor.
  * @param {element} dom The div DOM element to draw write on.
  * @this {Monitor}
  */
  function Monitor(instance, dom) {
  sigma.classes.Cascade.call(this);
   
  /**
  * Represents "this", without the well-known scope issue.
  * @private
  * @type {Monitor}
  */
  var self = this;
   
  /**
  * {@link Sigma} instance owning this Monitor instance.
  * @type {Sigma}
  */
  this.instance = instance;
   
  /**
  * Determines if the monitoring is activated or not.
  * @type {Boolean}
  */
  this.monitoring = false;
   
  /**
  * The different parameters that define how this instance should work. It
  * also contains the different probes.
  * @see sigma.classes.Cascade
  * @type {Object}
  */
  this.p = {
  fps: 40,
  dom: dom,
  globalProbes: {
  'Time (ms)': sigma.chronos.getExecutionTime,
  'Queue': sigma.chronos.getQueuedTasksCount,
  'Tasks': sigma.chronos.getTasksCount,
  'FPS': sigma.chronos.getFPS
  },
  localProbes: {
  'Nodes count': function() { return self.instance.graph.nodes.length; },
  'Edges count': function() { return self.instance.graph.edges.length; }
  }
  };
   
  /**
  * Activates the monitoring: Some texts describing some values about sigma.js
  * or the owning {@link Sigma} instance will appear over the graph, but
  * beneath the mouse sensible DOM element.
  * @return {Monitor} Returns itself.
  */
  function activate() {
  if (!self.monitoring) {
  self.monitoring = window.setInterval(routine, 1000 / self.p.fps);
  }
   
  return self;
  }
   
  /**
  * Desactivates the monitoring: Will disappear, and stop computing the
  * different probes.
  * @return {Monitor} Returns itself.
  */
  function desactivate() {
  if (self.monitoring) {
  window.clearInterval(self.monitoring);
  self.monitoring = null;
   
  self.p.dom.innerHTML = '';
  }
   
  return self;
  }
   
  /**
  * The private method dedicated to compute the different values to observe.
  * @private
  * @return {Monitor} Returns itself.
  */
  function routine() {
  var s = '';
   
  s += '<p>GLOBAL :</p>';
  for (var k in self.p.globalProbes) {
  s += '<p>' + k + ' : ' + self.p.globalProbes[k]() + '</p>';
  }
   
  s += '<br><p>LOCAL :</p>';
  for (var k in self.p.localProbes) {
  s += '<p>' + k + ' : ' + self.p.localProbes[k]() + '</p>';
  }
   
  self.p.dom.innerHTML = s;
   
  return self;
  }
   
  this.activate = activate;
  this.desactivate = desactivate;
  }
   
  /**
  * Sigma is the main class. It represents the core of any instance id sigma.js.
  * It is private and can be initialized only from inside sigma.js. To see its
  * public interface, see {@link SigmaPublic}.
  * It owns its own {@link Graph}, {@link MouseCaptor}, {@link Plotter}
  * and {@link Monitor}.
  * @constructor
  * @extends sigma.classes.Cascade
  * @extends sigma.classes.EventDispatcher
  * @param {element} root The DOM root of this instance (a div, for example).
  * @param {string} id The ID of this instance.
  * @this {Sigma}
  */
  function Sigma(root, id) {
  sigma.classes.Cascade.call(this);
  sigma.classes.EventDispatcher.call(this);
   
  /**
  * Represents "this", without the well-known scope issue.
  * @private
  * @type {Sigma}
  */
  var self = this;
   
  /**
  * The ID of the instance.
  * @type {string}
  */
  this.id = id.toString();
   
  /**
  * The different parameters that define how this instance should work.
  * @see sigma.classes.Cascade
  * @type {Object}
  */
  this.p = {
  auto: true,
  drawNodes: 2,
  drawEdges: 1,
  drawLabels: 2,
  lastNodes: 2,
  lastEdges: 0,
  lastLabels: 2,
  drawHoverNodes: true,
  drawActiveNodes: true
  };
   
  /**
  * The root DOM element of this instance, containing every other elements.
  * @type {element}
  */
  this.domRoot = root;
   
  /**
  * The width of this instance - initially, the root's width.
  * @type {number}
  */
  this.width = this.domRoot.offsetWidth;
   
  /**
  * The height of this instance - initially, the root's height.
  * @type {number}
  */
  this.height = this.domRoot.offsetHeight;
   
  /**
  * The graph of this instance - initiallyempty.
  * @type {Graph}
  */
  this.graph = new Graph();
   
  /**
  * An object referencing every DOM elements used by this instance.
  * @type {Object}
  */
  this.domElements = {};
   
  initDOM('edges', 'canvas');
  initDOM('nodes', 'canvas');
  initDOM('labels', 'canvas');
  initDOM('hover', 'canvas');
  initDOM('monitor', 'div');
  initDOM('mouse', 'canvas');
   
  /**
  * The class dedicated to manage the drawing process of the graph of the
  * different canvas.
  * @type {Plotter}
  */
  this.plotter = new Plotter(
  this.domElements.nodes.getContext('2d'),
  this.domElements.edges.getContext('2d'),
  this.domElements.labels.getContext('2d'),
  this.domElements.hover.getContext('2d'),
  this.graph,
  this.width,
  this.height
  );
   
  /**
  * The class dedicated to monitor different probes about the running
  * processes or the data, such as the number of nodes or edges, or how
  * many times the graph is drawn per second.
  * @type {Monitor}
  */
  this.monitor = new Monitor(
  this,
  this.domElements.monitor
  );
   
  /**
  * The class dedicated to manage the different mouse events.
  * @type {MouseCaptor}
  */
  this.mousecaptor = new MouseCaptor(
  this.domElements.mouse,
  this.id
  );
   
  // Interaction listeners:
  this.mousecaptor.bind('drag interpolate', function(e) {
  self.draw(
  self.p.auto ? 2 : self.p.drawNodes,
  self.p.auto ? 0 : self.p.drawEdges,
  self.p.auto ? 2 : self.p.drawLabels,
  true
  );
  }).bind('stopdrag stopinterpolate', function(e) {
  self.draw(
  self.p.auto ? 2 : self.p.drawNodes,
  self.p.auto ? 1 : self.p.drawEdges,
  self.p.auto ? 2 : self.p.drawLabels,
  true
  );
  }).bind('mousedown mouseup', function(e) {
  var targeted = self.graph.nodes.filter(function(n) {
  return !!n['hover'];
  }).map(function(n) {
  return n.id;
  });
   
  self.dispatch(
  e['type'] == 'mousedown' ?
  'downgraph' :
  'upgraph'
  );
   
  if (targeted.length) {
  self.dispatch(
  e['type'] == 'mousedown' ?
  'downnodes' :
  'upnodes',
  targeted
  );
  }
  }).bind('move', function() {
  self.domElements.hover.getContext('2d').clearRect(
  0,
  0,
  self.domElements.hover.width,
  self.domElements.hover.height
  );
   
  drawHover();
  drawActive();
  });
   
  sigma.chronos.bind('startgenerators', function() {
  if (sigma.chronos.getGeneratorsIDs().some(function(id) {
  return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
  })) {
  self.draw(
  self.p.auto ? 2 : self.p.drawNodes,
  self.p.auto ? 0 : self.p.drawEdges,
  self.p.auto ? 2 : self.p.drawLabels
  );
  }
  }).bind('stopgenerators', function() {
  self.draw();
  });
   
  /**
  * Resizes the element, and redraws the graph with the last settings.
  * @param {?number} w The new width (if undefined, it will use the root
  * width).
  * @param {?number} h The new height (if undefined, it will use the root
  * height).
  * @return {Sigma} Returns itself.
  */
  function resize(w, h) {
  var oldW = self.width, oldH = self.height;
   
  if (w != undefined && h != undefined) {
  self.width = w;
  self.height = h;
  }else {
  self.width = self.domRoot.offsetWidth;
  self.height = self.domRoot.offsetHeight;
  }
   
  if (oldW != self.width || oldH != self.height) {
  for (var k in self.domElements) {
  self.domElements[k].setAttribute('width', self.width + 'px');
  self.domElements[k].setAttribute('height', self.height + 'px');
  }
   
  self.plotter.resize(self.width, self.height);
   
  self.draw(
  self.p.lastNodes,
  self.p.lastEdges,
  self.p.lastLabels,
  true
  );
  }
  return self;
  };
   
  /**
  * Kills every drawing task currently running. Basically, it stops this
  * instance's drawing process.
  * @return {Sigma} Returns itself.
  */
  function clearSchedule() {
  sigma.chronos.removeTask(
  'node_' + self.id, 2
  ).removeTask(
  'edge_' + self.id, 2
  ).removeTask(
  'label_' + self.id, 2
  ).stopTasks();
  return self;
  };
   
  /**
  * Initialize a DOM element, that will be stores by this instance, to make
  * automatic these elements resizing.
  * @private
  * @param {string} id The element's ID.
  * @param {string} type The element's nodeName (Example : canvas, div, ...).
  * @return {Sigma} Returns itself.
  */
  function initDOM(id, type) {
  self.domElements[id] = document.createElement(type);
  self.domElements[id].style.position = 'absolute';
  self.domElements[id].setAttribute('id', 'sigma_' + id + '_' + self.id);
  self.domElements[id].setAttribute('class', 'sigma_' + id + '_' + type);
  self.domElements[id].setAttribute('width', self.width + 'px');
  self.domElements[id].setAttribute('height', self.height + 'px');
   
  self.domRoot.appendChild(self.domElements[id]);
  return self;
  };
   
  /**
  * Starts the graph drawing process. The three first parameters indicate
  * how the different layers have to be drawn:
  * . -1: The layer is not drawn, but it is not erased.
  * . 0: The layer is not drawn.
  * . 1: The layer is drawn progressively.
  * . 2: The layer is drawn directly.
  * @param {?number} nodes Determines if and how the nodes must be drawn.
  * @param {?number} edges Determines if and how the edges must be drawn.
  * @param {?number} labels Determines if and how the labels must be drawn.
  * @param {?boolean} safe If true, nothing will happen if any generator
  * affiliated to this instance is currently running
  * (an iterative layout, for example).
  * @return {Sigma} Returns itself.
  */
  function draw(nodes, edges, labels, safe) {
  if (safe && sigma.chronos.getGeneratorsIDs().some(function(id) {
  return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
  })) {
  return self;
  }
   
  var n = (nodes == undefined) ? self.p.drawNodes : nodes;
  var e = (edges == undefined) ? self.p.drawEdges : edges;
  var l = (labels == undefined) ? self.p.drawLabels : labels;
   
  var params = {
  nodes: n,
  edges: e,
  labels: l
  };
   
  self.p.lastNodes = n;
  self.p.lastEdges = e;
  self.p.lastLabels = l;
   
  // Remove tasks:
  clearSchedule();
   
  // Rescale graph:
  self.graph.rescale(
  self.width,
  self.height,
  n > 0,
  e > 0
  ).setBorders();
   
  self.mousecaptor.checkBorders(
  self.graph.borders,
  self.width,
  self.height
  );
   
  self.graph.translate(
  self.mousecaptor.stageX,
  self.mousecaptor.stageY,
  self.mousecaptor.ratio,
  n > 0,
  e > 0
  );
   
  self.dispatch(
  'graphscaled'
  );
   
  // Clear scene:
  for (var k in self.domElements) {
  if (
  self.domElements[k].nodeName.toLowerCase() == 'canvas' &&
  (params[k] == undefined || params[k] >= 0)
  ) {
  self.domElements[k].getContext('2d').clearRect(
  0,
  0,
  self.domElements[k].width,
  self.domElements[k].height
  );
  }
  }
   
  self.plotter.currentEdgeIndex = 0;
  self.plotter.currentNodeIndex = 0;
  self.plotter.currentLabelIndex = 0;
   
  var previous = null;
  var start = false;
   
  if (n) {
  if (n > 1) {
  while (self.plotter.task_drawNode()) {}
  }else {
  sigma.chronos.addTask(
  self.plotter.task_drawNode,
  'node_' + self.id,
  false
  );
   
  start = true;
  previous = 'node_' + self.id;
  }
  }
   
  if (l) {
  if (l > 1) {
  while (self.plotter.task_drawLabel()) {}
  } else {
  if (previous) {
  sigma.chronos.queueTask(
  self.plotter.task_drawLabel,
  'label_' + self.id,
  previous
  );
  } else {
  sigma.chronos.addTask(
  self.plotter.task_drawLabel,
  'label_' + self.id,
  false
  );
  }
   
  start = true;
  previous = 'label_' + self.id;
  }
  }
   
  if (e) {
  if (e > 1) {
  while (self.plotter.task_drawEdge()) {}
  }else {
  if (previous) {
  sigma.chronos.queueTask(
  self.plotter.task_drawEdge,
  'edge_' + self.id,
  previous
  );
  }else {
  sigma.chronos.addTask(
  self.plotter.task_drawEdge,
  'edge_' + self.id,
  false
  );
  }
   
  start = true;
  previous = 'edge_' + self.id;
  }
  }
   
  self.dispatch(
  'draw'
  );
   
  self.refresh();
   
  start && sigma.chronos.runTasks();
  return self;
  };
   
  /**
  * Draws the hover and active nodes labels.
  * @return {Sigma} Returns itself.
  */
  function refresh() {
  self.domElements.hover.getContext('2d').clearRect(
  0,
  0,
  self.domElements.hover.width,
  self.domElements.hover.height
  );
   
  drawHover();
  drawActive();
   
  return self;
  }
   
  /**
  * Draws the hover nodes labels. This method is applied directly, and does
  * not use the pseudo-asynchronous tasks process.
  * @return {Sigma} Returns itself.
  */
  function drawHover() {
  if (self.p.drawHoverNodes) {
  self.graph.checkHover(
  self.mousecaptor.mouseX,
  self.mousecaptor.mouseY
  );
   
  self.graph.nodes.forEach(function(node) {
  if (node.hover && !node.active) {
  self.plotter.drawHoverNode(node);
  }
  });
  }
   
  return self;
  }
   
  /**
  * Draws the active nodes labels. This method is applied directly, and does
  * not use the pseudo-asynchronous tasks process.
  * @return {Sigma} Returns itself.
  */
  function drawActive() {
  if (self.p.drawActiveNodes) {
  self.graph.nodes.forEach(function(node) {
  if (node.active) {
  self.plotter.drawActiveNode(node);
  }
  });
  }
   
  return self;
  }
   
  // Apply plugins:
  for (var i = 0; i < local.plugins.length; i++) {
  local.plugins[i](this);
  }
   
  this.draw = draw;
  this.resize = resize;
  this.refresh = refresh;
  this.drawHover = drawHover;
  this.drawActive = drawActive;
  this.clearSchedule = clearSchedule;
   
  window.addEventListener('resize', function() {
  self.resize();
  });
  }
   
  /**
  * This class draws the graph on the different canvas DOM elements. It just
  * contains all the different methods to draw the graph, synchronously or
  * pseudo-asynchronously.
  * @constructor
  * @param {CanvasRenderingContext2D} nodesCtx Context dedicated to draw nodes.
  * @param {CanvasRenderingContext2D} edgesCtx Context dedicated to draw edges.
  * @param {CanvasRenderingContext2D} labelsCtx Context dedicated to draw
  * labels.
  * @param {CanvasRenderingContext2D} hoverCtx Context dedicated to draw hover
  * nodes labels.
  * @param {Graph} graph A reference to the graph to
  * draw.
  * @param {number} w The width of the DOM root
  * element.
  * @param {number} h The width of the DOM root
  * element.
  * @extends sigma.classes.Cascade
  * @this {Plotter}
  */
  function Plotter(nodesCtx, edgesCtx, labelsCtx, hoverCtx, graph, w, h) {
  sigma.classes.Cascade.call(this);
   
  /**
  * Represents "this", without the well-known scope issue.
  * @private
  * @type {Plotter}
  */
  var self = this;
   
  /**
  * The different parameters that define how this instance should work.
  * @see sigma.classes.Cascade
  * @type {Object}
  */
  this.p = {
  // -------
  // LABELS:
  // -------
  // Label color:
  // - 'node'
  // - default (then defaultLabelColor
  // will be used instead)
  labelColor: 'default',
  defaultLabelColor: '#000',
  // Label hover background color:
  // - 'node'
  // - default (then defaultHoverLabelBGColor
  // will be used instead)
  labelHoverBGColor: 'default',
  defaultHoverLabelBGColor: '#fff',
  // Label hover shadow:
  labelHoverShadow: true,
  labelHoverShadowColor: '#000',
  // Label hover color:
  // - 'node'
  // - default (then defaultLabelHoverColor
  // will be used instead)
  labelHoverColor: 'default',
  defaultLabelHoverColor: '#000',
  // Label active background color:
  // - 'node'
  // - default (then defaultActiveLabelBGColor
  // will be used instead)
  labelActiveBGColor: 'default',
  defaultActiveLabelBGColor: '#fff',
  // Label active shadow:
  labelActiveShadow: true,
  labelActiveShadowColor: '#000',
  // Label active color:
  // - 'node'
  // - default (then defaultLabelActiveColor
  // will be used instead)
  labelActiveColor: 'default',
  defaultLabelActiveColor: '#000',
  // Label size:
  // - 'fixed'
  // - 'proportional'
  // Label size:
  // - 'fixed'
  // - 'proportional'
  labelSize: 'fixed',
  defaultLabelSize: 12, // for fixed display only
  labelSizeRatio: 2, // for proportional display only
  labelThreshold: 6,
  font: 'Arial',
  hoverFont: '',
  activeFont: '',
  fontStyle: '',
  hoverFontStyle: '',
  activeFontStyle: '',
  // ------
  // EDGES:
  // ------
  // Edge color:
  // - 'source'
  // - 'target'
  // - default (then defaultEdgeColor or edge['color']
  // will be used instead)
  edgeColor: 'source',
  defaultEdgeColor: '#aaa',
  defaultEdgeType: 'line',
  // ------
  // NODES:
  // ------
  defaultNodeColor: '#aaa',
  // HOVER:
  // Node hover color:
  // - 'node'
  // - default (then defaultNodeHoverColor
  // will be used instead)
  nodeHoverColor: 'node',
  defaultNodeHoverColor: '#fff',
  // ACTIVE:
  // Node active color:
  // - 'node'
  // - default (then defaultNodeActiveColor
  // will be used instead)
  nodeActiveColor: 'node',
  defaultNodeActiveColor: '#fff',
  // Node border color:
  // - 'node'
  // - default (then defaultNodeBorderColor
  // will be used instead)
  borderSize: 0,
  nodeBorderColor: 'node',
  defaultNodeBorderColor: '#fff',
  // --------
  // PROCESS:
  // --------
  edgesSpeed: 200,
  nodesSpeed: 200,
  labelsSpeed: 200
  };
   
  /**
  * The canvas context dedicated to draw the nodes.
  * @type {CanvasRenderingContext2D}
  */
  var nodesCtx = nodesCtx;
   
  /**
  * The canvas context dedicated to draw the edges.
  * @type {CanvasRenderingContext2D}
  */
  var edgesCtx = edgesCtx;
   
  /**
  * The canvas context dedicated to draw the labels.
  * @type {CanvasRenderingContext2D}
  */
  var labelsCtx = labelsCtx;
   
  /**
  * The canvas context dedicated to draw the hover nodes.
  * @type {CanvasRenderingContext2D}
  */
  var hoverCtx = hoverCtx;
   
  /**
  * A reference to the graph to draw.
  * @type {Graph}
  */
  var graph = graph;
   
  /**
  * The width of the stage to draw on.
  * @type {number}
  */
  var width = w;
   
  /**
  * The height of the stage to draw on.
  * @type {number}
  */
  var height = h;
   
  /**
  * The index of the next edge to draw.
  * @type {number}
  */
  this.currentEdgeIndex = 0;
   
  /**
  * The index of the next node to draw.
  * @type {number}
  */
  this.currentNodeIndex = 0;
   
  /**
  * The index of the next label to draw.
  * @type {number}
  */
  this.currentLabelIndex = 0;
   
  /**
  * An atomic function to drawn the N next edges, with N as edgesSpeed.
  * The counter is {@link this.currentEdgeIndex}.
  * This function has been designed to work with {@link sigma.chronos}, that
  * will insert frames at the middle of the calls, to make the edges drawing
  * process fluid for the user.
  * @see sigma.chronos
  * @return {boolean} Returns true if all the edges are drawn and false else.
  */
  function task_drawEdge() {
  var c = graph.edges.length;
  var s, t, i = 0;
   
  while (i++< self.p.edgesSpeed && self.currentEdgeIndex < c) {
  e = graph.edges[self.currentEdgeIndex];
  s = e['source'];
  t = e['target'];
  if (e['hidden'] ||
  s['hidden'] ||
  t['hidden'] ||
  (!self.isOnScreen(s) && !self.isOnScreen(t))) {
  self.currentEdgeIndex++;
  }else {
  drawEdge(graph.edges[self.currentEdgeIndex++]);
  }
  }
   
  return self.currentEdgeIndex < c;
  };
   
  /**
  * An atomic function to drawn the N next nodes, with N as nodesSpeed.
  * The counter is {@link this.currentEdgeIndex}.
  * This function has been designed to work with {@link sigma.chronos}, that
  * will insert frames at the middle of the calls, to make the nodes drawing
  * process fluid for the user.
  * @see sigma.chronos
  * @return {boolean} Returns true if all the nodes are drawn and false else.
  */
  function task_drawNode() {
  var c = graph.nodes.length;
  var i = 0;
   
  while (i++< self.p.nodesSpeed && self.currentNodeIndex < c) {
  if (!self.isOnScreen(graph.nodes[self.currentNodeIndex])) {
  self.currentNodeIndex++;
  }else {
  drawNode(graph.nodes[self.currentNodeIndex++]);
  }
  }
   
  return self.currentNodeIndex < c;
  };
   
  /**
  * An atomic function to drawn the N next labels, with N as labelsSpeed.
  * The counter is {@link this.currentEdgeIndex}.
  * This function has been designed to work with {@link sigma.chronos}, that
  * will insert frames at the middle of the calls, to make the labels drawing
  * process fluid for the user.
  * @see sigma.chronos
  * @return {boolean} Returns true if all the labels are drawn and false else.
  */
  function task_drawLabel() {
  var c = graph.nodes.length;
  var i = 0;
   
  while (i++< self.p.labelsSpeed && self.currentLabelIndex < c) {
  if (!self.isOnScreen(graph.nodes[self.currentLabelIndex])) {
  self.currentLabelIndex++;
  }else {
  drawLabel(graph.nodes[self.currentLabelIndex++]);
  }
  }
   
  return self.currentLabelIndex < c;
  };
   
  /**
  * Draws one node to the corresponding canvas.
  * @param {Object} node The node to draw.
  * @return {Plotter} Returns itself.
  */
  function drawNode(node) {
  var size = Math.round(node['displaySize'] * 10) / 10;
  var ctx = nodesCtx;
   
  ctx.fillStyle = node['color'];
  ctx.beginPath();
  ctx.arc(node['displayX'],
  node['displayY'],
  size,
  0,
  Math.PI * 2,
  true);
   
  ctx.closePath();
  ctx.fill();
   
  node['hover'] && drawHoverNode(node);
  return self;
  };
   
  /**
  * Draws one edge to the corresponding canvas.
  * @param {Object} edge The edge to draw.
  * @return {Plotter} Returns itself.
  */
  function drawEdge(edge) {
  var x1 = edge['source']['displayX'];
  var y1 = edge['source']['displayY'];
  var x2 = edge['target']['displayX'];
  var y2 = edge['target']['displayY'];
  var color = edge['color'];
   
  if (!color) {
  switch (self.p.edgeColor) {
  case 'source':
  color = edge['source']['color'] ||
  self.p.defaultNodeColor;
  break;
  case 'target':
  color = edge['target']['color'] ||
  self.p.defaultNodeColor;
  break;
  default:
  color = self.p.defaultEdgeColor;
  break;
  }
  }
   
  var ctx = edgesCtx;
   
  switch (edge['type'] || self.p.defaultEdgeType) {
  case 'curve':
  ctx.strokeStyle = color;
  ctx.lineWidth = edge['displaySize'] / 3;
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  ctx.quadraticCurveTo((x1 + x2) / 2 + (y2 - y1) / 4,
  (y1 + y2) / 2 + (x1 - x2) / 4,
  x2,
  y2);
  ctx.stroke();
  break;
  case 'line':
  default:
  ctx.strokeStyle = color;
  ctx.lineWidth = edge['displaySize'] / 3;
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y2);
   
  ctx.stroke();
  break;
  }
   
  return self;
  };
   
  /**
  * Draws one label to the corresponding canvas.
  * @param {Object} node The label to draw.
  * @return {Plotter} Returns itself.
  */
  function drawLabel(node) {
  var ctx = labelsCtx;
   
  if (node['displaySize'] >= self.p.labelThreshold || node['forceLabel']) {
  var fontSize = self.p.labelSize == 'fixed' ?
  self.p.defaultLabelSize :
  self.p.labelSizeRatio * node['displaySize'];
   
  ctx.font = self.p.fontStyle + fontSize + 'px ' + self.p.font;
   
  ctx.fillStyle = self.p.labelColor == 'node' ?
  (node['color'] || self.p.defaultNodeColor) :
  self.p.defaultLabelColor;
  ctx.fillText(
  node['label'],
  Math.round(node['displayX'] + node['displaySize'] * 1.5),
  Math.round(node['displayY'] + fontSize / 2 - 3)
  );
  }
   
  return self;
  };
   
  /**
  * Draws one hover node to the corresponding canvas.
  * @param {Object} node The hover node to draw.
  * @return {Plotter} Returns itself.
  */
  function drawHoverNode(node) {
  var ctx = hoverCtx;
   
  var fontSize = self.p.labelSize == 'fixed' ?
  self.p.defaultLabelSize :
  self.p.labelSizeRatio * node['displaySize'];
   
  ctx.font = (self.p.hoverFontStyle || self.p.fontStyle || '') + ' ' +
  fontSize + 'px ' +
  (self.p.hoverFont || self.p.font || '');
   
  ctx.fillStyle = self.p.labelHoverBGColor == 'node' ?
  (node['color'] || self.p.defaultNodeColor) :
  self.p.defaultHoverLabelBGColor;
   
  // Label background:
  ctx.beginPath();
   
  if (self.p.labelHoverShadow) {
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 0;
  ctx.shadowBlur = 4;
  ctx.shadowColor = self.p.labelHoverShadowColor;
  }
   
  sigma.tools.drawRoundRect(
  ctx,
  Math.round(node['displayX'] - fontSize / 2 - 2),
  Math.round(node['displayY'] - fontSize / 2 - 2),
  Math.round(ctx.measureText(node['label']).width +
  node['displaySize'] * 1.5 +
  fontSize / 2 + 4),
  Math.round(fontSize + 4),
  Math.round(fontSize / 2 + 2),
  'left'
  );
  ctx.closePath();
  ctx.fill();
   
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 0;
  ctx.shadowBlur = 0;
   
  // Node border:
  ctx.beginPath();
  ctx.fillStyle = self.p.nodeBorderColor == 'node' ?
  (node['color'] || self.p.defaultNodeColor) :
  self.p.defaultNodeBorderColor;
  ctx.arc(Math.round(node['displayX']),
  Math.round(node['displayY']),
  node['displaySize'] + self.p.borderSize,
  0,
  Math.PI * 2,
  true);
  ctx.closePath();
  ctx.fill();
   
  // Node:
  ctx.beginPath();
  ctx.fillStyle = self.p.nodeHoverColor == 'node' ?
  (node['color'] || self.p.defaultNodeColor) :
  self.p.defaultNodeHoverColor;
  ctx.arc(Math.round(node['displayX']),
  Math.round(node['displayY']),
  node['displaySize'],
  0,
  Math.PI * 2,
  true);
   
  ctx.closePath();
  ctx.fill();
   
  // Label:
  ctx.fillStyle = self.p.labelHoverColor == 'node' ?
  (node['color'] || self.p.defaultNodeColor) :
  self.p.defaultLabelHoverColor;
  ctx.fillText(
  node['label'],
  Math.round(node['displayX'] + node['displaySize'] * 1.5),
  Math.round(node['displayY'] + fontSize / 2 - 3)
  );
   
  return self;
  };
   
  /**
  * Draws one active node to the corresponding canvas.
  * @param {Object} node The active node to draw.
  * @return {Plotter} Returns itself.
  */
  function drawActiveNode(node) {
  var ctx = hoverCtx;
   
  if (!isOnScreen(node)) {
  return self;
  }
   
  var fontSize = self.p.labelSize == 'fixed' ?
  self.p.defaultLabelSize :
  self.p.labelSizeRatio * node['displaySize'];
   
  ctx.font = (self.p.activeFontStyle || self.p.fontStyle || '') + ' ' +
  fontSize + 'px ' +
  (self.p.activeFont || self.p.font || '');
   
  ctx.fillStyle = self.p.labelHoverBGColor == 'node' ?
  (node['color'] || self.p.defaultNodeColor) :
  self.p.defaultActiveLabelBGColor;
   
  // Label background:
  ctx.beginPath();
   
  if (self.p.labelActiveShadow) {
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 0;
  ctx.shadowBlur = 4;
  ctx.shadowColor = self.p.labelActiveShadowColor;
  }
   
  sigma.tools.drawRoundRect(
  ctx,
  Math.round(node['displayX'] - fontSize / 2 - 2),
  Math.round(node['displayY'] - fontSize / 2 - 2),
  Math.round(ctx.measureText(node['label']).width +
  node['displaySize'] * 1.5 +
  fontSize / 2 + 4),
  Math.round(fontSize + 4),
  Math.round(fontSize / 2 + 2),
  'left'
  );
  ctx.closePath();
  ctx.fill();
   
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 0;
  ctx.shadowBlur = 0;
   
  // Node border:
  ctx.beginPath();
  ctx.fillStyle = self.p.nodeBorderColor == 'node' ?
  (node['color'] || self.p.defaultNodeColor) :
  self.p.defaultNodeBorderColor;
  ctx.arc(Math.round(node['displayX']),
  Math.round(node['displayY']),
  node['displaySize'] + self.p.borderSize,
  0,
  Math.PI * 2,
  true);
  ctx.closePath();
  ctx.fill();
   
  // Node:
  ctx.beginPath();
  ctx.fillStyle = self.p.nodeActiveColor == 'node' ?
  (node['color'] || self.p.defaultNodeColor) :
  self.p.defaultNodeActiveColor;
  ctx.arc(Math.round(node['displayX']),
  Math.round(node['displayY']),
  node['displaySize'],
  0,
  Math.PI * 2,
  true);
   
  ctx.closePath();
  ctx.fill();
   
  // Label:
  ctx.fillStyle = self.p.labelActiveColor == 'node' ?
  (node['color'] || self.p.defaultNodeColor) :
  self.p.defaultLabelActiveColor;
  ctx.fillText(
  node['label'],
  Math.round(node['displayX'] + node['displaySize'] * 1.5),
  Math.round(node['displayY'] + fontSize / 2 - 3)
  );
   
  return self;
  };
   
  /**
  * Determines if a node is on the screen or not. The limits here are
  * bigger than the actual screen, to avoid seeing labels disappear during
  * the graph manipulation.
  * @param {Object} node The node to check if it is on or out the screen.
  * @return {boolean} Returns false if the node is hidden or not on the screen
  * or true else.
  */
  function isOnScreen(node) {
  if (isNaN(node['x']) || isNaN(node['y'])) {
  throw (new Error('A node\'s coordinate is not a ' +
  'number (id: ' + node['id'] + ')')
  );
  }
   
  return !node['hidden'] &&
  (node['displayX'] + node['displaySize'] > -width / 3) &&
  (node['displayX'] - node['displaySize'] < width * 4 / 3) &&
  (node['displayY'] + node['displaySize'] > -height / 3) &&
  (node['displayY'] - node['displaySize'] < height * 4 / 3);
  };
   
  /**
  * Resizes this instance.
  * @param {number} w The new width.
  * @param {number} h The new height.
  * @return {Plotter} Returns itself.
  */
  function resize(w, h) {
  width = w;
  height = h;
   
  return self;
  }
   
  this.task_drawLabel = task_drawLabel;
  this.task_drawEdge = task_drawEdge;
  this.task_drawNode = task_drawNode;
  this.drawActiveNode = drawActiveNode;
  this.drawHoverNode = drawHoverNode;
  this.isOnScreen = isOnScreen;
  this.resize = resize;
  }
   
  function SigmaPublic(sigmaInstance) {
  var s = sigmaInstance;
  var self = this;
  sigma.classes.EventDispatcher.call(this);
   
  this._core = sigmaInstance;
   
  this.kill = function() {
  // TODO
  };
   
  this.getID = function() {
  return s.id;
  };
   
  // Config:
  this.configProperties = function(a1, a2) {
  var res = s.config(a1, a2);
  return res == s ? self : res;
  };
   
  this.drawingProperties = function(a1, a2) {
  var res = s.plotter.config(a1, a2);
  return res == s.plotter ? self : res;
  };
   
  this.mouseProperties = function(a1, a2) {
  var res = s.mousecaptor.config(a1, a2);
  return res == s.mousecaptor ? self : res;
  };
   
  this.graphProperties = function(a1, a2) {
  var res = s.graph.config(a1, a2);
  return res == s.graph ? self : res;
  };
   
  this.getMouse = function() {
  return {
  mouseX: s.mousecaptor.mouseX,
  mouseY: s.mousecaptor.mouseY,
  down: s.mousecaptor.isMouseDown
  };
  };
   
  // Actions:
  this.position = function(stageX, stageY, ratio) {
  if (arguments.length == 0) {
  return {
  stageX: s.mousecaptor.stageX,
  stageY: s.mousecaptor.stageY,
  ratio: s.mousecaptor.ratio
  };
  }else {
  s.mousecaptor.stageX = stageX != undefined ?
  stageX :
  s.mousecaptor.stageX;
  s.mousecaptor.stageY = stageY != undefined ?
  stageY :
  s.mousecaptor.stageY;
  s.mousecaptor.ratio = ratio != undefined ?
  ratio :
  s.mousecaptor.ratio;
   
  return self;
  }
  };
   
  this.goTo = function(stageX, stageY, ratio) {
  s.mousecaptor.interpolate(stageX, stageY, ratio);
  return self;
  };
   
  this.zoomTo = function(x, y, ratio) {
  ratio = Math.min(
  Math.max(s.mousecaptor.config('minRatio'), ratio),
  s.mousecaptor.config('maxRatio')
  );
  if (ratio == s.mousecaptor.ratio) {
  s.mousecaptor.interpolate(
  x - s.width / 2 + s.mousecaptor.stageX,
  y - s.height / 2 + s.mousecaptor.stageY
  );
  }else {
  s.mousecaptor.interpolate(
  (ratio * x - s.mousecaptor.ratio * s.width/2) /
  (ratio - s.mousecaptor.ratio),
  (ratio * y - s.mousecaptor.ratio * s.height/2) /
  (ratio - s.mousecaptor.ratio),
  ratio
  );
  }
  return self;
  };
   
  this.resize = function(w, h) {
  s.resize(w, h);
  return self;
  };
   
  this.draw = function(nodes, edges, labels, safe) {
  s.draw(nodes, edges, labels, safe);
  return self;
  };
   
  this.refresh = function() {
  s.refresh();
  return self;
  };
   
  // Tasks methods:
  this.addGenerator = function(id, task, condition) {
  sigma.chronos.addGenerator(id + '_ext_' + s.id, task, condition);
  return self;
  };
   
  this.removeGenerator = function(id) {
  sigma.chronos.removeGenerator(id + '_ext_' + s.id);
  return self;
  };
   
  // Graph methods:
  this.addNode = function(id, params) {
  s.graph.addNode(id, params);
  return self;
  };
   
  this.addEdge = function(id, source, target, params) {
  s.graph.addEdge(id, source, target, params);
  return self;
  }
   
  this.dropNode = function(v) {
  s.graph.dropNode(v);
  return self;
  };
   
  this.dropEdge = function(v) {
  s.graph.dropEdge(v);
  return self;
  };
   
  this.pushGraph = function(object, safe) {
  object.nodes && object.nodes.forEach(function(node) {
  node['id'] && (!safe || !s.graph.nodesIndex[node['id']]) &&
  self.addNode(node['id'], node);
  });
   
  var isEdgeValid;
  object.edges && object.edges.forEach(function(edge) {
  validID = edge['source'] && edge['target'] && edge['id'];
  validID &&
  (!safe || !s.graph.edgesIndex[edge['id']]) &&
  self.addNode(
  edge['id'],
  edge['source'],
  edge['target'],
  edge
  );
  });
   
  return self;
  };
   
  this.emptyGraph = function() {
  s.graph.empty();
  return self;
  };
   
  this.getNodesCount = function() {
  return s.graph.nodes.length;
  };
   
  this.getEdgesCount = function() {
  return s.graph.edges.length;
  };
   
  this.iterNodes = function(fun, ids) {
  s.graph.iterNodes(fun, ids);
  return self;
  };
   
  this.iterEdges = function(fun, ids) {
  s.graph.iterEdges(fun, ids);
  return self;
  };
   
  this.getNodes = function(ids) {
  return s.graph.getNodes(ids);
  };
   
  this.getEdges = function(ids) {
  return s.graph.getEdges(ids);
  };
   
  // Monitoring
  this.activateMonitoring = function() {
  return s.monitor.activate();
  };
   
  this.desactivateMonitoring = function() {
  return s.monitor.desactivate();
  };
   
  // Events
  s.bind('downnodes upnodes downgraph upgraph', function(e) {
  self.dispatch(e.type, e.content);
  });
   
  s.graph.bind('overnodes outnodes', function(e) {
  self.dispatch(e.type, e.content);
  });
  }
   
  /**
  * The graph data model used in sigma.js.
  * @constructor
  * @extends sigma.classes.Cascade
  * @extends sigma.classes.EventDispatcher
  * @this {Graph}
  */
  function Graph() {
  sigma.classes.Cascade.call(this);
  sigma.classes.EventDispatcher.call(this);
   
  /**
  * Represents "this", without the well-known scope issue.
  * @private
  * @type {Graph}
  */
  var self = this;
   
  /**
  * The different parameters that determine how the nodes and edges should be
  * translated and rescaled.
  * @type {Object}
  */
  this.p = {
  minNodeSize: 0,
  maxNodeSize: 0,
  minEdgeSize: 0,
  maxEdgeSize: 0,
  // Scaling mode:
  // - 'inside' (default)
  // - 'outside'
  scalingMode: 'inside',
  nodesPowRatio: 0.5,
  edgesPowRatio: 0
  };
   
  /**
  * Contains the borders of the graph. These are useful to avoid the user to
  * drag the graph out of the canvas.
  * @type {Object}
  */
  this.borders = {};
   
  /**
  * Inserts a node in the graph.
  * @param {string} id The node's ID.
  * @param {object} params An object containing the different parameters
  * of the node.
  * @return {Graph} Returns itself.
  */
  function addNode(id, params) {
  if (self.nodesIndex[id]) {
  throw new Error('Node "' + id + '" already exists.');
  }
   
  params = params || {};
  var n = {
  // Numbers :
  'x': 0,
  'y': 0,
  'size': 1,
  'degree': 0,
  'inDegree': 0,
  'outDegree': 0,
  // Flags :
  'fixed': false,
  'active': false,
  'hidden': false,
  'forceLabel': false,
  // Strings :
  'label': id.toString(),
  'id': id.toString(),
  // Custom attributes :
  'attr': {}
  };
   
  for (var k in params) {
  switch (k) {
  case 'id':
  break;
  case 'x':
  case 'y':
  case 'size':
  n[k] = +params[k];
  break;
  case 'fixed':
  case 'active':
  case 'hidden':
  case 'forceLabel':
  n[k] = !!params[k];
  break;
  case 'color':
  case 'label':
  n[k] = params[k];
  break;
  default:
  n['attr'][k] = params[k];
  }
  }
   
  self.nodes.push(n);
  self.nodesIndex[id.toString()] = n;
   
  return self;
  };
   
  /**
  * Generates the clone of a node, to make it easier to be exported.
  * @private
  * @param {Object} node The node to clone.
  * @return {Object} The clone of the node.
  */
  function cloneNode(node) {
  return {
  'x': node['x'],
  'y': node['y'],
  'size': node['size'],
  'degree': node['degree'],
  'inDegree': node['inDegree'],
  'outDegree': node['outDegree'],
  'displayX': node['displayX'],
  'displayY': node['displayY'],
  'displaySize': node['displaySize'],
  'label': node['label'],
  'id': node['id'],
  'color': node['color'],
  'fixed': node['fixed'],
  'active': node['active'],
  'hidden': node['hidden'],
  'forceLabel': node['forceLabel'],
  'attr': node['attr']
  };
  };
   
  /**
  * Checks the clone of a node, and inserts its values when possible. For
  * example, it is possible to modify the size or the color of a node, but it
  * is not possible to modify its display values or its id.
  * @private
  * @param {Object} node The original node.
  * @param {Object} copy The clone.
  * @return {Graph} Returns itself.
  */
  function checkNode(node, copy) {
  for (var k in copy) {
  switch (k) {
  case 'id':
  case 'attr':
  case 'degree':
  case 'inDegree':
  case 'outDegree':
  case 'displayX':
  case 'displayY':
  case 'displaySize':
  break;
  case 'x':
  case 'y':
  case 'size':
  node[k] = +copy[k];
  break;
  case 'fixed':
  case 'active':
  case 'hidden':
  case 'forceLabel':
  node[k] = !!copy[k];
  break;
  case 'color':
  case 'label':
  node[k] = (copy[k] || '').toString();
  break;
  default:
  node['attr'][k] = copy[k];
  }
  }
   
  return self;
  };
   
  /**
  * Deletes one or several nodes from the graph, and the related edges.
  * @param {(string|Array.<string>)} v A string ID, or an Array of several
  * IDs.
  * @return {Graph} Returns itself.
  */
  function dropNode(v) {
  var a = (v instanceof Array ? v : [v]) || [];
   
  a.forEach(function(id) {
  if (self.nodesIndex[id]) {
  var index = null;
  self.nodes.some(function(n, i) {
  if (n['id'] == id) {
  index = i;
  return true;
  }
  return false;
  });
   
  index != null && self.nodes.splice(index, 1);
  delete self.nodesIndex[id];
   
  var edgesToRemove = [];
  self.edges = self.edges.filter(function(e) {
  if (e['source']['id'] == id) {
  delete self.edgesIndex[e['id']];
  e['target']['degree']--;
  e['target']['inDegree']--;
  return false;
  }else if (e['target']['id'] == id) {
  delete self.edgesIndex[e['id']];
  e['source']['degree']--;
  e['source']['outDegree']--;
  return false;
  }
  return true;
  });
  }else {
  sigma.log('Node "' + id + '" does not exist.');
  }
  });
   
  return self;
  };
   
  /**
  * Inserts an edge in the graph.
  * @param {string} id The edge ID.
  * @param {string} source The ID of the edge source.
  * @param {string} target The ID of the edge target.
  * @param {object} params An object containing the different parameters
  * of the edge.
  * @return {Graph} Returns itself.
  */
  function addEdge(id, source, target, params) {
  if (self.edgesIndex[id]) {
  throw new Error('Edge "' + id + '" already exists.');
  }
   
  if (!self.nodesIndex[source]) {
  var s = 'Edge\'s source "' + source + '" does not exist yet.';
  throw new Error(s);
  }
   
  if (!self.nodesIndex[target]) {
  var s = 'Edge\'s target "' + target + '" does not exist yet.';
  throw new Error(s);
  }
   
  params = params || {};
  var e = {
  'source': self.nodesIndex[source],
  'target': self.nodesIndex[target],
  'size': 1,
  'weight': 1,
  'displaySize': 0.5,
  'label': id.toString(),
  'id': id.toString(),
  'hidden': false,
  'attr': {}
  };
   
  e['source']['degree']++;
  e['source']['outDegree']++;
  e['target']['degree']++;
  e['target']['inDegree']++;
   
  for (var k in params) {
  switch (k) {
  case 'id':
  case 'source':
  case 'target':
  break;
  case 'hidden':
  e[k] = !!params[k];
  break;
  case 'size':
  case 'weight':
  e[k] = +params[k];
  break;
  case 'color':
  e[k] = params[k].toString();
  break;
  case 'type':
  e[k] = params[k].toString();
  break;
  case 'label':
  e[k] = params[k];
  break;
  default:
  e['attr'][k] = params[k];
  }
  }
   
  self.edges.push(e);
  self.edgesIndex[id.toString()] = e;
   
  return self;
  };
   
  /**
  * Generates the clone of a edge, to make it easier to be exported.
  * @private
  * @param {Object} edge The edge to clone.
  * @return {Object} The clone of the edge.
  */
  function cloneEdge(edge) {
  return {
  'source': edge['source']['id'],
  'target': edge['target']['id'],
  'size': edge['size'],
  'type': edge['type'],
  'weight': edge['weight'],
  'displaySize': edge['displaySize'],
  'label': edge['label'],
  'hidden': edge['hidden'],
  'id': edge['id'],
  'attr': edge['attr'],
  'color': edge['color']
  };
  };
   
  /**
  * Checks the clone of an edge, and inserts its values when possible. For
  * example, it is possible to modify the label or the type of an edge, but it
  * is not possible to modify its display values or its id.
  * @private
  * @param {Object} edge The original edge.
  * @param {Object} copy The clone.
  * @return {Graph} Returns itself.
  */
  function checkEdge(edge, copy) {
  for (var k in copy) {
  switch (k) {
  case 'id':
  case 'displaySize':
  break;
  case 'weight':
  case 'size':
  edge[k] = +copy[k];
  break;
  case 'source':
  case 'target':
  edge[k] = self.nodesIndex[k] || edge[k];
  break;
  case 'hidden':
  edge[k] = !!copy[k];
  break;
  case 'color':
  case 'label':
  case 'type':
  edge[k] = (copy[k] || '').toString();
  break;
  default:
  edge['attr'][k] = copy[k];
  }
  }
   
  return self;
  };
   
  /**
  * Deletes one or several edges from the graph.
  * @param {(string|Array.<string>)} v A string ID, or an Array of several
  * IDs.
  * @return {Graph} Returns itself.
  */
  function dropEdge(v) {
  var a = (v instanceof Array ? v : [v]) || [];
   
  a.forEach(function(id) {
  if (self.edgesIndex[id]) {
  self.edgesIndex[id]['source']['degree']--;
  self.edgesIndex[id]['source']['outDegree']--;
  self.edgesIndex[id]['target']['degree']--;
  self.edgesIndex[id]['target']['inDegree']--;
   
  var index = null;
  self.edges.some(function(n, i) {
  if (n['id'] == id) {
  index = i;
  return true;
  }
  return false;
  });
   
  index != null && self.edges.splice(index, 1);
  delete self.edgesIndex[id];
  }else {
  sigma.log('Edge "' + id + '" does not exist.');
  }
  });
   
  return self;
  };
   
  /**
  * Deletes every nodes and edges from the graph.
  * @return {Graph} Returns itself.
  */
  function empty() {
  self.nodes = [];
  self.nodesIndex = {};
  self.edges = [];
  self.edgesIndex = {};
   
  return self;
  };
   
  /**
  * Computes the display x, y and size of each node, relatively to the
  * original values and the borders determined in the parameters, such as
  * each node is in the described area.
  * @param {number} w The area width (actually the width of the DOM
  * root).
  * @param {number} h The area height (actually the height of the
  * DOM root).
  * @param {boolean} parseNodes Indicates if the nodes have to be parsed.
  * @param {boolean} parseEdges Indicates if the edges have to be parsed.
  * @return {Graph} Returns itself.
  */
  function rescale(w, h, parseNodes, parseEdges) {
  var weightMax = 0, sizeMax = 0;
   
  parseNodes && self.nodes.forEach(function(node) {
  sizeMax = Math.max(node['size'], sizeMax);
  });
   
  parseEdges && self.edges.forEach(function(edge) {
  weightMax = Math.max(edge['size'], weightMax);
  });
   
  sizeMax = sizeMax || 1;
  weightMax = weightMax || 1;
   
  // Recenter the nodes:
  var xMin, xMax, yMin, yMax;
  parseNodes && self.nodes.forEach(function(node) {
  xMax = Math.max(node['x'], xMax || node['x']);
  xMin = Math.min(node['x'], xMin || node['x']);
  yMax = Math.max(node['y'], yMax || node['y']);
  yMin = Math.min(node['y'], yMin || node['y']);
  });
   
  // First, we compute the scaling ratio, without considering the sizes
  // of the nodes : Each node will have its center in the canvas, but might
  // be partially out of it.
  var scale = self.p.scalingMode == 'outside' ?
  Math.max(w / Math.max(xMax - xMin, 1),
  h / Math.max(yMax - yMin, 1)) :
  Math.min(w / Math.max(xMax - xMin, 1),
  h / Math.max(yMax - yMin, 1));
   
  // Then, we correct that scaling ratio considering a margin, which is
  // basically the size of the biggest node.
  // This has to be done as a correction since to compare the size of the
  // biggest node to the X and Y values, we have to first get an
  // approximation of the scaling ratio.
  var margin = (self.p.maxNodeSize || sizeMax) / scale;
  xMax += margin;
  xMin -= margin;
  yMax += margin;
  yMin -= margin;
   
  scale = self.p.scalingMode == 'outside' ?
  Math.max(w / Math.max(xMax - xMin, 1),
  h / Math.max(yMax - yMin, 1)) :
  Math.min(w / Math.max(xMax - xMin, 1),
  h / Math.max(yMax - yMin, 1));
   
  // Size homothetic parameters:
  var a, b;
  if (!self.p.maxNodeSize && !self.p.minNodeSize) {
  a = 1;
  b = 0;
  }else if (self.p.maxNodeSize == self.p.minNodeSize) {
  a = 0;
  b = self.p.maxNodeSize;
  }else {
  a = (self.p.maxNodeSize - self.p.minNodeSize) / sizeMax;
  b = self.p.minNodeSize;
  }
   
  var c, d;
  if (!self.p.maxEdgeSize && !self.p.minEdgeSize) {
  c = 1;
  d = 0;
  }else if (self.p.maxEdgeSize == self.p.minEdgeSize) {
  c = 0;
  d = self.p.minEdgeSize;
  }else {
  c = (self.p.maxEdgeSize - self.p.minEdgeSize) / weightMax;
  d = self.p.minEdgeSize;
  }
   
  // Rescale the nodes:
  parseNodes && self.nodes.forEach(function(node) {
  node['displaySize'] = node['size'] * a + b;
   
  if (!node['fixed']) {
  node['displayX'] = (node['x'] - (xMax + xMin) / 2) * scale + w / 2;
  node['displayY'] = (node['y'] - (yMax + yMin) / 2) * scale + h / 2;
  }
  });
   
  parseEdges && self.edges.forEach(function(edge) {
  edge['displaySize'] = edge['size'] * c + d;
  });
   
  return self;
  };
   
  /**
  * Translates the display values of the nodes and edges relatively to the
  * scene position and zoom ratio.
  * @param {number} sceneX The x position of the scene.
  * @param {number} sceneY The y position of the scene.
  * @param {number} ratio The zoom ratio of the scene.
  * @param {boolean} parseNodes Indicates if the nodes have to be parsed.
  * @param {boolean} parseEdges Indicates if the edges have to be parsed.
  * @return {Graph} Returns itself.
  */
  function translate(sceneX, sceneY, ratio, parseNodes, parseEdges) {
  var sizeRatio = Math.pow(ratio, self.p.nodesPowRatio);
  parseNodes && self.nodes.forEach(function(node) {
  if (!node['fixed']) {
  node['displayX'] = node['displayX'] * ratio + sceneX;
  node['displayY'] = node['displayY'] * ratio + sceneY;
  }
   
  node['displaySize'] = node['displaySize'] * sizeRatio;
  });
   
  sizeRatio = Math.pow(ratio, self.p.edgesPowRatio);
  parseEdges && self.edges.forEach(function(edge) {
  edge['displaySize'] = edge['displaySize'] * sizeRatio;
  });
   
  return self;
  };
   
  /**
  * Determines the borders of the graph as it will be drawn. It is used to
  * avoid the user to drag the graph out of the canvas.
  */
  function setBorders() {
  self.borders = {};
   
  self.nodes.forEach(function(node) {
  self.borders.minX = Math.min(
  self.borders.minX == undefined ?
  node['displayX'] - node['displaySize'] :
  self.borders.minX,
  node['displayX'] - node['displaySize']
  );
   
  self.borders.maxX = Math.max(
  self.borders.maxX == undefined ?
  node['displayX'] + node['displaySize'] :
  self.borders.maxX,
  node['displayX'] + node['displaySize']
  );
   
  self.borders.minY = Math.min(
  self.borders.minY == undefined ?
  node['displayY'] - node['displaySize'] :
  self.borders.minY,
  node['displayY'] - node['displaySize']
  );
   
  self.borders.maxY = Math.max(
  self.borders.maxY == undefined ?
  node['displayY'] - node['displaySize'] :
  self.borders.maxY,
  node['displayY'] - node['displaySize']
  );
  });
  }
   
  /**
  * Checks which nodes are under the (mX, mY) points, representing the mouse
  * position.
  * @param {number} mX The mouse X position.
  * @param {number} mY The mouse Y position.
  * @return {Graph} Returns itself.
  */
  function checkHover(mX, mY) {
  var dX, dY, s, over = [], out = [];
  self.nodes.forEach(function(node) {
  if (node['hidden']) {
  node['hover'] = false;
  return;
  }
   
  dX = Math.abs(node['displayX'] - mX);
  dY = Math.abs(node['displayY'] - mY);
  s = node['displaySize'];
   
  var oldH = node['hover'];
  var newH = dX < s && dY < s && Math.sqrt(dX * dX + dY * dY) < s;
   
  if (oldH && !newH) {
  node['hover'] = false;
  out.push(node.id);
  } else if (newH && !oldH) {
  node['hover'] = true;
  over.push(node.id);
  }
  });
   
  over.length && self.dispatch('overnodes', over);
  out.length && self.dispatch('outnodes', out);
   
  return self;
  };
   
  /**
  * Applies a function to a clone of each node (or indicated nodes), and then
  * tries to apply the modifications made on the clones to the original nodes.
  * @param {function(Object)} fun The function to execute.
  * @param {?Array.<string>} ids An Array of node IDs (optional).
  * @return {Graph} Returns itself.
  */
  function iterNodes(fun, ids) {
  var a = ids ? ids.map(function(id) {
  return self.nodesIndex[id];
  }) : self.nodes;
   
  var aCopies = a.map(cloneNode);
  aCopies.forEach(fun);
   
  a.forEach(function(n, i) {
  checkNode(n, aCopies[i]);
  });
   
  return self;
  };
   
  /**
  * Applies a function to a clone of each edge (or indicated edges), and then
  * tries to apply the modifications made on the clones to the original edges.
  * @param {function(Object)} fun The function to execute.
  * @param {?Array.<string>} ids An Array of edge IDs (optional).
  * @return {Graph} Returns itself.
  */
  function iterEdges(fun, ids) {
  var a = ids ? ids.map(function(id) {
  return self.edgesIndex[id];
  }) : self.edges;
   
  var aCopies = a.map(cloneEdge);
  aCopies.forEach(fun);
   
  a.forEach(function(e, i) {
  checkEdge(e, aCopies[i]);
  });
   
  return self;
  };
   
  /**
  * Returns a specific node clone or an array of specified node clones.
  * @param {(string|Array.<string>)} ids The ID or an array of node IDs.
  * @return {(Object|Array.<Object>)} The clone or the array of clones.
  */
  function getNodes(ids) {
  var a = ((ids instanceof Array ? ids : [ids]) || []).map(function(id) {
  return cloneNode(self.nodesIndex[id]);
  });
   
  return (ids instanceof Array ? a : a[0]);
  };
   
  /**
  * Returns a specific edge clone or an array of specified edge clones.
  * @param {(string|Array.<string>)} ids The ID or an array of edge IDs.
  * @return {(Object|Array.<Object>)} The clone or the array of clones.
  */
  function getEdges(ids) {
  var a = ((ids instanceof Array ? ids : [ids]) || []).map(function(id) {
  return cloneEdge(self.edgesIndex[id]);
  });
   
  return (ids instanceof Array ? a : a[0]);
  };
   
  empty();
   
  this.addNode = addNode;
  this.addEdge = addEdge;
  this.dropNode = dropNode;
  this.dropEdge = dropEdge;
   
  this.iterEdges = iterEdges;
  this.iterNodes = iterNodes;
   
  this.getEdges = getEdges;
  this.getNodes = getNodes;
   
  this.empty = empty;
  this.rescale = rescale;
  this.translate = translate;
  this.setBorders = setBorders;
  this.checkHover = checkHover;
  }
   
  sigma.easing = {
  linear: {},
  quadratic: {}
  };
   
  sigma.easing.linear.easenone = function(k) {
  return k;
  };
   
  sigma.easing.quadratic.easein = function(k) {
  return k * k;
  };
   
  sigma.easing.quadratic.easeout = function(k) {
  return - k * (k - 2);
  };
   
  sigma.easing.quadratic.easeinout = function(k) {
  if ((k *= 2) < 1) return 0.5 * k * k;
  return - 0.5 * (--k * (k - 2) - 1);
  };
   
  sigma.debugMode = 0;
   
  sigma.log = function() {
  if (sigma.debugMode == 1) {
  for (var k in arguments) {
  console.log(arguments[k]);
  }
  }else if (sigma.debugMode > 1) {
  for (var k in arguments) {
  throw new Error(arguments[k]);
  }
  }
   
  return sigma;
  };
   
  /**
  * Add a function to the prototype of SigmaPublic, but with access to the
  * Sigma class properties.
  * @param {string} pluginName [description].
  * @param {function} caller [description].
  * @param {function(Sigma)} launcher [description].
  */
  sigma.addPlugin = function(pluginName, caller, launcher) {
  SigmaPublic.prototype[pluginName] = caller;
  local.plugins.push(launcher);
  };
  sigma.tools.drawRoundRect = function(ctx, x, y, w, h, ellipse, corners) {
  var e = ellipse ? ellipse : 0;
  var c = corners ? corners : [];
  c = ((typeof c) == 'string') ? c.split(' ') : c;
   
  var tl = e && (c.indexOf('topleft') >= 0 ||
  c.indexOf('top') >= 0 ||
  c.indexOf('left') >= 0);
  var tr = e && (c.indexOf('topright') >= 0 ||
  c.indexOf('top') >= 0 ||
  c.indexOf('right') >= 0);
  var bl = e && (c.indexOf('bottomleft') >= 0 ||
  c.indexOf('bottom') >= 0 ||
  c.indexOf('left') >= 0);
  var br = e && (c.indexOf('bottomright') >= 0 ||
  c.indexOf('bottom') >= 0 ||
  c.indexOf('right') >= 0);
   
  ctx.moveTo(x, y + e);
   
  if (tl) {
  ctx.arcTo(x, y, x + e, y, e);
  }else {
  ctx.lineTo(x, y);
  }
   
  if (tr) {
  ctx.lineTo(x + w - e, y);
  ctx.arcTo(x + w, y, x + w, y + e, e);
  }else {
  ctx.lineTo(x + w, y);
  }
   
  if (br) {
  ctx.lineTo(x + w, y + h - e);
  ctx.arcTo(x + w, y + h, x + w - e, y + h, e);
  }else {
  ctx.lineTo(x + w, y + h);
  }
   
  if (bl) {
  ctx.lineTo(x + e, y + h);
  ctx.arcTo(x, y + h, x, y + h - e, e);
  }else {
  ctx.lineTo(x, y + h);
  }
   
  ctx.lineTo(x, y + e);
  };
   
  sigma.tools.getRGB = function(s, asArray) {
  s = s.toString();
  var res = {
  'r': 0,
  'g': 0,
  'b': 0
  };
   
  if (s.length >= 3) {
  if (s.charAt(0) == '#') {
  var l = s.length - 1;
  if (l == 6) {
  res = {
  'r': parseInt(s.charAt(1) + s.charAt(2), 16),
  'g': parseInt(s.charAt(3) + s.charAt(4), 16),
  'b': parseInt(s.charAt(5) + s.charAt(5), 16)
  };
  }else if (l == 3) {
  res = {
  'r': parseInt(s.charAt(1) + s.charAt(1), 16),
  'g': parseInt(s.charAt(2) + s.charAt(2), 16),
  'b': parseInt(s.charAt(3) + s.charAt(3), 16)
  };
  }
  }
  }
   
  if (asArray) {
  res = [
  res['r'],
  res['g'],
  res['b']
  ];
  }
   
  return res;
  };
   
  sigma.tools.rgbToHex = function(R, G, B) {
  return sigma.tools.toHex(R) + sigma.tools.toHex(G) + sigma.tools.toHex(B);
  };
   
  sigma.tools.toHex = function(n) {
  n = parseInt(n, 10);
   
  if (isNaN(n)) {
  return '00';
  }
  n = Math.max(0, Math.min(n, 255));
  return '0123456789ABCDEF'.charAt((n - n % 16) / 16) +
  '0123456789ABCDEF'.charAt(n % 16);
  };
   
  /**
  * sigma.chronos manages frames insertion to simulate asynchronous computing.
  * It has been designed to make possible to execute heavy computing tasks
  * for the browser, without freezing it.
  * @constructor
  * @extends sigma.classes.Cascade
  * @extends sigma.classes.EventDispatcher
  * @this {sigma.chronos}
  */
  sigma.chronos = new (function() {
  sigma.classes.EventDispatcher.call(this);
   
  /**
  * Represents "this", without the well-known scope issue.
  * @private
  * @type {sigma.chronos}
  */
  var self = this;
   
  /**
  * Indicates whether any task is actively running or not.
  * @private
  * @type {boolean}
  */
  var isRunning = false;
   
  /**
  * Indicates the FPS "goal", that will define the theoretical
  * frame length.
  * @private
  * @type {number}
  */
  var fpsReq = 80;
   
  /**
  * Stores the last computed FPS value (FPS is computed only when any
  * task is running).
  * @private
  * @type {number}
  */
  var lastFPS = 0;
   
  /**
  * The number of frames inserted since the last start.
  * @private
  * @type {number}
  */
  var framesCount = 0;
   
  /**
  * The theoretical frame time.
  * @private
  * @type {number}
  */
  var frameTime = 1000 / fpsReq;
   
  /**
  * The theoretical frame length, minus the last measured delay.
  * @private
  * @type {number}
  */
  var correctedFrameTime = frameTime;
   
  /**
  * The measured length of the last frame.
  * @private
  * @type {number}
  */
  var effectiveTime = 0;
   
  /**
  * The time passed since the last runTasks action.
  * @private
  * @type {number}
  */
  var currentTime = 0;
   
  /**
  * The time when the last frame was inserted.
  * @private
  * @type {number}
  */
  var startTime = 0;
   
  /**
  * The difference between the theoretical frame length and the
  * last measured frame length.
  * @private
  * @type {number}
  */
  var delay = 0;
   
  /**
  * The container of all active generators.
  * @private
  * @type {Object.<string, Object>}
  */
  var generators = {};
   
  /**
  * The array of all the referenced and active tasks.
  * @private
  * @type {Array.<Object>}
  */
  var tasks = [];
   
  /**
  * The array of all the referenced and queued tasks.
  * @private
  * @type {Array.<Object>}
  */
  var queuedTasks = [];
   
  /**
  * The index of the next task to execute.
  * @private
  * @type {number}
  */
  var taskIndex = 0;
   
   
  /**
  * Inserts a frame before executing the callback.
  * @param {function()} callback The callback to execute after having
  * inserted the frame.
  * @return {sigma.chronos} Returns itself.
  */
  function insertFrame(callback) {
  window.setTimeout(callback, 0);
  return self;
  }
   
  /**
  * The local method that executes routine, and inserts frames when needed.
  * It dispatches a "frameinserted" event after having inserted any frame,
  * and an "insertframe" event before.
  * @private
  */
  function frameInserter() {
  self.dispatch('frameinserted');
  while (isRunning && tasks.length && routine()) {}
   
  if (!isRunning || !tasks.length) {
  stopTasks();
  } else {
  startTime = (new Date()).getTime();
  framesCount++;
  delay = effectiveTime - frameTime;
  correctedFrameTime = frameTime - delay;
   
  self.dispatch('insertframe');
  insertFrame(frameInserter);
  }
  };
   
  /**
  * The local method that executes the tasks, and compares the current frame
  * length to the ideal frame length.
  * @private
  * @return {boolean} Returns false if the current frame should be ended,
  * and true else.
  */
  function routine() {
  taskIndex = taskIndex % tasks.length;
   
  if (!tasks[taskIndex].task()) {
  var n = tasks[taskIndex].taskName;
   
  queuedTasks = queuedTasks.filter(function(e) {
  (e.taskParent == n) && tasks.push({
  taskName: e.taskName,
  task: e.task
  });
  return e.taskParent != n;
  });
   
  self.dispatch('killed', tasks.splice(taskIndex--, 1)[0]);
  }
   
  taskIndex++;
  effectiveTime = (new Date()).getTime() - startTime;
  return effectiveTime <= correctedFrameTime;
  };
   
  /**
  * Starts tasks execution.
  * @return {sigma.chronos} Returns itself.
  */
  function runTasks() {
  isRunning = true;
  taskIndex = 0;
  framesCount = 0;
   
  startTime = (new Date()).getTime();
  currentTime = startTime;
   
  self.dispatch('start');
  self.dispatch('insertframe');
  insertFrame(frameInserter);
  return self;
  };
   
  /**
  * Stops tasks execution, and dispatch a "stop" event.
  * @return {sigma.chronos} Returns itself.
  */
  function stopTasks() {
  self.dispatch('stop');
  isRunning = false;
  return self;
  };
   
  /**
  * A task is a function that will be executed continuously while it returns
  * true. As soon as it return false, the task will be removed.
  * If several tasks are present, they will be executed in parallele.
  * This method will add the task to this execution process.
  * @param {function(): boolean} task The task to add.
  * @param {string} name The name of the worker, used for
  * managing the different tasks.
  * @param {boolean} autostart If true, sigma.chronos will start
  * automatically if it is not working
  * yet.
  * @return {sigma.chronos} Returns itself.
  */
  function addTask(task, name, autostart) {
  if (typeof task != 'function') {
  throw new Error('Task "' + name + '" is not a function');
  }
   
  tasks.push({
  taskName: name,
  task: task
  });
   
  isRunning = !!(isRunning || (autostart && runTasks()) || true);
  return self;
  };
   
  /**
  * Will add a task that will be start to be executed as soon as a task
  * named as the parent will be removed.
  * @param {function(): boolean} task The task to add.
  * @param {string} name The name of the worker, used for
  * managing the different tasks.
  * @param {string} parent The name of the parent task.
  * @return {sigma.chronos} Returns itself.
  */
  function queueTask(task, name, parent) {
  if (typeof task != 'function') {
  throw new Error('Task "' + name + '" is not a function');
  }
   
  if (!tasks.concat(queuedTasks).some(function(e) {
  return e.taskName == parent;
  })) {
  throw new Error(
  'Parent task "' + parent + '" of "' + name + '" is not attached.'
  );
  }
   
  queuedTasks.push({
  taskParent: parent,
  taskName: name,
  task: task
  });
   
  return self;
  };
   
  /**
  * Removes a task.
  * @param {string} v If v is undefined, then every tasks will
  * be removed. If not, each task named v will
  * be removed.
  * @param {number} queueStatus Determines the queued tasks behaviour. If 0,
  * then nothing will happen. If 1, the tasks
  * queued to any removed task will be triggered.
  * If 2, the tasks queued to any removed task
  * will be removed as well.
  * @return {sigma.chronos} Returns itself.
  */
  function removeTask(v, queueStatus) {
  if (v == undefined) {
  tasks = [];
  if (queueStatus == 1) {
  queuedTasks = [];
  }else if (queueStatus == 2) {
  tasks = queuedTasks;
  queuedTasks = [];
  }
  stopTasks();
  } else {
  var n = (typeof v == 'string') ? v : '';
  tasks = tasks.filter(function(e) {
  if ((typeof v == 'string') ? e.taskName == v : e.task == v) {
  n = e.taskName;
  return false;
  }
  return true;
  });
   
  if (queueStatus > 0) {
  queuedTasks = queuedTasks.filter(function(e) {
  if (queueStatus == 1 && e.taskParent == n) {
  tasks.push(e);
  }
  return e.taskParent != n;
  });
  }
  }
   
  isRunning = !!(!tasks.length || (stopTasks() && false));
  return self;
  };
   
  /**
  * A generator is a pair task/condition. The task will be executed
  * while it returns true.
  * When it returns false, the condition will be tested. If
  * the condition returns true, the task will be executed
  * again at the next process iteration. If not, the generator
  * is removed.
  * If several generators are present, they will be executed one
  * by one: When the first stops, the second will start, etc. When
  * they are all ended, then the conditions will be tested to know
  * which generators have to be started again.
  * @param {string} id The generators ID.
  * @param {function(): boolean} task The generator's task.
  * @param {function(): boolean} condition The generator's condition.
  * @return {sigma.chronos} Returns itself.
  */
  function addGenerator(id, task, condition) {
  if (generators[id] != undefined) {
  return self;
  }
   
  generators[id] = {
  task: task,
  condition: condition
  };
   
  getGeneratorsCount(true) == 0 && startGenerators();
  return self;
  };
   
  /**
  * Removes a generator. It means that the task will continue being eecuted
  * until it returns false, but then the
  * condition will not be tested.
  * @param {string} id The generator's ID.
  * @return {sigma.chronos} Returns itself.
  */
  function removeGenerator(id) {
  if (generators[id]) {
  generators[id].on = false;
  generators[id].del = true;
  }
  return self;
  };
   
  /**
  * Returns the number of generators.
  * @private
  * @param {boolean} running If true, returns the number of active
  * generators instead.
  * @return {sigma.chronos} Returns itself.
  */
  function getGeneratorsCount(running) {
  return running ?
  Object.keys(generators).filter(function(id) {
  return !!generators[id].on;
  }).length :
  Object.keys(generators).length;
  };
   
  /**
  * Returns the array of the generators IDs.
  * @return {array.<string>} The array of IDs.
  */
  function getGeneratorsIDs() {
  return Object.keys(generators);
  }
   
  /**
  * startGenerators is the method that manages which generator
  * is the next to start when another one stops. It will dispatch
  * a "stopgenerators" event if there is no more generator to start,
  * and a "startgenerators" event else.
  * @return {sigma.chronos} Returns itself.
  */
  function startGenerators() {
  if (!Object.keys(generators).length) {
  self.dispatch('stopgenerators');
  }else {
  self.dispatch('startgenerators');
   
  self.unbind('killed', onTaskEnded);
  insertFrame(function() {
  for (var k in generators) {
  generators[k].on = true;
  addTask(
  generators[k].task,
  k,
  false
  );
  }
  });
   
  self.bind('killed', onTaskEnded).runTasks();
  }
   
  return self;
  };
   
  /**
  * A callback triggered everytime the task of a generator stops, that will
  * test the related generator's condition, and see if there is still any
  * generator to start.
  * @private
  * @param {Object} e The sigma.chronos "killed" event.
  */
  function onTaskEnded(e) {
  if (generators[e['content'].taskName] != undefined) {
  if (generators[e['content'].taskName].del ||
  !generators[e['content'].taskName].condition()) {
  delete generators[e['content'].taskName];
  }else {
  generators[e['content'].taskName].on = false;
  }
   
  if (getGeneratorsCount(true) == 0) {
  startGenerators();
  }
  }
  };
   
  /**
  * Either set or returns the fpsReq property. This property determines
  * the number of frames that should be inserted per second.
  * @param {?number} v The frequency asked.
  * @return {(Chronos|number)} Returns the frequency if v is undefined, and
  * itself else.
  */
  function frequency(v) {
  if (v != undefined) {
  fpsReq = Math.abs(1 * v);
  frameTime = 1000 / fpsReq;
  framesCount = 0;
  return self;
  } else {
  return fpsReq;
  }
  };
   
  /**
  * Returns the actual average number of frames that are inserted per
  * second.
  * @return {number} The actual average FPS.
  */
  function getFPS() {
  if (isRunning) {
  lastFPS =
  Math.round(
  framesCount /
  ((new Date()).getTime() - currentTime) *
  10000
  ) / 10;
  }
   
  return lastFPS;
  };
   
  /**
  * Returns the number of tasks.
  * @return {number} The number of tasks.
  */
  function getTasksCount() {
  return tasks.length;
  }
   
  /**
  * Returns the number of queued tasks.
  * @return {number} The number of queued tasks.
  */
  function getQueuedTasksCount() {
  return queuedTasks.length;
  }
   
  /**
  * Returns how long sigma.chronos has active tasks running
  * without interuption for, in ms.
  * @return {number} The time chronos is running without interuption for.
  */
  function getExecutionTime() {
  return startTime - currentTime;
  }
   
  this.frequency = frequency;
   
  this.runTasks = runTasks;
  this.stopTasks = stopTasks;
  this.insertFrame = insertFrame;
   
  this.addTask = addTask;
  this.queueTask = queueTask;
  this.removeTask = removeTask;
   
  this.addGenerator = addGenerator;
  this.removeGenerator = removeGenerator;
  this.startGenerators = startGenerators;
  this.getGeneratorsIDs = getGeneratorsIDs;
   
  this.getFPS = getFPS;
  this.getTasksCount = getTasksCount;
  this.getQueuedTasksCount = getQueuedTasksCount;
  this.getExecutionTime = getExecutionTime;
   
  return this;
  })();
   
  sigma.publicPrototype = SigmaPublic.prototype;
  })();
   
   
file:a/robots.txt -> file:b/robots.txt
# www.robotstxt.org/ # www.robotstxt.org/
# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449
   
User-agent: * User-agent: *
Disallow: /admin/ Disallow: /admin/
  Sitemap: http://orgs.disclosurelo.gs/sitemap.xml.php
file:b/sitemap.xml.php (new)
  <?php
 
  include ('include/common.inc.php');
  $last_updated = date('Y-m-d', @filemtime('cbrfeed.zip'));
  header("Content-Type: text/xml");
  echo "<?xml version='1.0' encoding='UTF-8'?>";
  echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
  echo " <url><loc>" . local_url() . "index.php</loc><priority>1.0</priority></url>\n";
  foreach (scandir("./") as $file) {
  if (strpos($file, ".php") !== false && $file != "index.php" && $file != "sitemap.xml.php")
  echo " <url><loc>" . local_url() . "$file</loc><priority>0.3</priority></url>\n";
  }
 
  $db = $server->get_db('disclosr-agencies');
  try {
  $rows = $db->get_view("app", "byCanonicalName")->rows;
  foreach ($rows as $row) {
  echo '<url><loc>' . local_url() . 'getAgency.php?id=' . $row->value->_id . "</loc><priority>0.6</priority></url>\n";
  }
  } catch (SetteeRestClientException $e) {
  setteErrorHandler($e);
  }
  echo '</urlset>';
  ?>