/* Gestion du SOCIOGRAMME SPATIAL * * [1] - Récupération du conteneur (élément du DOM/HTML) * [2] - Construction de la classe (objet type) * [3] - Construction de l'objet SIGMA * [4] - Récupération des données * [5] - Paramétrage de SIGMA * [6] - On extrait les noeuds/liens des données reçues * [7] - On ajoute les noeuds/liens à SIGMA * [8] - On positionne le tout dans l'espace * [9] - On bind() les évènements * [10]- On affiche le rendu * */ /* [1] Récupération du conteneur ======================================*/ function sociogramClass(container){ this.container = container; this.log('sociogram created'); } /* [2] Construction de la classe ======================================*/ sociogramClass.prototype = { container: this.container, sigma: null, request: { path: 'chart/network_data' }, response: null, nodes: [], edges: [], rad: 500, nodeDistance: 100, /* (0) Ajout de méthodes à @this.sigma.graph */ overloadGraph: function(){}, /* (1) Point d'amorçage */ load: function(){}, /* (2) Retourne la distance avec le noeud le plus près de la position (@x, @y) */ nodeAt: function(x, y){}, /* (3) Positionnement spatial d'un noeud d'id @nodeId à une position @pos(x,y) */ arrange: function(nodeId, pos, alone){}, /* (4) Extraction de des @this.nodes depuis @this.response */ extractNodesFromResponse: function(){}, /* (5) Extraction de des @this.edges depuis @this.response */ extractEdgesFromResponse: function(){}, /* (6) Ajout des noeuds @this.nodes au rendu */ renderNodes: function(){}, /* (7) Ajout des liens @this.edges au rendu */ renderEdges: function(){}, /* (8) Ajout des fonctions dans @this.sigma.graph */ overload: { // {1} Renvoie la liste des voisins directs et indirects du noeud d'id @nodeId // nodeNeighbors: function(nodeId){}, // {2} Renvoie la liste des voisins directs du noeud d'id @nodeId // nodeDirectNeighbors: function(nodeId){} }, /* (9) Fonctions de callback pour les évènements */ bindings: { // {1} Action quand on clique sur un noeud // clickNode: function(thisPtr, e){}, // {2} Action quand on clique dans le vide // clickStage: function(thisPtr, e){} }, log: function(message){ console.log('--[SOCIOGRAM]--'); console.warn(message); console.log('--[/SOCIOGRAM]--'); } }; /* SURCHARGE DU GRAPH (@this.sigma.graph) -> Ajout de méthodes * */ sociogramClass.prototype.overloadGraph = function(){ sigma.classes.graph.addMethod('nodeNeighbors', this.overload.nodeNeighbors); sigma.classes.graph.addMethod('nodeDirectNeighbors', this.overload.nodeDirectNeighbors); this.log('graph overloaded'); }; /* (1) Point d'amorçage */ sociogramClass.prototype.load = function(){ // {1} On instancie SIGMA // this.sigma = new sigma({ renderer: { container: this.container, 'type': 'canvas' } }); var thisPtr = this; // {2} On récupère les données via l'API // api.send(this.request, function(response){ thisPtr.log(response); // Si erreur, on quitte if( response.ModuleError != 0 ) return; // On enregistre la réponse thisPtr.response = response; // {3} On paramètre SIGMA // thisPtr.sigma.settings({ defaultNodeColor: '#348ed8', defaultLabelSize: 14, defaultLabelBGColor: "#ddd", defaultHoverLabelBGColor: "#002147", defaultLabelHoverColor: "#fff", labelThreshold: 10, defaultEdgeType: "line" }); // {4} On recupere les noeuds et les liens // thisPtr.extractNodesFromResponse(); thisPtr.extractEdgesFromResponse(); // {5} On ajoute les noeuds et les liens au rendu // thisPtr.addNodes(); thisPtr.addEdges(); // {6} On définit les évènements // // On affiche que les voisins d'un noeud quand on clique sur lui thisPtr.sigma.bind('clickNode', function(e){ thisPtr.bindings.clickNode(thisPtr, e); }); // On affiche tous les noeuds quand on clique dans le vide thisPtr.sigma.bind('clickStage', function(e){ thisPtr.bindings.clickStage(thisPtr, e); }); // {7} On positionne spatialement les noeuds // thisPtr.sigma.graph.nodes().forEach(function(n){ thisPtr.arrange(n.id, null, true); }); // {8} On affiche le rendu // thisPtr.sigma.camera.ratio = 1.2; thisPtr.sigma.refresh(); }); }; /* RETOURNE LA DISTANCE AVEC LE NOEUD LE PLUS PRES * * @x Abscisse du point * @y Ordonnees du point * * @return distance Retourne la distance du noeud le plus proche * */ sociogramClass.prototype.nodeAt = function(x, y){ var nodes = this.sigma.graph.nodes(); var minDistance = null; for( var nodeId in nodes ){ var distance = Math.sqrt( Math.pow(x-nodes[nodeId].x, 2) + Math.pow(y-nodes[nodeId].y, 2) ); if( minDistance == null || distance < minDistance ) minDistance = distance; } return minDistance; }; /* POSITIONNE LES VOISINS AUTOUR DU NOEUD COURANT * * @nodeId Id du noeud courant * @pos Contient {x, y} position initiale, sinon la position actuelle du noeud * */ sociogramClass.prototype.arrange = function(nodeId, pos, alone){ var node = this.sigma.graph.nodes(nodeId); // Si le noeud est deja place, on ne fais rien if( node.x != 0 || node.y != 0 ) return; pos = (pos==null) ? {x: node.x, y: node.y} : pos; // On recupere la position // Tant que le noeud est trop proche d'un autre, on l'eloigne // UNIQUEMENT si alone n'est pas NULL if( alone ){ while( this.nodeAt(pos.x, pos.y) < 2*this.nodeDistance ){ pos = { x: pos.x + 2*this.nodeDistance*Math.cos(Math.random()*2*Math.PI), y: pos.y + 2*this.nodeDistance*Math.sin(Math.random()*2*Math.PI) }; } } // On recupere les voisins directs var neighborsId = this.sigma.graph.nodeDirectNeighbors(nodeId); var neighbors = {}; var neighborsCount = 0; for( var neighborId in neighborsId ){ neighbors[neighborId] = this.sigma.graph.nodes(neighborId); neighborsCount++; } // On positionne le noeud node.x = pos.x; node.y = pos.y; var angles = []; // On positionne chaque voisin si c'est pas deja fait for( neighborId in neighbors ){ var current = this.sigma.graph.nodes(neighborId); // Si n'est pas deja positionne if( current.x == 0 && current.y == 0 ){ // On cherche un angle tant qu'il est pas trop pres d'un deja pris var angle, alreadyUsed = false; do{ angle = Math.random()*2*Math.PI; for( var i = 0 ; i < angles.length ; i++ ) if( Math.abs(angle-angles[i]) > Math.PI/10 ){ alreadyUsed = true; break; } }while( alreadyUsed ); current.x = pos.x + this.nodeDistance*Math.cos(angle); current.y = pos.y + this.nodeDistance*Math.sin(angle); this.arrange(current.id); } } this.sigma.refresh(); }; /* RENVOIE LA LISTE DES VOISINS (DIRECTS & INDIRECTS) D'UN NOEUD DONNÉ * * @nodeId Id du noeud en question * * @return neighbors Liste des voisins directs+indirects * */ sociogramClass.prototype.overload.nodeNeighbors = function(nodeId){ var neighbors = this.allNeighborsIndex[nodeId]; // Pile des voisins pour lesquels il faut chercher leurs voisins var stack = []; for( var neighborId in neighbors ) stack.push(neighborId); // Tant qu'il reste des voisins a trouver while( stack.length > 0 ){ var subneighbors = this.allNeighborsIndex[stack[0]]; for( var subId in subneighbors ) // Si le voisin est pas deja dans la liste/pile, on l'ajoute a la liste des voisins if( neighbors[subId] == null ){ stack.push(subId); // On ajoute a la pile neighbors[subId] = subneighbors[subId]; // On ajoute a la liste complete } stack.shift(); } // On retourne le resultat return neighbors; }; /* RENVOIE LA LISTE DES VOISINS DIRECTS D'UN NOEUD DONNÉ * * @nodeId Id du noeud en question * * @return neighbors Liste des voisins directs * */ sociogramClass.prototype.overload.nodeDirectNeighbors = function(nodeId){ return this.allNeighborsIndex[nodeId]; }; /* EXTRAIT LES NOEUDS DE LA RÉPONSE DE L'API * */ sociogramClass.prototype.extractNodesFromResponse = function(){ this.nodes = []; // Pour chaque alter for( var i = 0 ; i < this.response.data.alter.length ; i++ ) this.nodes.push({ 'id': 'n-'+this.response.data.alter[i][0], 'label': this.response.data.alter[i][1], 'x': 0, 'y': 0, 'size': this.response.data.alter[i][2] }); }; /* EXTRAIT LES NOEUDS DE LA RÉPONSE DE L'API * */ sociogramClass.prototype.extractEdgesFromResponse = function(){ this.edges = []; // Pour chaque inter for( var i = 0 ; i < this.response.data.inter.length ; i++ ) this.edges.push({ 'id': 'e-'+this.response.data.inter[i][0]+'-'+this.response.data.inter[i][1], 'source': 'n-'+this.response.data.inter[i][0], 'target': 'n-'+this.response.data.inter[i][1] }); }; /* AJOUTE LES NOEUDS AU RENDU (SIGMA GRAPH) * */ sociogramClass.prototype.addNodes = function(){ /* (5) On ajoute nos noeuds */ for( var i = 0 ; i < this.nodes.length ; i++) this.sigma.graph.addNode(this.nodes[i]); }; /* AJOUTE LES LIENS AU RENDU (SIGMA GRAPH) * */ sociogramClass.prototype.addEdges = function(){ /* (6) On ajoute nos liens */ for( var i = 0 ; i < this.edges.length ; i++) this.sigma.graph.addEdge(this.edges[i]); }; /* FONCTION ACTIVÉE LORS DU CLIC SUR UN NOEUD * */ sociogramClass.prototype.bindings.clickNode = function(thisPtr, e){ console.log( thisPtr ); var nodeId = e.data.node.id; // On recupere les voisins var neighborNodes = thisPtr.sigma.graph.nodeNeighbors(nodeId); neighborNodes[nodeId] = e.data.node; // on ajoute le noeud clique thisPtr.sigma.graph.nodes().forEach(function(n) { if( neighborNodes[n.id] != null ) n.color = n.originalColor; else n.color = '#888'; }); thisPtr.sigma.refresh(); }; /* FONCTION ACTIVÉE LORS DU CLIC DANS LE VIDE * */ sociogramClass.prototype.bindings.clickStage = function(thisPtr, e){ thisPtr.sigma.graph.nodes().forEach(function(n){ n.color = n.originalColor; }); thisPtr.sigma.refresh(); }; // var i, // s, // o, // L = 10, // N = 100, // E = 500, // g = { // nodes: [], // edges: [] // }, // step = 0; // // Generate a random graph: // for (i = 0; i < N; i++) { // o = { // id: 'n' + i, // label: 'Node ' + i, // circular_x: L * Math.cos(Math.PI * 2 * i / N - Math.PI / 2), // circular_y: L * Math.sin(Math.PI * 2 * i / N - Math.PI / 2), // circular_size: Math.random(), // circular_color: '#' + ( // Math.floor(Math.random() * 16777215).toString(16) + '000000' // ).substr(0, 6), // grid_x: i % L, // grid_y: Math.floor(i / L), // grid_size: 1, // grid_color: '#ccc' // }; // ['x', 'y', 'size', 'color'].forEach(function(val) { // o[val] = o['grid_' + val]; // }); // g.nodes.push(o); // } // for (i = 0; i < E; i++) // g.edges.push({ // id: 'e' + i, // source: 'n' + (Math.random() * N | 0), // target: 'n' + (Math.random() * N | 0), // }); // // Instantiate sigma: // s = new sigma({ // graph: g, // renderer: { container: document.getElementById('graph-container'), 'type': 'canvas' }, // settings: { // animationsTime: 1000 // } // }); // setInterval(function(){ // var prefix = ['grid_', 'circular_'][step = +!step]; // sigma.plugins.animate( // s, // { // x: prefix + 'x', // y: prefix + 'y', // size: prefix + 'size', // color: prefix + 'color' // } // ); // }, 10000);