Compendium/Metrics & Telemetry/Charts/Bucket Distribution
#061
Bucket Distribution
A small histogram showing the spread of ICS scores across a team's spec inventory.
STABLE DE
#svg
#chart
#real-content
Live
// team inventoryn=32
0-2020-4040-6060-8080-100
137129
Spec
Intent
A single ICS score is a point estimate; a team's spec inventory has a distribution. This histogram surfaces whether the team has a fat tail of bad specs or a tight cluster of good ones.
Acceptance Criteria
[ ] Five buckets: 0-20, 20-40, 40-60, 60-80, 80-100
[ ] Threshold (60) marked by a vertical line
[ ] Bar heights proportional, mono labels under each
[ ] Negative: does NOT show absolute pixel heights — uses domain scale
Constraints
Pure SVG, no JS
Tabular-nums for counts
// bucket-distribution.astro
---
import type { CompendiumMeta } from '../../../data/types';
export const meta: CompendiumMeta = {
id: 61,
slug: 'bucket-distribution',
name: 'Bucket Distribution',
category: 'metrics-telemetry',
subcategory: 'charts',
pillar: 'de',
description: 'A small histogram showing the spread of ICS scores across a team\'s spec inventory.',
spec: `## Intent
A single ICS score is a point estimate; a team's spec inventory has a distribution. This histogram surfaces whether the team has a fat tail of bad specs or a tight cluster of good ones.
## Acceptance Criteria
- [ ] Five buckets: 0-20, 20-40, 40-60, 60-80, 80-100
- [ ] Threshold (60) marked by a vertical line
- [ ] Bar heights proportional, mono labels under each
- [ ] Negative: does NOT show absolute pixel heights — uses domain scale
## Constraints
- Pure SVG, no JS
- Tabular-nums for counts`,
tags: ['svg', 'chart', 'real-content'],
status: 'stable',
health: { ics: 85, a11y: 'aa', themed: true, animated: false },
};
const buckets = [
{ label: '0-20', count: 1 },
{ label: '20-40', count: 3 },
{ label: '40-60', count: 7 },
{ label: '60-80', count: 12 },
{ label: '80-100', count: 9 },
];
const max = Math.max(...buckets.map((b) => b.count));
const total = buckets.reduce((a, b) => a + b.count, 0);
const colorFor = (i: number) => i < 2 ? 'var(--color-danger)' : i === 2 ? 'var(--color-do)' : 'var(--color-success)';
---
<div class="w-full bg-paper text-black border-4 border-black">
<header class="flex items-baseline justify-between px-3 py-2 border-b-2 border-black">
<span class="font-mono text-[10px] uppercase tracking-widest opacity-60">// team inventory</span>
<span class="font-mono text-[10px] uppercase tracking-widest">n=<span class="font-black tabular-nums">{total}</span></span>
</header>
<div class="p-3">
<svg viewBox="0 0 100 60" preserveAspectRatio="none" class="w-full h-24 block" aria-label={`ICS distribution across ${total} specs`}>
{buckets.map((b, i) => {
const h = (b.count / max) * 50;
const x = i * 20 + 2;
return (
<rect x={x} y={56 - h} width="16" height={h} fill={colorFor(i)} stroke="var(--color-ink)" stroke-width="0.5" vector-effect="non-scaling-stroke" />
);
})}
<line x1="60" y1="0" x2="60" y2="56" stroke="var(--color-ink)" stroke-width="0.8" stroke-dasharray="2 2" vector-effect="non-scaling-stroke" />
<line x1="0" y1="56" x2="100" y2="56" stroke="var(--color-ink)" stroke-width="1" vector-effect="non-scaling-stroke" />
</svg>
<div class="grid grid-cols-5 mt-1 font-mono text-[9px] uppercase tracking-widest opacity-60 text-center">
{buckets.map((b) => <span>{b.label}</span>)}
</div>
<div class="grid grid-cols-5 mt-0.5 font-black text-[11px] tabular-nums text-center">
{buckets.map((b) => <span>{b.count}</span>)}
</div>
</div>
</div>