Lección 5 — Composer en calques

Des graphiques lisibles

8 min de lectura

Este contenido aún no está traducido. Se muestra la versión original.

Des graphiques lisibles

Un graphique d'infographie est lu en trois secondes, sur un téléphone, dans un fil d'actualité. Le graphique de la leçon 1 généré par Flash Image avait tout pour échouer : barres doubles, légende, axe gradué, couleurs par année, badge posé sur une barre. Ici, le graphique est construit par le code à partir des données, avec des règles qui tiennent sur une page.

Le graphique de l'affiche finale : barres triées, étiquettes directes, une seule barre mise en avant

Le mécanisme : de la question au tracé

flowchart LR
  Q["Question : quel site consomme le plus, et qui a le plus baissé ?"] --> F["Forme : barres horizontales"]
  F --> T["Tri décroissant"]
  T --> E["Échelle : zéro à gauche, longueur proportionnelle"]
  E --> L["Étiquettes directes : valeur + variation"]
  L --> A["Une mise en avant : plus forte baisse"]
  A --> C["Contrôle : taille minimale, contraste"]
ChoixPourquoiCe qu'on évite
Barres horizontalesles noms de sites se lisent sans rotationétiquettes à 45° sous des colonnes
Tri par valeur décroissantele classement se lit sans effortl'ordre du CSV, arbitraire
Zéro comme ligne de basela longueur dit la valeurun axe tronqué qui exagère les écarts
Étiquettes directesla valeur est au bout de la barreaxe gradué, quadrillage, aller-retour vers une légende
Une seule série2026, avec la variation en textedeux barres par site et une légende
Une mise en avantun message, une couleursix couleurs pour six sites

La mise en œuvre

files/src/infographie/mise_en_page.py
def graphique(faits, titre, fmt, x, y, w, h):
    sites = sorted(faits.sites, key=lambda s: s.conso_2026, reverse=True)
    pas = (y + h - y0) / len(sites)
    t = max(10, min(fmt.base, round(pas * 0.5)))          # taille des étiquettes, bornée par la base
    col = min(w * 0.38, max(police("bold", t).getlength(s.nom) for s in sites) + t)
    valeurs = [(fr_nombre(s.conso_2026), fr_pct(s.variation_pct, signe=True)) for s in sites]
    lv = max(police("bold", t).getlength(a) + police("regular", t).getlength("  " + b) for a, b in valeurs)
    lmax = w - col - lv - t                                # place réelle laissée aux barres
    vmax = max(s.conso_2026 for s in sites)
    for i, (s, (val, var)) in enumerate(zip(sites, valeurs)):
        long = max(2.0, lmax * s.conso_2026 / vmax)
        couleur = ACCENT if s is faits.meilleur_site else BARRE
        ...

Trois détails qui font la différence :

  1. La place des étiquettes est mesurée avant de tracer. lmax est la largeur disponible une fois réservées la colonne des noms et la plus longue étiquette de valeur. Aucune étiquette ne sort du panneau, quel que soit le format.
  2. La taille du texte dépend de la hauteur de ligne, plafonnée par la base du format. Si six barres n'ont pas la place, les étiquettes rétrécissent, et le contrôle lisibilite de la leçon 6 bloque sous le minimum du format au lieu de laisser passer un graphique illisible.
  3. La valeur et la variation sont deux textes distincts : la valeur en gras (1 655), la variation en encre secondaire (−23 %). Deux niveaux de lecture sans couleur supplémentaire.

La mise en avant ne repose pas sur la couleur seule

ACCENT (sarcelle) et BARRE (ardoise) ont chacune un bon contraste sur blanc, mais entre elles le rapport n'est que de 1,15:1 : leurs luminances sont presque égales. Un lecteur daltonien, ou une impression en niveaux de gris, ne les distingue pas. Le code ajoute donc un second codage :

