1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/spip_ynh.git synced 2024-09-03 20:25:59 +02:00
spip_ynh/sources/plugins-dist/statistiques/javascript/jquery.tflot.js
2015-04-28 17:10:23 +02:00

736 lines
19 KiB
JavaScript

/**
* Librairie tFlot pour jQuery et jQuery.flot
* Licence GNU/GPL - Matthieu Marcillaud
* Version 1.4.0
*/
(function($){
/**
* Des variables a garder globalement
*
* collections : stockage de l'ensemble de toutes les valeurs de tous les graphs et leurs options
* collectionsActives : stockage des series actives
* plots : stockage des graphiques
* vignettes : stockage des vignettes
* idGraph : identifiant unique pour tous les graphs
*/
collections = [];
collectionsActives = [];
plots = [];
vignettes = [];
vignettesSelection = [];
idGraph = 0;
/*
* Fait un graphique d'un tableau donne
* $("table.graph").tFlot();
* necessite la librairie "jQuery flot".
* http://code.google.com/p/flot/
*/
$.fn.tFlot = function(settings) {
var options, flot;
options = {
width:'500px',
height:'250px',
parse:{
orientation:'row', // 'column' : tableaux verticaux par defaut...
axeOnTitle:false // les coordonnees x d'axe sont donnes dans l'attribut title du <th> et non dans le <th> ?
},
legendeExterne:false,
legendeActions:false, // ne fonctionne qu'avec l'option legende externe
modeDate:false, // pour calculer les timestamp automatiquement
moyenneGlissante:{
show:false, // pour calculer une moyenne glissante automatiquement
plage:7 // plage de glissement (nombre impair !)
},
grille:{weekend:false},
infobulle:{
show:false,
serie_color:false // utiliser comme couleur de fond la même couleur que les lignes du graph
},
zoom:false,
vignette:{
show:false,
width:'160px',
height:'100px'
},
flot:{
legend:{
show:true,
container:null,
labelFormatter:null,
noColumns: 3
},
bars: {fill:false},
yaxis: { min: 0 },
selection: { mode: "x" }
}
}
$.extend(true, options, settings);
$(this).each(function(){
// identifiant unique pour tous les graphs
// creer les cadres
// .graphique
// .graphResult
// .graphInfos
// .graphLegend
// .graphOverview
$(this).hide().wrap("<div class='graphique' id='graphique"+idGraph+"'></div>");
graphique = $(this).parent();
values = parseTable(this, options.parse);
$.extend(true, values.options, options.flot);
graph = $("<div class='graphResult' style='width:" + options.width + ";height:" + options.height + ";'></div>").appendTo(graphique);
graph.wrap("<div class='graphResult-wrap'></div>");
gInfo = $("<div class='graphInfo'></div>").appendTo(graphique);
// legende en dehors du dessin ?
if (options.legendeExterne) {
legend = $("<div class='graphLegend' id='grapLegend"+idGraph+"'></div>").appendTo(gInfo);
values.options.legend.container = legend;
}
// legende avec items clicables pour desactiver certaines series
if (options.legendeExterne && options.legendeActions) {
values.options.legend.labelFormatter = function(label) {
return '<a href="#label">' + label + '</a>';
}
}
// si mode time, on calcule des timestamp
// les series sont alors de la forme [[timestamp, valeur],...]
// et pas besoin de ticks declare
if (options.modeDate) {
timestamps = [];
// calcul des timestamps
$.each(values.options.xaxis.ticks, function(i, val){
timestamps.push([val[0], (new Date(val[1])).getTime()]);
});
// les remettre dans les series
$.each(values.series, function(i, val){
data = [];
$.each(val.data, function (j, d){
data.push([timestamps[j][1], d[1]]);
});
values.series[i].data = data;
});
// plus besoin du ticks
// mais toujours besoin des valeurs completes...
values.options.xaxis = $.extend(true, {
mode: "time",
timeformat: "%d/%m/%y"
},
values.options.xaxis,
{ticks: null}
);
if (options.grille.weekend) {
values.options.grid = { markings: weekendAreas }
}
if (options.grille.years) {
values.options.grid = { markings: yearsArea }
}
}
// en cas de moyenne glissante, on la calcule
if (options.moyenneGlissante.show) {
values.series = moyenneGlissante(values.series, options.moyenneGlissante);
}
// si infobulles, les ajouter
if (options.infobulle.show) {
$.extend(true, options.infobulle, {date:options.modeDate});
infobulle($('#graphique'+idGraph), options.infobulle);
$.extend(true, values.options, {
grid:{hoverable:true}
});
}
// dessiner
plots[idGraph] = $.plot(graph, values.series, values.options);
// prevoir les actions sur les labels
if (options.legendeExterne && options.legendeActions) {
$.extend(values.options, {legend:{container:null, show:false}});
actionsLegendes($('#graphique'+idGraph));
}
// ajouter une mini vue si demandee
if (options.vignette.show) {
$("<div class='graphVignette' id='#graphVignette"+idGraph
+ "' style='width:" + options.vignette.width + ";height:"
+ options.vignette.height + ";'></div>").appendTo(gInfo);
creerVignette($('#graphique'+idGraph), values.series, values.options, options.vignette);
}
// autoriser les zoom
if (options.zoom) {
zoomGraphique($('#graphique'+idGraph));
}
// stocker les valeurs
collections.push({id:idGraph, values:values}); // sources
collectionsActives = $.extend(true, {}, collections); // affiches
++idGraph;
});
/*
* Prendre une table HTML
* et calculer les donnees d'un graph jQuery.plot
*/
function parseTable(table, settings){
var options;
flot = [];
options = {
ticks:[], // [1:"label 1", 2:"label 2"]
orientation:'row', // 'column'
ticksReels:[], // on sauve les vraies donnees pour les infobulles (1 janvier 2008) et non le code de date (1/1/2008)
axeOnTitle:false,
defaultSerie:{
bars: {
barWidth: 0.9,
align: "center",
show:true,
fill:false
},
lines: {
show:false,
fill:false
}
}
}
$.extend(options, settings);
row = (options.orientation == 'row');
//
// recuperer les points d'axes
//
//
// Une fonction pour simplifier la recup
//
function getValue(element) {
if (options.axeOnTitle) {
return element.attr('title');
} else {
return element.text();
}
}
axe=0;
if (row) {
// dans le th de chaque tr
$(table).find('tr:not(:first)').each(function(){
$(this).find('th:first').each(function(){
options.ticks.push([++axe, getValue($(this))]);
options.ticksReels.push([axe, $(this).text()]);
});
});
} else {
// dans les th du premier tr
$(table).find('tr:first th:not(:first)').each(function(){
options.ticks.push([++axe, getValue($(this))]);
options.ticksReels.push([axe, $(this).text()]);
});
}
//
// recuperer les noms de series
//
axe = (axe ? 1 : 0);
if (row) {
// si axes definis, on saute une ligne
if (axe) {
columns = $(table).find('tr:first th:not(:first)');
} else {
columns = $(table).find('tr:first th');
}
// chaque colonne est une serie
for(i=0; i<columns.length; i++){
cpt=0, data=[];
th = $(table).find('tr:first th:eq(' + (i + axe) + ')');
label = th.text();
serieOptions = optionsCss(th);
$(table).find('tr td:nth-child(' + (i + 1 + axe) +')').each(function(){
val = parseFloat($(this).text());
data.push( [++cpt, val] );
});
serie = {label:label, data:data};
$.extend(serie, serieOptions);
flot.push(serie);
}
} else {
// si axes definis, on saute une colonne
if (axe) {
rows = $(table).find('tr:not(:first)');
} else {
rows = $(table).find('tr');
}
// chaque ligne est une serie
rows.each(function(){
cpt=0, data=[];
th = $(this).find('th');
label = th.text();
serieOptions = optionsCss(th);
// recuperer les valeurs
$(this).find('td').each(function(){
val = parseFloat($(this).text());
data.push( [++cpt, val] );
});
serie = {label:label, data:data};
$.extend(serie, serieOptions);
flot.push(serie);
});
}
//
// mettre les options dans les series
//
color=0;
$.each(flot, function(i, serie) {
if (!serie.color) {
serie.color = color++;
}
serie = $.extend(true, {}, options.defaultSerie, serie);
flot[i] = serie;
});
opt = {
xaxis: {}
}
if (options.ticks.length) {
opt.xaxis.ticks = options.ticks;
opt.xaxis.ticksReels = options.ticksReels;
}
return {series:flot, options:opt};
}
/*
*
* Recuperer les options en fonctions de CSS
*
*/
function optionsCss(element) {
var options = {};
$element = $(element);
if ($element.data('serie') == 'line') {
$.extend(true, options, {
lines:{show:true},
bars:{show:false},
points:{show:false}
});
}
if ($element.data('serie') == 'bar') {
$.extend(true, options, {
lines:{show:false},
bars:{show:true},
points:{show:false}
});
}
if ($element.data('serie') == 'lineBar') {
$.extend(true, options, {
lines:{show:true,steps:true},
bars:{show:false},
points:{show:false}
});
}
if ($element.data('fill')) {
$.extend(true, options, {
lines:{
fill:true,
fillColor: { colors: [ { opacity: 0.9 }, { opacity: 0.9 } ] }
},
bars:{
fill:true,
fillColor: { colors: [ { opacity: 0.9 }, { opacity: 0.9 } ] }
}
});
}
if (color = $element.data('color')) {
options.color = color;
}
return options;
}
/*
*
* calcul d'une moyenne glissante
*
*/
function moyenneGlissante(lesSeries, settings) {
var options;
options = {
plage: 7,
texte:"Moyenne glissante"
}
$.extend(options, settings);
g = options.plage;
series = [];
color = 0;
$.each(lesSeries, function(i, val){
// recuperer le numero de couleur max
color = ParseInt(Math.max(color,val.color));
data = [], moy = [];
$.each(val.data, function (j, d){
// ajout du nouvel element
// et retrait du trop vieux
moy.push(parseInt(d[1]));
if (moy.length>=g) { moy.shift();}
// calcul de la somme et ajout de la moyenne
for(var k=0,sum=0;k<moy.length;sum+=moy[k++]);
data.push([d[0], Math.round(sum/moy.length)]);
});
serieG = $.extend(true, {}, val, {
data:data,
label:val.label + " ("+options.texte+")",
lines:{
show:true,
fill:false
},
bars:{show:false}
});
series.push(val);
series.push(serieG);
});
// remettre les couleurs
$.each(series, function(i, val) {
if (!val.color) {
val.color = color++;
}
});
return series;
}
//
// Permettre de cacher certaines series
//
function actionsLegendes(graph) {
// actions sur les items de legende
// pour masquer / afficher certaines series
// a ne charger qu'une fois par graph !!!
$(graph).find('.legendLabel a').click(function(){
tr = $(this).parent().prev('.legendColorBox').toggleClass('cacher').parent();
master = tr.closest('.graphique');
pid = master.attr('id').substr(9); // enlever 'graphique'
var seriesActives = [];
tr.find('.legendColorBox:not(.cacher)').each(function(){
nom = $(this).next('.legendLabel').find('a').text();
n = collections[pid].values.series.length;
for(i=0;i<n;i++) {
if (collections[pid].values.series[i].label == nom) {
seriesActives.push(collections[pid].values.series[i]);
break;
}
}
});
collectionsActives[pid].values.series = seriesActives;
$.plot(master.find('.graphResult'), seriesActives, collections[pid].values.options);
// vignettes
if (master.find('.graphVignette').length) {
creerVignette(master, seriesActives, collections[pid].values.options);
}
});
}
//
// Afficher une miniature
//
function creerVignette(graphique, series, optionsParents, settings) {
var options;
options = {
show:true,
zoom:true,
flot:{
legend: { show: false },
shadowSize: 0,
lines: { show: true, lineWidth: 1 },
grid: { color: "#999", hoverable:null },
selection: { mode: "x" },
xaxis:{min:null, max:null},
yaxis:{min:null, max:null}
}
};
$.extend(true, options, settings);
options.flot = $.extend(true, {}, optionsParents, options.flot);
// demarrer la vignette
vignette = $(graphique).find('.graphVignette');
pid = vignette.closest('.graphique').attr('id').substr(9);
vignettes[pid] = $.plot(vignette, series, options.flot);
if (vignettesSelection[pid] !== undefined) {
vignettes[pid].setSelection(vignettesSelection[pid]);
}
}
//
// Permettre le zoom sur le graphique
// et sur la miniature
//
function zoomGraphique(graphique) {
pid = $(graphique).attr('id').substr(9);
$(graphique).find('.graphResult').bind("plotselected", function (event, ranges) {
graph = $(event.target);
pid = graph.closest('.graphique').attr('id').substr(9);
// clamp the zooming to prevent eternal zoom
if (ranges.xaxis.to - ranges.xaxis.from < 0.00001)
ranges.xaxis.to = ranges.xaxis.from + 0.00001;
if (ranges.yaxis.to - ranges.yaxis.from < 0.00001)
ranges.yaxis.to = ranges.yaxis.from + 0.00001;
// do the zooming
// et sauver les parametres du zoom
plots[pid] = $.plot(graph, collectionsActives[pid].values.series,
$.extend(true, collections[pid].values.options, {
xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to },
yaxis: { min: ranges.yaxis.from, max: ranges.yaxis.to }
}));
// don't fire event on the overview to prevent eternal loop
if (vignettes[pid] !== undefined) {
vignettes[pid].setSelection(ranges, true);
}
});
// raz sur double clic
$(graphique).find('.graphResult').dblclick(function (event) {
var graphique;
graphique = $(event.target).closest('.graphique');
pid = graphique.attr('id').substr(9);
vignettesSelection[pid] = undefined;
if (vignettes[pid] != undefined) {
vignettes[pid].clearSelection();
}
plots[pid] = $.plot(graphique.find('.graphResult'),
collectionsActives[pid].values.series,
$.extend(true, collections[pid].values.options, {
xaxis: { min: null, max: null },
yaxis: { min: null, max: null }
}));
});
// si une vignette est presente
vignette = $(graphique).find('.graphVignette');
if (vignette.length) {
// zoom depuis la miniature
vignette.bind("plotselected", function (event, ranges) {
graph = $(event.target);
pid = graph.closest('.graphique').attr('id').substr(9);
vignettesSelection[pid] = ranges;
plots[pid].setSelection(ranges);
});
// raz depuis la miniature sur double clic
vignette.dblclick(function (event) {
var graphique;
graphique = $(event.target).closest('.graphique');
pid = graphique.attr('id').substr(9);
vignettesSelection[pid] = undefined;
plots[pid] = $.plot(graphique.find('.graphResult'),
collectionsActives[pid].values.series,
$.extend(true, collections[pid].values.options, {
xaxis: { min: null, max: null },
yaxis: { min: null, max: null }
}));
});
}
}
/*
*
* Infobulles
*
*/
var previousPoint = null;
function infobulle(graph, settings) {
var options;
options = {
show:true
};
$.extend(true, options, settings);
$(graph).bind("plothover", function (event, pos, item) {
$("#x").text(pos.x.toFixed(2));
$("#y").text(pos.y.toFixed(2));
graph = $(event.target);
pid = graph.closest('.graphique').attr('id').substr(9);
if (options.show) {
if (item) {
if (previousPoint != item.datapoint) {
previousPoint = item.datapoint;
$("#tooltip").remove();
var x = item.datapoint[0],
y = item.datapoint[1];
var color = '';
if(options.serie_color){
color = item.series.color;
}
x = collectionsActives[pid].values.options.xaxis.ticksReels[item.dataIndex][1];
showTooltip(item.pageX, item.pageY,
item.series.label + " [" + x + "] = " + y,
color);
}
}
else {
$("#tooltip").remove();
previousPoint = null;
}
}
});
}
}
// Adapte du site de Flot (exemple de visites)
// helper for returning the weekends in a period
function weekendAreas(axes) {
var markings = [];
var heure = 60 * 60 * 1000;
var jour = 24 * heure;
// les week ends
// go to the first Saturday
var d = new Date(axes.xaxis.min);
d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 1) % 7))
d.setUTCSeconds(0);
d.setUTCMinutes(0);
d.setUTCHours(0);
var i = d.getTime();
do {
markings.push({ xaxis: { from: i, to: i + 2*jour }, color: '#e8e8e8' });
i += 7*jour;
} while (i < axes.xaxis.max);
// les mois et les ans...
$.each(yearsArea(axes), function(i,j){
markings.push(j);
});
return markings;
}
// une grille pour afficher les mois et les ans...
function yearsArea(axes){
var markings = [];
var heure = 60 * 60 * 1000;
var jour = 24 * heure;
var width_year = jour;
if (axes.xaxis.options.minTickSize[1]=="month")
width_year = 30.4*jour;
// les mois et les ans...
d = new Date(axes.xaxis.min);
y = d.getUTCFullYear();
m = d.getUTCMonth();
if (++m == 12) {m=0; ++y;}
d = new Date(Date.UTC(y,m,1,0,0,0));
do {
i = d.getTime();
if (m == 0) {couleur = '#CA5F18';}
else {couleur = '#D7C2AF'; }
markings.push({ xaxis: { from: i, to: i + (m==0?width_year:jour)}, color: couleur });
if (++m == 12) {m=0; ++y;}
d = new Date(Date.UTC(y,m,1,0,0,0));
} while (d.getTime() < axes.xaxis.max);
return markings;
}
/**
* Exemple adapte du site de Flot (exemple d'interactions)
* montrer les informations des points
*
* Arguments :
* x (Float) Coordonnee longitudinale de la bulle
* y (Float) Coordonnee latitudinale de la bulle
* contents (String) Le contenu de la bulle
* color La couleur de fond de l'infobulles
*/
function showTooltip(x, y, contents, color) {
$('<div id="tooltip">' + contents + '</div>').css( {
top: y + 5,
left: x + 5,
opacity: 0.80,
background:color
}).addClass('tooltip_statistiques').appendTo("body").fadeIn(200);
}
// copie de la fonction de jquery.flot.js
// pour utilisation dans infobulle
function formatDate(d, fmt, monthNames) {
var leftPad = function(n) {
n = "" + n;
return n.length == 1 ? "0" + n : n;
};
var r = [];
var escape = false;
if (monthNames == null)
monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
for (var i = 0; i < fmt.length; ++i) {
var c = fmt.charAt(i);
if (escape) {
switch (c) {
case 'h': c = "" + d.getUTCHours(); break;
case 'H': c = leftPad(d.getUTCHours()); break;
case 'M': c = leftPad(d.getUTCMinutes()); break;
case 'S': c = leftPad(d.getUTCSeconds()); break;
case 'd': c = "" + d.getUTCDate(); break;
case 'm': c = "" + (d.getUTCMonth() + 1); break;
case 'y': c = "" + d.getUTCFullYear(); break;
case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
}
r.push(c);
escape = false;
}
else {
if (c == "%")
escape = true;
else
r.push(c);
}
}
return r.join("");
}
})(jQuery);