/* 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 ( {fill && } ); } /* ---- 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 (
setHover(null)} onMouseMove={e=>{const r=e.currentTarget.getBoundingClientRect();const x=e.clientX-r.left; let i=Math.round((x-padL)/innerW*(labels.length-1)); i=Math.max(0,Math.min(labels.length-1,i)); setHover(i);}}> {Array.from({length:ticks+1}).map((_,i)=>{ const v=min+(rng*i/ticks); const y=Y(v); return {fmt?fmt(v):Math.round(v)} ; })} {labels.map((l,i)=>{l})} {series.map((s,si)=>{ const d=s.data.map((v,i)=>(i?'L':'M')+X(i).toFixed(1)+' '+Y(v).toFixed(1)).join(' '); const len= si===0? 1400:1400; if(s.dashed){ return ; } const area=d+` L ${X(labels.length-1)} ${padT+innerH} L ${X(0)} ${padT+innerH} Z`; const gid='lg'+si; return {s.fill && } {s.fill && } ; })} {hover!=null && } {hover!=null && series.filter(s=>!s.dashed).map((s,si)=>( ))} {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)=>{ const frac=d.value/sum; const len=frac*circ; const off=acc*circ; acc+=frac; return ; })}
{total}
{centerLabel}
{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 });