Files
g1flux/src/components/Sparkline.tsx
T
syoul 575ca7a1fc
ci/woodpecker/push/woodpecker Pipeline was successful
feat: raccourcis clavier, URL partageable, sparkline, recherche identité
- Raccourcis clavier : ←/→ (frames), Espace (play/pause), Échap
  (quitter animation/fermer info), H (basculer heatmap↔flux)
- URL partageable : ?period=7&view=flow&city=Paris — état restauré
  au chargement et mis à jour sans rechargement (history.replaceState)
- Sparkline : mini bar-chart SVG dans le StatsPanel montrant l'activité
  sur la période (données déjà en mémoire, aucune requête)
- Recherche identité : champ flottant (⌕) acceptant un nom Ğ1 ou une
  clé g1…, résout via Subsquid + Cesium+, bascule en vue flux et
  met la ville en focus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 12:28:58 +01:00

67 lines
1.8 KiB
TypeScript

import { useMemo } from 'react';
interface SparklineProps {
timestamps: number[];
periodDays: number;
}
/**
* Mini bar-chart SVG affichant l'activité journalière sur la période.
* Utilise les timestamps déjà en mémoire — aucune requête supplémentaire.
*/
export function Sparkline({ timestamps, periodDays }: SparklineProps) {
const buckets = useMemo(() => {
if (timestamps.length === 0) return [];
const n = periodDays === 1 ? 24 : Math.min(periodDays, 30);
const now = Date.now();
const start = now - periodDays * 864e5;
const step = (periodDays * 864e5) / n;
const counts = new Array(n).fill(0);
for (const ts of timestamps) {
const i = Math.floor((ts - start) / step);
if (i >= 0 && i < n) counts[i]++;
}
return counts;
}, [timestamps, periodDays]);
if (buckets.length === 0) return null;
const n = buckets.length;
const max = Math.max(...buckets, 1);
const W = 100;
const H = 32;
const barW = W / n;
const gap = barW * 0.18;
return (
<div className="space-y-1">
<svg
viewBox={`0 0 ${W} ${H}`}
preserveAspectRatio="none"
className="w-full h-8"
aria-hidden="true"
>
{buckets.map((count, i) => {
const h = Math.max(1, (count / max) * H);
return (
<rect
key={i}
x={i * barW + gap / 2}
y={H - h}
width={barW - gap}
height={h}
fill="#d4a843"
opacity={0.25 + 0.75 * (count / max)}
rx={0.5}
/>
);
})}
</svg>
<div className="flex justify-between text-[10px] text-[#4b5563]">
<span>{periodDays === 1 ? '0h' : 'J-' + periodDays}</span>
<span>{periodDays === 1 ? 'maintenant' : 'aujourd\'hui'}</span>
</div>
</div>
);
}