Merge ticket 046: chart legend squashing

This commit is contained in:
Hugh Kaznowski
2026-09-06 01:44:18 +01:00
+18 -2
View File
@@ -25,6 +25,22 @@ export function seriesLabel(labels: Record<string, string>): string {
// 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']
// One less than the palette size: above this many series, keep the top MAX_KEPT_SERIES (by value) and squash the
// rest into a single "Other" bucket, so the squashed range never exceeds COLORS.length and never repeats a colour.
const MAX_KEPT_SERIES = COLORS.length - 1
// Sorts rows by value descending and, if there are more than MAX_KEPT_SERIES, keeps the top MAX_KEPT_SERIES and
// collapses the remainder into a single "Other" row summing their values. Below the threshold, rows are returned
// unchanged so panels with few series keep their existing bar/slice order and colour assignment.
function squashRows(rows: { name: string; value: number }[]): { name: string; value: number }[] {
if (rows.length <= MAX_KEPT_SERIES) return rows
const sorted = [...rows].sort((a, b) => b.value - a.value)
const kept = sorted.slice(0, MAX_KEPT_SERIES)
const rest = sorted.slice(MAX_KEPT_SERIES)
const otherValue = rest.reduce((sum, r) => sum + r.value, 0)
return [...kept, { name: `Other (${rest.length})`, value: otherValue }]
}
// 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<string, number | string>[]; keys: string[] } {
const keys = series.map((s) => seriesLabel(s.labels))
@@ -101,7 +117,7 @@ export function PanelChart({ series, chartType }: { series: PromqlSeries[]; char
}
if (chartType === 'bar') {
const rows = series.map((s) => ({ name: seriesLabel(s.labels), value: latestValue(s) }))
const rows = squashRows(series.map((s) => ({ name: seriesLabel(s.labels), value: latestValue(s) })))
return (
<ResponsiveContainer width="100%" height={220}>
<BarChart data={rows} margin={{ top: 8, right: 16, left: 0, bottom: 0 }}>
@@ -119,7 +135,7 @@ export function PanelChart({ series, chartType }: { series: PromqlSeries[]; char
)
}
const rows = series.map((s) => ({ name: seriesLabel(s.labels), value: sumValue(s) }))
const rows = squashRows(series.map((s) => ({ name: seriesLabel(s.labels), value: sumValue(s) })))
const total = rows.reduce((sum, r) => sum + r.value, 0)
if (total === 0) {
return <div className="flex h-48 items-center justify-center text-xs text-text-faint">No non-zero values in range.</div>