d6307d96193756ac095e6687c857623e52184bdd
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
68 selection
: { mode
: "x" }
71 $.extend(true, options
, settings
);
74 $(this).each(function(){
76 // identifiant unique pour tous les graphs
83 $(this).hide().wrap("<div class='graphique' id='graphique"+idGraph
+"'></div>");
84 graphique
= $(this).parent();
85 values
= parseTable(this, options
.parse
);
86 $.extend(true, values
.options
, options
.flot
);
88 graph
= $("<div class='graphResult' style='width:" + options
.width
+ ";height:" + options
.height
+ ";'></div>").appendTo(graphique
);
89 gInfo
= $("<div class='graphInfo'></div>").appendTo(graphique
);
91 // legende en dehors du dessin ?
92 if (options
.legendeExterne
) {
93 legend
= $("<div class='graphLegend' id='grapLegend"+idGraph
+"'></div>").appendTo(gInfo
);
94 values
.options
.legend
.container
= legend
;
96 // legende avec items clicables pour desactiver certaines series
97 if (options
.legendeActions
) {
98 values
.options
.legend
.labelFormatter = function(label
) {
99 return '<a href="#label">' + label
+ '</a>';
102 // si mode time, on calcule des timestamp
103 // les series sont alors de la forme [[timestamp, valeur],...]
104 // et pas besoin de ticks declare
105 if (options
.modeDate
) {
107 // calcul des timestamps
108 $.each(values
.options
.xaxis
.ticks
, function(i
, val
){
109 timestamps
.push([val
[0], (new Date(val
[1])).getTime()]);
111 // les remettre dans les series
112 $.each(values
.series
, function(i
, val
){
114 $.each(val
.data
, function (j
, d
){
115 data
.push([timestamps
[j
][1], d
[1]]);
117 values
.series
[i
].data
= data
;
119 // plus besoin du ticks
120 // mais toujours besoin des valeurs completes...
121 values
.options
.xaxis
= $.extend(true, {
123 timeformat
: "%d/%m/%y"
125 values
.options
.xaxis
,
128 if (options
.grille
.weekend
) {
129 values
.options
.grid
= { markings
: weekendAreas
}
131 if (options
.grille
.years
) {
132 values
.options
.grid
= { markings
: yearsArea
}
136 // en cas de moyenne glissante, on la calcule
137 if (options
.moyenneGlissante
.show
) {
138 values
.series
= moyenneGlissante(values
.series
, options
.moyenneGlissante
);
141 // si infobulles, les ajouter
142 if (options
.infobulle
.show
) {
143 $.extend(true, options
.infobulle
, {date
:options
.modeDate
});
144 infobulle($('#graphique'+idGraph
), options
.infobulle
);
145 $.extend(true, values
.options
, {
146 grid
:{hoverable
:true}
152 plots
[idGraph
] = $.plot(graph
, values
.series
, values
.options
);
154 // prevoir les actions sur les labels
155 if (options
.legendeActions
) {
156 $.extend(values
.options
, {legend
:{container
:null, show
:false}});
157 actionsLegendes($('#graphique'+idGraph
));
160 // ajouter une mini vue si demandee
161 if (options
.vignette
.show
) {
162 $("<div class='graphVignette' id='#graphVignette"+idGraph
163 + "' style='width:" + options
.vignette
.width
+ ";height:"
164 + options
.vignette
.height
+ ";'></div>").appendTo(gInfo
);
165 creerVignette($('#graphique'+idGraph
), values
.series
, values
.options
, options
.vignette
);
167 // autoriser les zoom
169 zoomGraphique($('#graphique'+idGraph
));
172 // stocker les valeurs
173 collections
.push({id
:idGraph
, values
:values
}); // sources
174 collectionsActives
= $.extend(true, {}, collections
); // affiches
181 * Prendre une table HTML
182 * et calculer les donnees d'un graph jQuery.plot
184 function parseTable(table
, settings
){
189 ticks
:[], // [1:"label 1", 2:"label 2"]
190 orientation
:'row', // 'column'
191 ticksReels
:[], // on sauve les vraies donnees pour les infobulles (1 janvier 2008) et non le code de date (1/1/2008)
206 $.extend(options
, settings
);
208 row
= (options
.orientation
== 'row');
211 // recuperer les points d'axes
215 // Une fonction pour simplifier la recup
217 function getValue(element
) {
218 if (options
.axeOnTitle
) {
219 return element
.attr('title');
221 return element
.text();
227 // dans le th de chaque tr
228 $(table
).find('tr:not(:first)').each(function(){
229 $(this).find('th:first').each(function(){
230 options
.ticks
.push([++axe
, getValue($(this))]);
231 options
.ticksReels
.push([axe
, $(this).text()]);
236 // dans les th du premier tr
237 $(table
).find('tr:first th:not(:first)').each(function(){
238 options
.ticks
.push([++axe
, getValue($(this))]);
239 options
.ticksReels
.push([axe
, $(this).text()]);
245 // recuperer les noms de series
250 // si axes definis, on saute une ligne
252 columns
= $(table
).find('tr:first th:not(:first)');
254 columns
= $(table
).find('tr:first th');
256 // chaque colonne est une serie
258 for(i
=0; i
<columns
.length
; i
++){
260 th
= $(table
).find('tr:first th:eq(' + (i
+ axe
) + ')');
262 serieOptions
= optionsCss(th
);
263 $(table
).find('tr td:nth-child(' + (i
+ 1 + axe
) +')').each(function(){
264 val
= parseFloat($(this).text());
265 data
.push( [++cpt
, val
] );
267 serie
= {label
:label
, data
:data
};
268 $.extend(serie
, serieOptions
);
274 // si axes definis, on saute une colonne
276 rows
= $(table
).find('tr:not(:first)');
278 rows
= $(table
).find('tr');
280 // chaque ligne est une serie
281 rows
.each(function(){
283 th
= $(this).find('th');
285 serieOptions
= optionsCss(th
);
286 // recuperer les valeurs
287 $(this).find('td').each(function(){
288 val
= parseFloat($(this).text());
289 data
.push( [++cpt
, val
] );
291 serie
= {label
:label
, data
:data
};
292 $.extend(serie
, serieOptions
);
298 // mettre les options dans les series
302 $.each(flot
, function(i
, serie
) {
304 serie
.color
= color
++;
306 serie
= $.extend(true, {}, options
.defaultSerie
, serie
);
313 if (options
.ticks
.length
) {
314 opt
.xaxis
.ticks
= options
.ticks
;
315 opt
.xaxis
.ticksReels
= options
.ticksReels
;
318 return {series
:flot
, options
:opt
};
323 * Recuperer les options en fonctions de CSS
326 function optionsCss(element
) {
328 $element
= $(element
);
329 if ($element
.data('serie') == 'line') {
330 $.extend(true, options
, {
336 if ($element
.data('serie') == 'bar') {
337 $.extend(true, options
, {
343 if ($element
.data('serie') == 'lineBar') {
344 $.extend(true, options
, {
345 lines
:{show
:true,steps
:true},
350 if ($element
.data('fill')) {
351 $.extend(true, options
, {
354 fillColor
: { colors
: [ { opacity
: 0.7 }, { opacity
: 0 } ] }
358 fillColor
: { colors
: [ { opacity
: 0.7 }, { opacity
: 0 } ] }
362 if (color
= $element
.data('color')) {
363 options
.color
= color
;
370 * calcul d'une moyenne glissante
373 function moyenneGlissante(lesSeries
, settings
) {
377 texte
:"Moyenne glissante"
379 $.extend(options
, settings
);
384 $.each(lesSeries
, function(i
, val
){
385 // recuperer le numero de couleur max
386 color
= ParseInt(Math
.max(color
,val
.color
));
389 $.each(val
.data
, function (j
, d
){
390 // ajout du nouvel element
391 // et retrait du trop vieux
392 moy
.push(parseInt(d
[1]));
393 if (moy
.length
>=g
) { moy
.shift();}
395 // calcul de la somme et ajout de la moyenne
396 for(var k
=0,sum
=0;k
<moy
.length
;sum
+=moy
[k
++]);
397 data
.push([d
[0], Math
.round(sum
/moy
.length
)]);
400 serieG
= $.extend(true, {}, val
, {
402 label
:val
.label
+ " ("+options
.texte
+")",
412 // remettre les couleurs
413 $.each(series
, function(i
, val
) {
422 // Permettre de cacher certaines series
424 function actionsLegendes(graph
) {
425 // actions sur les items de legende
426 // pour masquer / afficher certaines series
427 // a ne charger qu'une fois par graph !!!
428 $(graph
).find('.legendLabel a').click(function(){
429 tr
= $(this).parent().parent();
430 tr
.toggleClass('cacher');
432 // bof bof tous ces parent() et ca marche qu'avec legendeExterne:true
433 master
= tr
.parent().parent().parent().parent().parent();
434 pid
= master
.attr('id').substr(9); // enlever 'graphique'
436 var seriesActives
= [];
437 tr
.parent().find('tr:not(.cacher)').each(function(){
438 nom
= $(this).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
.parent().parent().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
.parent().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
).parent().parent();
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
.parent().parent().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
).parent().parent().parent();
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
.parent().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
: '#f6f6f6' });
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
;
656 // les mois et les ans...
657 d
= new Date(axes
.xaxis
.min
);
658 y
= d
.getUTCFullYear();
660 if (++m
== 12) {m
=0; ++y
;}
661 d
= new Date(Date
.UTC(y
,m
,1,0,0,0));
664 if (m
== 0) {couleur
= '#CA5F18';}
665 else {couleur
= '#D7C2AF'; }
666 markings
.push({ xaxis
: { from: i
, to
: i
+ jour
}, color
: couleur
});
667 if (++m
== 12) {m
=0; ++y
;}
668 d
= new Date(Date
.UTC(y
,m
,1,0,0,0));
669 } while (d
.getTime() < axes
.xaxis
.max
);
675 * Exemple adapte du site de Flot (exemple d'interactions)
676 * montrer les informations des points
679 * x (Float) Coordonnee longitudinale de la bulle
680 * y (Float) Coordonnee latitudinale de la bulle
681 * contents (String) Le contenu de la bulle
682 * color La couleur de fond de l'infobulles
684 function showTooltip(x
, y
, contents
, color
) {
685 $('<div id="tooltip">' + contents
+ '</div>').css( {
690 }).addClass('tooltip_statistiques').appendTo("body").fadeIn(200);
694 // copie de la fonction de jquery.flot.js
695 // pour utilisation dans infobulle
696 function formatDate(d
, fmt
, monthNames
) {
697 var leftPad = function(n
) {
699 return n
.length
== 1 ? "0" + n
: n
;
704 if (monthNames
== null)
705 monthNames
= ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
706 for (var i
= 0; i
< fmt
.length
; ++i
) {
707 var c
= fmt
.charAt(i
);
711 case 'h': c
= "" + d
.getUTCHours(); break;
712 case 'H': c
= leftPad(d
.getUTCHours()); break;
713 case 'M': c
= leftPad(d
.getUTCMinutes()); break;
714 case 'S': c
= leftPad(d
.getUTCSeconds()); break;
715 case 'd': c
= "" + d
.getUTCDate(); break;
716 case 'm': c
= "" + (d
.getUTCMonth() + 1); break;
717 case 'y': c
= "" + d
.getUTCFullYear(); break;
718 case 'b': c
= "" + monthNames
[d
.getUTCMonth()]; break;