{% macro donut(pct, size, stroke, bg, fg, label) %} {# Clamp entre 0 et 100 manuellement #} {% set raw = pct ?? 0 %} {% set clamped = raw < 0 ? 0 : (raw > 100 ? 100 : raw) %} {% set r = (size - stroke) / 2 %} {% set dash = (clamped / 100) * (2 * 3.14159 * r) %} {% endmacro %} {# === Double donut (E/A et D/E) avec légende standardisée === #} {% macro double_donut(allocated, committed, disbursed, size, showLegend=true, showPctInLegend=true) %} {% import _self as M %} {# -- Palette KPLAN (cohérente avec les autres graphiques) -- #} {% set COL_OUTER = '#ff5b5b' %} {# Engagé (E) #} {% set COL_INNER = '#10C469' %} {# Décaissé (D) #} {% set BG_OUTER = '#ffbc00' %} {% set BG_INNER = '#eaeaea' %} {# -- Dimensions -- #} {% set size = size|default(80) %} {% set stroke = (size * 0.22)|round(0, 'floor') %} {# -- Safe divide + clamp -- #} {% set E_over_A = (allocated and allocated > 0) ? (100.0 * (committed|default(0)) / allocated) : 0 %} {% set D_over_E = (committed and committed > 0) ? (100.0 * (disbursed|default(0)) / committed) : 0 %} {% set outerPct = E_over_A < 0 ? 0 : (E_over_A > 100 ? 100 : E_over_A) %} {% set innerPct = D_over_E < 0 ? 0 : (D_over_E > 100 ? 100 : D_over_E) %} {# -- Mise en page : donut + légende à droite -- #} {% set legendW = showLegend ? 120 : 0 %} {% set totalW = size + (showLegend ? legendW + 8 : 0) %} {% set totalH = size %} {# Donut externe: Engagé / Alloué #} {{ M.donut(outerPct, size, stroke, BG_OUTER, COL_OUTER) }} {# Donut interne (imbriqué): Décaissé / Engagé #} {# Échelle et translation pour centrer à l’intérieur du donut externe #} {{ M.donut(innerPct, size, stroke, BG_INNER, COL_INNER) }} {# Légende standardisée (mêmes libellés/couleurs) #} {% if showLegend %} {% set lx = size + 8 %} {% set ly1 = 16 %} {% set ly2 = 32 %} E / A{% if showPctInLegend %} — {{ outerPct|round(0) }}%{% endif %} D / E{% if showPctInLegend %} — {{ innerPct|round(0) }}%{% endif %} {% endif %} {% endmacro %} {% macro bar_pair(allocated, committed, disbursed) %} {% set A = (allocated ?? 0) %} {% set C = (committed ?? 0) %} {% set D = (disbursed ?? 0) %} {% set w = 300 %}{% set h = 14 %} {% set cW = A > 0 ? (C/A * w) : 0 %} {% set dW = A > 0 ? (D/A * w) : 0 %} {% set cLabelX = cW + 6 > w - 6 ? w - 6 : cW + 6 %} {% set dLabelX = dW + 6 > w - 6 ? w - 6 : dW + 6 %} Engagé Décaissé {% endmacro %} {% macro spark(values, w, h, variant='auto', strokeWidth=1.6, showDot=true) %} {% set w = w|default(120) %} {% set h = h|default(36) %} {% set v = values|default([]) %} {# Palette via CSS variables #} {% set colorPrimary = 'var(--primary-color)' %} {# vert #} {% set colorSecondary = 'var(--secondary-color)' %} {# jaune #} {% set colorAccent = 'var(--accent-color)' %} {# rouge #} {% set colorSuccess = 'var(--success-color)' %} {% set colorWarning = 'var(--warning-color)' %} {% set colorDanger = 'var(--danger-color)' %} {% set colorMuted = 'var(--border-dark)' %} {# Choix couleur #} {% set strokeColor = variant == 'primary' ? colorPrimary : variant == 'secondary' ? colorSecondary : variant == 'accent' ? colorAccent : variant == 'success' ? colorSuccess : variant == 'warning' ? colorWarning : variant == 'danger' ? colorDanger : null %} {% if v is empty %} {% else %} {% set sorted = v|sort %} {% set minv = sorted|first %} {% set maxv = sorted|last %} {% set span = (maxv - minv) == 0 ? 1 : (maxv - minv) %} {% set n = v|length %} {# Couleur auto selon tendance si variant='auto' #} {% if strokeColor is null %} {% set trend = (v|last) - (v|first) %} {% set strokeColor = trend > 0 ? colorSuccess : (trend < 0 ? colorDanger : colorSecondary) %} {% endif %} {% if n < 2 %} {% set y = 3 + (1 - ((v|first - minv)/span)) * (h - 6) %} {% if showDot %} {% endif %} {% else %} {% set step = (w - 6) / (n - 1) %} {% set pts = [] %} {% for i, val in v %} {% set x = 3 + i * step %} {% set y = 3 + (1 - ((val - minv)/span)) * (h - 6) %} {% set pts = pts|merge([x ~ ',' ~ y]) %} {% endfor %} {% set lastX = 3 + (n - 1) * step %} {% set lastY = 3 + (1 - ((v|last - minv)/span)) * (h - 6) %} {# ligne guide (optionnelle) — gris neutre pour la lisibilité #} {% if showDot %} {% endif %} {% endif %} {% endif %} {% endmacro %} {% macro spark_area(values, w, h, variant='auto', strokeWidth=1.6, showDot=true) %} {% set w = w|default(140) %} {% set h = h|default(44) %} {% set v = values|default([]) %} {% set colorPrimary = 'var(--primary-color)' %} {# vert #} {% set colorSecondary = 'var(--secondary-color)' %} {# jaune #} {% set colorAccent = 'var(--accent-color)' %} {# rouge #} {% set colorSuccess = 'var(--success-color)' %} {% set colorWarning = 'var(--warning-color)' %} {% set colorDanger = 'var(--danger-color)' %} {% set colorMuted = 'var(--border-dark)' %} {% set strokeColor = variant == 'primary' ? colorPrimary : variant == 'secondary' ? colorSecondary : variant == 'accent' ? colorAccent : variant == 'success' ? colorSuccess : variant == 'warning' ? colorWarning : variant == 'danger' ? colorDanger : null %} {% if v is empty %} {% else %} {% set sorted = v|sort %} {% set minv = sorted|first %} {% set maxv = sorted|last %} {% set span = (maxv - minv) == 0 ? 1 : (maxv - minv) %} {% set n = v|length %} {# Couleur auto si non forcée #} {% if strokeColor is null %} {% set trend = (v|last) - (v|first) %} {% set strokeColor = trend > 0 ? colorSuccess : (trend < 0 ? colorDanger : colorSecondary) %} {% endif %} {% set step = n < 2 ? (w - 6) : (w - 6) / (n - 1) %} {% set pts = [] %} {% for i, val in v %} {% set x = 3 + i * step %} {% set y = 3 + (1 - ((val - minv)/span)) * (h - 6) %} {% set pts = pts|merge([x ~ ',' ~ y]) %} {% endfor %} {% set lastX = 3 + (n - 1) * step %} {% set lastY = 3 + (1 - ((v|last - minv)/span)) * (h - 6) %} {% set baseY = h - 3 %} {% set areaPts = (pts|join(' ')) ~ ' ' ~ lastX ~ ',' ~ baseY ~ ' ' ~ 3 ~ ',' ~ baseY %} {# id de dégradé simple pour éviter les collisions courantes #} {% set gid = 'grad-' ~ w ~ '-' ~ h ~ '-' ~ n %} {# Utilise la même couleur que le trait ; on joue sur l’opacité #} {# Ligne (courbe) #} {% if n == 1 %} {% set y = 3 + (1 - ((v|first - minv)/span)) * (h - 6) %} {% if showDot %} {% endif %} {% else %} {# Aire sous la courbe #} {# Trait #} {% if showDot %} {% endif %} {% endif %} {% endif %} {% endmacro %} {% macro gantt_svg(activities, width = 794, height = 200, BAR_HEIGHT = 10, ROW_GAP = 6) %} {% if activities is empty %}
Aucune activité avec des dates définies
{% else %} {# ----- Paramètres dérivés ----- #} {% set ROW_SPACING = BAR_HEIGHT + ROW_GAP %} {% set actLen = activities|length %} {% set rows = actLen < 8 ? actLen : 8 %} {% set dynamicHeight = 80 + (rows * ROW_SPACING) %} {% set H = height > dynamicHeight ? height : dynamicHeight %} {# ----- Recherche min/max via comparaisons (sans min/max) ----- #} {% set minTs = null %} {% set maxTs = null %} {% for activity in activities %} {% if activity.startDate %} {% set ts = activity.startDate|date('U') %} {% set minTs = minTs is null ? ts : (ts < minTs ? ts : minTs) %} {% endif %} {% if activity.endDate %} {% set te = activity.endDate|date('U') %} {% set maxTs = maxTs is null ? te : (te > maxTs ? te : maxTs) %} {% endif %} {% endfor %} {# Fallback si pas de dates #} {% if minTs is null or maxTs is null %} {% set fallbackMin = ('now'|date('Y-m-01'))|date('U') %} {% set fallbackMax = ('now'|date_modify('+3 months')|date('Y-m-d'))|date('U') %} {% set minTs = minTs is null ? fallbackMin : minTs %} {% set maxTs = maxTs is null ? fallbackMax : maxTs %} {% endif %} {% set totalDuration = maxTs - minTs %} {% set today = "now"|date('U') %} {% set graphWidth = width - 160 %} {% set todayPosition = totalDuration > 0 ? (((today - minTs) / totalDuration) * graphWidth)|round(2) + 160 : 160 %} {# Couleurs par statut #} {% set colorMap = { 'completed': '#10c469', 'in_progress': '#4f46e5', 'delayed': '#ef4444', 'not_started': '#94a3b8' } %} {# Titre #} Timeline des activités {# Ligne de temps #} {# Marqueur du jour #} Aujourd'hui {# Grille (5 repères) #} {% for i in 0..4 %} {% set xPos = 160 + (i * (graphWidth / 4)) %} {% set intervalTs = minTs + (i * (totalDuration / 4)) %} {{ intervalTs|date('M')|trans }} {% endfor %} {# Activités (max 8) #} {% for activity in activities|slice(0, 8) %} {% if activity.startDate and activity.endDate %} {% set sTs = activity.startDate|date('U') %} {% set eTs = activity.endDate|date('U') %} {% set startPos = 160 + (((sTs - minTs) / totalDuration) * graphWidth)|round(2) %} {% set endPos = 160 + (((eTs - minTs) / totalDuration) * graphWidth)|round(2) %} {% set activityWidth = endPos - startPos %} {% set activityWidth = activityWidth < 4 ? 4 : activityWidth %} {% set color = colorMap[activity.status|default('not_started')] ?? '#94a3b8' %} {% set yPos = 50 + (loop.index0 * ROW_SPACING) %} {# Label gauche #} {{ activity.name|slice(0, 20) }}{% if activity.name|length > 20 %}...{% endif %} {# Barre #} {# Progression #} {% set progressWidth = activityWidth * (activity.progress|default(0) / 100) %} {% if progressWidth > 0 %} {% endif %} {# % progression #} {% if activity.progress|default(0) > 0 %} {{ activity.progress|default(0) }}% {% endif %} {% endif %} {% endfor %} {# Années min/max (dérivées des timestamps) #} {{ minTs|date('Y') }} {{ maxTs|date('Y') }} {% endif %} {% endmacro %}