toolkit/exemples/couple/javascript/streamMap.js
changeset 47 c0b4a8b5a012
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolkit/exemples/couple/javascript/streamMap.js	Thu Apr 10 14:20:23 2014 +0200
@@ -0,0 +1,401 @@
+/**
+ * Creation of an object for streamgraph and treemap connected
+ */
+
+/**
+ * Parameters is the object containing basics initialization parameters
+ */
+function StreamMap(json, parametres) {
+	this.parametres = {
+		hauteurStream: 100,
+		largeurStream: 900,
+		selectorStream: "#streamgraph",
+		
+		hauteurTree: 900,
+		largeurTree: 900,
+		selectorTree: "#treemap",
+		
+		duree: 50,
+		nbClusters: 10,
+		nbArticles: 1000,
+		
+		couleurs: null,
+		
+		mx: 0,
+		my: 0,
+		
+		onFocus: null,
+		onBlur: null,
+		contenu: function(cellule, article) {
+			
+			var html;
+			var img = article.image;
+			var wi, hi, wc, hc, w, h, r;
+			
+			wi = img.largeur;
+			hi = img.hauteur;
+			wc = cellule.dx - 1;
+			hc = cellule.dy - 1;
+			r = Math.min(hi/hc, wi/wc);
+			w = wi/r;
+			h = hi/r;
+			
+			var posx, posy;
+			posx = -(w-wc)/2;
+			posy = -(h-hc)/2;
+			
+			html = "<img class = 'img' src = \"" + img.url + "\" style=\" width:"+ w + "px; height:" +h + "px; margin-top:" + posy + "px; margin-left:" + posx + "px;\" />";
+						
+			html += "<p class = 'title' style = \" width:" + wc + "px;\" >" + article.titre + "</p>";
+			return html;
+		}
+	};
+	this.etendre(parametres);
+	
+	var object = this;
+	this.treemap = null;
+	this.streamgraph = null;
+	this.dataTreemap = null;
+	this.articles = null;
+	this.dataStreamgraph = null;
+	this.data = null;
+	this.relations = [];
+	this.treemapHtml = null;
+	this.streamgraphHtml = null;
+	
+	this.area = d3.svg.area()
+		.x(function(d) { 
+			return d.x * object.parametres.largeurStream / object.parametres.mx; 
+		})
+		.y0(function(d) { 
+			return object.parametres.hauteurStream 
+				- d.y0 * object.parametres.hauteurStream / object.parametres.my; 
+		})
+		.y1(function(d) { 
+			return object.parametres.hauteurStream
+				- (d.y + d.y0) * object.parametres.hauteurStream 
+					/ object.parametres.my;
+		});
+	
+	// Consctructeur des deux graphes
+
+		object.dataJSON = json;
+		
+		/* -- Generate arrays for each graph -- */
+		
+		object.dataTreemap = object.getDataTreemap(json);
+		object.dataStreamgraph = object.getDataStreamgraph(json);
+		object.articles = object.dataJSON.articles[0];
+//		console.log(object.dataTreemap);
+//		console.log(object.dataStreamgraph);
+		
+		/* -- Create the streamgraph -- */
+		
+		if (null==object.parametres.couleurs) {
+			object.parametres.couleurs = d3.scale.category20c();
+		}
+		
+		object.parametres.mx = object.dataStreamgraph[0].length - 1;
+		object.parametres.my = d3.max(object.dataStreamgraph, function(d) {
+			  return d3.max(d, function(d) {
+			    return d.y0 + d.y;
+			  });
+			});
+		
+		object.streamgraphHtml = d3.select(object.parametres.selectorStream)
+			.style("width", object.parametres.largeurStream + "px")
+			.style("height", object.parametres.hauteurStream + "px")
+			.append("svg:svg");
+		
+		object.streamgraphHtml.selectAll("path")
+			.data(object.dataStreamgraph).enter().append("svg:path")
+				.style("fill", function() { 
+					return object.parametres.couleurs(Math.random()); 
+				})
+				.attr("d", object.area)
+				.each(function(datum, index) { 
+					var item = d3.select(this).attr("id", index);
+					object.relations.push({
+						name: "cluster_" + index, 
+						stream: item, 
+						map: null, 
+						color: item.style("fill")
+					});
+				});
+		
+		/* -- Create the treemap -- */
+		
+		object.treemap = d3.layout.treemap()
+			.size([object.parametres.largeurTree, object.parametres.hauteurTree])
+			.sticky(true)
+			.value(function(d) { 
+				return object.comptage(d.timeline); 
+			});
+			
+		object.treemapHtml = d3.select("#treemap").append('div')
+			.style('position', 'relative')
+			.style('width', object.parametres.largeurTree + 'px')
+			.style('height', object.parametres.hauteurTree + 'px')
+			.style('background-color', '#32375F');
+			
+		object.treemapHtml.data(object.dataTreemap).selectAll("div")
+			.data(object.treemap.nodes).enter().append("div")
+				.attr('class', 'cell')
+				.call(object.sizeCell)
+				.html(function(d) { 
+					return d.children? null: object.getContent(d); 
+				})
+	/*			.style('background', function(d) {
+					return !d.children? null: object.parametres.couleurs(d.name);
+				})*/
+				.each(function(datum) {
+					var item = d3.select(this);
+					for (var i=0; i<object.parametres.nbClusters; i++) {
+						if (datum.name==object.relations[i].name) {
+							object.relations[i].map = item;
+		//					item.style("background", object.relations[i].color);
+						}
+					}
+				});
+		
+		/* -- Bind the maps with streams -- */
+		for (var i=0, _end=object.parametres.nbClusters; i<_end; i++) {
+			object.bindMapToStream(
+					object.relations[i].stream, object.relations[i].map);
+			object.bindStreamToMap(
+					object.relations[i].map, object.relations[i].stream);
+		}
+};
+
+StreamMap.prototype = {
+	etendre: function(parametres) {
+		if (null==parametres || "object"!=typeof(parametres)) {
+			return;
+		}
+		
+		for (var cle in parametres) {
+			this.parametres[cle] = parametres[cle];
+		}
+	},
+	
+	// calcule la taille d'un cluster (somme de tout les articles)
+	comptage : function(timeline, start, end) {
+		var compteursize = 0, idMax = 0, poidsMax = 0;
+		
+		var tStart = (start==null || start<0)? 0: start;
+		var tEnd = (end==null || end>timeline.length)? 
+				timeline.length: end;
+		var index, endIndex, articleId;
+		for (t=tStart; t<tEnd; t++) { //boucle sur les t de la timeline
+			compteursize+= timeline[t].length;
+		}
+		
+		return compteursize;
+	},
+	
+	getContent: function(datumCell, start, end) {
+		var compteursize = 0, idMax = 0, poidsMax = 0;
+		var articles = this.articles;
+		
+		var tStart = (start==null || start<0)? 0: start;
+		var tEnd = (end==null || end>datumCell.timeline.length)? 
+				datumCell.timeline.length: end;
+		
+		var index, endIndex, articleId;
+		for (t=tStart; t<tEnd; t++) { //boucle sur les t de la timeline
+			for (index = 0, endIndex = datumCell.timeline[t].length; index<endIndex; ++index) {
+				articleId = datumCell.timeline[t][index];
+				if (articles[articleId].poids > poidsMax) {
+					poidsMax = articles[articleId].poids;
+					idMax = articleId;
+				}
+			}
+		}
+		
+		if (null!=this.parametres.contenu) {
+			return this.parametres.contenu(datumCell, articles[idMax]);
+		}
+		else {
+			return articles[idMax].titre;
+		}
+	},
+
+	getDataTreemap : function(json, start, end) {
+		var data = { name : "data", children : []};
+		var clusters = json.clusters;
+		
+		for (var i=0, _end=clusters.length; i<_end; i++) { // parcours les differents clusters
+			//var size = comptage(clusters[i], start, end);
+			var cluster = { name : "cluster_" + clusters[i].numero, 
+					timeline : clusters[i].timeline };
+			data.children.push(cluster);
+		}
+		
+		return [data];
+	},
+
+	getDataStreamgraph : function(json) {
+		var data = new Array(),
+			clusters = json.clusters,
+			duree = clusters[0].timeline.length;
+		
+		var dataCluster, timeline;
+		for (var i=0, end=clusters.length; i<end; i++) {
+			timeline = clusters[i].timeline;
+			dataCluster = [];
+			
+			for (var t=0; t<duree; t++) {
+				dataCluster.push({ x: t, y: timeline[t].length });
+			}
+			data.push(dataCluster);
+		}
+		
+		// Smooth the data
+		
+		return d3.layout.stack().offset("wiggle")(data);
+	},
+
+	sizeCell: function() {
+		this.style("left", function(d) { return d.x + "px"; })
+			.style("top", function(d) { return d.y + "px"; })
+			.style("width", function(d) { return d.dx - 1 + "px"; })
+			.style("height", function(d) { return d.dy - 1 + "px"; });
+	},
+
+	bindMapToStream: function(stream, map) {
+		var object = this;
+		if (null!=this.parametres.onFocus && null!=this.parametres.onBlur) {
+			stream.on("mouseover", function() {
+				object.parametres.onFocus(stream, map);
+			});
+			stream.on("mouseout", function() {
+				object.parametres.onBlur(stream, map);
+			});
+		}
+	},
+
+	bindStreamToMap: function(map, stream) {
+		var object = this;
+		if (null!=this.parametres.onFocus && null!=this.parametres.onBlur) {
+			map.on("mouseover", function() {
+				object.parametres.onFocus(stream, map);
+			});
+			map.on("mouseout", function() {
+				object.parametres.onBlur(stream, map);
+			});
+		}
+	}
+};
+
+/* -- Draggable window -- */
+
+var selectBars = {
+	focus: function(stream, map) {
+		stream.style("stroke", "black")
+				.transition()
+				.duration(300)
+				.delay(50)
+				.style("opacity", 0.5);
+		map.classed("selected", true)
+			.transition()
+			.duration(150)
+			.delay(50)
+			.style("opacity", 0.5);
+	},
+	
+	unfocus: function(stream, map) {
+		stream.style("stroke", "none")
+				.transition()
+				.duration(300)
+				.delay(75)
+				.style("opacity", 1);
+		map.classed("selected", false)
+			.transition()
+			.duration(100)
+			.delay(50)
+			.style("opacity", 1);
+	},
+	
+	calculerIndx: function() { //recalcul des valeurs
+		indx[0] = Math.round(parseInt($('#gauche').css("left"))
+				/(streamMap.parametres.largeurStream/streamMap.parametres.duree));
+		indx[1] = Math.round((parseInt($('#droite').css('left')) 
+				+ parseInt($('#droite').css('width')))
+					/(streamMap.parametres.largeurStream/streamMap.parametres.duree));
+	},
+	
+	followcache: function() { //déplacement des caches avec le drag'n'drop
+		d3.select('.cache.gauche').style("width", function() {
+			return $('#gauche').css("left")
+			});
+		
+		d3.select('.cache.droite').style("left", function() {
+			var a;
+			a = parseInt($('#droite').css("left")) + 10;
+			a = a + "px";
+			return a;
+			});
+		
+		d3.select('.cache.droite').style("width", function() {
+			var a;
+			a = 900 - parseInt($('.cache.droite').css('left'));
+			a = a + "px";
+			return a;
+			});
+	},
+	
+	
+	contain: function(a) {
+		var ar = [];
+		var lim;
+			//limite pour gauche
+			var lim = parseInt($('.selecteur.droite').css("left"));
+			ar = [0, 0, lim, 300];
+			$('.selecteur.gauche').draggable("option", "containment", ar);
+			//limite pour droite
+			lim = parseInt($('.selecteur.gauche').css("left"))+20;
+			ar = [lim, 0, 900, 300];
+			$('.selecteur.droite').draggable("option", "containment", ar);
+	},
+	
+	transitionTree: function(start, end) {
+		streamMap.treemapHtml.selectAll("div")
+			.data(streamMap.treemap.value(function(d) {
+				return streamMap.comptage(d.timeline, start, end); 
+			}))
+			.html(function(d) { return d.children? null: streamMap.getContent(d, start, end); })
+			.transition()
+				.duration(1500)
+				.call(streamMap.sizeCell);
+	}
+};
+
+var parametres = {
+	hauteurStream: 300,
+	hauteurTree: 300,
+	onFocus: selectBars.focus,
+	onBlur: selectBars.unfocus
+};
+var streamMap = new StreamMap(json, parametres);
+
+
+var indx = [0, streamMap.parametres.duree]; 
+/*l'objet indx contient les valeurs d'indexs de la zone correspondant au tree map. 
+Il se réactulise lorsqu'on lâche les barres.
+*/
+//méthode draggable de jquery pour drag'n'drop
+$('.selecteur').draggable({
+	axis : 'x',
+	grid: [streamMap.parametres.largeurStream/streamMap.parametres.duree, 1],
+	containment: [60,0,
+	    streamMap.parametres.largeurStream,streamMap.parametres.hauteurStream],
+	drag: selectBars.followcache,
+	stop: function(e, ui){
+		selectBars.calculerIndx();
+		selectBars.followcache();	
+		selectBars.contain(ui);
+		selectBars.transitionTree(indx[0], indx[1]);
+	}
+});
+
+