Annuaire › Code ouvert
Code publié par son auteur, en lecture seule. « Copier dans mon espace » crée un nouveau site à vous (non publié) que vous pouvez modifier librement.
js/sketch.js · 8.2 Ko
/* ============================================================
sketch.js - programme principal du tableau de bord pluviométrie
============================================================
Rôle : créer le canevas, tenir à jour l'état (année choisie,
mode, animation) et appeler les fonctions de dessin dans
l'ordre à chaque image.
setup() : appelée une fois ; draw() : appelée en boucle.
============================================================ */
/* ---------- État partagé (variables globales) ---------- */
let zone = { x0: 0, y0: 0, x1: 0, y1: 0, larg: 0, haut: 0, colonne: 0 }; // zone de tracé (calculée)
let echelle = { maxi: 500, pas: 100 }; // échelle verticale
let choixCourant = 'moyenne'; // 'moyenne' ou une année
let valeursCibles = []; // valeurs à atteindre (12 nombres)
let valeursAffichees = []; // valeurs actuellement dessinées (animées)
let moyenneSerie = []; // moyenne pluriannuelle (12 nombres)
let modeCourbe = false; // false = histogramme, true = courbe
let melange = 0; // 0 = histogramme pur ... 1 = courbe pure (fondu)
let indexSelection = -1; // mois sélectionné (-1 = aucun)
/* ------------------------------------------------------------
calculerTailleCanevas()
But : largeur = celle de la boîte HTML ; hauteur = ratio de la
largeur, bornée (voir config.js).
Retour : {w, h}
------------------------------------------------------------ */
function calculerTailleCanevas() {
const boite = document.getElementById('conteneur-canvas');
const w = Math.max(240, boite ? boite.clientWidth : windowWidth);
const h = constrain(w * RATIO_HAUTEUR, HAUTEUR_MIN, HAUTEUR_MAX);
return { w: Math.floor(w), h: Math.floor(h) };
}
/* ------------------------------------------------------------
calculerZone()
But : (re)calculer le rectangle de tracé à partir de la taille
du canevas et des marges. À rappeler si le canevas change.
------------------------------------------------------------ */
function calculerZone() {
zone.x0 = MARGE_GAUCHE;
zone.y0 = MARGE_HAUT;
zone.x1 = width - MARGE_DROITE;
zone.y1 = height - MARGE_BAS;
zone.larg = zone.x1 - zone.x0;
zone.haut = zone.y1 - zone.y0;
zone.colonne = zone.larg / 12; // 12 mois -> 12 colonnes de même largeur
}
/* ------------------------------------------------------------
setup()
But : préparer le canevas, les données et les commandes HTML.
------------------------------------------------------------ */
function setup() {
const t = calculerTailleCanevas();
createCanvas(t.w, t.h).parent('conteneur-canvas');
calculerZone();
textFont(POLICE);
// Données
echelle = echelleVerticale();
moyenneSerie = serieMoyenne();
valeursCibles = serieChoisie(choixCourant);
// Au départ, toutes les barres valent 0 : elles vont "pousser" jusqu'à
// leur valeur grâce à l'animation (c'est l'animation d'apparition).
valeursAffichees = new Array(12).fill(0);
// Éléments HTML
remplirChoixAnnee();
remplirTableauHTML();
brancherCommandes();
mettreAJourTextes();
}
/* ------------------------------------------------------------
draw()
But : redessiner le graphique, couche après couche.
Comme au théâtre : on commence par le fond, on termine par
l'infobulle qui doit passer PAR-DESSUS tout.
------------------------------------------------------------ */
function draw() {
background(COULEURS.fond); // efface l'image précédente
mettreAJourAnimation();
dessinerSurvol(); // 1. colonne survolée (arrière-plan)
dessinerGrille(); // 2. grille + graduations
dessinerNomsMois(); // 3. noms des mois
// 4. Les données : on dessine les deux modes avec une opacité
// complémentaire (fondu enchaîné). On saute celui qui est invisible.
if (melange < 0.995) dessinerHistogramme(valeursAffichees, 1 - melange);
if (melange > 0.005) dessinerCourbe(valeursAffichees, melange);
// 5. Ligne de moyenne (inutile si on affiche déjà la moyenne)
if (choixCourant !== 'moyenne') dessinerLigneMoyenne(moyenneSerie);
dessinerLegende(libelleChoix(choixCourant), choixCourant !== 'moyenne');
dessinerMention();
dessinerInfobulle(valeursAffichees); // 6. infobulle tout en haut
}
/* ------------------------------------------------------------
mettreAJourAnimation()
But : rapprocher les valeurs affichées de leurs cibles, et le
mélange de son état cible.
lerp(a, b, t) : renvoie un nombre entre a et b.
t = 0 -> a ; t = 1 -> b ; t = 0,1 -> 10 % du chemin de a vers b.
En répétant lerp() à chaque image avec la valeur actuelle comme
point de départ, on obtient un mouvement qui ralentit en
approchant du but (effet "amorti" très naturel).
deltaTime = temps (en ms) écoulé depuis l'image précédente.
On s'en sert pour que la vitesse soit LA MÊME sur un téléphone
lent (30 images/s) ou un ordinateur rapide (60 images/s) :
facteur = 1 - VITESSE^(temps écoulé en secondes)
------------------------------------------------------------ */
function mettreAJourAnimation() {
const dt = Math.min(deltaTime, 100); // on borne : évite un saut après un onglet en veille
const k = 1 - Math.pow(VITESSE_ANIMATION, dt / 1000);
for (let i = 0; i < 12; i++) {
valeursAffichees[i] = lerp(valeursAffichees[i], valeursCibles[i], k);
}
const km = 1 - Math.pow(VITESSE_MELANGE, dt / 1000);
melange = lerp(melange, modeCourbe ? 1 : 0, km);
if (Math.abs(melange - (modeCourbe ? 1 : 0)) < 0.002) melange = modeCourbe ? 1 : 0; // on "colle" à la cible
}
/* ------------------------------------------------------------
windowResized()
But : adapter le graphique à la nouvelle taille de la fenêtre.
------------------------------------------------------------ */
function windowResized() {
const t = calculerTailleCanevas();
resizeCanvas(t.w, t.h);
calculerZone();
}
/* ------------------------------------------------------------
remplirChoixAnnee()
But : fabriquer les <option> de la liste déroulante à partir
des données (rien à écrire à la main dans le HTML).
------------------------------------------------------------ */
function remplirChoixAnnee() {
const liste = document.getElementById('choix-annee');
let html = '<option value="moyenne">' + libelleChoix('moyenne') + '</option>';
for (const a of listeAnnees()) {
html += '<option value="' + a + '">' + a + '</option>';
}
liste.innerHTML = html;
liste.value = choixCourant;
}
/* ------------------------------------------------------------
brancherCommandes()
But : relier la liste déroulante et le bouton à notre code
avec addEventListener (écouteurs d'événements).
------------------------------------------------------------ */
function brancherCommandes() {
document.getElementById('choix-annee').addEventListener('change', function (e) {
choixCourant = e.target.value; // 'moyenne' ou '2023'...
valeursCibles = serieChoisie(choixCourant); // les barres vont s'animer vers ces valeurs
mettreAJourTextes();
});
document.getElementById('bouton-mode').addEventListener('click', function () {
modeCourbe = !modeCourbe; // ! inverse un booléen
mettreAJourTextes();
});
}
/* ------------------------------------------------------------
mettreAJourTextes()
But : synchroniser les textes HTML (bouton, résumé) et la
description accessible du canevas avec l'état courant.
------------------------------------------------------------ */
function mettreAJourTextes() {
const bouton = document.getElementById('bouton-mode');
bouton.textContent = modeCourbe ? 'Afficher l\'histogramme' : 'Afficher la courbe';
bouton.setAttribute('aria-pressed', modeCourbe ? 'true' : 'false');
const resume = texteResume(choixCourant);
document.getElementById('resume').textContent = resume;
// describe() : texte alternatif du canevas, lu par les lecteurs d'écran.
describe((modeCourbe ? 'Courbe' : 'Histogramme') + ' de la pluviométrie mensuelle. ' + resume);
}