import { Bar, BarChart, CartesianGrid, Cell, Legend, Line, LineChart, Pie, PieChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts' import type { ChartType, PromqlSeries } from '../lib/api' /// Renders a label combination as a series key/legend string, e.g. `{"path":"/a"}` -> `path=/a`; no labels renders as `value`. export function seriesLabel(labels: Record): string { const entries = Object.entries(labels) if (entries.length === 0) return 'value' return entries.map(([k, v]) => `${k}=${v}`).join(', ') } // Fixed, small categorical palette to avoid a colour-scale dependency; cycles if there are more series than colours. const COLORS = ['#38bdf8', '#f472b6', '#a78bfa', '#4ade80', '#fbbf24', '#fb7185', '#2dd4bf', '#f97316'] // Merges series into one row-per-timestamp dataset (recharts wants a single array keyed by series name); a timestamp missing from a series is left undefined there, which recharts skips when drawing that line. function toLineData(series: PromqlSeries[]): { rows: Record[]; keys: string[] } { const keys = series.map((s) => seriesLabel(s.labels)) const byTimestamp = new Map>() series.forEach((s, i) => { const key = keys[i] for (const [ts, value] of s.points) { let row = byTimestamp.get(ts) if (!row) { row = { timestamp_ms: ts } byTimestamp.set(ts, row) } row[key] = value } }) const rows = Array.from(byTimestamp.values()).sort( (a, b) => (a.timestamp_ms as number) - (b.timestamp_ms as number), ) return { rows, keys } } function formatTimestamp(ts: number): string { const date = new Date(ts) return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }) } // Bar/pie need a single scalar per series from a time series: bar uses the latest point (current snapshot), pie uses the sum (share of total volume). function latestValue(series: PromqlSeries): number { if (series.points.length === 0) return 0 return series.points[series.points.length - 1][1] } function sumValue(series: PromqlSeries): number { return series.points.reduce((total, [, value]) => total + value, 0) } export function PanelChart({ series, chartType }: { series: PromqlSeries[]; chartType: ChartType }) { if (series.length === 0) { return
No data in range.
} if (chartType === 'line') { const { rows, keys } = toLineData(series) return ( formatTimestamp(Number(ts))} contentStyle={{ background: 'var(--c-surface-1)', border: '1px solid var(--c-border-soft)', fontSize: 12 }} /> {keys.map((key, i) => ( ))} ) } if (chartType === 'bar') { const rows = series.map((s) => ({ name: seriesLabel(s.labels), value: latestValue(s) })) return ( {rows.map((_, i) => ( ))} ) } const rows = series.map((s) => ({ name: seriesLabel(s.labels), value: sumValue(s) })) const total = rows.reduce((sum, r) => sum + r.value, 0) if (total === 0) { return
No non-zero values in range.
} return ( {rows.map((_, i) => ( ))} { const num = typeof value === 'number' ? value : Number(value ?? 0) return `${num} (${((num / total) * 100).toFixed(1)}%)` }} contentStyle={{ background: 'var(--c-surface-1)', border: '1px solid var(--c-border-soft)', fontSize: 12 }} /> ) }