Charts chartsradarpolygonspidermetrics

Radar Metric Chart

Polygon spider radar chart visualizing multi-attribute scores (e.g. Speed, SEO, Accessibility, Security) with SVG canvas.

Preview

System Capabilities
Speed (95) A11y (98) SEO (100) Design (92) Zero-JS (96) Security (90)

Usage

Copy the full block below to use this component with its imports.

astro
---
import { RadarMetricChart } from 'astro-component-kit';
---

<RadarMetricChart title="Performance Audit" metrics={[{ label: 'Speed', value: 92 }, { label: 'SEO', value: 98 }, { label: 'a11y', value: 100 }, { label: 'Security', value: 95 }, { label: 'PWA', value: 88 }]} />
--- import { RadarMetricChart } from 'astro-component-kit'; --- <RadarMetricChart title="Performance Audit" metrics={[{ label: 'Speed', value: 92 }, { label: 'SEO', value: 98 }, { label: 'a11y', value: 100 }, { label: 'Security', value: 95 }, { label: 'PWA', value: 88 }]} />

Manual Installation

If you are not using the npm package, create a new file src/components/lib/RadarMetricChart.astro and paste the following code:

astro
---
/**
 * RadarMetricChart — Pure SVG radar / spider chart for multi-axis skill and performance visualization.
 * 
 * @param {string} title - Chart title. Default is 'Performance Metrics'.
 * @param {Array<{axis: string, value: number}>} data - 5 or 6 axis points (values 0 to 100).
 * @param {string} color - Primary chart fill & stroke color. Default is '#d4a017'.
 * @param {number} size - Pixel width/height of the radar. Default is 320.
 */
interface Props {
  title?: string;
  data?: Array<{ axis: string; value: number }>;
  color?: string;
  size?: number;
}

const {
  title = 'System Capabilities',
  data = [
    { axis: 'Speed', value: 95 },
    { axis: 'A11y', value: 98 },
    { axis: 'SEO', value: 100 },
    { axis: 'Design', value: 92 },
    { axis: 'Zero-JS', value: 96 },
    { axis: 'Security', value: 90 },
  ],
  color = '#d4a017',
  size = 320,
} = Astro.props;

const center = size / 2;
const radius = (size - 70) / 2;
const totalAxes = data.length;
const angleSlice = (Math.PI * 2) / totalAxes;

// Concentric polygon grid levels (20%, 40%, 60%, 80%, 100%)
const levels = [0.2, 0.4, 0.6, 0.8, 1.0];
const gridPolygons = levels.map((lvl) => {
  return data
    .map((_, i) => {
      const r = radius * lvl;
      const x = center + r * Math.cos(angleSlice * i - Math.PI / 2);
      const y = center + r * Math.sin(angleSlice * i - Math.PI / 2);
      return `${x},${y}`;
    })
    .join(' ');
});

// Data polygon points
const dataPoints = data
  .map((d, i) => {
    const r = radius * (Math.min(100, Math.max(0, d.value)) / 100);
    const x = center + r * Math.cos(angleSlice * i - Math.PI / 2);
    const y = center + r * Math.sin(angleSlice * i - Math.PI / 2);
    return `${x},${y}`;
  })
  .join(' ');
---

<div class="radar-card" style={`--radar-size: ${size}px; --radar-color: ${color};`}>
  {title && <h5 class="radar-card__title">{title}</h5>}

  <div class="radar-card__wrap">
    <svg width={size} height={size} class="radar-card__svg">
      <!-- Background concentric webs -->
      {gridPolygons.map((pts) => (
        <polygon points={pts} class="radar-web" />
      ))}

      <!-- Axes radial spokes -->
      {data.map((_, i) => {
        const x2 = center + radius * Math.cos(angleSlice * i - Math.PI / 2);
        const y2 = center + radius * Math.sin(angleSlice * i - Math.PI / 2);
        return <line x1={center} y1={center} x2={x2} y2={y2} class="radar-spoke" />;
      })}

      <!-- Data polygon -->
      <polygon points={dataPoints} class="radar-data" />

      <!-- Data nodes -->
      {data.map((d, i) => {
        const r = radius * (d.value / 100);
        const cx = center + r * Math.cos(angleSlice * i - Math.PI / 2);
        const cy = center + r * Math.sin(angleSlice * i - Math.PI / 2);
        return <circle cx={cx} cy={cy} r="4" class="radar-node" />;
      })}

      <!-- Axis Labels -->
      {data.map((d, i) => {
        const labelRadius = radius + 22;
        const lx = center + labelRadius * Math.cos(angleSlice * i - Math.PI / 2);
        const ly = center + labelRadius * Math.sin(angleSlice * i - Math.PI / 2);
        return (
          <text
            x={lx}
            y={ly}
            class="radar-label"
            text-anchor="middle"
            dominant-baseline="central"
          >
            {d.axis} ({d.value})
          </text>
        );
      })}
    </svg>
  </div>
</div>

