Gráficos y Estadísticaschartsvgzero-dependencypyramiddemographicsbutterflycomparison
Population Pyramid
Barras horizontales enfrentadas que comparan dos grupos en franjas ordenadas como edades.
Vista Previa en Vivo
Share of users per age band
- Women
- Men
2.1%
65+1.7%
4.8%
55–644.1%
8.2%
45–547.4%
12.6%
35–4411.8%
15.4%
25–3416.2%
7.9%
18–248.8%
View data
| Band | Women | Men |
|---|---|---|
| 65+ | 2.1% | 1.7% |
| 55–64 | 4.8% | 4.1% |
| 45–54 | 8.2% | 7.4% |
| 35–44 | 12.6% | 11.8% |
| 25–34 | 15.4% | 16.2% |
| 18–24 | 7.9% | 8.8% |
Uso
Copia y pega este código directamente en tu componente React:
tsx
import { PopulationPyramid } from 'react-component-kit-ui/charts';
<PopulationPyramid bands={['35–44', '25–34', '18–24']} left={[12.6, 15.4, 7.9]} right={[11.8, 16.2, 8.8]} leftLabel="Women" rightLabel="Men" />Instalación Manual
Si no estás usando el paquete npm, crea un archivo nuevo y pega el siguiente código: src/components/PopulationPyramid.tsx + PopulationPyramid.css:
PopulationPyramid.tsx
'use client';
import type { CSSProperties } from 'react';
import ChartFrame from './ChartFrame';
import { formatValue, colorAt } from './chart-utils';
import './PopulationPyramid.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;
}
/**
* PopulationPyramid — Back-to-back horizontal bars comparing two groups across
* ordered bands (age groups, tiers, price brackets).
*
* @param {string[]} bands - Row labels, top to bottom.
* @param {number[]} left - Values for the left group.
* @param {number[]} right - Values for the right group.
* @param {string} leftLabel - Left group name.
* @param {string} rightLabel - Right group name.
* @param {string} suffix - Value suffix.
* @param {string} variant - Card style (glass|solid|ghost).
* @param {string} size - Density (sm|md|lg).
*/
export interface PopulationPyramidProps {
title?: string;
subtitle?: string;
bands?: string[];
left?: number[];
right?: number[];
leftLabel?: string;
rightLabel?: string;
prefix?: string;
suffix?: string;
colors?: string[];
variant?: 'glass' | 'solid' | 'ghost';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
export function PopulationPyramid({
title = 'Audience by age',
subtitle = 'Share of users per age band',
bands = ['65+', '55–64', '45–54', '35–44', '25–34', '18–24'],
left = [2.1, 4.8, 8.2, 12.6, 15.4, 7.9],
right = [1.7, 4.1, 7.4, 11.8, 16.2, 8.8],
leftLabel = 'Women',
rightLabel = 'Men',
prefix = '',
suffix = '%',
colors,
variant = 'glass',
size = 'md',
className = '',
}: PopulationPyramidProps) {
const max = Math.max(...left, ...right) || 1;
const cl = colorAt(0, colors);
const cr = colorAt(1, colors);
const fmt = (v: number) => formatValue(v, prefix, suffix);
return (
<>
<ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className} legend={[{ label: leftLabel, color: cl }, { label: rightLabel, color: cr }]} table={{ headers: ['Band', leftLabel, rightLabel], rows: bands.map((b, i) => [b, fmt(left[i]), fmt(right[i])]) }}>
<div className="pp" role="list" data-rk-b22a54="">
{bands.map((b, i) => (
<div key={i} className="pp__row" role="listitem" data-rk-b22a54="">
<div className="pp__side pp__side--l" data-rk-b22a54="">
<span className="pp__val" data-rk-b22a54="">{fmt(left[i])}</span>
<span className="pp__bar cf-mark" tabIndex={0} style={st(`width:${(left[i] / max) * 100}%; background:${cl}`)} aria-label={`${b}, ${leftLabel}: ${fmt(left[i])}`} data-tip-title={`${b} · ${leftLabel}`} data-tip={`${cl}::${fmt(left[i])}`} data-rk-b22a54=""></span>
</div>
<span className="pp__band" data-rk-b22a54="">{b}</span>
<div className="pp__side pp__side--r" data-rk-b22a54="">
<span className="pp__bar cf-mark" tabIndex={0} style={st(`width:${(right[i] / max) * 100}%; background:${cr}`)} aria-label={`${b}, ${rightLabel}: ${fmt(right[i])}`} data-tip-title={`${b} · ${rightLabel}`} data-tip={`${cr}::${fmt(right[i])}`} data-rk-b22a54=""></span>
<span className="pp__val" data-rk-b22a54="">{fmt(right[i])}</span>
</div>
</div>
))}
</div>
</ChartFrame>
</>
);
}
export default PopulationPyramid;PopulationPyramid.css
/* PopulationPyramid — ported from astro-component-kit (charts/PopulationPyramid.astro) */
.pp:where([data-rk-b22a54]) { display: grid; gap: 4px; }
.pp__row:where([data-rk-b22a54]) {
display: grid;
grid-template-columns: 1fr 4rem 1fr;
align-items: center;
gap: 0.5rem;
}
.pp__side:where([data-rk-b22a54]) {
display: flex;
align-items: center;
gap: 0.5rem;
height: 24px;
}
.pp__side--l:where([data-rk-b22a54]) { justify-content: flex-end; }
.pp__bar:where([data-rk-b22a54]) {
height: 100%;
min-width: 2px;
max-width: calc(100% - 3rem);
transition: opacity 0.2s ease;
}
.pp__side--l:where([data-rk-b22a54]) .pp__bar:where([data-rk-b22a54]) { border-radius: 4px 0 0 4px; }
.pp__side--r:where([data-rk-b22a54]) .pp__bar:where([data-rk-b22a54]) { border-radius: 0 4px 4px 0; }
.pp__bar:hover:where([data-rk-b22a54]) { opacity: 0.85; }
.pp__bar:focus-visible:where([data-rk-b22a54]) { outline: 2px solid var(--c-text-1, #fff); outline-offset: 1px; }
.pp__band:where([data-rk-b22a54]) {
text-align: center;
font-size: 0.75rem;
font-weight: 600;
color: var(--c-text-1, #fff);
}
.pp__val:where([data-rk-b22a54]) {
font-size: 0.75rem;
color: var(--c-text-2, #a3a3a3);
font-variant-numeric: tabular-nums;
min-width: 2.5rem;
}
.pp__side--l:where([data-rk-b22a54]) .pp__val:where([data-rk-b22a54]) { text-align: right; }Información Rápida
- Categoría
- Gráficos y Estadísticas
- Nombre de archivo
PopulationPyramid.tsx- Dependencias
- Ninguna — React + CSS puro
- Etiquetas
- chartsvgzero-dependencypyramiddemographicsbutterflycomparison