Squash chart bars/legend into top-N + Other for many-series panels
PanelChart's BarChart and PieChart rendered one bar/slice and legend row per PromqlSeries with no bound, so panels with many series wrapped the 8-color palette (repeating colors) and produced unreadable, unbounded legends. Add squashRows(), which sorts by value descending and collapses series beyond COLORS.length - 1 into a single "Other" bucket (sum of the rest), applied to both bar and pie chart data before render. Panels at or below the threshold are unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
85fb145be7
commit
8a8e3f60ec
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user