Charts & Statschartsvgzero-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%
- Media182 GB
- Backups96 GB
- Databases64 GB
- Logs38 GB
- Other22 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 React component:
tsx
import { SegmentedBarChart } from 'react-component-kit-ui/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/SegmentedBarChart.tsx + SegmentedBarChart.css:
SegmentedBarChart.tsx
'use client';
import type { CSSProperties } from 'react';
import ChartFrame from './ChartFrame';
import { colorAt, formatValue, sum } from './chart-utils';
import './SegmentedBarChart.css';
/** "a: b; --c: d" โ style object */
function st(css: string | undefined): CSSProperties {
const o: Record<string, string> = {};
for (const decl of String(css ?? '').split(';')) {
const i = decl.indexOf(':');
if (i < 0) continue;
const k = decl.slice(0, i).trim();
if (k) o[k.startsWith('--') ? k : k.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = decl.slice(i + 1).trim();
}
return o as CSSProperties;
}
/**
* 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).
*/
export interface SegmentedBarChartProps {
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;
}
export function SegmentedBarChart({
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 = '',
}: SegmentedBarChartProps) {
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) }));
return (
<>
<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 className="sb__bar" role="list" aria-label={`${title} breakdown`} data-rk-740040="">
{segs.map((s, __i: number) => (
<span key={__i} className="sb__seg cf-mark" role="listitem" tabIndex={0} style={st(`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)}%`} data-rk-740040="">
{s.pct >= 9 && <span className="sb__pct" data-rk-740040="">{s.pct.toFixed(0)}%</span>}
</span>
))}
</div>
<ul className="sb__list" data-rk-740040="">
{segs.map((s, __i: number) => (
<li key={__i} data-rk-740040="">
<span className="sb__dot" style={st(`background:${s.color}`)} aria-hidden="true" data-rk-740040=""></span>
<span className="sb__name" data-rk-740040="">{s.label}</span>
<span className="sb__val" data-rk-740040="">{fmt(s.value)}</span>
</li>
))}
</ul>
</ChartFrame>
</>
);
}
export default SegmentedBarChart;SegmentedBarChart.css
/* SegmentedBarChart โ ported from astro-component-kit (charts/SegmentedBarChart.astro) */
.sb__bar:where([data-rk-740040]) {
display: flex;
gap: 2px;
height: 44px;
border-radius: 8px;
overflow: hidden;
}
.sb__seg:where([data-rk-740040]) {
flex-basis: 0;
min-width: 4px;
display: flex;
align-items: center;
padding-left: 0.6rem;
transition: filter 0.2s ease;
}
.sb__seg:hover:where([data-rk-740040]) { filter: brightness(1.15); }
.sb__seg:focus-visible:where([data-rk-740040]) { outline: 2px solid var(--c-text-1, #fff); outline-offset: -2px; }
.sb__pct:where([data-rk-740040]) {
font-size: 0.75rem;
font-weight: 700;
color: #fff;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}
.sb__list:where([data-rk-740040]) {
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:where([data-rk-740040]) li:where([data-rk-740040]) { display: flex; align-items: center; gap: 0.5rem; font-size: 0.8125rem; }
.sb__dot:where([data-rk-740040]) { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
.sb__name:where([data-rk-740040]) { color: var(--c-text-2, #a3a3a3); }
.sb__val:where([data-rk-740040]) { margin-left: auto; color: var(--c-text-1, #fff); font-weight: 600; font-variant-numeric: tabular-nums; }Quick Info
- Category
- Charts & Stats
- Filename
SegmentedBarChart.tsx- Dependencies
- None โ pure React + CSS
- Tags
- chartsvgzero-dependencysegmentedpart-to-wholestoragebreakdown