Cómo crear un gráfico radar SVG animado en React
Un gráfico radar SVG animado en React dibuja rejillas poligonales concéntricas y líneas de eje con SVG puro, y luego representa cada conjunto de datos como un elemento `<path>` cuyo `pathLength` se anima de 0 a 1 mediante el `whileInView` de Framer Motion. Los puntos de datos aparecen como elementos `<circle>` escalonados una vez que el path termina de dibujarse.
- Stack: React + Framer Motion 11 + CSS custom properties, ~135 líneas, cero bibliotecas de gráficos.
- Geometría solo con SVG: conversión polar a cartesiana con Math.cos/sin, sin canvas, sin D3.
- Requiere al menos 3 ejes; devuelve null por debajo de ese umbral para evitar formas degeneradas.
- Accesible: las etiquetas de texto SVG son nodos de texto DOM reales, legibles por los lectores de pantalla.
- Responsive en móvil gracias al layout en columna flex, aunque el SVG fijo de 300×300 no se reescala, considera un enfoque basado en viewBox para pantallas más pequeñas.
Comparison Radar Chart representa un gráfico radar SVG puro que compara dos o más opciones a través de varias dimensiones puntuadas. Prescinde por completo de las bibliotecas de gráficos: las rejillas poligonales, las líneas de eje y los polígonos de datos se calculan con trigonometría básica en el momento del render. Framer Motion gestiona la animación de dibujo activada por scroll, convirtiendo cifras en bruto en una sección que los visitantes leen en segundos.
Anatomía
El componente envuelve un SVG centrado de 300×300 dentro de una columna flex que también contiene una fila de leyenda debajo. Dentro del SVG, cuatro polígonos concéntricos forman el fondo de la rejilla, cada uno al 25/50/75/100% del radio. Líneas radiales finas conectan el centro con cada extremo de eje. Encima de ellas, un `<motion.path>` por opción dibuja el polígono de datos relleno. Elementos `<motion.circle>` escalonados marcan cada punto de datos. Las etiquetas de texto se sitúan 24px más allá de los extremos del radio, calculadas con el mismo ángulo polar que su eje.
Cómo funciona
Las primitivas clave son `polarToXY` (convierte un ángulo de 0-360 grados y un radio en coordenadas x/y SVG, desplazado -90° para que el primer eje apunte hacia arriba) y `buildPath` (asigna cada valor a su punto y los une con comandos de path SVG M/L). Cada `<motion.path>` anima `pathLength` de 0 a 1 mediante `whileInView`, con un escalonado de 0.3s de retardo entre opciones. Los círculos de datos usan un escalonado secundario: `0.5 + optionIndex * 0.3 + axisIndex * 0.05` segundos, para que parezcan surgir a lo largo del path mientras se dibuja. La curva de easing `[0.16, 1, 0.3, 1]` es un cubic-bezier personalizado que aporta una sensación de rápido al inicio y lento al final en todo momento.
Cómo crearlo en React
Configurar los helpers de geometría polar
Escribe dos funciones puras: `polarToXY` convierte un ángulo en grados y un radio en coordenadas x/y SVG (resta 90° para que 0° apunte hacia arriba), y `buildPath` transforma un array de valores de 0-100 en una cadena de path SVG cerrada. Ambas son JavaScript puro, sin necesidad de bibliotecas.
function polarToXY(angle: number, r: number): [number, number] { const rad = ((angle - 90) * Math.PI) / 180; return [CENTER + r * Math.cos(rad), CENTER + r * Math.sin(rad)]; } function buildPath(values: number[], count: number): string { return values .map((v, i) => { const angle = (360 / count) * i; const [x, y] = polarToXY(angle, (v / 100) * RADIUS); return `${i === 0 ? "M" : "L"}${x},${y}`; }) .join(" ") + " Z"; }Dibujar la rejilla y las líneas de eje
Representa 4 elementos `<polygon>` concéntricos calculando cada vértice en `(level / LEVELS) * RADIUS` a lo largo del ángulo de cada eje. Luego añade una `<line>` por eje desde el centro hasta su extremo. Ambos usan la variable CSS `--color-border` para adaptarse automáticamente a cualquier preset de tema.
{Array.from({ length: LEVELS }).map((_, li) => { const r = ((li + 1) / LEVELS) * RADIUS; const pts = axes .map((_, ai) => polarToXY((360 / count) * ai, r).join(",")) .join(" "); return <polygon key={li} points={pts} fill="none" stroke="var(--color-border)" strokeWidth="1" opacity={0.5} />; })}Animar los polígonos de datos con pathLength
Para cada conjunto de datos, representa un `<motion.path>` con el resultado de `buildPath`. Anima `pathLength` de 0 a 1 con `whileInView` para que el dibujo se active al hacer scroll. Escalona varias opciones con `delay: index * 0.3` para que aparezcan de forma secuencial en lugar de todas a la vez.
<motion.path d={buildPath(opt.values, count)} fill="var(--color-accent)" fillOpacity={0.15} stroke="var(--color-accent)" strokeWidth={2} initial={{ opacity: 0, pathLength: 0 }} whileInView={{ opacity: 1, pathLength: 1 }} viewport={{ once: true }} transition={{ duration: 1, delay: index * 0.3, ease: [0.16, 1, 0.3, 1] }} />Añadir los círculos de puntos de datos escalonados y las etiquetas
Recorre cada valor de cada opción para representar un `<motion.circle>` en la posición polar calculada. Usa un retardo compuesto `0.5 + optionIndex * 0.3 + axisIndex * 0.05` para que los círculos parezcan recorrer el path. Coloca los elementos `<text>` 24px más allá del extremo del radio para las etiquetas de eje, usando `textAnchor='middle'` y `dominantBaseline='central'` para que se centren correctamente independientemente del ángulo.
Cuándo usarlo
Recurre a un gráfico radar cuando necesites comparar 2-3 opciones a través de 5-8 dimensiones cualitativas o puntuadas de forma simultánea: comparativas de funcionalidades en páginas de precios SaaS, perfiles de competencias de candidatos o fichas de especificaciones de producto. Evítalo cuando tus datos sean ordinales o secuenciales (un gráfico de barras o de líneas es más claro), cuando tengas más de 3 opciones (los polígonos superpuestos se vuelven ilegibles) o cuando necesites valores numéricos precisos (los gráficos radar destacan la forma por encima de los números).
Usado por
- GitHub, Usa visualizaciones de competencias de tipo radar en los perfiles de desarrollador y en las funciones de análisis de contribuciones.
- Notion, Los dashboards de Notion de terceros incrustan con frecuencia gráficos radar para las matrices de competencias de equipo y el seguimiento de OKR.
- HubSpot, Los dashboards de análisis de contactos y deals usan gráficos radar para mostrar de un vistazo las puntuaciones de leads con múltiples atributos.
FAQ
¿Por qué no usar Chart.js o Recharts en lugar de SVG en bruto?
Chart.js añade ~60 KB y Recharts ~150 KB a tu bundle. Para un solo gráfico radar en una página de marketing, las dos funciones utilitarias mostradas aquí cubren el 100% de la funcionalidad sin ese peso. Usa una biblioteca de gráficos si necesitas actualizaciones de datos dinámicas, tooltips o un dashboard con muchos tipos de gráficos.
¿Cómo hago que el SVG escale de forma responsive?
Elimina los atributos fijos `width` y `height` del elemento `<svg>`, pon `width='100%'` y mantén `viewBox='0 0 300 300'`. El navegador escalará entonces el SVG proporcionalmente a su contenedor. Puede que necesites aumentar ligeramente el tamaño de fuente de las etiquetas, ya que será relativo a las 300 unidades del viewBox, no al tamaño renderizado.
¿Puedo añadir tooltips al pasar el cursor sobre cada punto de datos?
Sí. Sustituye los elementos `<motion.circle>` por un `<g>` que envuelva el círculo y un `<foreignObject>` renderizado condicionalmente que contenga un tooltip de React, o usa un elemento hijo `title` para un tooltip SVG nativo. En producción, una pequeña biblioteca de tooltips como Floating UI es más limpia que gestionar los cálculos de coordenadas SVG para la posición del tooltip.
La animación de pathLength parpadea en Safari. ¿Cómo lo soluciono?
Safari tiene problemas conocidos con `pathLength` de SVG combinado con `stroke-dasharray`. Añade `pathLength={1}` como prop estática al `<path>` subyacente para forzar al navegador a normalizar los offsets de los guiones, y luego deja que Framer Motion anime de 0 a 1. Si sigue parpadeando, anima `opacity` y `scale` como alternativa, menos espectacular pero compatible de forma universal.