1 estrella en GitHub = 1 componente nuevo.

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

Scatter Plot

Diagrama de dispersión X/Y para hasta tres grupos con formas distintas y línea de tendencia.

Vista Previa en Vivo

Ad spend vs signups

Per campaign

  • Search
  • Social
  • Video
0100200300020406080100Spend ($k)Signups
View data
GroupSpend ($k)Signups
Search549
Search4285
Search79189
Search2192
Search58152
Search95205
Search3799
Search74184
Search1655
Search53139
Search90193
Search3290
Search69159
Search1147
Search48149
Search85205
Social529
Social4242
Social7987
Social2162
Social58103
Social95135
Social3790
Social74137
Social162
Social5358
Social90156
Social3227
Social69101
Social1118
Video525
Video4251
Video7989
Video2152
Video5880
Video9581
Video3780
Video7497
Video1635
Video5360

Uso

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

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

<ScatterPlot groups={[
    { name: 'Search', points: [{ x: 10, y: 40 }, { x: 25, y: 70 }, { x: 40, y: 98 }] },
  ]} xLabel="Spend ($k)" yLabel="Signups" trend />

Instalación Manual

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

ScatterPlot.tsx
'use client';

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

interface Pt { x: number; y: number; label?: string }

const gen = (seed: number, n: number, slope: number, base: number, noise: number): Pt[] =>
  Array.from({ length: n }, (_, i) => {
    const r = Math.sin((i + 1) * seed) * 10000;
    const j = r - Math.floor(r);
    const xv = 5 + ((i * 37) % 95);
    return { x: xv, y: Math.round(base + slope * xv + (j - 0.5) * noise) };
  });

/**
 * ScatterPlot — X/Y relationship plot for up to 3 groups, with an optional
 * least-squares trend line and per-point tooltips.
 *
 * @param {Array<{name: string, points: Array<{x: number, y: number, label?: string}>}>} groups - Point groups (max 3).
 * @param {string} xLabel - X-axis title.
 * @param {string} yLabel - Y-axis title.
 * @param {boolean} trend - Draw a linear regression line across all points.
 * @param {string[]} colors - Optional per-group colors.
 * @param {string} variant - Card style (glass|solid|ghost).
 * @param {string} size - Density (sm|md|lg).
 */
export interface ScatterPlotProps {
  title?: string;
  subtitle?: string;
  groups?: Array<{ name: string; points: Pt[] }>;
  xLabel?: string;
  yLabel?: string;
  trend?: boolean;
  height?: number;
  colors?: string[];
  variant?: 'glass' | 'solid' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

export function ScatterPlot({
  title = 'Ad spend vs signups',
  subtitle = 'Per campaign',
  groups = [
    { name: 'Search', points: gen(3.1, 16, 2.1, 20, 60) },
    { name: 'Social', points: gen(7.7, 14, 1.3, 10, 70) },
    { name: 'Video', points: gen(5.3, 10, 0.8, 30, 50) },
  ],
  xLabel = 'Spend ($k)',
  yLabel = 'Signups',
  trend = true,
  height = 300,
  colors,
  variant = 'glass',
  size = 'md',
  className = '',
}: ScatterPlotProps) {
  const W = 640;
  const H = height;
  const m = { t: 12, r: 16, b: 40, l: 52 };
  const all = groups.flatMap((g) => g.points);
  const xt = niceTicks(Math.min(0, ...all.map((p) => p.x)), Math.max(...all.map((p) => p.x)), 5);
  const yt = niceTicks(Math.min(0, ...all.map((p) => p.y)), Math.max(...all.map((p) => p.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);
  
  // Ordinary least squares over all points.
  const n = all.length;
  const mx = all.reduce((a, p) => a + p.x, 0) / n;
  const my = all.reduce((a, p) => a + p.y, 0) / n;
  const slope = all.reduce((a, p) => a + (p.x - mx) * (p.y - my), 0) / (all.reduce((a, p) => a + (p.x - mx) ** 2, 0) || 1);
  const icpt = my - slope * mx;
  const r2 = (() => {
    const ssTot = all.reduce((a, p) => a + (p.y - my) ** 2, 0) || 1;
    const ssRes = all.reduce((a, p) => a + (p.y - (icpt + slope * p.x)) ** 2, 0);
    return 1 - ssRes / ssTot;
  })();
  const shapes = ['circle', 'square', 'diamond'];
  return (
    <>
      <ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className} legend={groups.slice(0, 3).map((g, i) => ({ label: g.name, color: colorAt(i, colors) }))} table={{ headers: ['Group', xLabel, yLabel], rows: groups.flatMap((g) => g.points.map((p) => [p.label ? `${g.name} · ${p.label}` : g.name, p.x, p.y])) }}>
        <svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${title}: scatter plot of ${n} points${trend ? `, trend R² ${r2.toFixed(2)}` : ''}`}>
          {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) => (
            <g key={__i}>
              <line className="cf-grid" x1={x(t)} x2={x(t)} y1={m.t} y2={H - m.b} />
              <text className="cf-label" x={x(t)} y={H - m.b + 16} textAnchor="middle">{formatCompact(t)}</text>
            </g>
          ))}
          <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>
      
          {trend && (
            <g aria-hidden="true">
              <line x1={x(xt[0])} y1={y(icpt + slope * xt[0])} x2={x(xt[xt.length - 1])} y2={y(icpt + slope * xt[xt.length - 1])} stroke="var(--c-text-1, #fff)" strokeOpacity="0.45" strokeWidth="1.5" />
              <text className="cf-label" x={W - m.r} y={y(icpt + slope * xt[xt.length - 1]) - 8} textAnchor="end">R² {r2.toFixed(2)}</text>
            </g>
          )}
      
          {groups.slice(0, 3).map((g, gi) => {
            const col = colorAt(gi, colors);
            return g.points.map((p) => {
              const px = x(p.x);
              const py = y(p.y);
              const common = {
                class: 'cf-mark cf-dim',
                fill: col,
                'fill-opacity': 0.85,
                stroke: 'var(--chart-surface, #0d0d0f)',
                'stroke-width': 1.5,
                tabindex: 0,
                role: 'img' as const,
                'aria-label': `${g.name}${p.label ? ` ${p.label}` : ''}: ${xLabel} ${p.x}, ${yLabel} ${p.y}`,
                'data-tip-title': p.label ?? g.name,
                'data-tip': `${col}::${g.name}|${xLabel}: ${p.x}|${yLabel}: ${p.y}`,
              };
              return shapes[gi] === 'square'
                ? <rect {...common} x={px - 5} y={py - 5} width="10" height="10" rx="2" />
                : shapes[gi] === 'diamond'
                  ? <path {...common} d={`M${px},${py - 6.5} L${px + 6.5},${py} L${px},${py + 6.5} L${px - 6.5},${py} Z`} />
                  : <circle {...common} cx={px} cy={py} r="5.5" />;
            });
          })}
        </svg>
      </ChartFrame>
    </>
  );
}

export default ScatterPlot;

Información Rápida

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