diff --git a/frontend/src/pages/DonutPage.vue b/frontend/src/pages/DonutPage.vue index 328d6df..0464ea9 100644 --- a/frontend/src/pages/DonutPage.vue +++ b/frontend/src/pages/DonutPage.vue @@ -58,7 +58,8 @@ const R_CEILING_OUTER = 225 const R_CEILING_INNER = 200 const R_FLOOR_OUTER = 100 const R_FLOOR_INNER = 78 -const R_CENTER = 56 +const R_SOCIAL_LABEL = 67 // étiquettes des dimensions du plancher social (intérieur) +const R_CENTER = 50 /** Les 9 limites planétaires de Kate Raworth, placées autour de l'anneau extérieur. */ const PLANETARY_BOUNDARIES = [ @@ -73,6 +74,65 @@ const PLANETARY_BOUNDARIES = [ "Couche d'ozone", ] +/** Les 12 dimensions du plancher social de Kate Raworth, placées entre le centre et l'anneau intérieur. */ +const SOCIAL_FOUNDATIONS = [ + 'Alimentation', + 'Santé', + 'Éducation', + 'Revenu & travail', + 'Eau', + 'Énergie', + 'Liens sociaux', + 'Logement', + 'Égalité de genre', + 'Équité sociale', + 'Voix politique', + 'Paix & justice', +] + +/** + * Génère pour chaque label un petit arc de cercle (utilisé par ). + * Le texte épouse alors la courbure de l'anneau au lieu d'être posé en ligne droite. + * + * Astuce de lisibilité : on inverse le sens du path (CW vs CCW) selon la moitié + * du cercle pour que les labels du bas restent lisibles à l'endroit. Sans ça, + * les labels du bas seraient écrits à l'envers ou inversés (lus de droite à gauche). + * + * - Moitié haute (angles proches de 0°) : path va de θ-δ à θ+δ, sweep=1 (CW visuellement) + * → direction RIGHT au sommet → texte "left of right" = UP = OUTSIDE + * - Moitié basse (angles proches de 180°) : path va de θ+δ à θ-δ, sweep=0 (CCW visuellement) + * → direction RIGHT au bas → texte "left of right" = UP = vers le CENTRE + * (= lisible à l'endroit pour un lecteur fixe en face de l'écran) + * + * @param labels Liste des libellés + * @param radius Rayon de l'arc de chaque label + * @param idPrefix Préfixe pour générer les `id` des paths (chaque path doit être unique en DOM) + * @param halfSpanDeg Demi-largeur angulaire du path (= halfSpan ≈ 90 % du segment alloué au label) + */ +function arcLabelPaths(labels: readonly string[], radius: number, idPrefix: string, halfSpanDeg: number) { + const n = labels.length + const stepDeg = 360 / n + return labels.map((label, i) => { + const centerDeg = i * stepDeg + stepDeg / 2 + const startDeg = centerDeg - halfSpanDeg + const endDeg = centerDeg + halfSpanDeg + + // Top half ⇨ chemin CW (sweep=1) ; bottom half ⇨ chemin CCW (sweep=0). + const normalized = ((centerDeg % 360) + 360) % 360 + const isTopHalf = normalized <= 90 || normalized >= 270 + + const fromDeg = isTopHalf ? startDeg : endDeg + const toDeg = isTopHalf ? endDeg : startDeg + const sweep = isTopHalf ? 1 : 0 + + const [x1, y1] = pt(fromDeg, radius) + const [x2, y2] = pt(toDeg, radius) + const d = `M ${x1.toFixed(2)} ${y1.toFixed(2)} A ${radius} ${radius} 0 0 ${sweep} ${x2.toFixed(2)} ${y2.toFixed(2)}` + + return { label, pathId: `${idPrefix}-${i}`, d, key: i } + }) +} + /** deg → rad avec offset -90° (0° en haut). */ function rad(deg: number): number { return (deg - 90) * Math.PI / 180 @@ -139,27 +199,11 @@ const categoryLabels = computed(() => { }) }) -/** Étiquettes des 9 limites planétaires, autour de l'anneau extérieur. - * - * Texte tangent à la circonférence + flip 180° dans la moitié inférieure - * pour rester lisible (sinon le label serait à l'envers en bas du donut). - * - * - angle 0 (haut) : rotation 0 → texte horizontal lisible - * - angle 90 (droite) : rotation 90 → texte vertical lisible de haut en bas - * - angle 180 (bas) : rotation 180+180 = 0 → texte horizontal lisible - * - angle 270 (gauche) : rotation 270 → texte vertical lisible de bas en haut - */ -const planetaryLabels = computed(() => { - const n = PLANETARY_BOUNDARIES.length - const step = 360 / n - return PLANETARY_BOUNDARIES.map((label, i) => { - const angle = i * step + step / 2 - const [x, y] = pt(angle, R_PLANET_LABEL) - let rotation = angle - if (angle > 90 && angle < 270) rotation += 180 - return { label, x, y, rotation, key: i } - }) -}) +/** Paths d'arc pour les 9 limites planétaires (anneau extérieur). */ +const planetaryArcs = computed(() => arcLabelPaths(PLANETARY_BOUNDARIES, R_PLANET_LABEL, 'pb', 18)) + +/** Paths d'arc pour les 12 dimensions du plancher social (anneau intérieur). */ +const socialArcs = computed(() => arcLabelPaths(SOCIAL_FOUNDATIONS, R_SOCIAL_LABEL, 'sf', 14)) interface DotView { project: ProjectListRow @@ -274,22 +318,31 @@ function gotoProject(p: ProjectListRow) { + + + - + - - + {{ p.label }} - - + + @@ -311,7 +364,7 @@ function gotoProject(p: ProjectListRow) { - + {{ l.cat.name }} - - PLAFOND ÉCOLOGIQUE - PLANCHER SOCIAL + + + + + {{ s.label }} + + +