Le but de ce tuto, est d'afficher un graphique Highstock qui compare la pluviométrie mensuelle aux Normales 1981-2010.
A la fin de ce tuto, vous devriez obtenir un graphique ressemblant à celui-ci.
http://www.monsite-meteo.eu/Page/graphd ... rmales.php
Ce graphique est inspiré de celui que Pierre-André avait fait il y a un moment. Cependant, il présentait quelques inconvénients.
Pour afficher les "Normales, il fallait les saisir manuellement tous les ans. C'était fastidieux et on loupait le réveillon de nouvel an pour mettre à jour les données en temps réel .
Le code PHP de la page s’allongeait tous les ans et la courbe des Normales était affichée jusqu'à la fin de l'année sans qu'il y ait de données pluie correspondantes.
Avec la méthode employée pour extraire les données de la BDD on utilisait la fonction DataGrouping de Highstock pour les afficher.
Le problème avec cette méthode, est que le nombre de données "Rain_jour" (des centaines voire plus), extraites de la BDD et le nombre de données "Normales" (12 par an) n'est pas le même.
Ceci a pour conséquence, si l'on fait un zoom sur le graphique, d'avoir des courbes qui ne veulent plus dire grand-chose car elles ne sont pas synchronisées.
J'ai donc trouvé la solution que je vous donne ici .
Informations préalables
Je reprends ici la citation de Helmain dans son tuto "Rose des Vents";
Les requêtes indiquées dans ce tuto sont faites sur une base MySql créée avec le logiciel Data2Sql de Jean TURLIER.
Si votre table ou vos rubriques ont été créés par un autre soft, il convient de changer les noms de la table et de la rubrique indiqués dans ce tutoriel.
Et je lui ajoute;
Suivant les conseils de Helmain, le code PHP de ce tuto utilise l'extension mysqli. Donc si vous utilisez toujours mysql, vous devrez adapter votre code. Le mieux, je pense, et de passer à mysqli.
Pour ce faire, vous pouvez suivre les conseil de Helmain dans ce post http://www.boock.ch/meteo/forum/viewtopic.php?f=6&t=48.
Vous devez également récupérer les "Normales" valables pour votre situation géographique .
A moins que vous n'habitiez dans ma région, n'utilisez pas celles de ce tuto. Ces "Normales" sont celles de la station Météo France de Metz Marly Frescaty, la station Météo France de référence la plus proche de ma station.
Passons maintenant au code PHP .
En premier lieu, il faut se connecter à la base de données en utilisant un script de connexion (rechercher dans ce forum comment faire, c'est déjà expliqué à plusieurs reprises).
Pour ce faire, on utilise le code suivant;
Code : Tout sélectionner
<?php
// Appel du script de connexion
require("Votre Chemin/mysqli_connect.php");
Ensuite, on récupère les données dans la BDD.
Ici on n’a pas besoin de $Start ni de $Stop car on récupère la totalité des données.
Pour ce faire, on utilise la requête SQL suivante.
Code : Tout sélectionner
// Récupération des données en faisant la somme de Rain_jour en créant une variable AnneeMois et en groupant les données sur cette variable AnneeMois.
$sql = "SELECT tstamp, SUM(Rain_jour), substr(recdateTZ,1,6) AS AnneeMois FROM `MiniMaxidata` GROUP BY AnneeMois";
$query = mysqli_query($conn,$sql);
- On récupère SELECT
- les données
- tstamp
- Rain_jour en en faisant la somme SUM
- la variable AnneeMois que l'on créé à partir de la donnée recdateTZ dont on extrait l'année et le mois substr(recdateTZ,1,6) AS AnneeMois
- depuis FROM la table MiniMaxidata
- et que l'on groupe GROUP BY par la variable précédemment créée AnneeMois
Voilà, c'est tout simple. Essayez cette requête directement sur votre BDD avec phpMyAdmin et vous verrez que vous aurez un tableau ou tous les cumuls ont déjà été calculé .
Ensuite, nous allons faire une boucle While pour mettre nos données sous forme de tableau et dans laquelle nous allons ajouter une boucle if elseif pour y insérer les données Normales, puis nous allons calculer la variable Ecart entre les précipitations réelles et les Normales.
Code : Tout sélectionner
$i=0;
while ($list = mysqli_fetch_assoc($query)) {
if (date("I",time())==0) {
$time[$i]=($list['tstamp']+3600)*1000;
}
else {
$time[$i]=($list['tstamp']+7200)*1000;
}
$Rain_jour[$i]=$list['SUM(Rain_jour)']*1;
if (date("m",$time[$i]/1000)==1) {$NormaleMensuelle[$i]=64.2;}
elseif (date("m",$time[$i]/1000)==2) {$NormaleMensuelle[$i]=56.9;}
elseif (date("m",$time[$i]/1000)==3) {$NormaleMensuelle[$i]=61.3;}
elseif (date("m",$time[$i]/1000)==4) {$NormaleMensuelle[$i]=50.5;}
elseif (date("m",$time[$i]/1000)==5) {$NormaleMensuelle[$i]=58.9;}
elseif (date("m",$time[$i]/1000)==6) {$NormaleMensuelle[$i]=61.7;}
elseif (date("m",$time[$i]/1000)==7) {$NormaleMensuelle[$i]=63.7;}
elseif (date("m",$time[$i]/1000)==8) {$NormaleMensuelle[$i]=60.8;}
elseif (date("m",$time[$i]/1000)==9) {$NormaleMensuelle[$i]=62.7;}
elseif (date("m",$time[$i]/1000)==10) {$NormaleMensuelle[$i]=71.8;}
elseif (date("m",$time[$i]/1000)==11) {$NormaleMensuelle[$i]=64.0;}
elseif (date("m",$time[$i]/1000)==12) {$NormaleMensuelle[$i]=78.8;}
$Ecart[$i]=$Rain_jour[$i]-$NormaleMensuelle[$i];
$i++;
}
?>
- On initialise la variable $i
- On commence la boucle while while ($list = mysqli_fetch_assoc($query)) {
- On vérifie si le tstamp est à l'heure d'été ou pas (rappelez-vous que le tstamp est en temps Unix)
if (date("I",time())==0) {
$time[$i]=($list['tstamp']+3600)*1000;
} , si oui, on lui ajoute 1 heure à la variable $time[$i]
else {
$time[$i]=($list['tstamp']+7200)*1000;
}, si non, on y ajoute 2 heures à la variable $time[$i]
Attention, le calcul ci-dessus n'est valable que pour ceux qui sont dans le même fuseau horaire que moi soit GMT+1 en été et GMT+2 en hiver.
- On définit la variable Rain_jour[$i] en disant qu'elle est égale à la somme de Rain_jour[$i] de la requête SQL $Rain_jour[$i]=$list['SUM(Rain_jour)']*1; et on la multiplie par 1 pour obtenir une variable numérique exploitable
-
On fait une boucle de test if elseif pour définir la variable $NormaleMensuelle[$i]
if (date("m",$time[$i]/1000)==1) {$NormaleMensuelle[$i]=64.2;}
En français dans le texte; Si le mois "m" de la variable $times[$i] est comme "==" 1 (janvier), alors la variable $NormaleMensuelle[$i] est égale "=" à la valeur de la normale mensuelle de janvier valable pour votre station (soit 64.2mm pour ma station)
sinon elseif Si le mois "m" de la variable $times[$i] est comme "==" 2 (février), alors la variable $NormaleMensuelle[$i] est égale "=" à la valeur de la normale mensuelle de février valable pour votre station (soit 56.9mm pour ma station)
Etc., etc. jusqu'a 12.
Attention au signe double "==". C'est un opérateur de comparaison dans une chaîne alors que le signe simple "=" défini une valeur numérique .
Voir ici pour mieux comprendre http://php.net//manual/fr/language.oper ... arison.php
ATTENTION ici, de bien remplacer les valeurs mensuelles des variables $NormalesMensuelle[$i] par celle valables pour l'emplacement géographique de votre station !!!
- On calcule maintenant la valeur de la variable $Ecart[$i]
$Ecart[$i]=$Rain_jour[$i]-$NormaleMensuelle[$i];
En clair, l'écart est égal au cumul des précipitations mensuelles, moins la valeur de la normale mensuelle. -
Enfin on incrémente $i et la boucle recommence jusqu’à la fin des enregistrement trouvés dans la BDD
$i++;
}
?>
Encodage des données
Maintenant, avant de pour voir être exploitées par Highstock, les données doivent êtres encodées en json. C'est ce que fait le code java script suivant.
Code : Tout sélectionner
<script type="text/javascript">
eval(<?php echo "'var time = ".json_encode($time)."'" ?>);
eval(<?php echo "'var Rain_jour = ".json_encode($Rain_jour)."'" ?>);
eval(<?php echo "'var NormaleMensuelle = ".json_encode($NormaleMensuelle)."'" ?>);
eval(<?php echo "'var Ecart = ".json_encode($Ecart)."'" ?>);
</script>
Je ne vous décrit pas ici tous le code, Pierre-André l'a déjà excellemment fait dans son tuto Tutoriel MySQL - Highcharts.
Je vous mets donc le code sans explications.
Cependant n'oubliez pas, dans le code suivant, de changer les chemins des répertoires contenant les fichiers java script et autres images (là ou j'ai mis A changer ou Nom de votre site, etc...
J'ai également supprimé du code les chemins absolus d'accès à mon serveur. Ceci pour éviter que des petits malins qui aurait la flemme de changer ces chemins n'utilisent la bande passante de mon serveur (comme cela s'est déjà vu dans ce forum).
Vous pouvez faire votre propre page html avec votre propre code Highstock. Mais ça serait sympa de ne pas vous approprier le code et de laisser la partie credits de Highstock.
Code : Tout sélectionner
<!DOCTYPE HTML>
<html>
<head>
<title>Votre site</title>
<meta name="title" content="Votre site">
<meta name="description" content="à changer">
<meta name="keywords" content="à changer">
<meta name="robots" content="index, follow, archive">
<meta name="category" content="education, information, weather">
<meta name="author" content="Pascal WERMELINGER">
<meta name="copyright" content="© monsite-meteo.eu 2014">
<meta name="language" content="fr">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!-- 1. Add these JavaScript inclusions in the head of your page -->
<script type="text/javascript" src=".A changer/jquery.min.js"></script>
<script src="A changer/Highstock/js/highstock.js"></script>
<script src="A changer/Highstock/js/highcharts-more.js"></script>
<script src="A changer/Highstock/js/modules/exporting.js"></script>
<script src="A changer/Highstock/js/themes/grid.js"></script>
<link rel="stylesheet" href="A changer/jquery.ui.all.css">
<script A changer/jquery-1.9.1.js"></script>
<script src="A changer/jquery.ui.core.js"></script>
<script src="A changer/jquery.ui.widget.js"></script>
<script src="A changer/jquery.ui.datepicker.js"></script>
<script src="A changer/jquery.ui.datepicker-fr.js"></script>
<link rel="stylesheet" href="A changer/demos.css">
<script type="text/javascript" src="A changer/overlib.js"></script>
<script type="text/javascript" src="A changer/overlib_fade.js"></script>
<!-- 2. Add the JavaScript to initialize the chart on document ready -->
<script type="text/javascript">
function comArr(unitsArray) {
var outarr = [];
for (var i = 0; i < time.length; i++) {
outarr[i] = [time[i], unitsArray[i]];
}
return outarr;
}
$(function () {
var chart;
$(document).ready(function() {
var highchartsOptions = Highcharts.setOptions(Highcharts.theme);
Highcharts.setOptions({
lang: {
months: ["Janvier "," Février "," Mars "," Avril "," Mai "," Juin "," Juillet "," Août "," Septembre "," Octobre "," Novembre "," Décembre"],
weekdays: ["Dim "," Lun "," Mar "," Mer "," Jeu "," Ven "," Sam"],
shortMonths: ['Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Juin', 'Juil','Août', 'Sept', 'Oct', 'Nov', 'Déc'],
decimalPoint: ',',
resetZoom: 'Réinitialiser le zoom',
resetZoomTitle: 'Réinitialiser le zoom à 1:1',
downloadPNG: "Télécharger au format PNG image",
downloadJPEG: "Télécharger au format JPEG image",
downloadPDF: "Télécharger au format PDF document",
downloadSVG: "Télécharger au format SVG vector image",
exportButtonTitle: "Exporter image ou document",
printButtonTitle: "Imprimer le graphique",
printChart: "Imprimer le graphique",
loading: "Chargement en cours...",
rangeSelectorFrom: "Du",
rangeSelectorTo: "Au"
}
});
window.chart = new Highcharts.StockChart({
chart: {
renderTo: 'container',
zoomType: 'x',
type: 'column',
alignTicks: false,
plotShadow: true,
spacingLeft: 1,
spacingRight: 20,
//marginTop: 60,
//marginLeft: 80,
//marginRight: 30,
marginBottom: 60,
borderRadius: 10,
backgroundColor: {
linearGradient: [0, 0, 600, 570],
stops: [
[0, '#FFFFFF'],
[1, '#CDDEFE'],
]
},
plotBorderColor: '#346691',
plotBorderWidth: 1,
plotBackgroundColor: {
linearGradient: [0, 0, 0, 400],
stops: [
[0, '#A8D3FF'],
[1, '#FFFFFF']
]
},
},
title: {
text: 'Pluviométrie mensuelle depuis 2012 comparée aux normales 1981-2010',
align: 'center',
x: 0 //center
},
subtitle: {
text: 'Source : Le nom de votre site',
align: 'center',
x: 0
},
credits: {
text: 'Météo Conflans en Jarnisy',
href: 'http://www.monsite-meteo.eu'
},
rangeSelector : {
inputEditDateFormat: '%d-%m-%Y',
inputDateFormat: '%d-%m-%Y',
buttonSpacing: 2,
labelStyle: {
color: 'red',
fontWeight: 'bold'
},
inputBoxBorderColor: 'gray',
inputBoxWidth: 120,
inputBoxHeight: 18,
inputStyle: {
color: '#0000A0',
fontWeight: 'bold'
},
buttonTheme: { // styles for the buttons
width: 100,
fill: 'white',
stroke: 'none',
'stroke-width': 1,
r: 3,
style: {
color: '#0000A0',
fontWeight: 'bold'
},
states: {
hover: {
fill: '#FF0000',
style: {
color: '#FFFF00'
}
},
select: {
fill: '#0000A0',
style: {
color: 'white'
}
}
}
},
buttons: [{
type: 'year',
count: 1,
text: '1 an'
},{
type: 'ytd',
count: 1,
text: 'Cette année'
},{
type: 'all',
count: 1,
text: 'Tout'
}],
inputEnabled: true,
selected : 1,
},
scrollbar: {
barBackgroundColor: '#0000A0',
barBorderRadius: 7,
barBorderWidth: 0,
buttonBackgroundColor: 'silver',
buttonBorderWidth: 0,
buttonArrowColor: 'yellow',
buttonBorderRadius: 3,
rifleColor: 'yellow',
trackBackgroundColor: '#DFF4FF',
trackBorderWidth: 1,
trackBorderColor: 'silver',
trackBorderRadius: 3
},
navigator: {
outlineColor: 'blue',
outlineWidth: 1,
maskFill: 'rgba(230, 242, 255, 0.75)',
handles: {
backgroundColor: 'yellow',
borderColor: 'red'
}
},
legend: {
enabled: true,
align: 'center',
verticalAlign: 'center',
x: 0,
y: 460,
backgroundColor: '#FFFFCC',
borderColor: 'royalblue',
borderWidth: 1,
borderRadius: 5,
layout: 'horizontal',
shadow: true,
},
yAxis: { // 1er yAxis (numero 0)
opposite:false,
showFirstLabel: true,
showLastLabel: true,
labels: {
formatter: function() {
return this.value +' mm';
},
align : 'right',
x : -10,
y : 3,
style: {
color: '#0000FF'
}
},
title: {
text: 'Précipitations en mm',
style: {
color: '#0000FF'
}
}
},
xAxis: {
type: 'datetime',
labels: {
format: '{value: %b<br/>%Y}',
align: 'center',
}
},
tooltip: {
crosshairs: true,
borderColor: 'royalblue',
valueDecimals: 2,
shared: true,
backgroundColor: {
linearGradient: [0, 0, 0, 60],
stops: [
[0, '#6495ed'],
[1, '#ffffff'],
]
},
borderRadius: 5,
formatter: function() {
var s = '<b>'+ Highcharts.dateFormat('%B %Y', this.x) +'</b>';
$.each(this.points, function(i, point) {
var unit = {
'Précipitations': ' mm',
'Normales 1981-2010': ' mm',
'Ecart': ' mm',
}[this.point.series.name];
s = s + '<br/>' + '<span style="color:'+ point.series.color +'">' + point.series.name + '</span> : ' +Highcharts.numberFormat(point.y,1,","," ")
+ unit;
});
return s;
},
},
plotOptions: {
column: {
grouping: false,
shadow: false,
},
},
series: [{
name: 'Précipitations',
id :'Precipitations',
type: 'column',
pointPadding: -0.2,
color: {
linearGradient: { x1: 0, x2: 0, y1: 1, y1: 1.5 },
stops: [
[0, 'rgba(0, 0, 160, .9)'],
[1, 'rgba(0, 128, 192, .8)']
]
},
data: comArr(Rain_jour),
},{
name: 'Ecart',
id : 'Ecart',
type: 'column',
color: {
linearGradient: { x1: 0, x2: 0, y1: 1, y1: 1.5 },
stops: [
[0, 'rgba(255, 0, 0, .9)'],
[1, 'rgba(255, 255, 0, .8)']
]
},
data: comArr(Ecart),
},{
name: 'Normales 1981-2010',
id : 'Normales',
type: 'spline',
color: '#008080',
lineWidth : 0.5,
marker: {
enabled: true,
radius: 4,
symbol: 'triangle',
lineWidth: 2,
lineColor: '#FF0000',
fillColor: 'white'
},
data: comArr(NormaleMensuelle),
},
]
},
function(chart) {// on complete
chart.renderer.image('http://www.votre site.xx/Images de votre choix.png', 80, 5, 45, 50) // Doit etre un chemin absolu
.add()
chart.renderer.image('http://www.votre site.xx/Images de votre choix.png', 1131, 5, 39, 50) // Doit etre un chemin absolu
.add();
// apply the date pickers
setTimeout(function() {
$('input.highcharts-range-selector', $('#' + chart.options.chart.renderTo)).datepicker({minDate:-880,maxDate:0,})
},0)
// Set the datepickers date format
$.datepicker.setDefaults({
dateFormat: 'yy-mm-dd',
//dateFormat: 'dd-mm-yyyy',
onSelect: function(dateText) {
this.onchange();
this.onblur();
}
});
});
});
});
/**
* @license
* Highcharts compatibilty pack for bringing the 2.x look for the print/export buttons back in Highcharts 3.
* License: MIT
* Author: Torstein Honsi
*/
(function (Highcharts) {
var defaultOptions = Highcharts.getOptions(),
symbols = Highcharts.Renderer.prototype.symbols,
extend = Highcharts.extend,
merge = Highcharts.merge;
// Add language keys
extend(defaultOptions.lang, {
exportButtonTitle: 'Export to raster or vector image',
printButtonTitle: 'Print the chart'
});
// The old button look
defaultOptions.navigation.buttonOptions = merge(defaultOptions.navigation.buttonOptions, {
borderColor: '#B0B0B0',
height: 20,
theme: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#FFF'],
[1, '#DDD']
]
},
stroke: '#BBB'
},
symbolSize: 12,
symbolStroke: '#A0A0A0',
symbolStrokeWidth: 1
});
// Add the buttons to default options
extend(defaultOptions.exporting.buttons, {
exportButton: {
//enabled: true,
symbol: 'exportIcon',
x: -10,
symbolFill: '#A8BF77',
_id: 'exportButton',
_titleKey: 'exportButtonTitle',
menuItems: defaultOptions.exporting.buttons.contextButton.menuItems.splice(2, 4)
},
printButton: {
//enabled: true,
symbol: 'printIcon',
x: -36,
symbolFill: '#B5C9DF',
_id: 'printButton',
_titleKey: 'printButtonTitle',
onclick: function () {
this.print();
}
}
});
delete defaultOptions.exporting.buttons.contextButton;
/**
* Crisp for 1px stroke width, which is default. In the future, consider a smarter,
* global function.
*/
function crisp(arr) {
var i = arr.length;
while (i--) {
if (typeof arr[i] === 'number') {
arr[i] = Math.round(arr[i]) - 0.5;
}
}
return arr;
}
// Create the export icon
symbols.exportIcon = function (x, y, width, height) {
return crisp([
'M', // the disk
x, y + width,
'L',
x + width, y + height,
x + width, y + height * 0.8,
x, y + height * 0.8,
'Z',
'M', // the arrow
x + width * 0.5, y + height * 0.8,
'L',
x + width * 0.8, y + height * 0.4,
x + width * 0.4, y + height * 0.4,
x + width * 0.4, y,
x + width * 0.6, y,
x + width * 0.6, y + height * 0.4,
x + width * 0.2, y + height * 0.4,
'Z'
]);
};
// Create the print icon
symbols.printIcon = function (x, y, width, height) {
return crisp([
'M', // the printer
x, y + height * 0.7,
'L',
x + width, y + height * 0.7,
x + width, y + height * 0.4,
x, y + height * 0.4,
'Z',
'M', // the upper sheet
x + width * 0.2, y + height * 0.4,
'L',
x + width * 0.2, y,
x + width * 0.8, y,
x + width * 0.8, y + height * 0.4,
'Z',
'M', // the lower sheet
x + width * 0.2, y + height * 0.7,
'L',
x, y + height,
x + width, y + height,
x + width * 0.8, y + height * 0.7,
'Z'
]);
};
}(Highcharts));
</script>
<style type="text/css">
body {
background-image: url(A changer/PapierGris.jpg);
background-repeat: repeat;
text-align: center;
}
ul {
text-align:left
}
</style>
<link href="StyleCssPourLesPages.css" rel="stylesheet" type="text/css">
</head>
<body>
<a id="Haut"></a>
<table width="1260" border="0" align="center">
<tr>
<td align="right" valign="middle">
<a href="" onMouseOver="return overlib('<ul><li>Vous pouvez zoomer sur une zone spécifique d’un graphique en maintenant le bouton de la souris et en faisant glisser le curseur à l’intérieur du graphique. <li>Vous pouvez changer la façon dont les mesures sont affichées dans un graphique en cliquant dans la légende sur le nom de la donnée dont vous voulez modifier l’affichage.<li> Les graphiques sont automatiquement mis à jour toutes les 5 minutes</ul>',OPACITY, 90, LEFT, FGCOLOR, '#b5cee1', BGCOLOR, '#4682b4', BORDER, 2, WIDTH, 250, TEXTSIZE, 2, CAPTION, 'Informations' ,CAPTIONSIZE,'3');" onMouseOut="return nd();"><img src="A changer/info.png" width="40" height="40" border="0"></a>
<a href="javascript:void(0);" onMouseOver="return overlib('Actualiser le graphique', OPACITY, 90, LEFT, FGCOLOR, '#b5cee1', TEXTSIZE,'2', WIDTH, 160 );" onMouseOut="return nd();"><img src="A changer/refresh.png" width="42" height="43" onClick="javascript:location.reload();"></a></td>
</tr>
</table>
<div id="container" style="width: 1260px; height: 570px; margin: 0 auto"></div><br>
</div>
</html>
Mais cela vous sera expliqué dans d'autres tutos.
Bon un rayon de soleil est de retour, je vais en profiter pour aller tondre la pelouse.