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
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