Radar Chart

Spider/radar chart for multi-metric comparison across categories

Installation

pnpm dlx shadcn@latest add "https://vritti.thesatyajit.com/r/radar-chart"

Dependencies

pnpm add @visx/responsive @visx/shape motion

Examples

Comparison

"use client"
import RadarChart, { RadarArea, RadarAxis, RadarGrid, RadarLabels } from "@/components/vritti/radar-chart"

const metrics = [
  { key: "react", label: "React" },
  { key: "typescript", label: "TypeScript" },
  { key: "css", label: "CSS" },
  { key: "testing", label: "Testing" },
  { key: "perf", label: "Performance" },
  { key: "a11y", label: "Accessibility" },
]

const data = [
  {
    label: "Frontend",
    color: "hsl(217, 91%, 60%)",
    values: { react: 95, typescript: 80, css: 90, testing: 65, perf: 75, a11y: 70 },
  },
  {
    label: "Fullstack",
    color: "hsl(142, 71%, 45%)",
    values: { react: 75, typescript: 85, css: 65, testing: 80, perf: 70, a11y: 60 },
  },
]

export function RadarChartComparisonDemo() {
  return (
    <div className="flex justify-center p-4">
      <RadarChart data={data} metrics={metrics} size={320}>
        <RadarGrid />
        <RadarAxis />
        <RadarLabels />
        {data.map((_, i) => (
          <RadarArea key={i} index={i} />
        ))}
      </RadarChart>
    </div>
  )
}

Legend

"use client"
import { useState } from "react"
import RadarChart, {
  Legend,
  LegendItem,
  LegendLabel,
  LegendMarker,
  LegendValue,
  RadarArea,
  RadarAxis,
  RadarGrid,
  RadarLabels,
  type LegendItemData,
  type RadarData,
  type RadarMetric,
} from "@/components/vritti/radar-chart"

const metrics: RadarMetric[] = [
  { key: "engagement", label: "Engagement" },
  { key: "pagesPerSession", label: "Pages/Session" },
  { key: "sessionDuration", label: "Duration" },
  { key: "conversionRate", label: "Conversion" },
  { key: "bounceInverse", label: "Retention" },
]

const campaignData: RadarData[] = [
  {
    label: "Google Search",
    color: "#3b82f6",
    values: {
      engagement: 72,
      pagesPerSession: 68,
      sessionDuration: 70,
      conversionRate: 75,
      bounceInverse: 65,
    },
  },
  {
    label: "Display Ads",
    color: "#f59e0b",
    values: {
      engagement: 85,
      pagesPerSession: 45,
      sessionDuration: 40,
      conversionRate: 30,
      bounceInverse: 88,
    },
  },
  {
    label: "Newsletter",
    color: "#10b981",
    values: {
      engagement: 45,
      pagesPerSession: 90,
      sessionDuration: 92,
      conversionRate: 88,
      bounceInverse: 42,
    },
  },
  {
    label: "Social",
    color: "#ec4899",
    values: {
      engagement: 95,
      pagesPerSession: 35,
      sessionDuration: 25,
      conversionRate: 55,
      bounceInverse: 78,
    },
  },
]

export function RadarChartLegendDemo() {
  const [hoveredIndex, setHoveredIndex] = useState<number | null>(null)

  const legendItems: LegendItemData[] = campaignData.map((d, index) => ({
    label: d.label,
    value:
      Object.values(d.values).reduce((a, b) => a + b, 0) / metrics.length,
    maxValue: 100,
    color: d.color ?? `var(--chart-${index + 1})`,
  }))

  return (
    <div className="flex flex-col items-center justify-center gap-8 p-4 lg:flex-row lg:gap-12">
      <RadarChart
        data={campaignData}
        hoveredIndex={hoveredIndex}
        metrics={metrics}
        onHoverChange={setHoveredIndex}
        size={260}
      >
        <RadarGrid />
        <RadarAxis />
        <RadarLabels />
        {campaignData.map((item, index) => (
          <RadarArea index={index} key={item.label} />
        ))}
      </RadarChart>

      <Legend
        hoveredIndex={hoveredIndex}
        items={legendItems}
        onHoverChange={setHoveredIndex}
        title="Campaign Performance"
      >
        <LegendItem className="flex items-center gap-3">
          <LegendMarker />
          <LegendLabel className="flex-1 text-sm font-medium" />
          <LegendValue formatValue={(v) => `${v.toFixed(0)}%`} />
        </LegendItem>
      </Legend>
    </div>
  )
}

Minimal

"use client"
import RadarChart, {
  RadarArea,
  RadarAxis,
  RadarGrid,
  RadarLabels,
  type RadarData,
  type RadarMetric,
} from "@/components/vritti/radar-chart"

const simpleMetrics: RadarMetric[] = [
  { key: "speed", label: "Speed" },
  { key: "power", label: "Power" },
  { key: "technique", label: "Technique" },
  { key: "stamina", label: "Stamina" },
  { key: "agility", label: "Agility" },
]

const simpleData: RadarData[] = [
  {
    label: "Player A",
    color: "#6366f1",
    values: { speed: 85, power: 70, technique: 90, stamina: 75, agility: 88 },
  },
  {
    label: "Player B",
    color: "#f97316",
    values: { speed: 65, power: 95, technique: 60, stamina: 88, agility: 55 },
  },
]

export function RadarChartMinimalDemo() {
  return (
    <div className="flex justify-center p-4">
      <RadarChart data={simpleData} levels={4} metrics={simpleMetrics} size={300}>
        <RadarGrid showLabels={false} />
        <RadarAxis />
        <RadarLabels fontSize={12} offset={20} />
        {simpleData.map((item, index) => (
          <RadarArea index={index} key={item.label} />
        ))}
      </RadarChart>
    </div>
  )
}