toolkit/exemples/couple/javascript/streamMap.js
changeset 47 c0b4a8b5a012
equal deleted inserted replaced
46:efd9c589177a 47:c0b4a8b5a012
       
     1 /**
       
     2  * Creation of an object for streamgraph and treemap connected
       
     3  */
       
     4 
       
     5 /**
       
     6  * Parameters is the object containing basics initialization parameters
       
     7  */
       
     8 function StreamMap(json, parametres) {
       
     9 	this.parametres = {
       
    10 		hauteurStream: 100,
       
    11 		largeurStream: 900,
       
    12 		selectorStream: "#streamgraph",
       
    13 		
       
    14 		hauteurTree: 900,
       
    15 		largeurTree: 900,
       
    16 		selectorTree: "#treemap",
       
    17 		
       
    18 		duree: 50,
       
    19 		nbClusters: 10,
       
    20 		nbArticles: 1000,
       
    21 		
       
    22 		couleurs: null,
       
    23 		
       
    24 		mx: 0,
       
    25 		my: 0,
       
    26 		
       
    27 		onFocus: null,
       
    28 		onBlur: null,
       
    29 		contenu: function(cellule, article) {
       
    30 			
       
    31 			var html;
       
    32 			var img = article.image;
       
    33 			var wi, hi, wc, hc, w, h, r;
       
    34 			
       
    35 			wi = img.largeur;
       
    36 			hi = img.hauteur;
       
    37 			wc = cellule.dx - 1;
       
    38 			hc = cellule.dy - 1;
       
    39 			r = Math.min(hi/hc, wi/wc);
       
    40 			w = wi/r;
       
    41 			h = hi/r;
       
    42 			
       
    43 			var posx, posy;
       
    44 			posx = -(w-wc)/2;
       
    45 			posy = -(h-hc)/2;
       
    46 			
       
    47 			html = "<img class = 'img' src = \"" + img.url + "\" style=\" width:"+ w + "px; height:" +h + "px; margin-top:" + posy + "px; margin-left:" + posx + "px;\" />";
       
    48 						
       
    49 			html += "<p class = 'title' style = \" width:" + wc + "px;\" >" + article.titre + "</p>";
       
    50 			return html;
       
    51 		}
       
    52 	};
       
    53 	this.etendre(parametres);
       
    54 	
       
    55 	var object = this;
       
    56 	this.treemap = null;
       
    57 	this.streamgraph = null;
       
    58 	this.dataTreemap = null;
       
    59 	this.articles = null;
       
    60 	this.dataStreamgraph = null;
       
    61 	this.data = null;
       
    62 	this.relations = [];
       
    63 	this.treemapHtml = null;
       
    64 	this.streamgraphHtml = null;
       
    65 	
       
    66 	this.area = d3.svg.area()
       
    67 		.x(function(d) { 
       
    68 			return d.x * object.parametres.largeurStream / object.parametres.mx; 
       
    69 		})
       
    70 		.y0(function(d) { 
       
    71 			return object.parametres.hauteurStream 
       
    72 				- d.y0 * object.parametres.hauteurStream / object.parametres.my; 
       
    73 		})
       
    74 		.y1(function(d) { 
       
    75 			return object.parametres.hauteurStream
       
    76 				- (d.y + d.y0) * object.parametres.hauteurStream 
       
    77 					/ object.parametres.my;
       
    78 		});
       
    79 	
       
    80 	// Consctructeur des deux graphes
       
    81 
       
    82 		object.dataJSON = json;
       
    83 		
       
    84 		/* -- Generate arrays for each graph -- */
       
    85 		
       
    86 		object.dataTreemap = object.getDataTreemap(json);
       
    87 		object.dataStreamgraph = object.getDataStreamgraph(json);
       
    88 		object.articles = object.dataJSON.articles[0];
       
    89 //		console.log(object.dataTreemap);
       
    90 //		console.log(object.dataStreamgraph);
       
    91 		
       
    92 		/* -- Create the streamgraph -- */
       
    93 		
       
    94 		if (null==object.parametres.couleurs) {
       
    95 			object.parametres.couleurs = d3.scale.category20c();
       
    96 		}
       
    97 		
       
    98 		object.parametres.mx = object.dataStreamgraph[0].length - 1;
       
    99 		object.parametres.my = d3.max(object.dataStreamgraph, function(d) {
       
   100 			  return d3.max(d, function(d) {
       
   101 			    return d.y0 + d.y;
       
   102 			  });
       
   103 			});
       
   104 		
       
   105 		object.streamgraphHtml = d3.select(object.parametres.selectorStream)
       
   106 			.style("width", object.parametres.largeurStream + "px")
       
   107 			.style("height", object.parametres.hauteurStream + "px")
       
   108 			.append("svg:svg");
       
   109 		
       
   110 		object.streamgraphHtml.selectAll("path")
       
   111 			.data(object.dataStreamgraph).enter().append("svg:path")
       
   112 				.style("fill", function() { 
       
   113 					return object.parametres.couleurs(Math.random()); 
       
   114 				})
       
   115 				.attr("d", object.area)
       
   116 				.each(function(datum, index) { 
       
   117 					var item = d3.select(this).attr("id", index);
       
   118 					object.relations.push({
       
   119 						name: "cluster_" + index, 
       
   120 						stream: item, 
       
   121 						map: null, 
       
   122 						color: item.style("fill")
       
   123 					});
       
   124 				});
       
   125 		
       
   126 		/* -- Create the treemap -- */
       
   127 		
       
   128 		object.treemap = d3.layout.treemap()
       
   129 			.size([object.parametres.largeurTree, object.parametres.hauteurTree])
       
   130 			.sticky(true)
       
   131 			.value(function(d) { 
       
   132 				return object.comptage(d.timeline); 
       
   133 			});
       
   134 			
       
   135 		object.treemapHtml = d3.select("#treemap").append('div')
       
   136 			.style('position', 'relative')
       
   137 			.style('width', object.parametres.largeurTree + 'px')
       
   138 			.style('height', object.parametres.hauteurTree + 'px')
       
   139 			.style('background-color', '#32375F');
       
   140 			
       
   141 		object.treemapHtml.data(object.dataTreemap).selectAll("div")
       
   142 			.data(object.treemap.nodes).enter().append("div")
       
   143 				.attr('class', 'cell')
       
   144 				.call(object.sizeCell)
       
   145 				.html(function(d) { 
       
   146 					return d.children? null: object.getContent(d); 
       
   147 				})
       
   148 	/*			.style('background', function(d) {
       
   149 					return !d.children? null: object.parametres.couleurs(d.name);
       
   150 				})*/
       
   151 				.each(function(datum) {
       
   152 					var item = d3.select(this);
       
   153 					for (var i=0; i<object.parametres.nbClusters; i++) {
       
   154 						if (datum.name==object.relations[i].name) {
       
   155 							object.relations[i].map = item;
       
   156 		//					item.style("background", object.relations[i].color);
       
   157 						}
       
   158 					}
       
   159 				});
       
   160 		
       
   161 		/* -- Bind the maps with streams -- */
       
   162 		for (var i=0, _end=object.parametres.nbClusters; i<_end; i++) {
       
   163 			object.bindMapToStream(
       
   164 					object.relations[i].stream, object.relations[i].map);
       
   165 			object.bindStreamToMap(
       
   166 					object.relations[i].map, object.relations[i].stream);
       
   167 		}
       
   168 };
       
   169 
       
   170 StreamMap.prototype = {
       
   171 	etendre: function(parametres) {
       
   172 		if (null==parametres || "object"!=typeof(parametres)) {
       
   173 			return;
       
   174 		}
       
   175 		
       
   176 		for (var cle in parametres) {
       
   177 			this.parametres[cle] = parametres[cle];
       
   178 		}
       
   179 	},
       
   180 	
       
   181 	// calcule la taille d'un cluster (somme de tout les articles)
       
   182 	comptage : function(timeline, start, end) {
       
   183 		var compteursize = 0, idMax = 0, poidsMax = 0;
       
   184 		
       
   185 		var tStart = (start==null || start<0)? 0: start;
       
   186 		var tEnd = (end==null || end>timeline.length)? 
       
   187 				timeline.length: end;
       
   188 		var index, endIndex, articleId;
       
   189 		for (t=tStart; t<tEnd; t++) { //boucle sur les t de la timeline
       
   190 			compteursize+= timeline[t].length;
       
   191 		}
       
   192 		
       
   193 		return compteursize;
       
   194 	},
       
   195 	
       
   196 	getContent: function(datumCell, start, end) {
       
   197 		var compteursize = 0, idMax = 0, poidsMax = 0;
       
   198 		var articles = this.articles;
       
   199 		
       
   200 		var tStart = (start==null || start<0)? 0: start;
       
   201 		var tEnd = (end==null || end>datumCell.timeline.length)? 
       
   202 				datumCell.timeline.length: end;
       
   203 		
       
   204 		var index, endIndex, articleId;
       
   205 		for (t=tStart; t<tEnd; t++) { //boucle sur les t de la timeline
       
   206 			for (index = 0, endIndex = datumCell.timeline[t].length; index<endIndex; ++index) {
       
   207 				articleId = datumCell.timeline[t][index];
       
   208 				if (articles[articleId].poids > poidsMax) {
       
   209 					poidsMax = articles[articleId].poids;
       
   210 					idMax = articleId;
       
   211 				}
       
   212 			}
       
   213 		}
       
   214 		
       
   215 		if (null!=this.parametres.contenu) {
       
   216 			return this.parametres.contenu(datumCell, articles[idMax]);
       
   217 		}
       
   218 		else {
       
   219 			return articles[idMax].titre;
       
   220 		}
       
   221 	},
       
   222 
       
   223 	getDataTreemap : function(json, start, end) {
       
   224 		var data = { name : "data", children : []};
       
   225 		var clusters = json.clusters;
       
   226 		
       
   227 		for (var i=0, _end=clusters.length; i<_end; i++) { // parcours les differents clusters
       
   228 			//var size = comptage(clusters[i], start, end);
       
   229 			var cluster = { name : "cluster_" + clusters[i].numero, 
       
   230 					timeline : clusters[i].timeline };
       
   231 			data.children.push(cluster);
       
   232 		}
       
   233 		
       
   234 		return [data];
       
   235 	},
       
   236 
       
   237 	getDataStreamgraph : function(json) {
       
   238 		var data = new Array(),
       
   239 			clusters = json.clusters,
       
   240 			duree = clusters[0].timeline.length;
       
   241 		
       
   242 		var dataCluster, timeline;
       
   243 		for (var i=0, end=clusters.length; i<end; i++) {
       
   244 			timeline = clusters[i].timeline;
       
   245 			dataCluster = [];
       
   246 			
       
   247 			for (var t=0; t<duree; t++) {
       
   248 				dataCluster.push({ x: t, y: timeline[t].length });
       
   249 			}
       
   250 			data.push(dataCluster);
       
   251 		}
       
   252 		
       
   253 		// Smooth the data
       
   254 		
       
   255 		return d3.layout.stack().offset("wiggle")(data);
       
   256 	},
       
   257 
       
   258 	sizeCell: function() {
       
   259 		this.style("left", function(d) { return d.x + "px"; })
       
   260 			.style("top", function(d) { return d.y + "px"; })
       
   261 			.style("width", function(d) { return d.dx - 1 + "px"; })
       
   262 			.style("height", function(d) { return d.dy - 1 + "px"; });
       
   263 	},
       
   264 
       
   265 	bindMapToStream: function(stream, map) {
       
   266 		var object = this;
       
   267 		if (null!=this.parametres.onFocus && null!=this.parametres.onBlur) {
       
   268 			stream.on("mouseover", function() {
       
   269 				object.parametres.onFocus(stream, map);
       
   270 			});
       
   271 			stream.on("mouseout", function() {
       
   272 				object.parametres.onBlur(stream, map);
       
   273 			});
       
   274 		}
       
   275 	},
       
   276 
       
   277 	bindStreamToMap: function(map, stream) {
       
   278 		var object = this;
       
   279 		if (null!=this.parametres.onFocus && null!=this.parametres.onBlur) {
       
   280 			map.on("mouseover", function() {
       
   281 				object.parametres.onFocus(stream, map);
       
   282 			});
       
   283 			map.on("mouseout", function() {
       
   284 				object.parametres.onBlur(stream, map);
       
   285 			});
       
   286 		}
       
   287 	}
       
   288 };
       
   289 
       
   290 /* -- Draggable window -- */
       
   291 
       
   292 var selectBars = {
       
   293 	focus: function(stream, map) {
       
   294 		stream.style("stroke", "black")
       
   295 				.transition()
       
   296 				.duration(300)
       
   297 				.delay(50)
       
   298 				.style("opacity", 0.5);
       
   299 		map.classed("selected", true)
       
   300 			.transition()
       
   301 			.duration(150)
       
   302 			.delay(50)
       
   303 			.style("opacity", 0.5);
       
   304 	},
       
   305 	
       
   306 	unfocus: function(stream, map) {
       
   307 		stream.style("stroke", "none")
       
   308 				.transition()
       
   309 				.duration(300)
       
   310 				.delay(75)
       
   311 				.style("opacity", 1);
       
   312 		map.classed("selected", false)
       
   313 			.transition()
       
   314 			.duration(100)
       
   315 			.delay(50)
       
   316 			.style("opacity", 1);
       
   317 	},
       
   318 	
       
   319 	calculerIndx: function() { //recalcul des valeurs
       
   320 		indx[0] = Math.round(parseInt($('#gauche').css("left"))
       
   321 				/(streamMap.parametres.largeurStream/streamMap.parametres.duree));
       
   322 		indx[1] = Math.round((parseInt($('#droite').css('left')) 
       
   323 				+ parseInt($('#droite').css('width')))
       
   324 					/(streamMap.parametres.largeurStream/streamMap.parametres.duree));
       
   325 	},
       
   326 	
       
   327 	followcache: function() { //déplacement des caches avec le drag'n'drop
       
   328 		d3.select('.cache.gauche').style("width", function() {
       
   329 			return $('#gauche').css("left")
       
   330 			});
       
   331 		
       
   332 		d3.select('.cache.droite').style("left", function() {
       
   333 			var a;
       
   334 			a = parseInt($('#droite').css("left")) + 10;
       
   335 			a = a + "px";
       
   336 			return a;
       
   337 			});
       
   338 		
       
   339 		d3.select('.cache.droite').style("width", function() {
       
   340 			var a;
       
   341 			a = 900 - parseInt($('.cache.droite').css('left'));
       
   342 			a = a + "px";
       
   343 			return a;
       
   344 			});
       
   345 	},
       
   346 	
       
   347 	
       
   348 	contain: function(a) {
       
   349 		var ar = [];
       
   350 		var lim;
       
   351 			//limite pour gauche
       
   352 			var lim = parseInt($('.selecteur.droite').css("left"));
       
   353 			ar = [0, 0, lim, 300];
       
   354 			$('.selecteur.gauche').draggable("option", "containment", ar);
       
   355 			//limite pour droite
       
   356 			lim = parseInt($('.selecteur.gauche').css("left"))+20;
       
   357 			ar = [lim, 0, 900, 300];
       
   358 			$('.selecteur.droite').draggable("option", "containment", ar);
       
   359 	},
       
   360 	
       
   361 	transitionTree: function(start, end) {
       
   362 		streamMap.treemapHtml.selectAll("div")
       
   363 			.data(streamMap.treemap.value(function(d) {
       
   364 				return streamMap.comptage(d.timeline, start, end); 
       
   365 			}))
       
   366 			.html(function(d) { return d.children? null: streamMap.getContent(d, start, end); })
       
   367 			.transition()
       
   368 				.duration(1500)
       
   369 				.call(streamMap.sizeCell);
       
   370 	}
       
   371 };
       
   372 
       
   373 var parametres = {
       
   374 	hauteurStream: 300,
       
   375 	hauteurTree: 300,
       
   376 	onFocus: selectBars.focus,
       
   377 	onBlur: selectBars.unfocus
       
   378 };
       
   379 var streamMap = new StreamMap(json, parametres);
       
   380 
       
   381 
       
   382 var indx = [0, streamMap.parametres.duree]; 
       
   383 /*l'objet indx contient les valeurs d'indexs de la zone correspondant au tree map. 
       
   384 Il se réactulise lorsqu'on lâche les barres.
       
   385 */
       
   386 //méthode draggable de jquery pour drag'n'drop
       
   387 $('.selecteur').draggable({
       
   388 	axis : 'x',
       
   389 	grid: [streamMap.parametres.largeurStream/streamMap.parametres.duree, 1],
       
   390 	containment: [60,0,
       
   391 	    streamMap.parametres.largeurStream,streamMap.parametres.hauteurStream],
       
   392 	drag: selectBars.followcache,
       
   393 	stop: function(e, ui){
       
   394 		selectBars.calculerIndx();
       
   395 		selectBars.followcache();	
       
   396 		selectBars.contain(ui);
       
   397 		selectBars.transitionTree(indx[0], indx[1]);
       
   398 	}
       
   399 });
       
   400 
       
   401