1 estrella en GitHub = 1 componente nuevo.

Dar una estrella
Gráficos y Estadísticaschartsvgzero-dependencybubblescatterportfoliothree-variables

Bubble Chart

Dispersión de tres variables donde el área de la burbuja codifica una tercera medida.

Vista Previa en Vivo

Market opportunity

Growth vs margin, bubble = revenue

  • Core
  • Mature
  • Emerging
020406080020406080YoY growth (%)Gross margin (%)PaymentsCRMSecurityAnalyticsHostingDesignAI toolsEmail
View data
ItemGroupYoY growth (%)Gross margin (%)Revenue ($M)
AnalyticsCore3462120
PaymentsCore1841260
CRMMature955180
HostingMature222890
AI toolsEmerging714870
SecurityEmerging2772140
EmailMature66660
DesignCore415885

Uso

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

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

<BubbleChart groups={['Core', 'Emerging']} items={[
    { label: 'Analytics', x: 34, y: 62, z: 120, group: 0 },
    { label: 'AI tools', x: 71, y: 48, z: 70, group: 1 },
  ]} />

Instalación Manual

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

BubbleChart.tsx
'use client';

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

/**
 * BubbleChart — Three-variable scatter: x, y and bubble area (z). Larger
 * bubbles are drawn first so small ones stay visible on top.
 *
 * @param {Array<{label: string, x: number, y: number, z: number, group?: number}>} items - Bubbles.
 * @param {string[]} groups - Group names (max 3) mapped to color slots.
 * @param {string} xLabel - X-axis title.
 * @param {string} yLabel - Y-axis title.
 * @param {string} zLabel - Bubble size meaning.
 * @param {string[]} colors - Optional per-group colors.
 * @param {string} variant - Card style (glass|solid|ghost).
 * @param {string} size - Density (sm|md|lg).
 */
export interface BubbleChartProps {
  title?: string;
  subtitle?: string;
  items?: Array<{ label: string; x: number; y: number; z: number; group?: number }>;
  groups?: string[];
  xLabel?: string;
  yLabel?: string;
  zLabel?: string;
  height?: number;
  colors?: string[];
  variant?: 'glass' | 'solid' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

export function BubbleChart({
  title = 'Market opportunity',
  subtitle = 'Growth vs margin, bubble = revenue',
  items = [
    { label: 'Analytics', x: 34, y: 62, z: 120, group: 0 },
    { label: 'Payments', x: 18, y: 41, z: 260, group: 0 },
    { label: 'CRM', x: 9, y: 55, z: 180, group: 1 },
    { label: 'Hosting', x: 22, y: 28, z: 90, group: 1 },
    { label: 'AI tools', x: 71, y: 48, z: 70, group: 2 },
    { label: 'Security', x: 27, y: 72, z: 140, group: 2 },
    { label: 'Email', x: 6, y: 66, z: 60, group: 1 },
    { label: 'Design', x: 41, y: 58, z: 85, group: 0 },
  ],
  groups = ['Core', 'Mature', 'Emerging'],
  xLabel = 'YoY growth (%)',
  yLabel = 'Gross margin (%)',
  zLabel = 'Revenue ($M)',
  height = 320,
  colors,
  variant = 'glass',
  size = 'md',
  className = '',
}: BubbleChartProps) {
  const W = 640;
  const H = height;
  const m = { t: 20, r: 24, b: 40, l: 48 };
  const xt = niceTicks(0, Math.max(...items.map((i) => i.x)), 5);
  const yt = niceTicks(0, Math.max(...items.map((i) => i.y)), 4);
  const x = scaleLinear(xt[0], xt[xt.length - 1], m.l, W - m.r);
  const y = scaleLinear(yt[0], yt[yt.length - 1], H - m.b, m.t);
  const zMax = Math.max(...items.map((i) => i.z)) || 1;
  const r = (z: number) => 6 + Math.sqrt(z / zMax) * 30; // area-proportional radius
  const sorted = [...items].sort((a, b) => b.z - a.z);
  return (
    <>
      <ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className} legend={groups.slice(0, 3).map((g, i) => ({ label: g, color: colorAt(i, colors) }))} table={{ headers: ['Item', 'Group', xLabel, yLabel, zLabel], rows: items.map((i) => [i.label, groups[i.group ?? 0] ?? '—', i.x, i.y, i.z]) }}>
        <svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${title}: bubble chart of ${items.length} items`}>
          {yt.map((t, __i: number) => (
            <g key={__i}>
              <line className="cf-grid" x1={m.l} x2={W - m.r} y1={y(t)} y2={y(t)} />
              <text className="cf-label" x={m.l - 8} y={y(t) + 4} textAnchor="end">{formatCompact(t)}</text>
            </g>
          ))}
          {xt.map((t, __i: number) => <text key={__i} className="cf-label" x={x(t)} y={H - m.b + 16} textAnchor="middle">{formatCompact(t)}</text>)}
          <text className="cf-label" x={(m.l + W - m.r) / 2} y={H - 4} textAnchor="middle">{xLabel}</text>
          <text className="cf-label" transform={`translate(12 ${(m.t + H - m.b) / 2}) rotate(-90)`} textAnchor="middle">{yLabel}</text>
      
          {sorted.map((it) => {
            const col = colorAt(it.group ?? 0, colors);
            const rad = r(it.z);
            return (
              <g className="cf-mark cf-dim" tabIndex={0} role="img" aria-label={`${it.label}: ${xLabel} ${it.x}, ${yLabel} ${it.y}, ${zLabel} ${it.z}`} data-tip-title={it.label} data-tip={`${col}::${groups[it.group ?? 0] ?? ''}|${xLabel}: ${it.x}|${yLabel}: ${it.y}|${zLabel}: ${it.z}`}>
                <circle cx={x(it.x)} cy={y(it.y)} r={rad} fill={col} fillOpacity="0.55" stroke={col} strokeWidth="1.5" />
                {rad > 18 && <text x={x(it.x)} y={y(it.y) + 4} textAnchor="middle" fill="#fff" fontSize="11" fontWeight="600">{it.label}</text>}
              </g>
            );
          })}
        </svg>
      </ChartFrame>
    </>
  );
}

export default BubbleChart;

Información Rápida

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