2 * Librairie tFlot pour jQuery et jQuery.flot
3 * Licence GNU/GPL - Matthieu Marcillaud
10 * Des variables a garder globalement
12 * collections : stockage de l'ensemble de toutes les valeurs de tous les graphs et leurs options
13 * collectionsActives : stockage des series actives
14 * plots : stockage des graphiques
15 * vignettes : stockage des vignettes
16 * idGraph : identifiant unique pour tous les graphs
19 collectionsActives
= [];
22 vignettesSelection
= [];
26 * Fait un graphique d'un tableau donne
27 * $("table.graph").tFlot();
28 * necessite la librairie "jQuery flot".
29 * http://code.google.com/p/flot/
31 $.fn
.tFlot = function(settings
) {
39 orientation
:'row', // 'column' : tableaux verticaux par defaut...
40 axeOnTitle
:false // les coordonnees x d'axe sont donnes dans l'attribut title du <th> et non dans le <th> ?
43 legendeActions
:false, // ne fonctionne qu'avec l'option legende externe
44 modeDate
:false, // pour calculer les timestamp automatiquement
46 show
:false, // pour calculer une moyenne glissante automatiquement
47 plage
:7 // plage de glissement (nombre impair !)
49 grille
:{weekend
:false},
52 serie_color
:false // utiliser comme couleur de fond la même couleur que les lignes du graph
69 selection
: { mode
: "x" }
72 $.extend(true, options
, settings
);
75 $(this).each(function(){
77 // identifiant unique pour tous les graphs
84 $(this).hide().wrap("<div class='graphique' id='graphique"+idGraph
+"'></div>");
85 graphique
= $(this).parent();
86 values
= parseTable(this, options
.parse
);
87 $.extend(true, values
.options
, options
.flot
);
89 graph
= $("<div class='graphResult' style='width:" + options
.width
+ ";height:" + options
.height
+ ";'></div>").appendTo(graphique
);
90 graph
.wrap("<div class='graphResult-wrap'></div>");
91 gInfo
= $("<div class='graphInfo'></div>").appendTo(graphique
);
93 // legende en dehors du dessin ?
94 if (options
.legendeExterne
) {
95 legend
= $("<div class='graphLegend' id='grapLegend"+idGraph
+"'></div>").appendTo(gInfo
);
96 values
.options
.legend
.container
= legend
;
98 // legende avec items clicables pour desactiver certaines series
99 if (options
.legendeExterne
&& options
.legendeActions
) {
100 values
.options
.legend
.labelFormatter = function(label
) {
101 return '<a href="#label">' + label
+ '</a>';
104 // si mode time, on calcule des timestamp
105 // les series sont alors de la forme [[timestamp, valeur],...]
106 // et pas besoin de ticks declare
107 if (options
.modeDate
) {
109 // calcul des timestamps
110 $.each(values
.options
.xaxis
.ticks
, function(i
, val
){
111 timestamps
.push([val
[0], (new Date(val
[1])).getTime()]);
113 // les remettre dans les series
114 $.each(values
.series
, function(i
, val
){
116 $.each(val
.data
, function (j
, d
){
117 data
.push([timestamps
[j
][1], d
[1]]);
119 values
.series
[i
].data
= data
;
121 // plus besoin du ticks
122 // mais toujours besoin des valeurs completes...
123 values
.options
.xaxis
= $.extend(true, {
125 timeformat
: "%d/%m/%y"
127 values
.options
.xaxis
,
130 if (options
.grille
.weekend
) {
131 values
.options
.grid
= { markings
: weekendAreas
}
133 if (options
.grille
.years
) {
134 values
.options
.grid
= { markings
: yearsArea
}
138 // en cas de moyenne glissante, on la calcule
139 if (options
.moyenneGlissante
.show
) {
140 values
.series
= moyenneGlissante(values
.series
, options
.moyenneGlissante
);
143 // si infobulles, les ajouter
144 if (options
.infobulle
.show
) {
145 $.extend(true, options
.infobulle
, {date
:options
.modeDate
});
146 infobulle($('#graphique'+idGraph
), options
.infobulle
);
147 $.extend(true, values
.options
, {
148 grid
:{hoverable
:true}
154 plots
[idGraph
] = $.plot(graph
, values
.series
, values
.options
);
156 // prevoir les actions sur les labels
157 if (options
.legendeExterne
&& options
.legendeActions
) {
158 $.extend(values
.options
, {legend
:{container
:null, show
:false}});
159 actionsLegendes($('#graphique'+idGraph
));
162 // ajouter une mini vue si demandee
163 if (options
.vignette
.show
) {
164 $("<div class='graphVignette' id='#graphVignette"+idGraph
165 + "' style='width:" + options
.vignette
.width
+ ";height:"
166 + options
.vignette
.height
+ ";'></div>").appendTo(gInfo
);
167 creerVignette($('#graphique'+idGraph
), values
.series
, values
.options
, options
.vignette
);
169 // autoriser les zoom
171 zoomGraphique($('#graphique'+idGraph
));
174 // stocker les valeurs
175 collections
.push({id
:idGraph
, values
:values
}); // sources
176 collectionsActives
= $.extend(true, {}, collections
); // affiches
183 * Prendre une table HTML
184 * et calculer les donnees d'un graph jQuery.plot
186 function parseTable(table
, settings
){
191 ticks
:[], // [1:"label 1", 2:"label 2"]
192 orientation
:'row', // 'column'
193 ticksReels
:[], // on sauve les vraies donnees pour les infobulles (1 janvier 2008) et non le code de date (1/1/2008)
208 $.extend(options
, settings
);
210 row
= (options
.orientation
== 'row');
213 // recuperer les points d'axes
217 // Une fonction pour simplifier la recup
219 function getValue(element
) {
220 if (options
.axeOnTitle
) {
221 return element
.attr('title');
223 return element
.text();
229 // dans le th de chaque tr
230 $(table
).find('tr:not(:first)').each(function(){
231 $(this).find('th:first').each(function(){
232 options
.ticks
.push([++axe
, getValue($(this))]);
233 options
.ticksReels
.push([axe
, $(this).text()]);
238 // dans les th du premier tr
239 $(table
).find('tr:first th:not(:first)').each(function(){
240 options
.ticks
.push([++axe
, getValue($(this))]);
241 options
.ticksReels
.push([axe
, $(this).text()]);
247 // recuperer les noms de series
252 // si axes definis, on saute une ligne
254 columns
= $(table
).find('tr:first th:not(:first)');
256 columns
= $(table
).find('tr:first th');
258 // chaque colonne est une serie
260 for(i
=0; i
<columns
.length
; i
++){
262 th
= $(table
).find('tr:first th:eq(' + (i
+ axe
) + ')');
264 serieOptions
= optionsCss(th
);
265 $(table
).find('tr td:nth-child(' + (i
+ 1 + axe
) +')').each(function(){
266 val
= parseFloat($(this).text());
267 data
.push( [++cpt
, val
] );
269 serie
= {label
:label
, data
:data
};
270 $.extend(serie
, serieOptions
);
276 // si axes definis, on saute une colonne
278 rows
= $(table
).find('tr:not(:first)');
280 rows
= $(table
).find('tr');
282 // chaque ligne est une serie
283 rows
.each(function(){
285 th
= $(this).find('th');
287 serieOptions
= optionsCss(th
);
288 // recuperer les valeurs
289 $(this).find('td').each(function(){
290 val
= parseFloat($(this).text());
291 data
.push( [++cpt
, val
] );
293 serie
= {label
:label
, data
:data
};
294 $.extend(serie
, serieOptions
);
300 // mettre les options dans les series
304 $.each(flot
, function(i
, serie
) {
306 serie
.color
= color
++;
308 serie
= $.extend(true, {}, options
.defaultSerie
, serie
);
315 if (options
.ticks
.length
) {
316 opt
.xaxis
.ticks
= options
.ticks
;
317 opt
.xaxis
.ticksReels
= options
.ticksReels
;
320 return {series
:flot
, options
:opt
};
325 * Recuperer les options en fonctions de CSS
328 function optionsCss(element
) {
330 $element
= $(element
);
331 if ($element
.data('serie') == 'line') {
332 $.extend(true, options
, {
338 if ($element
.data('serie') == 'bar') {
339 $.extend(true, options
, {
345 if ($element
.data('serie') == 'lineBar') {
346 $.extend(true, options
, {
347 lines
:{show
:true,steps
:true},
352 if ($element
.data('fill')) {
353 $.extend(true, options
, {
356 fillColor
: { colors
: [ { opacity
: 0.9 }, { opacity
: 0.9 } ] }
360 fillColor
: { colors
: [ { opacity
: 0.9 }, { opacity
: 0.9 } ] }
364 if (color
= $element
.data('color')) {
365 options
.color
= color
;
372 * calcul d'une moyenne glissante
375 function moyenneGlissante(lesSeries
, settings
) {
379 texte
:"Moyenne glissante"
381 $.extend(options
, settings
);
386 $.each(lesSeries
, function(i
, val
){
387 // recuperer le numero de couleur max
388 color
= ParseInt(Math
.max(color
,val
.color
));
391 $.each(val
.data
, function (j
, d
){
392 // ajout du nouvel element
393 // et retrait du trop vieux
394 moy
.push(parseInt(d
[1]));
395 if (moy
.length
>=g
) { moy
.shift();}
397 // calcul de la somme et ajout de la moyenne
398 for(var k
=0,sum
=0;k
<moy
.length
;sum
+=moy
[k
++]);
399 data
.push([d
[0], Math
.round(sum
/moy
.length
)]);
402 serieG
= $.extend(true, {}, val
, {
404 label
:val
.label
+ " ("+options
.texte
+")",
414 // remettre les couleurs
415 $.each(series
, function(i
, val
) {
424 // Permettre de cacher certaines series
426 function actionsLegendes(graph
) {
427 // actions sur les items de legende
428 // pour masquer / afficher certaines series
429 // a ne charger qu'une fois par graph !!!
430 $(graph
).find('.legendLabel a').click(function(){
431 tr
= $(this).parent().prev('.legendColorBox').toggleClass('cacher').parent();
433 master
= tr
.closest('.graphique');
434 pid
= master
.attr('id').substr(9); // enlever 'graphique'
436 var seriesActives
= [];
437 tr
.find('.legendColorBox:not(.cacher)').each(function(){
438 nom
= $(this).next('.legendLabel').find('a').text();
439 n
= collections
[pid
].values
.series
.length
;
441 if (collections
[pid
].values
.series
[i
].label
== nom
) {
442 seriesActives
.push(collections
[pid
].values
.series
[i
]);
447 collectionsActives
[pid
].values
.series
= seriesActives
;
449 $.plot(master
.find('.graphResult'), seriesActives
, collections
[pid
].values
.options
);
451 if (master
.find('.graphVignette').length
) {
452 creerVignette(master
, seriesActives
, collections
[pid
].values
.options
);
459 // Afficher une miniature
461 function creerVignette(graphique
, series
, optionsParents
, settings
) {
467 legend
: { show
: false },
469 lines
: { show
: true, lineWidth
: 1 },
470 grid
: { color
: "#999", hoverable
:null },
471 selection
: { mode
: "x" },
472 xaxis
:{min
:null, max
:null},
473 yaxis
:{min
:null, max
:null}
476 $.extend(true, options
, settings
);
477 options
.flot
= $.extend(true, {}, optionsParents
, options
.flot
);
479 // demarrer la vignette
480 vignette
= $(graphique
).find('.graphVignette');
481 pid
= vignette
.closest('.graphique').attr('id').substr(9);
482 vignettes
[pid
] = $.plot(vignette
, series
, options
.flot
);
484 if (vignettesSelection
[pid
] !== undefined) {
485 vignettes
[pid
].setSelection(vignettesSelection
[pid
]);
492 // Permettre le zoom sur le graphique
493 // et sur la miniature
495 function zoomGraphique(graphique
) {
496 pid
= $(graphique
).attr('id').substr(9);
498 $(graphique
).find('.graphResult').bind("plotselected", function (event
, ranges
) {
499 graph
= $(event
.target
);
500 pid
= graph
.closest('.graphique').attr('id').substr(9);
502 // clamp the zooming to prevent eternal zoom
503 if (ranges
.xaxis
.to
- ranges
.xaxis
.from < 0.00001)
504 ranges
.xaxis
.to
= ranges
.xaxis
.from + 0.00001;
505 if (ranges
.yaxis
.to
- ranges
.yaxis
.from < 0.00001)
506 ranges
.yaxis
.to
= ranges
.yaxis
.from + 0.00001;
509 // et sauver les parametres du zoom
510 plots
[pid
] = $.plot(graph
, collectionsActives
[pid
].values
.series
,
511 $.extend(true, collections
[pid
].values
.options
, {
512 xaxis
: { min
: ranges
.xaxis
.from, max
: ranges
.xaxis
.to
},
513 yaxis
: { min
: ranges
.yaxis
.from, max
: ranges
.yaxis
.to
}
516 // don't fire event on the overview to prevent eternal loop
517 if (vignettes
[pid
] !== undefined) {
518 vignettes
[pid
].setSelection(ranges
, true);
522 // raz sur double clic
523 $(graphique
).find('.graphResult').dblclick(function (event
) {
525 graphique
= $(event
.target
).closest('.graphique');
526 pid
= graphique
.attr('id').substr(9);
527 vignettesSelection
[pid
] = undefined;
528 if (vignettes
[pid
] != undefined) {
529 vignettes
[pid
].clearSelection();
531 plots
[pid
] = $.plot(graphique
.find('.graphResult'),
532 collectionsActives
[pid
].values
.series
,
533 $.extend(true, collections
[pid
].values
.options
, {
534 xaxis
: { min
: null, max
: null },
535 yaxis
: { min
: null, max
: null }
541 // si une vignette est presente
542 vignette
= $(graphique
).find('.graphVignette');
544 if (vignette
.length
) {
546 // zoom depuis la miniature
547 vignette
.bind("plotselected", function (event
, ranges
) {
548 graph
= $(event
.target
);
549 pid
= graph
.closest('.graphique').attr('id').substr(9);
550 vignettesSelection
[pid
] = ranges
;
551 plots
[pid
].setSelection(ranges
);
554 // raz depuis la miniature sur double clic
555 vignette
.dblclick(function (event
) {
557 graphique
= $(event
.target
).closest('.graphique');
558 pid
= graphique
.attr('id').substr(9);
559 vignettesSelection
[pid
] = undefined;
561 plots
[pid
] = $.plot(graphique
.find('.graphResult'),
562 collectionsActives
[pid
].values
.series
,
563 $.extend(true, collections
[pid
].values
.options
, {
564 xaxis
: { min
: null, max
: null },
565 yaxis
: { min
: null, max
: null }
577 var previousPoint
= null;
578 function infobulle(graph
, settings
) {
583 $.extend(true, options
, settings
);
585 $(graph
).bind("plothover", function (event
, pos
, item
) {
586 $("#x").text(pos
.x
.toFixed(2));
587 $("#y").text(pos
.y
.toFixed(2));
588 graph
= $(event
.target
);
589 pid
= graph
.closest('.graphique').attr('id').substr(9);
593 if (previousPoint
!= item
.datapoint
) {
594 previousPoint
= item
.datapoint
;
596 $("#tooltip").remove();
597 var x
= item
.datapoint
[0],
598 y
= item
.datapoint
[1];
601 if(options
.serie_color
){
602 color
= item
.series
.color
;
604 x
= collectionsActives
[pid
].values
.options
.xaxis
.ticksReels
[item
.dataIndex
][1];
606 showTooltip(item
.pageX
, item
.pageY
,
607 item
.series
.label
+ " [" + x
+ "] = " + y
,
612 $("#tooltip").remove();
613 previousPoint
= null;
621 // Adapte du site de Flot (exemple de visites)
622 // helper for returning the weekends in a period
623 function weekendAreas(axes
) {
625 var heure
= 60 * 60 * 1000;
626 var jour
= 24 * heure
;
629 // go to the first Saturday
630 var d
= new Date(axes
.xaxis
.min
);
631 d
.setUTCDate(d
.getUTCDate() - ((d
.getUTCDay() + 1) % 7))
637 markings
.push({ xaxis
: { from: i
, to
: i
+ 2*jour
}, color
: '#e8e8e8' });
639 } while (i
< axes
.xaxis
.max
);
642 // les mois et les ans...
643 $.each(yearsArea(axes
), function(i
,j
){
650 // une grille pour afficher les mois et les ans...
651 function yearsArea(axes
){
653 var heure
= 60 * 60 * 1000;
654 var jour
= 24 * heure
;
655 var width_year
= jour
;
656 if (axes
.xaxis
.options
.minTickSize
[1]=="month")
657 width_year
= 30.4*jour
;
659 // les mois et les ans...
660 d
= new Date(axes
.xaxis
.min
);
661 y
= d
.getUTCFullYear();
663 if (++m
== 12) {m
=0; ++y
;}
664 d
= new Date(Date
.UTC(y
,m
,1,0,0,0));
667 if (m
== 0) {couleur
= '#CA5F18';}
668 else {couleur
= '#D7C2AF'; }
669 markings
.push({ xaxis
: { from: i
, to
: i
+ (m
==0?width_year
:jour
)}, color
: couleur
});
670 if (++m
== 12) {m
=0; ++y
;}
671 d
= new Date(Date
.UTC(y
,m
,1,0,0,0));
672 } while (d
.getTime() < axes
.xaxis
.max
);
678 * Exemple adapte du site de Flot (exemple d'interactions)
679 * montrer les informations des points
682 * x (Float) Coordonnee longitudinale de la bulle
683 * y (Float) Coordonnee latitudinale de la bulle
684 * contents (String) Le contenu de la bulle
685 * color La couleur de fond de l'infobulles
687 function showTooltip(x
, y
, contents
, color
) {
688 $('<div id="tooltip">' + contents
+ '</div>').css( {
693 }).addClass('tooltip_statistiques').appendTo("body").fadeIn(200);
697 // copie de la fonction de jquery.flot.js
698 // pour utilisation dans infobulle
699 function formatDate(d
, fmt
, monthNames
) {
700 var leftPad = function(n
) {
702 return n
.length
== 1 ? "0" + n
: n
;
707 if (monthNames
== null)
708 monthNames
= ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
709 for (var i
= 0; i
< fmt
.length
; ++i
) {
710 var c
= fmt
.charAt(i
);
714 case 'h': c
= "" + d
.getUTCHours(); break;
715 case 'H': c
= leftPad(d
.getUTCHours()); break;
716 case 'M': c
= leftPad(d
.getUTCMinutes()); break;
717 case 'S': c
= leftPad(d
.getUTCSeconds()); break;
718 case 'd': c
= "" + d
.getUTCDate(); break;
719 case 'm': c
= "" + (d
.getUTCMonth() + 1); break;
720 case 'y': c
= "" + d
.getUTCFullYear(); break;
721 case 'b': c
= "" + monthNames
[d
.getUTCMonth()]; break;