1 estrella en GitHub = 1 componente nuevo.

Dar una estrella
Gráficos y Estadísticaschartsvgzero-dependencyslopecomparisonbefore-after

Slope Chart

Comparación antes/después entre dos momentos con líneas ascendentes y descendentes en colores de estado.

Vista Previa en Vivo

Conversion rate by plan

Q1 vs Q2

Q1Q2Enterprise 7.8%9.6% EnterpriseTeam 6.1%6.9% TeamPro 5.4%4.6% ProStarter 3.2%4.1% StarterFree trial 2.4%1.9% Free trial
View data
ItemQ1Q2Change
Enterprise7.8%9.6%+1.8
Team6.1%6.9%+0.8
Pro5.4%4.6%-0.8
Starter3.2%4.1%+0.9
Free trial2.4%1.9%-0.5

Uso

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

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

<SlopeChart fromLabel="2025" toLabel="2026" items={[
    { label: 'Enterprise', from: 7.8, to: 9.6 },
    { label: 'Pro', from: 5.4, to: 4.6 },
  ]} suffix="%" />

Instalación Manual

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

SlopeChart.tsx
'use client';

import ChartFrame from './ChartFrame';
import { scaleLinear, formatValue, POSITIVE, NEGATIVE } from './chart-utils';

/**
 * SlopeChart — Before/after comparison of several items between two points in
 * time. Rising lines use the positive status color, falling lines the negative.
 *
 * @param {string} fromLabel - Left column label.
 * @param {string} toLabel - Right column label.
 * @param {Array<{label: string, from: number, to: number}>} items - Items to compare.
 * @param {string} highlight - Optional item label to emphasise (others dim).
 * @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 SlopeChartProps {
  title?: string;
  subtitle?: string;
  fromLabel?: string;
  toLabel?: string;
  items?: Array<{ label: string; from: number; to: number }>;
  highlight?: string;
  prefix?: string;
  suffix?: string;
  height?: number;
  variant?: 'glass' | 'solid' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

export function SlopeChart({
  title = 'Conversion rate by plan',
  subtitle = 'Q1 vs Q2',
  fromLabel = 'Q1',
  toLabel = 'Q2',
  items = [
    { label: 'Enterprise', from: 7.8, to: 9.6 },
    { label: 'Team', from: 6.1, to: 6.9 },
    { label: 'Pro', from: 5.4, to: 4.6 },
    { label: 'Starter', from: 3.2, to: 4.1 },
    { label: 'Free trial', from: 2.4, to: 1.9 },
  ],
  highlight,
  prefix = '',
  suffix = '%',
  height = 300,
  variant = 'glass',
  size = 'md',
  className = '',
}: SlopeChartProps) {
  const W = 640;
  const H = height;
  const m = { t: 34, b: 20 };
  const x0 = 190;
  const x1 = W - 190;
  const vals = items.flatMap((i) => [i.from, i.to]);
  const y = scaleLinear(Math.min(...vals), Math.max(...vals), H - m.b, m.t);
  const fmt = (v: number) => formatValue(v, prefix, suffix);
  return (
    <>
      <ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className} table={{
          headers: ['Item', fromLabel, toLabel, 'Change'],
          rows: items.map((it) => [it.label, fmt(it.from), fmt(it.to), `${it.to >= it.from ? '+' : ''}${(it.to - it.from).toFixed(1)}`]),
        }}>
        <svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${title}: slope chart from ${fromLabel} to ${toLabel}`}>
          <line className="cf-axis" x1={x0} x2={x0} y1={m.t - 8} y2={H - m.b + 8} />
          <line className="cf-axis" x1={x1} x2={x1} y1={m.t - 8} y2={H - m.b + 8} />
          <text className="cf-label cf-label--strong" x={x0} y={14} textAnchor="middle">{fromLabel}</text>
          <text className="cf-label cf-label--strong" x={x1} y={14} textAnchor="middle">{toLabel}</text>
      
          {items.map((it) => {
            const up = it.to >= it.from;
            const c = up ? POSITIVE : NEGATIVE;
            const dim = highlight && highlight !== it.label;
            return (
              <g className="cf-mark cf-dim" tabIndex={0} role="img" opacity={dim ? 0.35 : 1} aria-label={`${it.label}: ${fmt(it.from)} to ${fmt(it.to)} (${up ? 'up' : 'down'})`} data-tip-title={it.label} data-tip={`${fromLabel}: ${fmt(it.from)}|${toLabel}: ${fmt(it.to)}|${c}::${up ? 'Up' : 'Down'} ${Math.abs(it.to - it.from).toFixed(1)}`}>
                <line x1={x0} y1={y(it.from)} x2={x1} y2={y(it.to)} stroke={c} strokeWidth="2" />
                <line x1={x0} y1={y(it.from)} x2={x1} y2={y(it.to)} stroke="transparent" strokeWidth="14" />
                <circle cx={x0} cy={y(it.from)} r="4.5" fill={c} stroke="var(--chart-surface, #0d0d0f)" strokeWidth="2" />
                <circle cx={x1} cy={y(it.to)} r="4.5" fill={c} stroke="var(--chart-surface, #0d0d0f)" strokeWidth="2" />
                <text className="cf-label" x={x0 - 12} y={y(it.from) + 4} textAnchor="end">{it.label} <tspan className="cf-label--strong">{fmt(it.from)}</tspan></text>
                <text className="cf-label" x={x1 + 12} y={y(it.to) + 4}><tspan className="cf-label--strong">{fmt(it.to)}</tspan> {it.label}</text>
              </g>
            );
          })}
        </svg>
      </ChartFrame>
    </>
  );
}

export default SlopeChart;

Información Rápida

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