files/src/infographie/mise_en_page.py
# la mise en avant ne repose pas sur la couleur seule (teal/ardoise : 1,15:1 entre elles) : étiquette en gras
graisse = "bold" if s is faits.meilleur_site else "regular"

« Hub Centre » est en gras. Le message survit à la perte de la couleur.

Le directeur financier préfère un axe qui commence à 500 MWh : « on voit mieux les différences ». Que répondez-vous ?

Pourquoi pas de légende ni d'axe gradué

Une seule série : le titre du graphique la nomme (par défaut « Consommation 2026 par site (MWh) »). Les valeurs sont écrites au bout des barres : un axe gradué répéterait l'information en moins précis. La ligne de base reste, en encre secondaire de 2 px : elle matérialise le zéro.

Le titre du graphique est proposé par le modèle (titre_graphique). C'est le seul texte du graphique qui ne vient pas directement des données ; il passe par les mêmes règles que le titre principal. Au run final, le modèle a écrit « Évolution de la consommation par site entre 2025 et 2026 » : acceptable, car les étiquettes de variation portent la comparaison avec 2025, mais un relecteur exigeant préférerait que le titre dise que les barres montrent 2026. C'est le genre de nuance qu'aucune règle automatique ne tranche.

Le CSV de l'an prochain contiendra 11 sites. Que se passe-t-il dans le format story, et quelle est la bonne décision ?

Ce qui casse en production

SymptômeCauseParade
Étiquette de valeur coupée au bord du panneaulongueur des barres calculée sur toute la largeurréserver la largeur de la plus longue étiquette (lv) avant de tracer
Deux sites « −23 % » dont un seul mis en avantégalité à l'arrondiassumer le départage au centième, ou mettre en avant les deux et l'écrire
Valeur négative (production nette d'un site solaire)barres horizontales partant de zéro vers la droitetraiter explicitement : barres divergentes, ou exclure avec une note
Barre invisible pour une petite valeurlongueur de moins d'un pixellongueur minimale de 2 px, valeur toujours écrite

Règle de décision

  • Barres horizontales triées pour comparer des catégories ; courbe pour une évolution dans le temps ; pas de camembert au-delà de trois parts.
  • Une couleur de mise en avant, toujours doublée d'un second codage (gras, annotation).
  • Si le graphique ne tient pas lisiblement dans un format, on retire de l'information, on ne réduit pas la police.

Exercice

Ajoutez au graphique une marque pour la consommation 2025 de chaque site (un trait vertical fin au bout de l'ancienne longueur), sans légende. Où placez-vous l'explication ?

Voir la solution

Dans la boucle, après la barre :

x25 = xb + lmax * s.conso_2025 / vmax2   # vmax2 = max(conso_2025, conso_2026) sur tous les sites
rects.append(Rect(x25 - 1, cy - epaisseur * 0.7, 2, epaisseur * 1.4, ENCRE_2))

Attention : l'échelle doit maintenant couvrir les valeurs 2025 (2 150 pour Hub Centre), sinon le trait sort du panneau. Et l'étiquette de valeur doit se placer après max(long, x25 - xb).

L'explication va dans le titre du graphique (« Consommation 2026 par site, trait : niveau 2025 ») plutôt que dans une légende séparée. Deux séries sans légende ne passent qu'avec ce texte explicite.

Résultat obtenu

Run final du 2026-10-15 21:06 : sur les quatre formats, le graphique passe les contrôles de contraste, de débordement et de lisibilité.

affiche  1240x1754  6 calques  0 bloquant(s)  0 alerte(s)
carre    1080x1080  6 calques  0 bloquant(s)  1 alerte(s)
story    1080x1920  6 calques  0 bloquant(s)  1 alerte(s)
linkedin 1200x627   6 calques  0 bloquant(s)  1 alerte(s)

Je sais faire

  • Choisir la forme de graphique à partir de la question posée
  • Construire des barres triées à zéro avec étiquettes directes
  • Doubler une mise en avant colorée d'un second codage
  • Retirer de l'information plutôt que réduire la police
Siguiente