|
1 /** |
|
2 * Creation of a treemap |
|
3 */ |
|
4 |
|
5 /** |
|
6 * Parameters is the object containing basics initialization parameters |
|
7 */ |
|
8 function Treemap(json, parametres) { |
|
9 this.parametres = { |
|
10 name: null, |
|
11 hauteur: 300, |
|
12 largeur: 500, |
|
13 selector: "#treemap", |
|
14 |
|
15 couleurs: null, |
|
16 relations: null, |
|
17 |
|
18 contenu: null, |
|
19 template: null, |
|
20 |
|
21 transitionDuration: 1000 |
|
22 }; |
|
23 |
|
24 this.etendre(parametres); |
|
25 |
|
26 /* -- Gestion des valeurs par défaut -- */ |
|
27 if (null==this.parametres.name) { |
|
28 this.parametres.name = this.parametres.selector; |
|
29 } |
|
30 if (null==this.parametres.contenu) { |
|
31 if (null!=this.parametres.template) { |
|
32 this.template = jQuery(this.parametres.template.selector); |
|
33 this.template.remove(); |
|
34 this.parametres.contenu = this.renderContenu; |
|
35 |
|
36 // Check for forgotten values |
|
37 this.setDefaultValue(this.parametres.template.maxTextSpace, 50); |
|
38 this.setDefaultValue(this.parametres.template.minFontSize, 1); |
|
39 } |
|
40 else { |
|
41 this.parametres.contenu = this.defaultContent; |
|
42 } |
|
43 } |
|
44 |
|
45 /* -- Initialisation d'attributs -- */ |
|
46 var object = this; |
|
47 this.treemap = null; |
|
48 this.dataTreemap = null; |
|
49 this.articles = null; |
|
50 this.treemapHtml = null; |
|
51 |
|
52 this.dataJSON = json; |
|
53 this.dataTreemap = this.getDataTreemap(json); |
|
54 this.articles = this.dataJSON.articles[0]; |
|
55 |
|
56 this.duree = this.dataJSON.clusters[0].timeline.length; |
|
57 this.nbClusters = this.dataJSON.clusters.length; |
|
58 this.nbArticles = this.articles.length; |
|
59 |
|
60 /* -- Create the treemap -- */ |
|
61 |
|
62 if (null==this.parametres.couleurs) { |
|
63 this.parametres.couleurs = new Couleurs(this.nbClusters); |
|
64 } |
|
65 |
|
66 this.treemap = d3.layout.treemap() |
|
67 .size([this.parametres.largeur, this.parametres.hauteur]) |
|
68 .sticky(true) |
|
69 .value(function(d) { |
|
70 return object.comptage(d); |
|
71 }); |
|
72 |
|
73 this.treemapHtml = d3.select("#treemap").append('div') |
|
74 .style('position', 'relative') |
|
75 .style('width', this.parametres.largeur + 'px') |
|
76 .style('height', this.parametres.hauteur + 'px') |
|
77 .style('background-color', '#32375F'); |
|
78 |
|
79 this.treemapHtml.data(this.dataTreemap).selectAll("div") |
|
80 .data(this.treemap.nodes).enter().append("div") |
|
81 .attr('class', 'cell') |
|
82 .style('background-color', function(datum) { |
|
83 return d.children? null: object.parametres.couleurs.get(datum.id); |
|
84 }) |
|
85 .call(this.sizeCell) |
|
86 .html(function(d) { |
|
87 return d.children? null: object.getContent(d); |
|
88 }) |
|
89 .attr("datetime", function(d) { |
|
90 return d.children? 0: object.articles[d.idMax].date; |
|
91 }); |
|
92 |
|
93 /* -- Register items in relations -- */ |
|
94 |
|
95 if (null!=this.parametres.relations) { |
|
96 this.parametres.relations.add(this.parametres.name, this); |
|
97 |
|
98 this.treemapHtml.selectAll("div") |
|
99 .each(function(datum) { |
|
100 var item = d3.select(this); |
|
101 datum["relationName"] = object.parametres.name +'.'+ datum.name; |
|
102 object.parametres.relations.add(datum.relationName, item); |
|
103 }); |
|
104 } |
|
105 }; |
|
106 |
|
107 Treemap.prototype = { |
|
108 constructor: Treemap, |
|
109 |
|
110 etendre: function(parametres) { |
|
111 if (null==parametres || "object"!=typeof(parametres)) { |
|
112 return; |
|
113 } |
|
114 |
|
115 for (var cle in parametres) { |
|
116 this.parametres[cle] = parametres[cle]; |
|
117 } |
|
118 }, |
|
119 |
|
120 setDefaultValue: function(attribute, value) { |
|
121 if (undefined==attribute || null==attribute) { |
|
122 attribute = value; |
|
123 } |
|
124 }, |
|
125 |
|
126 // calcule la taille d'un cluster (somme de tout les articles) |
|
127 // et l'élément de poids maximal |
|
128 comptage : function(datumCell, start, end) { |
|
129 var compteursize = 0, idMax = 0, poidsMax = 0; |
|
130 var timeline = datumCell.timeline; |
|
131 |
|
132 var tStart = (start==null || start<0)? 0: start; |
|
133 var tEnd = (end==null || end>timeline.length)? |
|
134 timeline.length: end; |
|
135 var index, endIndex, articleId; |
|
136 for (var t=tStart; t<tEnd; t++) { //boucle sur les t de la timeline |
|
137 compteursize+= timeline[t].length; |
|
138 for (index = 0, endIndex = timeline[t].length; |
|
139 index < endIndex; ++index) { |
|
140 articleId = timeline[t][index]; |
|
141 if (this.articles[articleId].poids > poidsMax) { |
|
142 poidsMax = this.articles[articleId].poids; |
|
143 idMax = articleId; |
|
144 } |
|
145 } |
|
146 } |
|
147 |
|
148 datumCell.poidsMax = poidsMax; |
|
149 datumCell.idMax = idMax; |
|
150 return compteursize; |
|
151 }, |
|
152 |
|
153 getContent: function(datumCell, start, end) { |
|
154 if (null!=this.parametres.contenu) { |
|
155 return this.parametres.contenu.call( |
|
156 this, datumCell, this.articles[datumCell.idMax]); |
|
157 } |
|
158 else { |
|
159 return this.articles[datumCell.idMax].titre; |
|
160 } |
|
161 }, |
|
162 |
|
163 getDataTreemap : function(json, start, end) { |
|
164 var data = { name : "data", children : []}; |
|
165 var clusters = json.clusters; |
|
166 |
|
167 for (var i=0, _end=clusters.length; i<_end; i++) { // parcours les differents clusters |
|
168 var cluster = { id: clusters[i].numero, name : "cluster." + clusters[i].numero, timeline : clusters[i].timeline }; |
|
169 data.children.push(cluster); |
|
170 } |
|
171 |
|
172 return [data]; |
|
173 }, |
|
174 |
|
175 sizeCell: function() { |
|
176 this.style("left", function(d) { return d.x + "px"; }) |
|
177 .style("top", function(d) { return d.y + "px"; }) |
|
178 .style("width", function(d) { return d.dx - 1 + "px"; }) |
|
179 .style("height", function(d) { return d.dy - 1 + "px"; }); |
|
180 }, |
|
181 |
|
182 focus: function(map) { |
|
183 map.classed("selected", true) |
|
184 .transition() |
|
185 .duration(150) |
|
186 .delay(50) |
|
187 .style("opacity", 0.5); |
|
188 }, |
|
189 |
|
190 blur: function(map) { |
|
191 map.classed("selected", false) |
|
192 .transition() |
|
193 .duration(100) |
|
194 .delay(50) |
|
195 .style("opacity", 1); |
|
196 }, |
|
197 |
|
198 transition: function(start, end) { |
|
199 var object = this; |
|
200 // To avoid conflicts while elements are hovered, lock the actions |
|
201 |
|
202 this.treemapHtml.selectAll("div") |
|
203 .each(function(datum) { |
|
204 object.parametres.relations.get(datum.relationName) |
|
205 .lock(); |
|
206 }) |
|
207 .data(this.treemap.value(function(d) { |
|
208 return object.comptage(d, start, end); |
|
209 })) |
|
210 .attr("datetime", function(d) { |
|
211 return d.children? 0: object.articles[d.idMax].date; |
|
212 }) |
|
213 .html(function(d) { return d.children? null: object.getContent(d, start, end); }) |
|
214 .transition() |
|
215 .duration(this.parametres.transitionDuration) |
|
216 .call(this.sizeCell) |
|
217 // Free the lock |
|
218 .each("end", function(datum) { |
|
219 object.parametres.relations.get(datum.relationName) |
|
220 .unlock(); |
|
221 }); |
|
222 }, |
|
223 |
|
224 defaultContent: function(cellule, article) { |
|
225 var html; |
|
226 var img = article.image; |
|
227 var wi, hi, wc, hc, w, h, r; |
|
228 |
|
229 wi = img.largeur; |
|
230 hi = img.hauteur; |
|
231 wc = cellule.dx - 1; |
|
232 hc = cellule.dy - 1; |
|
233 r = Math.min(hi/hc, wi/wc); |
|
234 w = wi/r; |
|
235 h = hi/r; |
|
236 |
|
237 var posx, posy; |
|
238 posx = -(w-wc)/2; |
|
239 posy = -(h-hc)/2; |
|
240 |
|
241 html = "<img class = 'img' src = \"" + img.url + "\" style=\" width:"+ w + "px; height:" +h + "px; margin-top:" + posy + "px; margin-left:" + posx + "px;\" />"; |
|
242 html += "<p class = 'title' style = \" width:" + wc + "px;\" >" + article.titre + "</p>"; |
|
243 |
|
244 return html; |
|
245 }, |
|
246 |
|
247 renderContenu: function(cellule, article) { |
|
248 var image = article.image; |
|
249 |
|
250 var wi = image.largeur, |
|
251 hi = image.hauteur, |
|
252 wc = cellule.dx - 1, |
|
253 hc = cellule.dy - 1, |
|
254 r = Math.min(hi/hc, wi/wc), |
|
255 w = wi/r, |
|
256 h = hi/r, |
|
257 posx = -(w-wc)/2, |
|
258 posy = -(h-hc)/2; |
|
259 |
|
260 var data = { |
|
261 'urlImage': image.url, |
|
262 'widhtImage': w +"px", |
|
263 'heightImage': h +"px", |
|
264 'cssOffsetImage': "margin-top: "+ posy +"px; margin-left: "+posx +"px;", |
|
265 'titreArticle': article.titre, |
|
266 'dateArticle': article.date, |
|
267 'widthArticle': wc +"px", |
|
268 'heightArticle': hc +"px", |
|
269 'cssWidthArticle': "width:" +wc +"px;", |
|
270 'cssHeightArticle': "height:" +hc +"px;", |
|
271 'cssDimensionsArticle': "width:" +wc +"px;height:" +hc +"px;", |
|
272 'dateArticle': article.date |
|
273 }; |
|
274 |
|
275 return this.template |
|
276 .render(data, this.parametres.template.directives).html(); |
|
277 }, |
|
278 |
|
279 on: function(event, action) { |
|
280 switch(event) { |
|
281 default: |
|
282 this.treemapHtml.on(event, action); |
|
283 } |
|
284 } |
|
285 }; |