<style>
  .radar-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: var(--sp-6, 1.5rem);
    background: rgba(255, 255, 255, 0.02);
    backdrop-filter: blur(16px);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--r-xl, 24px);
    width: 100%;
    max-width: fit-content;
    font-family: var(--font-sans, system-ui, sans-serif);
  }

  .radar-card__title {
    font-size: 0.95rem;
    font-weight: 800;
    color: var(--c-text-1, #ffffff);
    margin: 0 0 12px 0;
  }

  .radar-card__wrap {
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .radar-web {
    fill: none;
    stroke: rgba(255, 255, 255, 0.08);
    stroke-width: 1;
  }

  .radar-spoke {
    stroke: rgba(255, 255, 255, 0.08);
    stroke-width: 1;
  }

  .radar-data {
    fill: rgba(184, 134, 11, 0.25);
    stroke: var(--radar-color);
    stroke-width: 2.5;
    transition: all 0.4s ease;
  }

  .radar-node {
    fill: var(--radar-color);
    stroke: #050505;
    stroke-width: 2;
  }

  .radar-label {
    fill: var(--c-text-2, #a3a3a3);
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
  }
</style>
--- /** * RadarMetricChart — Pure SVG radar / spider chart for multi-axis skill and performance visualization. * * @param {string} title - Chart title. Default is 'Performance Metrics'. * @param {Array<{axis: string, value: number}>} data - 5 or 6 axis points (values 0 to 100). * @param {string} color - Primary chart fill & stroke color. Default is '#d4a017'. * @param {number} size - Pixel width/height of the radar. Default is 320. */ interface Props { title?: string; data?: Array<{ axis: string; value: number }>; color?: string; size?: number; } const { title = 'System Capabilities', data = [ { axis: 'Speed', value: 95 }, { axis: 'A11y', value: 98 }, { axis: 'SEO', value: 100 }, { axis: 'Design', value: 92 }, { axis: 'Zero-JS', value: 96 }, { axis: 'Security', value: 90 }, ], color = '#d4a017', size = 320, } = Astro.props; const center = size / 2; const radius = (size - 70) / 2; const totalAxes = data.length; const angleSlice = (Math.PI * 2) / totalAxes; // Concentric polygon grid levels (20%, 40%, 60%, 80%, 100%) const levels = [0.2, 0.4, 0.6, 0.8, 1.0]; const gridPolygons = levels.map((lvl) => { return data .map((_, i) => { const r = radius * lvl; const x = center + r * Math.cos(angleSlice * i - Math.PI / 2); const y = center + r * Math.sin(angleSlice * i - Math.PI / 2); return `${x},${y}`; }) .join(' '); }); // Data polygon points const dataPoints = data .map((d, i) => { const r = radius * (Math.min(100, Math.max(0, d.value)) / 100); const x = center + r * Math.cos(angleSlice * i - Math.PI / 2); const y = center + r * Math.sin(angleSlice * i - Math.PI / 2); return `${x},${y}`; }) .join(' '); --- <div class="radar-card" style={`--radar-size: ${size}px; --radar-color: ${color};`}> {title && <h5 class="radar-card__title">{title}</h5>} <div class="radar-card__wrap"> <svg width={size} height={size} class="radar-card__svg"> <!-- Background concentric webs --> {gridPolygons.map((pts) => ( <polygon points={pts} class="radar-web" /> ))} <!-- Axes radial spokes --> {data.map((_, i) => { const x2 = center + radius * Math.cos(angleSlice * i - Math.PI / 2); const y2 = center + radius * Math.sin(angleSlice * i - Math.PI / 2); return <line x1={center} y1={center} x2={x2} y2={y2} class="radar-spoke" />; })} <!-- Data polygon --> <polygon points={dataPoints} class="radar-data" /> <!-- Data nodes --> {data.map((d, i) => { const r = radius * (d.value / 100); const cx = center + r * Math.cos(angleSlice * i - Math.PI / 2); const cy = center + r * Math.sin(angleSlice * i - Math.PI / 2); return <circle cx={cx} cy={cy} r="4" class="radar-node" />; })} <!-- Axis Labels --> {data.map((d, i) => { const labelRadius = radius + 22; const lx = center + labelRadius * Math.cos(angleSlice * i - Math.PI / 2); const ly = center + labelRadius * Math.sin(angleSlice * i - Math.PI / 2); return ( <text x={lx} y={ly} class="radar-label" text-anchor="middle" dominant-baseline="central" > {d.axis} ({d.value}) </text> ); })} </svg> </div> </div> <style> .radar-card { display: flex; flex-direction: column; align-items: center; padding: var(--sp-6, 1.5rem); background: rgba(255, 255, 255, 0.02); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: var(--r-xl, 24px); width: 100%; max-width: fit-content; font-family: var(--font-sans, system-ui, sans-serif); } .radar-card__title { font-size: 0.95rem; font-weight: 800; color: var(--c-text-1, #ffffff); margin: 0 0 12px 0; } .radar-card__wrap { display: flex; align-items: center; justify-content: center; } .radar-web { fill: none; stroke: rgba(255, 255, 255, 0.08); stroke-width: 1; } .radar-spoke { stroke: rgba(255, 255, 255, 0.08); stroke-width: 1; } .radar-data { fill: rgba(184, 134, 11, 0.25); stroke: var(--radar-color); stroke-width: 2.5; transition: all 0.4s ease; } .radar-node { fill: var(--radar-color); stroke: #050505; stroke-width: 2; } .radar-label { fill: var(--c-text-2, #a3a3a3); font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; } </style>

Quick Info

Category
Charts
Filename
RadarMetricChart.astro
Dependencies
apexcharts (peer)
Tags
chartsradarpolygonspidermetrics