import React from "react";
/* Pure-SVG chart primitives ported from the design. */
export function Sparkline( { points, color = "#00b884", width = 100, height = 28, filled = true, strokeWidth = 1.5 } ) {
if ( ! points || points.length < 2 ) return null;
const max = Math.max( ...points ), min = Math.min( ...points ), range = max - min || 1;
const stepX = width / ( points.length - 1 );
const yOf = ( v ) => height - 2 - ( ( v - min ) / range ) * ( height - 4 );
const d = points.map( ( v, i ) => `${ i === 0 ? "M" : "L" }${ i * stepX },${ yOf( v ) }` ).join( " " );
const id = React.useId();
return (
);
}
export function AreaChart( { series, labels, height = 240, colors, formatY = ( v ) => v } ) {
const ref = React.useRef( null );
const [ w, setW ] = React.useState( 800 );
const baseId = React.useId().replace( /:/g, "" );
React.useEffect( () => {
if ( ! ref.current ) return;
const ro = new ResizeObserver( ( entries ) => setW( entries[ 0 ].contentRect.width ) );
ro.observe( ref.current );
return () => ro.disconnect();
}, [] );
const padL = 44, padR = 16, padT = 12, padB = 28;
const innerW = Math.max( 100, w - padL - padR );
const innerH = height - padT - padB;
const allValues = series.flatMap( ( s ) => s.data );
const max = Math.max( ...allValues, 1 );
const pow = Math.pow( 10, Math.floor( Math.log10( max ) ) );
const niceMax = Math.ceil( max / pow ) * pow;
const yMax = Math.max( niceMax, max * 1.1 );
const xOf = ( i ) => padL + ( i / Math.max( 1, labels.length - 1 ) ) * innerW;
const yOf = ( v ) => padT + innerH - ( v / yMax ) * innerH;
const yTicks = 4;
const ticks = Array.from( { length: yTicks + 1 }, ( _, i ) => ( yMax / yTicks ) * i );
const xTickStep = Math.max( 1, Math.ceil( labels.length / 8 ) );
const [ hover, setHover ] = React.useState( null );
return (
{ hover !== null && (
{ labels[ hover ] }
{ series.map( ( s, si ) => (
{ s.name }
{ formatY( s.data[ hover ] ) }
) ) }
) }
);
}
export function Donut( { segments, size = 180, thickness = 26, centerLabel, centerValue } ) {
const cx = size / 2, cy = size / 2, r = ( size - thickness ) / 2;
const total = segments.reduce( ( s, x ) => s + x.value, 0 ) || 1;
let acc = 0;
const arcs = segments.map( ( seg ) => {
const start = ( acc / total ) * Math.PI * 2 - Math.PI / 2;
acc += seg.value;
const end = ( acc / total ) * Math.PI * 2 - Math.PI / 2;
const large = end - start > Math.PI ? 1 : 0;
return {
d: `M${ cx + Math.cos( start ) * r },${ cy + Math.sin( start ) * r } A${ r },${ r } 0 ${ large } 1 ${ cx + Math.cos( end ) * r },${ cy + Math.sin( end ) * r }`,
color: seg.color
};
} );
return (
);
}
export function HBar( { items, max, color = "#00b884", format = ( v ) => v } ) {
const m = max || Math.max( ...items.map( ( i ) => i.value ), 1 );
return (
{ items.map( ( it, i ) => (
{ it.name }
{ format( it.value ) }
) ) }
);
}