1 estrella en GitHub = 1 componente nuevo.

Dar una estrella
Gráficos y Estadísticas chartsvgzero-dependencyrankbumpleaderboardtime-series

Bump Chart

Gráfico de ranking en el tiempo que sigue la posición de cada entidad con nodos numerados.

Vista Previa en Vivo

Framework popularity rank

Developer survey position by year

  • Astro
  • Next
  • Svelte
  • Nuxt
  • Remix
2021 2022 2023 2024 2025 2026 5 4 3 2 1 1 Astro Astro 1 1 1 1 2 2 Next Next 3 2 2 3 3 4 Svelte Svelte 2 3 4 4 5 3 Nuxt Nuxt 4 5 5 5 4 5 Remix Remix
View data
Name202120222023202420252026
Astro#5#4#3#2#1#1
Next#1#1#1#1#2#2
Svelte#3#2#2#3#3#4
Nuxt#2#3#4#4#5#3
Remix#4#5#5#5#4#5

Uso

Copia y pega este código directamente en tu página de Astro:

astro
---
import { BumpChart } from 'astro-component-kit/charts';
---

<BumpChart
  periods={['2024', '2025', '2026']}
  series={[
    { name: 'Astro', ranks: [3, 2, 1] },
    { name: 'Next', ranks: [1, 1, 2] },
    { name: 'Svelte', ranks: [2, 3, 3] },
  ]}
/>
--- import { BumpChart } from 'astro-component-kit/charts'; --- <BumpChart periods={['2024', '2025', '2026']} series={[ { name: 'Astro', ranks: [3, 2, 1] }, { name: 'Next', ranks: [1, 1, 2] }, { name: 'Svelte', ranks: [2, 3, 3] }, ]} />

Instalación Manual

Si no estás usando el paquete npm, crea un archivo nuevo y pega el siguiente código: src/components/lib/BumpChart.astro:

astro
---
/**
 * BumpChart — Rank-over-time chart. Each line tracks one entity's position
 * (1 = top) across periods, with labels at both ends.
 *
 * @param {string[]} periods - X-axis labels.
 * @param {Array<{name: string, ranks: number[]}>} series - Rank per period (1-based), max 8.
 * @param {string[]} colors - Optional per-series colors.
 * @param {string} variant - Card style (glass|solid|ghost).
 * @param {string} size - Density (sm|md|lg).
 */
import ChartFrame from './ChartFrame.astro';
import { colorAt, smoothPath, type Point } from './chart-utils';

interface Props {
  title?: string;
  subtitle?: string;
  periods?: string[];
  series?: Array<{ name: string; ranks: number[] }>;
  colors?: string[];
  variant?: 'glass' | 'solid' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

const {
  title = 'Framework popularity rank',
  subtitle = 'Developer survey position by year',
  periods = ['2021', '2022', '2023', '2024', '2025', '2026'],
  series = [
    { name: 'Astro', ranks: [5, 4, 3, 2, 1, 1] },
    { name: 'Next', ranks: [1, 1, 1, 1, 2, 2] },
    { name: 'Svelte', ranks: [3, 2, 2, 3, 3, 4] },
    { name: 'Nuxt', ranks: [2, 3, 4, 4, 5, 3] },
    { name: 'Remix', ranks: [4, 5, 5, 5, 4, 5] },
  ],
  colors,
  variant = 'glass',
  size = 'md',
  className = '',
} = Astro.props;

const W = 640;
const maxRank = Math.max(...series.flatMap((s) => s.ranks));
const rowH = 44;
const H = maxRank * rowH + 36;
const m = { t: 30, l: 80, r: 80 };
const n = periods.length;
const x = (i: number) => m.l + (i / Math.max(1, n - 1)) * (W - m.l - m.r);
const y = (r: number) => m.t + (r - 1) * rowH + 8;
const lines = series.slice(0, 8).map((s, si) => ({
  ...s,
  color: colorAt(si, colors),
  pts: s.ranks.map((r, i): Point => [x(i), y(r)]),
}));
---

<ChartFrame
  title={title}
  subtitle={subtitle}
  variant={variant}
  size={size}
  className={className}
  legend={lines.map((l) => ({ label: l.name, color: l.color }))}
  table={{ headers: ['Name', ...periods], rows: series.map((s) => [s.name, ...s.ranks.map((r) => `#${r}`)]) }}
>
  <svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${title}: bump chart of ranks over ${n} periods`}>
    {periods.map((p, i) => (
      <g>
        <line class="cf-grid" x1={x(i)} x2={x(i)} y1={m.t - 6} y2={H - 10} />
        <text class="cf-label" x={x(i)} y={14} text-anchor="middle">{p}</text>
      </g>
    ))}
    {lines.map((l) => (
      <g class="cf-mark cf-dim" tabindex="0" role="img"
        aria-label={`${l.name}: ${l.ranks.map((r, i) => `${periods[i]} #${r}`).join(', ')}`}
        data-tip-title={l.name}
        data-tip={l.ranks.map((r, i) => `${periods[i]}: #${r}`).join('|')}>
        <path d={smoothPath(l.pts)} fill="none" stroke={l.color} stroke-width="3" stroke-linecap="round" />
        <path d={smoothPath(l.pts)} fill="none" stroke="transparent" stroke-width="16" />
        {l.pts.map(([px, py], i) => (
          <g>
            <circle cx={px} cy={py} r="11" fill={l.color} stroke="var(--chart-surface, #0d0d0f)" stroke-width="2" />
            <text x={px} y={py + 4} text-anchor="middle" font-size="10" font-weight="700" fill="#fff">{l.ranks[i]}</text>
          </g>
        ))}
        <text class="cf-label cf-label--strong" x={m.l - 18} y={l.pts[0][1] + 4} text-anchor="end">{l.name}</text>
        <text class="cf-label cf-label--strong" x={W - m.r + 18} y={l.pts[n - 1][1] + 4}>{l.name}</text>
      </g>
    ))}
  </svg>
