Charts & Stats chartsvgzero-dependencysegmentedpart-to-wholestoragebreakdown
Segmented Bar Chart
A single tall part-to-whole bar with labelled segments and a breakdown list; clearer than a pie.
Live Preview
512 GB plan
402 GB
45% 24% 16% 9%
- Media 182 GB
- Backups 96 GB
- Databases 64 GB
- Logs 38 GB
- Other 22 GB
View data
| Segment | Value | Share |
|---|---|---|
| Media | 182 GB | 45.3% |
| Backups | 96 GB | 23.9% |
| Databases | 64 GB | 15.9% |
| Logs | 38 GB | 9.5% |
| Other | 22 GB | 5.5% |
Usage
Copy and paste this code into your Astro page:
astro
---
import { SegmentedBarChart } from 'astro-component-kit/charts';
---
<SegmentedBarChart
segments={[
{ label: 'Media', value: 182 },
{ label: 'Backups', value: 96 },
{ label: 'Other', value: 22 },
]}
suffix=" GB"
/> ---
import { SegmentedBarChart } from 'astro-component-kit/charts';
---
<SegmentedBarChart
segments={[
{ label: 'Media', value: 182 },
{ label: 'Backups', value: 96 },
{ label: 'Other', value: 22 },
]}
suffix=" GB"
/> Manual Installation
If you are not using the npm package, create a new file and paste the following code: src/components/lib/SegmentedBarChart.astro:
astro
---
/**
* SegmentedBarChart — A single tall part-to-whole bar with labelled segments
* and a breakdown list. A clearer alternative to a pie for ≤ 6 parts.
*
* @param {Array<{label: string, value: number}>} segments - Parts of the whole.
* @param {string} total - Optional formatted total for the header.
* @param {string} prefix - Value prefix.
* @param {string} suffix - Value suffix.
* @param {string[]} colors - Optional per-segment colors.
* @param {string} variant - Card style (glass|solid|ghost).
* @param {string} size - Density (sm|md|lg).
*/
import ChartFrame from './ChartFrame.astro';
import { colorAt, formatValue, sum } from './chart-utils';
interface Props {
title?: string;
subtitle?: string;
segments?: Array<{ label: string; value: number }>;
total?: string;
prefix?: string;
suffix?: string;
colors?: string[];
variant?: 'glass' | 'solid' | 'ghost';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
const {
title = 'Storage usage',
subtitle = '512 GB plan',
segments = [
{ label: 'Media', value: 182 },
{ label: 'Backups', value: 96 },
{ label: 'Databases', value: 64 },
{ label: 'Logs', value: 38 },
{ label: 'Other', value: 22 },
],
total,
prefix = '',
suffix = ' GB',
colors,
variant = 'glass',
size = 'md',
className = '',
} = Astro.props;
const tot = sum(segments.map((s) => s.value)) || 1;
const fmt = (v: number) => formatValue(v, prefix, suffix, false);
const segs = segments.slice(0, 8).map((s, i) => ({ ...s, pct: (s.value / tot) * 100, color: colorAt(i, colors) }));
---
<ChartFrame
title={title}
subtitle={subtitle}
value={total ?? fmt(tot)}
variant={variant}
size={size}
className={className}
table={{ headers: ['Segment', 'Value', 'Share'], rows: segs.map((s) => [s.label, fmt(s.value), `${s.pct.toFixed(1)}%`]) }}
>
<div class="sb__bar" role="list" aria-label={`${title} breakdown`}>
{segs.map((s) => (
<span class="sb__seg cf-mark" role="listitem" tabindex="0" style={`flex-grow:${s.value}; background:${s.color}`}
aria-label={`${s.label}: ${fmt(s.value)} (${s.pct.toFixed(1)}%)`}
data-tip-title={s.label} data-tip={`${s.color}::${fmt(s.value)} · ${s.pct.toFixed(1)}%`}>
{s.pct >= 9 && <span class="sb__pct">{s.pct.toFixed(0)}%</span>}
</span>
))}
</div>
<ul class="sb__list">
{segs.map((s) => (
<li>
<span class="sb__dot" style={`background:${s.color}`} aria-hidden="true"></span>
<span class="sb__name">{s.label}</span>
<span class="sb__val">{fmt(s.value)}</span>
</li>
))}
</ul>
</ChartFrame>
<style>
.sb__bar {
display: flex;
gap: 2px;
height: 44px;
border-radius: 8px;
overflow: hidden;
}
.sb__seg {
flex-basis: 0;
min-width: 4px;
display: flex;
align-items: center;
padding-left: 0.6rem;
transition: filter 0.2s ease;
}
.sb__seg:hover { filter: brightness(1.15); }
.sb__seg:focus-visible { outline: 2px solid var(--c-text-1, #fff); outline-offset: -2px; }
.sb__pct {
font-size: 0.75rem;
font-weight: 700;
color: #fff;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}
.sb__list {
list-style: none;
margin: 1rem 0 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 0.6rem 1.25rem;
}
.sb__list li { display: flex; align-items: center; gap: 0.5rem; font-size: 0.8125rem; }
.sb__dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
.sb__name { color: var(--c-text-2, #a3a3a3); }
.sb__val { margin-left: auto; color: var(--c-text-1, #fff); font-weight: 600; font-variant-numeric: tabular-nums; }
</style> ---
/**
* SegmentedBarChart — A single tall part-to-whole bar with labelled segments
* and a breakdown list. A clearer alternative to a pie for ≤ 6 parts.
*
* @param {Array<{label: string, value: number}>} segments - Parts of the whole.
* @param {string} total - Optional formatted total for the header.
* @param {string} prefix - Value prefix.
* @param {string} suffix - Value suffix.
* @param {string[]} colors - Optional per-segment colors.
* @param {string} variant - Card style (glass|solid|ghost).
* @param {string} size - Density (sm|md|lg).
*/
import ChartFrame from './ChartFrame.astro';
import { colorAt, formatValue, sum } from './chart-utils';
interface Props {
title?: string;
subtitle?: string;
segments?: Array<{ label: string; value: number }>;
total?: string;
prefix?: string;
suffix?: string;
colors?: string[];
variant?: 'glass' | 'solid' | 'ghost';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
const {
title = 'Storage usage',
subtitle = '512 GB plan',
segments = [
{ label: 'Media', value: 182 },
{ label: 'Backups', value: 96 },
{ label: 'Databases', value: 64 },
{ label: 'Logs', value: 38 },
{ label: 'Other', value: 22 },
],
total,
prefix = '',
suffix = ' GB',
colors,
variant = 'glass',
size = 'md',
className = '',
} = Astro.props;
const tot = sum(segments.map((s) => s.value)) || 1;
const fmt = (v: number) => formatValue(v, prefix, suffix, false);
const segs = segments.slice(0, 8).map((s, i) => ({ ...s, pct: (s.value / tot) * 100, color: colorAt(i, colors) }));
---
<ChartFrame
title={title}
subtitle={subtitle}
value={total ?? fmt(tot)}
variant={variant}
size={size}
className={className}
table={{ headers: ['Segment', 'Value', 'Share'], rows: segs.map((s) => [s.label, fmt(s.value), `${s.pct.toFixed(1)}%`]) }}
>
<div class="sb__bar" role="list" aria-label={`${title} breakdown`}>
{segs.map((s) => (
<span class="sb__seg cf-mark" role="listitem" tabindex="0" style={`flex-grow:${s.value}; background:${s.color}`}
aria-label={`${s.label}: ${fmt(s.value)} (${s.pct.toFixed(1)}%)`}
data-tip-title={s.label} data-tip={`${s.color}::${fmt(s.value)} · ${s.pct.toFixed(1)}%`}>
{s.pct >= 9 && <span class="sb__pct">{s.pct.toFixed(0)}%</span>}
</span>
))}
</div>
<ul class="sb__list">
{segs.map((s) => (
<li>
<span class="sb__dot" style={`background:${s.color}`} aria-hidden="true"></span>
<span class="sb__name">{s.label}</span>
<span class="sb__val">{fmt(s.value)}</span>
</li>
))}
</ul>
</ChartFrame>
<style>
.sb__bar {
display: flex;
gap: 2px;
height: 44px;
border-radius: 8px;
overflow: hidden;
}
.sb__seg {
flex-basis: 0;
min-width: 4px;
display: flex;
align-items: center;
padding-left: 0.6rem;
transition: filter 0.2s ease;
}
.sb__seg:hover { filter: brightness(1.15); }
.sb__seg:focus-visible { outline: 2px solid var(--c-text-1, #fff); outline-offset: -2px; }
.sb__pct {
font-size: 0.75rem;
font-weight: 700;
color: #fff;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}
.sb__list {
list-style: none;
margin: 1rem 0 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 0.6rem 1.25rem;
}
.sb__list li { display: flex; align-items: center; gap: 0.5rem; font-size: 0.8125rem; }
.sb__dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
.sb__name { color: var(--c-text-2, #a3a3a3); }
.sb__val { margin-left: auto; color: var(--c-text-1, #fff); font-weight: 600; font-variant-numeric: tabular-nums; }
</style>
Quick Info
- Category
- Charts & Stats
- Filename
SegmentedBarChart.astro- Dependencies
- apexcharts (peer)
- Tags
- chartsvgzero-dependencysegmentedpart-to-wholestoragebreakdown