Skip to content

Instantly share code, notes, and snippets.

@yrochat
Last active October 27, 2015 15:22
Show Gist options
  • Save yrochat/9327e79756de68c15bd2 to your computer and use it in GitHub Desktop.
Save yrochat/9327e79756de68c15bd2 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<!-- Initial code was (long ago) 04_force.html by Scott Murray -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>Network transition</title>
<script src="https://cdn.rawgit.com/maladesimaginaires/intnetviz/master/include/js/d3.v3.js" charset="utf-8"></script>
<script src="https://cdn.rawgit.com/maladesimaginaires/intnetviz/master/include/js/d3.slider.js" charset="utf-8"></script>
<link rel="stylesheet" href="https://cdn.rawgit.com/maladesimaginaires/intnetviz/master/include/css/d3.slider.css" />
<link rel="stylesheet" href="https://cdn.rawgit.com/maladesimaginaires/intnetviz/master/include/css/styles.css" />
</head>
<body>
<div id='conteneurPrincipal'>
<div id="entete">
<div id='line_reference'></div>
</div>
<div id="reseau">
<div id='slider'></div>
<div id='conteneurReseau'></div>
</div>
<div id="repliques">
<div id='previous_line_container' style="color:#cccccc"></div>
<div id='current_line_container' style="color:#333333"></div>
<div id='next_line_container' style="color:#cccccc"></div>
</div>
</div>
<script type="text/javascript">
// Variables de couleurs
var color_not_yet_activated = "#eeeeee";
var color_not_yet_live = "#eeeeee";
var color_previously_activated = "#666666";
var color_previously_live = "#666666";
var color_activated = "#ff6600";
var color_live = "#ff6600";
var color_active = "#ffffff";
// Chemin des fichiers d'input...
var path_characters
= "https://cdn.rawgit.com/maladesimaginaires/intnetviz/master/data/moliere/l_ecole_des_femmes_personnages.txt";
var path_text
= "https://cdn.rawgit.com/maladesimaginaires/intnetviz/master/data/moliere/l_ecole_des_femmes_texte.txt";
// Largeur et hauteur de la fenêtre où apparaît le réseau
var w = 640;
var h = 480;
// La rapidité d'animation en ms
var step_duration = 150;
// Epaisseur du contour des noeuds...
var node_stroke_width = 2
// Tailles min et max des noeuds et aretes...
var min_node_radius = 7;
var max_node_radius = 50;
var min_edge_width = 1;
var max_edge_width = 15;
// Decalage des labels...
var label_x_offset = 5
var label_y_offset = 5
// current_line nous donne la replique actuelle.
var current_line = 0;
// Indique si l'animation est en cours.
var animation_is_on = 0
// La variable temp permet d'appeler le tableau de données en dehors de d3.tsv, par exemple dans la console
var temp = [];
// Idem pour dataset etc...
var dataset;
var svg;
var number_of_lines;
var position_slider;
// Importation des donnees et precalcul des etats.
import_play_data();
function import_play_data() {
// Importation des noeuds et gestion des fichiers à l'interieur de
// la fonction d3.tsv pour pallier aux problemes d'asychronisme de
// JavaScript
d3.tsv(path_characters, function(data_nodes) {
// Importation du second fichier contenant les répliques
d3.tsv(path_text, function(data_text) {
// pour tester que les données soient bien chargées
// console.log(data_nodes);
// temp est déclarée en dehors de d3.tsv pour debug
temp = data_nodes;
// colnames donne le nom de toutes les colonnes grâce à
// la fonction Object.keys. Avec [0] on choisit la
// première ligne, celle des en-têtes
var colnames = Object.keys(data_nodes[0]);
// ici les noms des personnages, qui donnent aussi les
// "keys" du tableau de données. Ce sont les noms
// obtenus au stade précédent après exclusion des
// 4 premières colonnes
var nodes_names = colnames.slice(4, colnames.length);
// node5 est composé d'un objet par noeud
// chaque noeud a un nom (key) et un array qui donne
// tous ses états
var tmp_nodes = [];
// variables d'optimisation
var number_of_nodes = nodes_names.length;
number_of_lines = data_nodes.length;
// Precalcul du rayon et des etats des noeuds; 4 etats
// sont possibles:
// - not_yet_activated (NYA)
// - previously_activated (PA)
// - activated (AD)
// - active (AE)
// Pour chaque noeud...
for (var i = 0; i < number_of_nodes; i++) {
// Recuperer le nom du perso.
var character = nodes_names[i]
// Initialisations...
var tmp_node_states = [];
var has_been_activated = 0;
var tmp_node_radius = [];
var radius = min_node_radius;
var radius_increment = (
max_node_radius
- min_node_radius
)
/ number_of_lines;
// Pour chaque replique...
for (var k = 0; k < number_of_lines; k++) {
// Si le perso est present...
if (data_nodes[k][character] == 1) {
// S'il prononce la replique => AE
if (data_text[k]['personnage'] == character) {
tmp_node_states.push('active')
radius += radius_increment;
}
// Dans le cas contraire => AD
else {
tmp_node_states.push('activated')
}
// Signaler qu'il a ete active.
has_been_activated = 1
}
// Si le perso est absent...
else {
// S'il a ete active => PA
if (has_been_activated == 1) {
tmp_node_states.push('previously_activated')
}
// Dans le cas contraire => NYA
else {
tmp_node_states.push('not_yet_activated')
}
}
tmp_node_radius.push(radius)
}
// Stocker personnage, sequence d'etats et de poids...
tmp_nodes.push({
name: character,
x: Math.random()*w,
y: Math.random()*h,
fixed: false,
step: tmp_node_states,
radius: tmp_node_radius,
});
}
// Precalcul des états et poids des arêtes; le poids est
// defini comme la proportion des repliques ou les deux
// personnages en question sont copresents; 4 etats sont
// possibles:
// - never_live (NL)
// - not_yet_live (NYL)
// - previously_live (PL)
// - live (L)
var tmp_edges = [];
// Pour chaque paire de noeuds...
for (var i = 0; i < number_of_nodes-1; i++) {
for (var j = i+1; j < number_of_nodes; j++) {
var tmp_edge_width = [];
var width = min_edge_width;
var width_increment = (
max_edge_width
- min_edge_width
)
/ number_of_lines;
var tmp_edge_states = []
// Calculer le nombre total de copresences et
// construire le vecteur des copresences...
var num_copresences = 0;
var copresences = []
for (var k = 0; k < number_of_lines; k++) {
var copresence = data_nodes[k][nodes_names[i]]
* data_nodes[k][nodes_names[j]]
;
num_copresences += copresence;
copresences.push(copresence)
width += copresence * width_increment
tmp_edge_width.push(width)
}
// Si ces noeuds ne sont jamais copresents...
if (num_copresences == 0) {
// Tous les etats sont NL.
tmp_edge_states = Array.apply(
null,
Array(number_of_lines)
)
.map(
function() {
return 'never_live'
}
);
}
// Si ces noeuds sont copresents au moins une fois...
else {
// Initialisations...
var has_been_live = 0;
var tmp_edge_states = [];
// Pour chaque replique...
for (var k = 0; k < number_of_lines; k++) {
// Si ces noeuds sont copresents => L
if (copresences[k] == 1) {
tmp_edge_states.push('live')
has_been_live = 1;
}
// Sinon s'ils ont deja ete copresents => PL
else if (has_been_live == 1) {
tmp_edge_states.push('previously_live')
}
// Sinon => NYL
else {
tmp_edge_states.push('not_yet_live')
}
}
}
// Stocker le poids et la sequence d'etats...
tmp_edges.push({
source: i,
target: j,
value: num_copresences/number_of_lines,
step: tmp_edge_states,
width: tmp_edge_width,
})
}
}
// Stockage du dataset (y.c. texte des repliques)
dataset = {
nodes: tmp_nodes,
edges: tmp_edges,
lines: data_text
};
// Initialisation du reseau...
init_svg();
update_display(current_line);
// Initialisation du slider...
position_slider = d3.slider()
.min(1)
.value(1)
.max(number_of_lines)
.on("slide", function(evt, value) {
current_line = Math.round(value);
update_display(current_line);
});
d3.select('#slider').call(position_slider);
});
});
}
// définition d'une fonction pour arrêter/démarrer l'animation
function play_pause() {
d3.event.preventDefault();
if (animation_is_on === 0) {
animation_is_on = 1;
play_animation();
}
else {
animation_is_on = 0;
}
}
// Initialisation du reseau...
function init_svg() {
// Creation du SVG (responsive)...
svg = d3.select("div#conteneurReseau")
.append("svg")
.attr("id", "playgraph")
// better to keep the viewBox dimensions with variables
.attr("viewBox", "0 0 " + w + " " + h )
.attr("preserveAspectRatio", "xMaxYMax");
// ajout de la fonction play_pause au réseau
svg.on("click", play_pause);
// réaction aux touches
corps = d3.select("body").on("keydown", function(){
if(d3.event.keyCode == "32"){
play_pause();
}
// flèche à gauche, pour une réplique de moins
else if(d3.event.keyCode == "37"){
update_display(current_line--);
}
// flèche à droite, pour une réplique de plus
else if(d3.event.keyCode == "39"){
update_display(current_line++);
}
// ctrl+R pour revenir au début
else if(d3.event.ctrlKey){
if(d3.event.keyCode == "82"){
update_display(current_line = 0);
}
}
});
// Creation des aretes comme lignes...
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
// Creation des noeuds comme cercles...
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
// ajout des noms des personnages à chaque noeud
var labels = svg.selectAll("text")
.data(dataset.nodes)
.enter()
.append("text")
.text(function(nodes) { return nodes.name; });
//Initialisation du force layout...
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([0.9*w, 0.98*h])
.linkDistance(function(data_nodes) {
return h/2*(1-data_nodes.value);
})
.charge(-h/2)
.start();
// Ceci est appele a chaque etape du force layout...
force.on("tick", function() {
nodes.attr("cx", function(nodes) { return nodes.x; })
.attr("cy", function(nodes) { return nodes.y; });
edges.attr("x1", function(nodes) { return nodes.source.x; })
.attr("y1", function(nodes) { return nodes.source.y; })
.attr("x2", function(nodes) { return nodes.target.x; })
.attr("y2", function(nodes) { return nodes.target.y; });
labels.attr("x", function(nodes) {
return nodes.x + (
nodes.radius[current_line] / 2
+ node_stroke_width
+ label_x_offset
)
})
.attr("y", function(nodes) {
return nodes.y - (
nodes.radius[current_line] / 2
+ node_stroke_width
+ label_y_offset
)
});
});
}
// Animation de la piece depuis la position actuelle...
function play_animation() {
if (animation_is_on != 0){
if (current_line < number_of_lines-1) {
current_line++;
position_slider.value(current_line);
update_display();
setTimeout(
play_animation,
step_duration
);
}
else {
current_line = 0;
}
}
}
// Mise a jour de l'affichage...
function update_display() {
// "Adresse" de la replique...
var line_reference = document.getElementById("line_reference");
line_reference.innerHTML =
"<b>Pièce </b>" + dataset.lines[current_line].piece
+ "<b> Acte </b>" + dataset.lines[current_line].acte
+ "<b> Scène </b>" + dataset.lines[current_line].scene
+ "<b> Réplique </b>" + dataset.lines[current_line].replique
;
// Afficher le texte...
document.getElementById("current_line_container")
.innerHTML = format_line(dataset.lines, 0);
if (number_of_lines > 1) {
document.getElementById("next_line_container")
.innerHTML = format_line(dataset.lines, 1);
}
if (current_line > 1) {
document.getElementById("previous_line_container")
.innerHTML = format_line(
dataset.lines,
current_line - 1
);
}
document.getElementById("current_line_container")
.innerHTML = format_line(
dataset.lines,
current_line
);
if (current_line < number_of_lines-1) {
document.getElementById("next_line_container")
.innerHTML = format_line(
dataset.lines,
current_line + 1
);
}
else {
document.getElementById("next_line_container")
.innerHTML = "";
}
// Mise a jour des noeuds...
svg.selectAll("circle")
.transition()
.duration(step_duration)
.attr("r", function(nodes) {
return nodes.radius[current_line]
})
.style("fill", function(nodes) {
switch (nodes.step[current_line]) {
case 'not_yet_activated': return color_not_yet_activated;
case 'previously_activated': return color_previously_activated;
case 'activated': return color_activated;
case 'active': return color_active;
}
})
.style("stroke", function(nodes) {
switch (nodes.step[current_line]) {
case 'not_yet_activated': return color_not_yet_activated;
case 'previously_activated': return color_previously_activated;
case 'activated': return color_activated;
case 'active': return color_live;
}
})
.style("stroke-width", node_stroke_width);
// Mise a jour des aretes...
svg.selectAll("line")
.transition()
.duration(step_duration)
// tout ce qui suit est remplaçable par la ligne de code
// suivante, mais on perd l'état initial:
// .classed(function(edges){return edges.step[current_line];})
.style("stroke", function(edges) {
switch (edges.step[current_line]) {
case 'not_yet_live': return color_not_yet_live;
case 'previously_live': return color_previously_live;
case 'live': return color_live;
}
})
.style("stroke-width", function(edges) {
if (edges.step[current_line] == 'never_live') {
return 0;
}
else {
return edges.width[current_line];
}
});
// Mise à jour des labels...
svg.selectAll("text")
.transition()
.duration(step_duration)
.attr("x", function(nodes) {
return nodes.x + (
nodes.radius[current_line] / 2
+ node_stroke_width
+ label_x_offset
);
})
.attr("y", function(nodes) {
return nodes.y - (
nodes.radius[current_line] / 2
+ node_stroke_width
+ label_y_offset
);
});
};
// Formatage des répliques...
function format_line(data_text, line_num) {
var output_text = "<br/><p><b>"
+ data_text[line_num]['personnage']
+ "</b>"
if (data_text[line_num]['didascalie'] != "") {
output_text += "<i>, "
+ data_text[line_num]['didascalie']
+ "</i>"
}
output_text += "</p>"
+ data_text[line_num]['texte']
.replace(/l>/g, "p>")
.replace(/<didascalie>/g, "<p><i>")
.replace(/<\/didascalie>/g, "</p></i>")
return output_text
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment