1 звезда на GitHub = 1 новый компонент.

Поставить звезду
Графики и статистика chartsvgzero-dependencyradarspidercomparisonattributes

Radar Chart

SVG-радар для сравнения до трёх сущностей по 5–8 атрибутам на общей шкале.

Живое превью

Framework comparison

Scored 0–10 by the team

  • Astro
  • Next
Performance DX Ecosystem SEO Learning curve Flexibility
View data
AxisAstroNext
Performance9.57.5
DX8.58
Ecosystem79.5
SEO9.58
Learning curve8.56.5
Flexibility88.5

Использование

Скопируйте и вставьте этот код на вашу страницу Astro:

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

<RadarChart
  axes={['Speed', 'DX', 'Ecosystem', 'SEO', 'Flexibility']}
  series={[
    { name: 'Astro', data: [9.5, 8.5, 7, 9.5, 8] },
    { name: 'Next', data: [7.5, 8, 9.5, 8, 8.5] },
  ]}
  max={10}
/>
--- import { RadarChart } from 'astro-component-kit/charts'; --- <RadarChart axes={['Speed', 'DX', 'Ecosystem', 'SEO', 'Flexibility']} series={[ { name: 'Astro', data: [9.5, 8.5, 7, 9.5, 8] }, { name: 'Next', data: [7.5, 8, 9.5, 8, 8.5] }, ]} max={10} />

Ручная установка

Если вы не используете пакет npm, создайте файл и вставьте следующий код: src/components/lib/RadarChart.astro:

astro
---
/**
 * RadarChart — Pure SVG spider chart comparing up to 3 entities across 5–8
 * axes on a shared 0–max scale, with value tooltips on every vertex.
 *
 * @param {string[]} axes - Attribute names (5–8 recommended).
 * @param {Array<{name: string, data: number[]}>} series - Entities (max 3).
 * @param {number} max - Scale maximum.
 * @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 { polar, linePath, colorAt, type Point } from './chart-utils';

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

const {
  title = 'Framework comparison',
  subtitle = 'Scored 0–10 by the team',
  axes = ['Performance', 'DX', 'Ecosystem', 'SEO', 'Learning curve', 'Flexibility'],
  series = [
    { name: 'Astro', data: [9.5, 8.5, 7, 9.5, 8.5, 8] },
    { name: 'Next', data: [7.5, 8, 9.5, 8, 6.5, 8.5] },
  ],
  max = 10,
  colors,
  variant = 'glass',
  size = 'md',
  className = '',
} = Astro.props;

const S = 340;
const c = S / 2;
const R = S / 2 - 54;
const n = axes.length;
const ang = (i: number) => (i / n) * Math.PI * 2;
const levels = [0.25, 0.5, 0.75, 1];
const shapes = series.slice(0, 3).map((s, si) => {
  const pts: Point[] = s.data.map((v, i) => polar(c, c, R * Math.max(0, Math.min(1, v / max)), ang(i)));
  return { ...s, pts, d: `${linePath(pts)} Z`, color: colorAt(si, colors) };
});
---

<ChartFrame
  title={title}
  subtitle={subtitle}
  variant={variant}
  size={size}
  className={className}
  legend={shapes.map((s) => ({ label: s.name, color: s.color }))}
  table={{ headers: ['Axis', ...series.map((s) => s.name)], rows: axes.map((a, i) => [a, ...series.map((s) => s.data[i])]) }}
>
  <div class="rd">
    <svg viewBox={`0 0 ${S} ${S}`} role="img" aria-label={`${title}: radar chart across ${n} axes`}>
      {levels.map((l) => (
        <path class="cf-grid" fill="none" d={`${linePath(axes.map((_, i) => polar(c, c, R * l, ang(i))))} Z`} />
      ))}
      {axes.map((a, i) => {
        const [x, y] = polar(c, c, R, ang(i));
        const [lx, ly] = polar(c, c, R + 18, ang(i));
        const anchor = 4 > Math.abs(lx - c) ? 'middle' : lx > c ? 'start' : 'end';
        return (
          <g>
            <line class="cf-grid" x1={c} y1={c} x2={x} y2={y} />
            <text class="cf-label" x={lx} y={ly + 4} text-anchor={anchor}>{a}</text>
          </g>
        );
      })}
      {shapes.map((s) => (
        <path d={s.d} fill={s.color} fill-opacity="0.18" stroke={s.color} stroke-width="2" stroke-linejoin="round" aria-hidden="true" />
      ))}
      {shapes.map((s) => s.pts.map(([x, y], i) => (
        <circle class="cf-mark" cx={x} cy={y} r="4.5" fill={s.color} stroke="var(--chart-surface, #0d0d0f)" stroke-width="2"
          tabindex="0" role="img" aria-label={`${s.name}, ${axes[i]}: ${s.data[i]} of ${max}`}
          data-tip-title={axes[i]} data-tip={shapes.map((o) => `${o.color}::${o.name}: ${o.data[i]}`).join('|')} />
      )))}
    </svg>
  </div>
</ChartFrame>

<style>
  .rd { max-width: 400px; margin: 0 auto; }
</style>
--- /** * RadarChart — Pure SVG spider chart comparing up to 3 entities across 5–8 * axes on a shared 0–max scale, with value tooltips on every vertex. * * @param {string[]} axes - Attribute names (5–8 recommended). * @param {Array<{name: string, data: number[]}>} series - Entities (max 3). * @param {number} max - Scale maximum. * @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 { polar, linePath, colorAt, type Point } from './chart-utils'; interface Props { title?: string; subtitle?: string; axes?: string[]; series?: Array<{ name: string; data: number[] }>; max?: number; colors?: string[]; variant?: 'glass' | 'solid' | 'ghost'; size?: 'sm' | 'md' | 'lg'; className?: string; } const { title = 'Framework comparison', subtitle = 'Scored 0–10 by the team', axes = ['Performance', 'DX', 'Ecosystem', 'SEO', 'Learning curve', 'Flexibility'], series = [ { name: 'Astro', data: [9.5, 8.5, 7, 9.5, 8.5, 8] }, { name: 'Next', data: [7.5, 8, 9.5, 8, 6.5, 8.5] }, ], max = 10, colors, variant = 'glass', size = 'md', className = '', } = Astro.props; const S = 340; const c = S / 2; const R = S / 2 - 54; const n = axes.length; const ang = (i: number) => (i / n) * Math.PI * 2; const levels = [0.25, 0.5, 0.75, 1]; const shapes = series.slice(0, 3).map((s, si) => { const pts: Point[] = s.data.map((v, i) => polar(c, c, R * Math.max(0, Math.min(1, v / max)), ang(i))); return { ...s, pts, d: `${linePath(pts)} Z`, color: colorAt(si, colors) }; }); --- <ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className} legend={shapes.map((s) => ({ label: s.name, color: s.color }))} table={{ headers: ['Axis', ...series.map((s) => s.name)], rows: axes.map((a, i) => [a, ...series.map((s) => s.data[i])]) }} > <div class="rd"> <svg viewBox={`0 0 ${S} ${S}`} role="img" aria-label={`${title}: radar chart across ${n} axes`}> {levels.map((l) => ( <path class="cf-grid" fill="none" d={`${linePath(axes.map((_, i) => polar(c, c, R * l, ang(i))))} Z`} /> ))} {axes.map((a, i) => { const [x, y] = polar(c, c, R, ang(i)); const [lx, ly] = polar(c, c, R + 18, ang(i)); const anchor = 4 > Math.abs(lx - c) ? 'middle' : lx > c ? 'start' : 'end'; return ( <g> <line class="cf-grid" x1={c} y1={c} x2={x} y2={y} /> <text class="cf-label" x={lx} y={ly + 4} text-anchor={anchor}>{a}</text> </g> ); })} {shapes.map((s) => ( <path d={s.d} fill={s.color} fill-opacity="0.18" stroke={s.color} stroke-width="2" stroke-linejoin="round" aria-hidden="true" /> ))} {shapes.map((s) => s.pts.map(([x, y], i) => ( <circle class="cf-mark" cx={x} cy={y} r="4.5" fill={s.color} stroke="var(--chart-surface, #0d0d0f)" stroke-width="2" tabindex="0" role="img" aria-label={`${s.name}, ${axes[i]}: ${s.data[i]} of ${max}`} data-tip-title={axes[i]} data-tip={shapes.map((o) => `${o.color}::${o.name}: ${o.data[i]}`).join('|')} /> )))} </svg> </div> </ChartFrame> <style> .rd { max-width: 400px; margin: 0 auto; } </style>

Краткая информация

Категория
Графики и статистика
Имя файла
RadarChart.astro
Зависимости
apexcharts (peer)
Теги
chartsvgzero-dependencyradarspidercomparisonattributes