1 estrella en GitHub = 1 componente nuevo.

Dar una estrella
Gráficos y Estadísticaschartsvgzero-dependencybarstackedcompositionhorizontal

Stacked Bar Chart

Barras apiladas verticales u horizontales con modo 100% y separación de 2px entre segmentos.

Vista Previa en Vivo

Tickets by status

Per sprint

  • Done
  • In review
  • Blocked
020406080S18S19S20S21S22S23
View data
CategoryDoneIn reviewBlockedTotal
S184212559
S19489764
S205114368
S214611663
S225810472
S23638273

Uso

Copia y pega este código directamente en tu componente React:

tsx
import { StackedBarChart } from 'react-component-kit-ui/charts';

<StackedBarChart categories={['S1', 'S2', 'S3']} series={[
    { name: 'Done', data: [42, 48, 51] },
    { name: 'Blocked', data: [5, 7, 3] },
  ]} horizontal={false} normalized={false} />

Instalación Manual

Si no estás usando el paquete npm, crea un archivo nuevo y pega el siguiente código: src/components/StackedBarChart.tsx + StackedBarChart.css:

StackedBarChart.tsx
'use client';

import ChartFrame from './ChartFrame';
import { colorAt, scaleLinear, niceTicks, formatValue } from './chart-utils';

/**
 * StackedBarChart — Vertical or horizontal stacked bars, with an optional 100%
 * mode. Segments are separated by a 2px surface gap instead of borders.
 *
 * @param {string[]} categories - Bar labels.
 * @param {Array<{name: string, data: number[]}>} series - Stack layers (max 8).
 * @param {boolean} horizontal - Draw bars horizontally.
 * @param {boolean} normalized - Show each bar as a 100% share.
 * @param {string} prefix - Value prefix.
 * @param {string} suffix - Value suffix.
 * @param {string} variant - Card style (glass|solid|ghost).
 * @param {string} size - Density (sm|md|lg).
 */
export interface StackedBarChartProps {
  title?: string;
  subtitle?: string;
  categories?: string[];
  series?: Array<{ name: string; data: number[] }>;
  horizontal?: boolean;
  normalized?: boolean;
  prefix?: string;
  suffix?: string;
  height?: number;
  colors?: string[];
  variant?: 'glass' | 'solid' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

export function StackedBarChart({
  title = 'Tickets by status',
  subtitle = 'Per sprint',
  categories = ['S18', 'S19', 'S20', 'S21', 'S22', 'S23'],
  series = [
    { name: 'Done', data: [42, 48, 51, 46, 58, 63] },
    { name: 'In review', data: [12, 9, 14, 11, 10, 8] },
    { name: 'Blocked', data: [5, 7, 3, 6, 4, 2] },
  ],
  horizontal = false,
  normalized = false,
  prefix = '',
  suffix = '',
  height = 280,
  colors,
  variant = 'glass',
  size = 'md',
  className = '',
}: StackedBarChartProps) {
  const W = 640;
  const H = horizontal ? Math.max(height, categories.length * 40 + 40) : height;
  const m = horizontal ? { t: 8, r: 16, b: 24, l: 72 } : { t: 12, r: 12, b: 28, l: 44 };
  const totals = categories.map((_, i) => series.reduce((a, s) => a + (s.data[i] ?? 0), 0));
  const ticks = normalized ? [0, 25, 50, 75, 100] : niceTicks(0, Math.max(...totals), 4);
  const maxT = ticks[ticks.length - 1];
  const v = horizontal ? scaleLinear(0, maxT, m.l, W - m.r) : scaleLinear(0, maxT, H - m.b, m.t);
  const band = horizontal ? (H - m.t - m.b) / categories.length : (W - m.l - m.r) / categories.length;
  const thick = band * 0.6;
  const GAP = 2;
  const fmt = (n: number) => formatValue(n, prefix, suffix);
  const fmtAxis = (n: number) => (normalized ? `${n}%` : fmt(n));
  
  const bars = categories.map((c, ci) => {
    let acc = 0;
    const segs = series.slice(0, 8).map((s, si) => {
      const raw = s.data[ci] ?? 0;
      const val = normalized ? (raw / (totals[ci] || 1)) * 100 : raw;
      const start = acc;
      acc += val;
      return { name: s.name, raw, val, start, end: acc, color: colorAt(si, colors) };
    });
    return { c, ci, segs };
  });
  return (
    <>
      <ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className} legend={series.map((s, i) => ({ label: s.name, color: colorAt(i, colors) }))} table={{ headers: ['Category', ...series.map((s) => s.name), 'Total'], rows: categories.map((c, i) => [c, ...series.map((s) => fmt(s.data[i])), fmt(totals[i])]) }}>
        <svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${title}: ${normalized ? '100% ' : ''}stacked bar chart`}>
          {ticks.map((t) => horizontal ? (
            <g>
              <line className={t === 0 ? 'cf-axis' : 'cf-grid'} x1={v(t)} x2={v(t)} y1={m.t} y2={H - m.b} />
              <text className="cf-label" x={v(t)} y={H - 6} textAnchor="middle">{fmtAxis(t)}</text>
            </g>
          ) : (
            <g>
              <line className={t === 0 ? 'cf-axis' : 'cf-grid'} x1={m.l} x2={W - m.r} y1={v(t)} y2={v(t)} />
              <text className="cf-label" x={m.l - 8} y={v(t) + 4} textAnchor="end">{fmtAxis(t)}</text>
            </g>
          ))}
      
          {bars.map(({ c, ci, segs }) => {
            const pos = (horizontal ? m.t : m.l) + ci * band + (band - thick) / 2;
            return (
              <g>
                {horizontal
                  ? <text className="cf-label" x={m.l - 8} y={pos + thick / 2 + 4} textAnchor="end">{c}</text>
                  : <text className="cf-label" x={pos + thick / 2} y={H - 8} textAnchor="middle">{c}</text>}
                {segs.map((s, si) => {
                  const a = v(s.start);
                  const b = v(s.end);
                  const isLast = si === segs.length - 1;
                  const len = Math.abs(b - a) - (isLast ? 0 : GAP);
                  if (!(len > 0)) return null;
                  const rect = horizontal
                    ? { x: a, y: pos, width: len, height: thick }
                    : { x: pos, y: b + (isLast ? 0 : GAP), width: thick, height: len };
                  return (
                    <rect className="cf-mark cf-dim" {...rect} rx={isLast ? 3 : 0} fill={s.color} tabIndex={0} role="img" aria-label={`${c}, ${s.name}: ${fmt(s.raw)}${normalized ? ` (${s.val.toFixed(0)}%)` : ''}`} data-tip-title={c} data-tip={`${s.color}::${s.name}: ${fmt(s.raw)}${normalized ? ` · ${s.val.toFixed(0)}%` : ''}`} />
                  );
                })}
              </g>
            );
          })}
        </svg>
      </ChartFrame>
    </>
  );
}

export default StackedBarChart;

Información Rápida

Categoría
Gráficos y Estadísticas
Nombre de archivo
StackedBarChart.tsx
Dependencias
Ninguna — React + CSS puro
Etiquetas
chartsvgzero-dependencybarstackedcompositionhorizontal