</ChartFrame>
--- /** * BumpChart — Rank-over-time chart. Each line tracks one entity's position * (1 = top) across periods, with labels at both ends. * * @param {string[]} periods - X-axis labels. * @param {Array<{name: string, ranks: number[]}>} series - Rank per period (1-based), max 8. * @param {string[]} colors - Optional per-series colors. * @param {string} variant - Card style (glass|solid|ghost). * @param {string} size - Density (sm|md|lg). */ import ChartFrame from './ChartFrame.astro'; import { colorAt, smoothPath, type Point } from './chart-utils'; interface Props { title?: string; subtitle?: string; periods?: string[]; series?: Array<{ name: string; ranks: number[] }>; colors?: string[]; variant?: 'glass' | 'solid' | 'ghost'; size?: 'sm' | 'md' | 'lg'; className?: string; } const { title = 'Framework popularity rank', subtitle = 'Developer survey position by year', periods = ['2021', '2022', '2023', '2024', '2025', '2026'], series = [ { name: 'Astro', ranks: [5, 4, 3, 2, 1, 1] }, { name: 'Next', ranks: [1, 1, 1, 1, 2, 2] }, { name: 'Svelte', ranks: [3, 2, 2, 3, 3, 4] }, { name: 'Nuxt', ranks: [2, 3, 4, 4, 5, 3] }, { name: 'Remix', ranks: [4, 5, 5, 5, 4, 5] }, ], colors, variant = 'glass', size = 'md', className = '', } = Astro.props; const W = 640; const maxRank = Math.max(...series.flatMap((s) => s.ranks)); const rowH = 44; const H = maxRank * rowH + 36; const m = { t: 30, l: 80, r: 80 }; const n = periods.length; const x = (i: number) => m.l + (i / Math.max(1, n - 1)) * (W - m.l - m.r); const y = (r: number) => m.t + (r - 1) * rowH + 8; const lines = series.slice(0, 8).map((s, si) => ({ ...s, color: colorAt(si, colors), pts: s.ranks.map((r, i): Point => [x(i), y(r)]), })); --- <ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className} legend={lines.map((l) => ({ label: l.name, color: l.color }))} table={{ headers: ['Name', ...periods], rows: series.map((s) => [s.name, ...s.ranks.map((r) => `#${r}`)]) }} > <svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${title}: bump chart of ranks over ${n} periods`}> {periods.map((p, i) => ( <g> <line class="cf-grid" x1={x(i)} x2={x(i)} y1={m.t - 6} y2={H - 10} /> <text class="cf-label" x={x(i)} y={14} text-anchor="middle">{p}</text> </g> ))} {lines.map((l) => ( <g class="cf-mark cf-dim" tabindex="0" role="img" aria-label={`${l.name}: ${l.ranks.map((r, i) => `${periods[i]} #${r}`).join(', ')}`} data-tip-title={l.name} data-tip={l.ranks.map((r, i) => `${periods[i]}: #${r}`).join('|')}> <path d={smoothPath(l.pts)} fill="none" stroke={l.color} stroke-width="3" stroke-linecap="round" /> <path d={smoothPath(l.pts)} fill="none" stroke="transparent" stroke-width="16" /> {l.pts.map(([px, py], i) => ( <g> <circle cx={px} cy={py} r="11" fill={l.color} stroke="var(--chart-surface, #0d0d0f)" stroke-width="2" /> <text x={px} y={py + 4} text-anchor="middle" font-size="10" font-weight="700" fill="#fff">{l.ranks[i]}</text> </g> ))} <text class="cf-label cf-label--strong" x={m.l - 18} y={l.pts[0][1] + 4} text-anchor="end">{l.name}</text> <text class="cf-label cf-label--strong" x={W - m.r + 18} y={l.pts[n - 1][1] + 4}>{l.name}</text> </g> ))} </svg> </ChartFrame>

Información Rápida

Categoría
Gráficos y Estadísticas
Nombre de archivo
BumpChart.astro
Dependencias
apexcharts (peer)
Etiquetas
chartsvgzero-dependencyrankbumpleaderboardtime-series