From e6e79dfe06249016f05c2f5e680d6f5591fa5f04 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 1 Aug 2026 09:46:15 +0000 Subject: [PATCH] Carte tendance : desencombrer le graphique, il etait illisible La densite etait le vrai probleme. La version precedente imprimait les 24 valeurs (12 quantites + 12 magasins) et titrait chaque bande DANS le SVG. Resultat sur ecran : les titres passaient par-dessus les courbes, les nombres des extremites etaient coupes par le bord du cadre, et les etiquettes des magasins se collaient au trait pointille. - Les titres sortent du dessin : legende HTML au-dessus, plus aucun recouvrement possible quelle que soit la forme des courbes. - La bande des magasins ne porte plus que sa DERNIERE valeur ; son niveau se lit a la forme et au maximum rappele dans la legende. - Les etiquettes des bords sont ancrees vers l'interieur (start / end au lieu de middle) : plus rien n'est tronque. - Chaque mois expose une infobulle native avec les valeurs exactes, ce qui rend inutile d'imprimer tous les nombres. - Cadre elargi (520 de large, marge haute pour les etiquettes du sommet) et modale passee en max-w-xl. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu --- src/features/grid/components/heatmap-grid.tsx | 2 +- .../grid/components/network-charts.tsx | 127 ++++++++++++------ 2 files changed, 86 insertions(+), 43 deletions(-) diff --git a/src/features/grid/components/heatmap-grid.tsx b/src/features/grid/components/heatmap-grid.tsx index b604cdc..325b11d 100644 --- a/src/features/grid/components/heatmap-grid.tsx +++ b/src/features/grid/components/heatmap-grid.tsx @@ -203,7 +203,7 @@ function NetworkMonthlyModal({ row, onClose }: { row: ProductRow; onClose: () => // hausse due à l'élargissement de la diffusion d'une vraie accélération. const magasins = computeStoresSeries(row.nbMagReseauByMonth); return ( - + {row.libelle1} diff --git a/src/features/grid/components/network-charts.tsx b/src/features/grid/components/network-charts.tsx index 5f0b434..776a1cc 100644 --- a/src/features/grid/components/network-charts.tsx +++ b/src/features/grid/components/network-charts.tsx @@ -81,10 +81,15 @@ export function storesColorFor(couleurQuantites: string): string { * passe AU-DESSUS de celle des quantités, ce qui se lit spontanément comme « il * y a plus de magasins que de ventes » — un contresens. * - * Les deux séries sont donc tracées dans deux bandes distinctes, alignées sur le - * même axe des mois. On compare ce qui est comparable — la forme de l'une avec - * la forme de l'autre, mois par mois — sans jamais suggérer un rapport de - * grandeur entre elles. + * ⚠️ LA DENSITÉ EST LE VRAI ENNEMI. Une première version imprimait les 24 + * valeurs (12 quantités + 12 magasins) et titrait chaque bande dans le SVG : + * titres par-dessus les courbes, nombres des extrémités coupés par le bord, + * étiquettes des magasins collées au trait. Illisible. D'où : + * - les titres sortent du dessin (HTML au-dessus, plus aucun recouvrement) ; + * - la bande des magasins ne porte plus que sa dernière valeur, son niveau se + * lisant à la forme et au maximum rappelé dans la légende ; + * - les étiquettes des bords sont ancrées vers l'intérieur, jamais tronquées ; + * - chaque mois expose une infobulle native avec les valeurs exactes. * * Lecture : quantités qui montent avec les magasins = élargissement de la * diffusion ; quantités qui montent à magasins constants = vraie accélération @@ -105,15 +110,14 @@ export function NetworkLineChart({ const avecMagasins = Array.isArray(stores) && stores.length === values.length && stores.length > 0; const n = values.length; - const W = 460; - const padL = 10, padR = 10, padB = 22; - const titreH = 12; // hauteur réservée au titre d'une bande - const bandeQ = 120; // bande des quantités - const bandeM = avecMagasins ? 46 : 0; // bande des magasins - const ecart = avecMagasins ? 18 : 0; - const hautQ = titreH + 10; // sommet de la bande des quantités - const hautM = hautQ + bandeQ + ecart + titreH; - const H = hautQ + bandeQ + ecart + (avecMagasins ? titreH + bandeM : 0) + padB; + const W = 520; + const padL = 14, padR = 14; + const hautQ = 16; // marge haute : place aux étiquettes du sommet + const bandeQ = 116; + const ecart = avecMagasins ? 12 : 0; + const bandeM = avecMagasins ? 44 : 0; + const moisH = 18; + const H = hautQ + bandeQ + ecart + bandeM + moisH; const plotW = W - padL - padR; // Les quantités Qlik peuvent être NÉGATIVES (retours supérieurs aux ventes @@ -126,29 +130,49 @@ export function NetworkLineChart({ const x = (i: number) => (n > 1 ? padL + (i / (n - 1)) * plotW : padL + plotW / 2); const y = (v: number) => hautQ + bandeQ - ((v - minQ) / etendueQ) * bandeQ; - const yM = (v: number) => hautM + bandeM - (v / maxM) * bandeM; + const yM = (v: number) => hautQ + bandeQ + ecart + bandeM - (v / maxM) * bandeM; const zero = y(0); + /** Aux extrémités, on ancre l'étiquette vers l'intérieur : sinon elle sort du cadre. */ + const ancrage = (i: number): "start" | "middle" | "end" => + i === 0 ? "start" : i === n - 1 ? "end" : "middle"; + const linePts = values.map((v, i) => `${x(i).toFixed(1)},${y(v).toFixed(1)}`).join(" "); const areaPts = `${padL},${zero.toFixed(1)} ${linePts} ${(padL + plotW).toFixed(1)},${zero.toFixed(1)}`; const storesColor = storesColorFor(color); const storePts = avecMagasins ? stores!.map((v, i) => `${x(i).toFixed(1)},${yM(v).toFixed(1)}`).join(" ") : ""; + const fmt = (v: number) => Math.round(v).toLocaleString("fr-FR"); return (
+ {/* Titres HORS du dessin : plus rien ne recouvre les courbes. */} +
+ + + Quantités vendues + · max {fmt(maxQ)} + + {avecMagasins && ( + + + Magasins vendeurs + · max {fmt(maxM)} + + )} +
{/* ─── Bande 1 : quantités ─────────────────────────────── */} - - QUANTITÉS VENDUES - {n > 1 && } {n > 1 && } @@ -158,12 +182,12 @@ export function NetworkLineChart({ = 0 ? y(v) - 6 : y(v) + 12} - textAnchor="middle" - fontSize={9} + textAnchor={ancrage(i)} + fontSize={8.5} fontWeight={700} fill="var(--text-primary)" > - {Math.round(v).toLocaleString("fr-FR")} + {fmt(v)} ))} @@ -171,15 +195,11 @@ export function NetworkLineChart({ {/* ─── Bande 2 : magasins vendeurs ─────────────────────── */} {avecMagasins && ( <> - - MAGASINS VENDEURS - · {maxM.toLocaleString("fr-FR")} au maximum - @@ -195,30 +215,53 @@ export function NetworkLineChart({ /> )} {stores!.map((v, i) => ( - - {/* point évidé : lisible même sans couleur */} - - - {Math.round(v).toLocaleString("fr-FR")} - - + // Point évidé : lisible même sans couleur (impression N&B, daltonisme). + ))} + {/* Seule la dernière valeur est écrite : le reste se lit à la forme. */} + + {fmt(stores![n - 1])} mag. + )} {/* ─── Axe des mois, commun aux deux bandes ────────────── */} {labels.map((lab, i) => ( - + {fmtMonthShort(lab)} ))} + + {/* Zones de survol : valeurs exactes du mois, sans encombrer le dessin. */} + {labels.map((lab, i) => ( + + + {`${fmtMonthShort(lab)} · ${fmt(values[i])} vendues${avecMagasins ? ` · ${fmt(stores![i])} magasins` : ""}`} + + + ))}
);