/* Gráficos SVG leves — Maximiano */
const { useState, useEffect, useRef, useMemo } = React;
function useMounted(){ return true; } /* charts render at visible end-state immediately — bulletproof against throttled timers; motion comes from interactions (hover tooltips, hover states, progress widths) */
/* ---- Sparkline ---- */
function Sparkline({data,w=120,h=40,color='#b68b4c',fill=true}){
const min=Math.min(...data),max=Math.max(...data),rng=(max-min)||1;
const pts=data.map((v,i)=>[ (i/(data.length-1))*w, h-4-((v-min)/rng)*(h-8) ]);
const d=pts.map((p,i)=>(i?'L':'M')+p[0].toFixed(1)+' '+p[1].toFixed(1)).join(' ');
const area=d+` L ${w} ${h} L 0 ${h} Z`;
const id='sp'+Math.random().toString(36).slice(2,7);
return (
);
}
/* ---- Line chart com comparativo ---- */
function LineChart({series, labels, height=240, fmt}){
const mounted=useMounted();
const ref=useRef(null);
const [w,setW]=useState(640);
useEffect(()=>{
if(!ref.current) return;
const ro=new ResizeObserver(es=>setW(es[0].contentRect.width));
ro.observe(ref.current); return ()=>ro.disconnect();
},[]);
const padL=58, padR=14, padT=14, padB=28;
const all=series.flatMap(s=>s.data);
let max=Math.max(...all), min=Math.min(...all);
const span=max-min; max+=span*0.12; min-=span*0.12; min=Math.max(0,min);
const rng=(max-min)||1;
const innerW=w-padL-padR, innerH=height-padT-padB;
const X=i=>padL+(i/(labels.length-1))*innerW;
const Y=v=>padT+innerH-((v-min)/rng)*innerH;
const ticks=4;
const [hover,setHover]=useState(null);
return (
{hover!=null && (
{labels[hover]} / 2026
{series.map((s,i)=>(
{s.name}
{fmt?fmt(s.data[hover]):s.data[hover]}
))}
)}
);
}
/* ---- Bar chart vertical ---- */
function BarChart({data, height=220, fmt, accent='#2d3245'}){
const mounted=useMounted();
const max=Math.max(...data.map(d=>d.value))*1.1;
return (
{data.map((d,i)=>{
const h=(d.value/max)*100;
return (
{fmt?fmt(d.value):d.value}
{d.label}
);
})}
);
}
/* ---- Donut ---- */
function Donut({data, size=180, thickness=26, total, fmt, centerLabel}){
const mounted=useMounted();
const sum=data.reduce((a,d)=>a+d.value,0);
const r=(size-thickness)/2, c=size/2, circ=2*Math.PI*r;
let acc=0;
return (
{data.map((d,i)=>(
{d.label}
{fmt?fmt(d.value):d.value}
))}
);
}
/* ---- Gauge (meta x realizado) ---- */
function Gauge({value, max, size=180, fmt}){
const mounted=useMounted();
const pct=Math.min(value/max,1.15);
const r=size/2-16, c=size/2, circ=Math.PI*r; // semicircle
const filled=Math.min(pct,1)*circ;
const over=pct>1;
return (
{Math.round(pct*100)}%
da meta {fmt?`· ${fmt(max)}`:''}
);
}
Object.assign(window,{ Sparkline, LineChart, BarChart, Donut, Gauge, useMounted });