Radar Chart

A composable multi-series radar chart with animated polygons, hover interactions, and customizable metrics

Preview

Campaign Performance

Google Search70%
Display Ads58%
Newsletter71%
Social58%

Installation

pnpm dlx shadcn@latest add https://ui.bklit.com/r/radar-chart.json

Usage

The Radar Chart uses a composable API. Define your metrics and data, then combine components:

import { RadarChart, RadarGrid, RadarAxis, RadarLabels, RadarArea } from "@bklitui/ui/charts";

const metrics = [
  { key: "speed", label: "Speed" },
  { key: "power", label: "Power" },
  { key: "technique", label: "Technique" },
];

const data = [
  { label: "Player A", color: "#3b82f6", values: { speed: 85, power: 70, technique: 90 } },
  { label: "Player B", color: "#f59e0b", values: { speed: 65, power: 95, technique: 60 } },
];

export default function PerformanceRadar() {
  return (
    <RadarChart data={data} metrics={metrics} size={400}>
      <RadarGrid />
      <RadarAxis />
      <RadarLabels />
      {data.map((item, index) => (
        <RadarArea key={item.label} index={index} />
      ))}
    </RadarChart>
  );
}

Components

RadarChart

The root container that provides context to all children.

PropTypeDefaultDescription
dataRadarData[]requiredArray of data series
metricsRadarMetric[]requiredMetrics to display
sizenumberautoFixed size in pixels
levelsnumber5Number of grid circles
marginnumber60Margin around chart
animatebooleantrueEnable animations
hoveredIndexnumber | null-Controlled hover state
onHoverChange(index: number | null) => void-Hover callback
classNamestring""Additional CSS class

RadarGrid

Renders the circular grid lines (spider web pattern).

PropTypeDefaultDescription
showLabelsbooleantrueShow level value labels
classNamestring""Additional CSS class

RadarAxis

Renders axis lines from center to each metric.

PropTypeDefaultDescription
classNamestring""Additional CSS class

RadarLabels

Renders metric labels around the perimeter.

PropTypeDefaultDescription
offsetnumber24Distance from chart edge
fontSizenumber11Font size for labels
interactivebooleanfalseEnable hover effects on labels
classNamestring""Additional CSS class

RadarArea

Renders a single data polygon with hover effects.

PropTypeDefaultDescription
indexnumberrequiredIndex in the data array
colorstringfrom dataOptional color override
showPointsbooleantrueShow data point circles
showGlowbooleantrueShow glow effect on hover
classNamestring""Additional CSS class

Data Shape

interface RadarMetric {
  key: string;    // Unique identifier
  label: string;  // Display label
}

interface RadarData {
  label: string;                    // Series label
  color: string;                    // Series color
  values: Record<string, number>;   // metric key -> value (0-100)
}

Examples

Basic Radar Chart

<RadarChart data={data} metrics={metrics} size={350}><RadarGrid /><RadarAxis /><RadarLabels />{data.map((item, index) => (  <RadarArea key={item.label} index={index} />))}</RadarChart>

Minimal Style

<RadarChart data={data} metrics={metrics} size={300} levels={4}><RadarGrid showLabels={false} /><RadarAxis /><RadarLabels fontSize={12} offset={20} />{data.map((item, index) => (  <RadarArea key={item.label} index={index} />))}</RadarChart>

Synchronized Legend

Connect the legend hover state to the chart for bidirectional interaction. See the Legend documentation for more customization options.

import { useState } from "react";
import {
  RadarChart, RadarGrid, RadarAxis, RadarLabels, RadarArea,
  Legend, LegendItemComponent, LegendMarker, LegendLabel, LegendValue
} from "@bklitui/ui/charts";

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

  // Convert radar data to legend items
  const legendItems = data.map((d) => ({
    label: d.label,
    value: Object.values(d.values).reduce((a, b) => a + b, 0) / metrics.length,
    maxValue: 100,
    color: d.color,
  }));

  return (
    <div className="flex items-center gap-12">
      <RadarChart
        data={data}
        metrics={metrics}
        size={400}
        hoveredIndex={hoveredIndex}
        onHoverChange={setHoveredIndex}
      >
        <RadarGrid />
        <RadarAxis />
        <RadarLabels />
        {data.map((item, index) => (
          <RadarArea key={item.label} index={index} />
        ))}
      </RadarChart>

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

Without Points

<RadarChart data={data} metrics={metrics} size={350}>
  <RadarGrid />
  <RadarAxis />
  <RadarLabels />
  {data.map((item, index) => (
    <RadarArea key={item.label} index={index} showPoints={false} />
  ))}
</RadarChart>

Hooks

useRadar

Access the radar context from any child component:

import { useRadar } from "@bklitui/ui/charts";

function CustomComponent() {
  const {
    data,
    metrics,
    radius,
    hoveredIndex,
    setHoveredIndex,
    getPointPosition,
  } = useRadar();
  // ...
}

Animation

The radar chart features a multi-phase animation on mount:

  1. Grid Expansion - Concentric circles scale in from center
  2. Axis Growth - Lines grow outward from center
  3. Label Fade - Metric labels fade in
  4. Area Expansion - Data polygons animate from center to values

All animations use spring physics for natural motion. Hover interactions are instant with no delays.

Dependencies

pnpm add @visx/group @visx/responsive @visx/scale @visx/shape motion