toolkit/javascript/nco/treemap.js
author Nicolas Sauret <nicolas.sauret@iri.centrepompidou.fr>
Fri, 18 Apr 2014 14:31:58 +0200
changeset 51 79833eaa394a
parent 47 c0b4a8b5a012
permissions -rw-r--r--
set up second level for navigation

/**
 * Creation of a treemap
 */

/**
 * Parameters is the object containing basics initialization parameters
 */
function Treemap(json, parametres) {
	this.parametres = {
		name: null,
		hauteur: 300,
		largeur: 500,
		selector: "#treemap",
		
		couleurs: null,
		relations: null,
		
		contenu: null,
		template: null,
	
		transitionDuration: 1000
	};
	
	this.etendre(parametres);
	
	/* -- Gestion des valeurs par défaut -- */
	if (null==this.parametres.name) {
		this.parametres.name = this.parametres.selector;
	}
	if (null==this.parametres.contenu) {
		if (null!=this.parametres.template) {
			this.template = jQuery(this.parametres.template.selector);
			this.template.remove();
			this.parametres.contenu = this.renderContenu;
			
			// Check for forgotten values
			this.setDefaultValue(this.parametres.template.maxTextSpace, 50);
			this.setDefaultValue(this.parametres.template.minFontSize, 1);
		}
		else {
			this.parametres.contenu = this.defaultContent;
		}
	}
	
	/* -- Initialisation d'attributs -- */
	var object = this;
	this.treemap = null;
	this.dataTreemap = null;
	this.articles = null;
	this.treemapHtml = null;

	this.dataJSON = json;	
	this.dataTreemap = this.getDataTreemap(json);
	this.articles = this.dataJSON.articles[0];
	
	this.duree = this.dataJSON.clusters[0].timeline.length;
	this.nbClusters = this.dataJSON.clusters.length;
	this.nbArticles = this.articles.length;
	
	/* -- Create the treemap -- */
	
	if (null==this.parametres.couleurs) {
		this.parametres.couleurs = new Couleurs(this.nbClusters);
	}
	
	this.treemap = d3.layout.treemap()
		.size([this.parametres.largeur, this.parametres.hauteur])
		.sticky(true)
		.value(function(d) { 
			return object.comptage(d); 
		});
		
	this.treemapHtml = d3.select("#treemap").append('div')
		.style('position', 'relative')
		.style('width', this.parametres.largeur + 'px')
		.style('height', this.parametres.hauteur + 'px')
		.style('background-color', '#32375F');
		
	this.treemapHtml.data(this.dataTreemap).selectAll("div")
		.data(this.treemap.nodes).enter().append("div")
			.attr('class', 'cell')
			.style('background-color', function(datum) {
				return d.children? null: object.parametres.couleurs.get(datum.id);
			})
			.call(this.sizeCell)
			.html(function(d) { 
				return d.children? null: object.getContent(d); 
			})
			.attr("datetime", function(d) {
				return d.children? 0: object.articles[d.idMax].date;
			});
	
	/* -- Register items in relations -- */
	
	if (null!=this.parametres.relations) {
		this.parametres.relations.add(this.parametres.name, this);
		
		this.treemapHtml.selectAll("div")
			.each(function(datum) {
				var item = d3.select(this);
				datum["relationName"] = object.parametres.name +'.'+ datum.name;
				object.parametres.relations.add(datum.relationName, item);
			});
	}
};

Treemap.prototype = {
	constructor: Treemap,
	
	etendre: function(parametres) {
		if (null==parametres || "object"!=typeof(parametres)) {
			return;
		}
		
		for (var cle in parametres) {
			this.parametres[cle] = parametres[cle];
		}
	},
	
	setDefaultValue: function(attribute, value) {
		if (undefined==attribute || null==attribute) {
			attribute = value;
		}
	},
	
	// calcule la taille d'un cluster (somme de tout les articles)
	// et l'élément de poids maximal
	comptage : function(datumCell, start, end) {
		var compteursize = 0, idMax = 0, poidsMax = 0;
		var timeline = datumCell.timeline;
		
		var tStart = (start==null || start<0)? 0: start;
		var tEnd = (end==null || end>timeline.length)? 
				timeline.length: end;
		var index, endIndex, articleId;
		for (var t=tStart; t<tEnd; t++) { //boucle sur les t de la timeline
			compteursize+= timeline[t].length;
			for (index = 0, endIndex = timeline[t].length; 
				index < endIndex; ++index) {
				articleId = timeline[t][index];
				if (this.articles[articleId].poids > poidsMax) {
					poidsMax = this.articles[articleId].poids;
					idMax = articleId;
				}
			}
		}
		
		datumCell.poidsMax = poidsMax;
		datumCell.idMax = idMax;
		return compteursize;
	},
	
	getContent: function(datumCell, start, end) {
		if (null!=this.parametres.contenu) {
			return this.parametres.contenu.call(
				this, datumCell, this.articles[datumCell.idMax]);
		}
		else {
			return this.articles[datumCell.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 cluster = { id: clusters[i].numero, name : "cluster." + clusters[i].numero, timeline : clusters[i].timeline };
			data.children.push(cluster);
		}
		
		return [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"; });
	},
	
	focus: function(map) {
		map.classed("selected", true)
			.transition()
			.duration(150)
			.delay(50)
			.style("opacity", 0.5);
	},
	
	blur: function(map) {
		map.classed("selected", false)
			.transition()
			.duration(100)
			.delay(50)
			.style("opacity", 1);
	},
	
	transition: function(start, end) {
		var object = this;
		// To avoid conflicts while elements are hovered, lock the actions
		
		this.treemapHtml.selectAll("div")
			.each(function(datum) {
				object.parametres.relations.get(datum.relationName)
						.lock();
			})
			.data(this.treemap.value(function(d) {
				return object.comptage(d, start, end); 
			}))
			.attr("datetime", function(d) {
				return d.children? 0: object.articles[d.idMax].date;
			})
			.html(function(d) { return d.children? null: object.getContent(d, start, end); })
			.transition()
				.duration(this.parametres.transitionDuration)
				.call(this.sizeCell)
				// Free the lock
				.each("end", function(datum) {
					object.parametres.relations.get(datum.relationName)
						.unlock();
				});
	},
	
	defaultContent: 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;
	},
	
	renderContenu: function(cellule, article) {
		var image = article.image;
		
		var wi = image.largeur,
			hi = image.hauteur,
			wc = cellule.dx - 1,
			hc = cellule.dy - 1,
			r = Math.min(hi/hc, wi/wc),
			w = wi/r,
			h = hi/r,
			posx = -(w-wc)/2,
			posy = -(h-hc)/2;
		
		var data = {
			'urlImage': image.url,
			'widhtImage': w +"px",
			'heightImage': h +"px",
			'cssOffsetImage': "margin-top: "+ posy +"px; margin-left: "+posx +"px;",
			'titreArticle': article.titre,
			'dateArticle': article.date,
			'widthArticle': wc +"px",
			'heightArticle': hc +"px",
			'cssWidthArticle': "width:" +wc +"px;",
			'cssHeightArticle': "height:" +hc +"px;",
			'cssDimensionsArticle': "width:" +wc +"px;height:" +hc +"px;",
			'dateArticle': article.date
		};
		
		return this.template
			.render(data, this.parametres.template.directives).html();
	},
	
	on: function(event, action) {
		switch(event) {
		default:
			this.treemapHtml.on(event, action);
		}
	}
};