---
title: Radar Chart
description: Beautiful radar charts with filled and lines variants, gradient colors, and glow effects
image: /og/radar-chart.png
links:
  github: https://github.com/legions-developer/evilcharts/blob/main/src/registry/charts/radar-chart.tsx
  doc: https://recharts.github.io/en-US/examples/SimpleRadarChart
  api: https://recharts.github.io/en-US/api/RadarChart
---

### Basic Chart

```tsx
"use client";

import { EvilRadarChart } from "@/components/evilcharts/charts/radar-chart";
import { type ChartConfig } from "@/components/evilcharts/ui/chart";

const data = [
  { skill: "JavaScript", desktop: 186, mobile: 80 },
  { skill: "TypeScript", desktop: 305, mobile: 200 },
  { skill: "React", desktop: 237, mobile: 120 },
  { skill: "Node.js", desktop: 173, mobile: 190 },
  { skill: "CSS", desktop: 209, mobile: 130 },
  { skill: "Python", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    colors: {
      light: ["#3b82f6"],
      dark: ["#60a5fa"],
    },
  },
  mobile: {
    label: "Mobile",
    colors: {
      light: ["#10b981"],
      dark: ["#34d399"],
    },
  },
} satisfies ChartConfig;

export function EvilExampleRadarChart() {
  return (
    <EvilRadarChart
      isClickable
      className="h-full w-full p-4"
      data={data}
      dataKey="skill"
      dotVariant="colored-border"
      activeDotVariant="default"
      chartConfig={chartConfig}
      variant="filled" // [!code highlight]
    />
  );
}

```

## Installation


  
  
    ### npm

```bash
npx shadcn@latest add @evilcharts/radar-chart
```

### yarn

```bash
yarn shadcn@latest add @evilcharts/radar-chart
```

### bun

```bash
bunx --bun shadcn@latest add @evilcharts/radar-chart
```

### pnpm

```bash
pnpm dlx shadcn@latest add @evilcharts/radar-chart
```
  
  
    
      
        ### Install the following dependencies:
        
          ### npm

```bash
npm install recharts
```

### yarn

```bash
yarn add recharts
```

### bun

```bash
bun add recharts
```

### pnpm

```bash
pnpm add recharts
```
        
      
      
        ### Copy and paste the following code snippets into your project.
         
          To use the chart, first create the folder `evilcharts` and a subfolder called `charts` inside your `components` directory.
          Then, copy the following base radar-chart code into a new file in that folder.
        
        
          ### components/evilcharts/charts/radar-chart.tsx

```tsx
"use client";

import {
  ChartTooltip,
  ChartTooltipContent,
  type TooltipRoundness,
  type TooltipVariant,
} from "@/components/evilcharts/ui/tooltip";
import {
  type ChartConfig,
  ChartContainer,
  getColorsCount,
  LoadingIndicator,
} from "@/components/evilcharts/ui/chart";
import { ChartLegend, ChartLegendContent, type ChartLegendVariant } from "@/components/evilcharts/ui/legend";
import { useCallback, useEffect, useId, useMemo, useState, type ComponentProps } from "react";
import { PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart } from "recharts";
import { ChartBackground, type BackgroundVariant } from "@/components/evilcharts/ui/background";
import type { TypedDataKey } from "recharts/types/util/typedDataKey";
import { ChartDot, DotVariant } from "@/components/evilcharts/ui/dot";

// Loading animation constants
const LOADING_POINTS = 6;
const LOADING_ANIMATION_DURATION = 1500;

// Constants
const DEFAULT_FILL_OPACITY = 0.3;

type ChartProps = ComponentProps<typeof RadarChart>;
type RadarProps = ComponentProps<typeof Radar>;
type PolarGridProps = ComponentProps<typeof PolarGrid>;

type RadarVariant = "filled" | "lines";

// Extract only keys from TData where the value is a number
type NumericDataKeys<T> = {
  [K in keyof T]: T[K] extends number ? K : never;
}[keyof T];

type EvilRadarChartProps<
  TData extends Record<string, unknown>,
  TConfig extends Record<string, ChartConfig[string]>,
> = {
  // Data
  data: TData[];
  dataKey: keyof TData & string; // The key for the angle axis (e.g., "month", "category")
  chartConfig: TConfig;
  className?: string;
  chartProps?: ChartProps;
  radarProps?: Omit<RadarProps, "dataKey">;
  polarGridProps?: PolarGridProps;

  // Variant
  variant?: RadarVariant;
  fillOpacity?: number;

  // Axes
  hideAngleAxis?: boolean;
  hideRadiusAxis?: boolean;
  hideGrid?: boolean;
  gridType?: "polygon" | "circle";

  // Hide Stuffs
  hideTooltip?: boolean;
  hideLegend?: boolean;
  hideDots?: boolean;
  legendVariant?: ChartLegendVariant;
  // Tooltip
  tooltipRoundness?: TooltipRoundness;
  tooltipVariant?: TooltipVariant;
  tooltipDefaultIndex?: number;
  dotVariant?: DotVariant;
  activeDotVariant?: DotVariant;

  // Interactive Stuffs
  isLoading?: boolean;

  // Glow Effects
  glowingRadars?: NumericDataKeys<TData>[];
  // Background
  backgroundVariant?: BackgroundVariant;
};

type EvilRadarChartClickable = {
  isClickable: true;
  onSelectionChange?: (selectedDataKey: string | null) => void;
};

type EvilRadarChartNotClickable = {
  isClickable?: false;
  onSelectionChange?: never;
};

type EvilRadarChartPropsWithCallback<
  TData extends Record<string, unknown>,
  TConfig extends Record<string, ChartConfig[string]>,
> = EvilRadarChartProps<TData, TConfig> & (EvilRadarChartClickable | EvilRadarChartNotClickable);

export function EvilRadarChart<
  TData extends Record<string, unknown>,
  TConfig extends Record<string, ChartConfig[string]>,
>({
  data,
  dataKey,
  chartConfig,
  className,
  chartProps,
  radarProps,
  polarGridProps,
  variant = "filled",
  fillOpacity = DEFAULT_FILL_OPACITY,
  hideAngleAxis = false,
  hideRadiusAxis = true,
  hideGrid = false,
  gridType = "polygon",
  hideTooltip = false,
  hideLegend = false,
  hideDots = false,
  legendVariant,
  tooltipRoundness,
  tooltipVariant,
  tooltipDefaultIndex,
  dotVariant,
  activeDotVariant,
  isClickable = false,
  isLoading = false,
  glowingRadars = [],
  onSelectionChange,
  backgroundVariant,
}: EvilRadarChartPropsWithCallback<TData, TConfig>) {
  const [selectedRadar, setSelectedRadar] = useState<string | null>(null);
  const chartId = useId().replace(/:/g, "");
  const loadingData = useLoadingData(isLoading, dataKey);

  // Wrapper function to update state and call parent callback
  const handleSelectionChange = useCallback(
    (newSelectedRadar: string | null) => {
      setSelectedRadar(newSelectedRadar);
      if (isClickable && onSelectionChange) {
        onSelectionChange(newSelectedRadar);
      }
    },
    [onSelectionChange, isClickable],
  );

  // Get radar data keys from chartConfig
  const radarDataKeys = Object.keys(chartConfig);

  return (
    <ChartContainer className={className} config={chartConfig}>
      <LoadingIndicator isLoading={isLoading} />
      <RadarChart
        id="evil-charts-radar-chart"
        data={isLoading ? loadingData : data}
        {...chartProps}
      >
        {backgroundVariant && <ChartBackground variant={backgroundVariant} />}
        {!hideGrid && (
          <PolarGrid
            gridType={gridType}
            stroke="currentColor"
            strokeOpacity={0.2}
            strokeDasharray="3 4"
            {...polarGridProps}
          />
        )}

        {!hideAngleAxis && !isLoading && (
          <PolarAngleAxis
            dataKey={dataKey as TypedDataKey<TData>}
            tick={{ fill: "currentColor", fontSize: 12 }}
            tickLine={false}
          />
        )}

        {!hideRadiusAxis && !isLoading && (
          <PolarRadiusAxis
            tick={{ fill: "currentColor", fontSize: 10 }}
            tickLine={false}
            axisLine={false}
          />
        )}

        {!hideLegend && !isLoading && (
          <ChartLegend
            verticalAlign="bottom"
            align="center"
            content={
              <ChartLegendContent
                selected={selectedRadar}
                onSelectChange={handleSelectionChange}
                isClickable={isClickable}
                variant={legendVariant}
              />
            }
          />
        )}

        {!hideTooltip && !isLoading && (
          <ChartTooltip
            defaultIndex={tooltipDefaultIndex}
            cursor={false}
            content={
              <ChartTooltipContent
                selected={selectedRadar}
                roundness={tooltipRoundness}
                variant={tooltipVariant}
              />
            }
          />
        )}

        {/* Render radars for each data key in chartConfig */}
        {!isLoading &&
          radarDataKeys.map((radarKey) => {
            const isGlowing = glowingRadars.includes(radarKey as NumericDataKeys<TData>);
            const isSelected = selectedRadar === null || selectedRadar === radarKey;
            const opacity = isClickable && !isSelected ? 0.2 : 1;

            const getFilter = () => {
              if (isGlowing) return `url(#${chartId}-radar-glow-${radarKey})`;
              return undefined;
            };

            const showDots = !hideDots;
            const dot = showDots ? (
              dotVariant ? (
                <ChartDot
                  fillOpacity={opacity}
                  type={dotVariant}
                  dataKey={radarKey}
                  chartId={chartId}
                />
              ) : (
                true
              )
            ) : (
              false
            );
            const activeDot = showDots ? (
              activeDotVariant ? (
                <ChartDot
                  fillOpacity={opacity}
                  type={activeDotVariant}
                  dataKey={radarKey}
                  chartId={chartId}
                />
              ) : undefined
            ) : (
              false
            );

            return (
              <Radar
                {...radarProps}
                key={radarKey}
                dataKey={radarKey}
                stroke={`url(#${chartId}-radar-stroke-${radarKey})`}
                fill={variant === "filled" ? `url(#${chartId}-radar-fill-${radarKey})` : "none"}
                fillOpacity={variant === "filled" ? fillOpacity * opacity : 0}
                strokeOpacity={opacity}
                strokeWidth={1}
                dot={dot}
                activeDot={activeDot}
                filter={getFilter()}
                style={isClickable ? { cursor: "pointer" } : undefined}
                onClick={() => {
                  if (!isClickable) return;
                  handleSelectionChange(selectedRadar === radarKey ? null : radarKey);
                }}
                className="transition-opacity duration-200"
              />
            );
          })}

        {/* Loading state radar */}
        {isLoading && (
          <Radar
            dataKey="value"
            stroke="currentColor"
            fill="currentColor"
            fillOpacity={0.1}
            strokeOpacity={0.3}
            strokeWidth={2}
            dot={false}
            isAnimationActive
            animationDuration={LOADING_ANIMATION_DURATION}
            animationEasing="ease-in-out"
          />
        )}

        {/* ======== CHART STYLES ======== */}
        <defs>
          {/* Shared horizontal color gradient for dots */}
          <HorizontalColorGradientStyle chartConfig={chartConfig} chartId={chartId} />

          {/* Stroke and fill gradients for each radar */}
          <RadarGradientStyle chartConfig={chartConfig} chartId={chartId} />

          {/* Glow filters */}
          {glowingRadars.length > 0 && (
            <GlowFilterStyle chartId={chartId} glowingRadars={glowingRadars as string[]} />
          )}
        </defs>
      </RadarChart>
    </ChartContainer>
  );
}

// Generate random loading data for radar chart animation
function generateLoadingData(dataKey: string) {
  const categories = ["A", "B", "C", "D", "E", "F"];
  return categories.slice(0, LOADING_POINTS).map((cat) => ({
    [dataKey]: cat,
    value: 30 + Math.random() * 70,
  }));
}

function useLoadingData(isLoading: boolean, dataKey: string) {
  const [refreshKey, setRefreshKey] = useState(0);

  useEffect(() => {
    if (!isLoading) return;

    const interval = setInterval(() => {
      setRefreshKey((prev) => prev + 1);
    }, LOADING_ANIMATION_DURATION);

    return () => clearInterval(interval);
  }, [isLoading]);

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const loadingData = useMemo(() => generateLoadingData(dataKey), [dataKey, refreshKey]);

  return loadingData;
}

// Create stroke and fill gradients for radar chart paths
const RadarGradientStyle = ({
  chartConfig,
  chartId,
}: {
  chartConfig: ChartConfig;
  chartId: string;
}) => {
  return (
    <>
      {Object.entries(chartConfig).map(([dataKey, config]) => {
        const colorsCount = getColorsCount(config);

        return (
          <g key={dataKey}>
            {/* Stroke gradient */}
            <linearGradient id={`${chartId}-radar-stroke-${dataKey}`} x1="0" y1="0" x2="1" y2="1">
              {colorsCount === 1 ? (
                <>
                  <stop offset="0%" stopColor={`var(--color-${dataKey}-0)`} />
                  <stop offset="100%" stopColor={`var(--color-${dataKey}-0)`} />
                </>
              ) : (
                Array.from({ length: colorsCount }, (_, index) => (
                  <stop
                    key={index}
                    offset={`${(index / (colorsCount - 1)) * 100}%`}
                    stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}
                  />
                ))
              )}
            </linearGradient>

            {/* Fill gradient (radial for better effect) */}
            <radialGradient id={`${chartId}-radar-fill-${dataKey}`} cx="50%" cy="50%" r="50%">
              {colorsCount === 1 ? (
                <>
                  <stop offset="0%" stopColor={`var(--color-${dataKey}-0)`} stopOpacity={0.8} />
                  <stop offset="100%" stopColor={`var(--color-${dataKey}-0)`} stopOpacity={0.3} />
                </>
              ) : (
                Array.from({ length: colorsCount }, (_, index) => (
                  <stop
                    key={index}
                    offset={`${(index / (colorsCount - 1)) * 100}%`}
                    stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}
                    stopOpacity={index === 0 ? 0.8 : 0.3}
                  />
                ))
              )}
            </radialGradient>
          </g>
        );
      })}
    </>
  );
};

// Shared horizontal color gradient (left to right) - used by dots
const HorizontalColorGradientStyle = ({
  chartConfig,
  chartId,
}: {
  chartConfig: ChartConfig;
  chartId: string;
}) => {
  return (
    <>
      {Object.entries(chartConfig).map(([dataKey, config]) => {
        const colorsCount = getColorsCount(config);

        return (
          <linearGradient
            key={`${chartId}-colors-${dataKey}`}
            id={`${chartId}-colors-${dataKey}`}
            x1="0"
            y1="0"
            x2="1"
            y2="0"
          >
            {colorsCount === 1 ? (
              <>
                <stop offset="0%" stopColor={`var(--color-${dataKey}-0)`} />
                <stop offset="100%" stopColor={`var(--color-${dataKey}-0)`} />
              </>
            ) : (
              Array.from({ length: colorsCount }, (_, index) => (
                <stop
                  key={index}
                  offset={`${(index / (colorsCount - 1)) * 100}%`}
                  stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}
                />
              ))
            )}
          </linearGradient>
        );
      })}
    </>
  );
};

// Apply soft glow filter effect to radar areas using SVG filters
const GlowFilterStyle = ({
  chartId,
  glowingRadars,
}: {
  chartId: string;
  glowingRadars: string[];
}) => {
  return (
    <>
      {glowingRadars.map((radarKey) => (
        <filter
          key={`${chartId}-radar-glow-${radarKey}`}
          id={`${chartId}-radar-glow-${radarKey}`}
          x="-50%"
          y="-50%"
          width="200%"
          height="200%"
        >
          <feGaussianBlur in="SourceGraphic" stdDeviation="4" result="blur" />
          <feColorMatrix
            in="blur"
            type="matrix"
            values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 0.6 0"
            result="glow"
          />
          <feMerge>
            <feMergeNode in="glow" />
            <feMergeNode in="SourceGraphic" />
          </feMerge>
        </filter>
      ))}
    </>
  );
};

```
        
      
       
        ### Now, Let's add the chart component to your project.
        
          These Components are required by the chart component to render the chart. Make a folder called `ui` inside the `evilcharts` folder and paste the code there.

          Below is main chart component.
        
        
          ### components/evilcharts/ui/chart.tsx

```tsx
"use client";

import * as RechartsPrimitive from "recharts";
import { cn } from "@/lib/utils";
import * as React from "react";

// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const;

type ThemeKey = keyof typeof THEMES;

// All Keys are optional at first
type ThemeColorsBase = {
  [K in ThemeKey]?: string[];
};

// Require at least one theme key
type AtLeastOneThemeColor = {
  [K in ThemeKey]: Required<Pick<ThemeColorsBase, K>> & Partial<Omit<ThemeColorsBase, K>>;
}[ThemeKey];

const VALID_THEME_KEYS = Object.keys(THEMES) as ThemeKey[];

// Validation for chart config colors at runtime
function validateChartConfigColors(config: ChartConfig): void {
  for (const [key, value] of Object.entries(config)) {
    if (value.colors) {
      const hasValidThemeKey = VALID_THEME_KEYS.some(
        (themeKey) => value.colors?.[themeKey] !== undefined,
      );

      if (!hasValidThemeKey) {
        throw new Error(
          `[EvilCharts] Invalid chart config for "${key}": colors object must have at least one theme key (${VALID_THEME_KEYS.join(", ")}). Received empty object or invalid keys.`,
        );
      }
    }
  }
}

export type ChartConfig = Record<
  string,
  {
    label?: React.ReactNode;
    icon?: React.ComponentType;
    colors?: AtLeastOneThemeColor;
  }
>;

interface ChartContextProps {
  config: ChartConfig;
}

const ChartContext = React.createContext<ChartContextProps | null>(null);

export function useChart() {
  const context = React.useContext(ChartContext);

  if (!context) {
    throw new Error("useChart must be used within a <ChartContainer />");
  }

  return context;
}

interface ChartContainerProps
  extends
    Omit<React.ComponentProps<"div">, "children">,
    Pick<
      React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>,
      | "initialDimension"
      | "aspect"
      | "debounce"
      | "minHeight"
      | "minWidth"
      | "maxHeight"
      | "height"
      | "width"
      | "onResize"
      | "children"
    > {
  config: ChartConfig;
  innerResponsiveContainerStyle?: React.ComponentProps<
    typeof RechartsPrimitive.ResponsiveContainer
  >["style"];
  /** Optional content rendered below the chart (e.g. EvilBrush) */
  footer?: React.ReactNode;
}

function ChartContainer({
  id,
  config,
  initialDimension = { width: 320, height: 200 },
  className,
  children,
  footer,
  ...props
}: Readonly<ChartContainerProps>) {
  const uniqueId = React.useId();
  const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`;

  // Validate chart config at runtime
  validateChartConfigColors(config);

  return (
    <ChartContext.Provider value={{ config }}>
      <div
        data-slot="chart"
        data-chart={chartId}
        className={cn(
          "min-h-0 w-full flex-1",
          "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border relative flex flex-col justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
          !footer && "aspect-video",
          className,
        )}
        {...props}
      >
        <ChartStyle id={chartId} config={config} />
        <RechartsPrimitive.ResponsiveContainer
          className="min-h-0 w-full flex-1"
          initialDimension={initialDimension}
        >
          {children}
        </RechartsPrimitive.ResponsiveContainer>
        {footer}
      </div>
    </ChartContext.Provider>
  );
}

function LoadingIndicator({ isLoading }: { isLoading: boolean }) {
  if (!isLoading) {
    return null;
  }

  return (
    <div className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center">
      <div className="text-primary bg-background flex items-center justify-center gap-2 rounded-md border px-2 py-0.5 text-sm">
        <div className="border-border border-t-primary h-3 w-3 animate-spin rounded-full border" />
        <span>Loading</span>
      </div>
    </div>
  );
}

// Distribute colors evenly across slots, extra slots go to last color(s)
// Example: 2 colors for 4 slots → [red, red, pink, pink]
// Example: 3 colors for 4 slots → [red, pink, blue, blue]
function distributeColors(colorsArray: string[], maxCount: number): string[] {
  const availableCount = colorsArray.length;
  if (availableCount >= maxCount) {
    return colorsArray.slice(0, maxCount);
  }

  const result: string[] = [];
  const baseSlots = Math.floor(maxCount / availableCount);
  const extraSlots = maxCount % availableCount;

  // First (availableCount - extraSlots) colors get baseSlots each
  // Last extraSlots colors get (baseSlots + 1) each
  for (let colorIdx = 0; colorIdx < availableCount; colorIdx++) {
    const isExtraColor = colorIdx >= availableCount - extraSlots;
    const slotsForThisColor = baseSlots + (isExtraColor ? 1 : 0);
    for (let j = 0; j < slotsForThisColor; j++) {
      result.push(colorsArray[colorIdx]);
    }
  }

  return result;
}

const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
  const colorConfig = Object.entries(config).filter(([, config]) => config.colors);

  if (!colorConfig.length) {
    return null;
  }

  const generateCssVars = (theme: keyof typeof THEMES) =>
    colorConfig
      .flatMap(([key, itemConfig]) => {
        const colorsArray = itemConfig.colors?.[theme];
        if (!colorsArray || !Array.isArray(colorsArray) || colorsArray.length === 0) {
          return [];
        }

        // Get max count across all themes for this key
        const maxCount = getColorsCount(itemConfig);

        // Distribute colors evenly across all required slots
        const distributedColors = distributeColors(colorsArray, maxCount);

        return distributedColors.map((color, index) => `  --color-${key}-${index}: ${color};`);
      })
      .filter(Boolean)
      .join("\n");

  const css = Object.entries(THEMES)
    .map(
      ([theme, prefix]) =>
        `${prefix} [data-chart=${id}] {\n${generateCssVars(theme as keyof typeof THEMES)}\n}`,
    )
    .join("\n");

  return <style dangerouslySetInnerHTML={{ __html: css }} />;
};

// Helper to extract item config from a payload.
export function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {
  if (typeof payload !== "object" || payload === null) {
    return undefined;
  }

  const payloadPayload =
    "payload" in payload && typeof payload.payload === "object" && payload.payload !== null
      ? payload.payload
      : undefined;

  let configLabelKey: string = key;

  if (key in payload && typeof payload[key as keyof typeof payload] === "string") {
    configLabelKey = payload[key as keyof typeof payload] as string;
  } else if (
    payloadPayload &&
    key in payloadPayload &&
    typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
  ) {
    configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;
  }

  return configLabelKey in config ? config[configLabelKey] : config[key];
}

// Format values to percent for expanded charts
function axisValueToPercentFormatter(value: number) {
  return `${Math.round(value * 100).toFixed(0)}%`;
}

// Get max colors count across all themes for a config entry
function getColorsCount(config: ChartConfig[string]): number {
  if (!config.colors) return 1;
  const counts = VALID_THEME_KEYS.map((theme) => config.colors?.[theme]?.length ?? 0);
  return Math.max(...counts, 1);
}

// Generate random loading data for skeleton/loading state
// min/max represent percentage of the range (0-100), defaults to 20-80 for realistic look
export const getLoadingData = (points: number = 10, min: number = 0, max: number = 70) => {
  const range = max - min;
  return Array.from({ length: points }, () => ({
    loading: Math.floor(Math.random() * range) + min,
  }));
};

export {
  ChartContainer,
  ChartStyle,
  axisValueToPercentFormatter,
  LoadingIndicator,
  getColorsCount,
};

```
        
      
       
        ### Now, We need to add sub components.
        
          Create a file called `tooltip.tsx` inside the `evilcharts/ui` folder and paste the code there.
        
        
          ### components/evilcharts/ui/tooltip.tsx

```tsx
import { getPayloadConfigFromPayload, getColorsCount, useChart } from "@/components/evilcharts/ui/chart";
import type { NameType, ValueType } from "recharts/types/component/DefaultTooltipContent";
import * as RechartsPrimitive from "recharts";
import { cn } from "@/lib/utils";
import * as React from "react";

type TooltipRoundness = "sm" | "md" | "lg" | "xl";
type TooltipVariant = "default" | "frosted-glass";

const roundnessMap: Record<TooltipRoundness, string> = {
  sm: "rounded-sm",
  md: "rounded-md",
  lg: "rounded-lg",
  xl: "rounded-xl",
};

const variantMap: Record<TooltipVariant, string> = {
  default: "bg-background",
  "frosted-glass": "bg-background/70 backdrop-blur-sm",
};

function ChartTooltipContent({
  active,
  payload,
  className,
  indicator = "dot",
  hideLabel = false,
  hideIndicator = false,
  label,
  labelFormatter,
  labelClassName,
  formatter,
  nameKey,
  labelKey,
  selected,
  roundness = "lg",
  variant = "default",
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
  React.ComponentProps<"div"> & {
    hideLabel?: boolean;
    hideIndicator?: boolean;
    indicator?: "line" | "dot" | "dashed";
    nameKey?: string;
    labelKey?: string;
    selected?: string | null;
    roundness?: TooltipRoundness;
    variant?: TooltipVariant;
  } & Omit<
    RechartsPrimitive.DefaultTooltipContentProps<ValueType, NameType>,
    "accessibilityLayer"
  >) {
  const { config } = useChart();

  const tooltipLabel = React.useMemo(() => {
    if (hideLabel || !payload?.length) {
      return null;
    }

    const [item] = payload;
    const key = `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}`;
    const itemConfig = getPayloadConfigFromPayload(config, item, key);
    const value =
      !labelKey && typeof label === "string" ? (config[label]?.label ?? label) : itemConfig?.label;

    if (labelFormatter) {
      return (
        <div className={cn("font-medium", labelClassName)}>{labelFormatter(value, payload)}</div>
      );
    }

    if (!value) {
      return null;
    }

    return <div className={cn("font-medium", labelClassName)}>{value}</div>;
  }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);

  if (!active || !payload?.length) {
    // Empty tooltip - to prevent position getting 0.0 so it doesnt animate tooltip every time from 0.0 origin
    return <span className="p-4" />;
  }

  const nestLabel = payload.length === 1 && indicator !== "dot";

  return (
    <div
      className={cn(
        "border-border/50 grid min-w-32 items-start gap-1.5 border px-2.5 py-1.5 text-xs shadow-xl",
        roundnessMap[roundness],
        variantMap[variant],
        className,
      )}
    >
      {!nestLabel ? tooltipLabel : null}
      <div className="grid gap-1.5">
        {payload
          .filter((item) => item.type !== "none")
          .map((item, index) => {
            // For pie charts, item.name contains the sector name (e.g., "chrome")
            // For radial charts, the name is in item.payload[nameKey]
            // For other charts, item.name or item.dataKey contains the series name
            const payloadName =
              nameKey && item.payload
                ? (item.payload as Record<string, unknown>)[nameKey]
                : undefined;
            const key = `${payloadName ?? item.name ?? item.dataKey ?? "value"}`;
            const itemConfig = getPayloadConfigFromPayload(config, item, key);

            // Get colors count for this item to determine gradient vs solid
            const colorsCount = itemConfig ? getColorsCount(itemConfig) : 1;

            return (
              <div
                key={index}
                className={cn(
                  "[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
                  indicator === "dot" && "items-center",
                  selected != null && selected !== item.dataKey && "opacity-30",
                )}
              >
                {formatter && item?.value !== undefined && item.name ? (
                  formatter(item.value, item.name, item, index, item.payload)
                ) : (
                  <>
                    {itemConfig?.icon ? (
                      <itemConfig.icon />
                    ) : (
                      !hideIndicator && (
                        <div
                          className={cn("shrink-0 rounded-[2px]", {
                            "h-2.5 w-2.5": indicator === "dot",
                            "w-1": indicator === "line",
                            "w-0 border-[1.5px] border-dashed bg-transparent!":
                              indicator === "dashed",
                            "my-0.5": nestLabel && indicator === "dashed",
                          })}
                          style={getIndicatorColorStyle(key, colorsCount)}
                        />
                      )
                    )}
                    <div
                      className={cn(
                        "flex flex-1 justify-between gap-4 leading-none",
                        nestLabel ? "items-end" : "items-center",
                      )}
                    >
                      <div className="grid gap-1.5">
                        {nestLabel ? tooltipLabel : null}
                        <span className="text-muted-foreground">
                          {itemConfig?.label ?? item.name}
                        </span>
                      </div>
                      {item.value != null && (
                        <span className="text-foreground font-mono font-medium tabular-nums">
                          {typeof item.value === "number"
                            ? item.value.toLocaleString()
                            : String(item.value)}
                        </span>
                      )}
                    </div>
                  </>
                )}
              </div>
            );
          })}
      </div>
    </div>
  );
}

function getIndicatorColorStyle(dataKey: string, colorsCount: number): React.CSSProperties {
  if (colorsCount <= 1) {
    return { background: `var(--color-${dataKey}-0)` };
  }

  // Multiple colors: create linear gradient with evenly distributed stops
  const stops = Array.from({ length: colorsCount }, (_, index) => {
    const offset = (index / (colorsCount - 1)) * 100;
    return `var(--color-${dataKey}-${index}) ${offset}%`;
  }).join(", ");

  return { background: `linear-gradient(to right, ${stops})` };
}

const ChartTooltip = ({
  animationDuration = 200,
  ...props
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip>) => (
  <RechartsPrimitive.Tooltip animationDuration={animationDuration} {...props} />
);

export { ChartTooltip, ChartTooltipContent };
export type { TooltipRoundness, TooltipVariant };

```
        
        
          Now, create another file called `legend.tsx` inside the `evilcharts/ui` folder and paste the code there.
        
        
          ### components/evilcharts/ui/legend.tsx

```tsx
import { getPayloadConfigFromPayload, getColorsCount, useChart } from "@/components/evilcharts/ui/chart";
import * as RechartsPrimitive from "recharts";
import { cn } from "@/lib/utils";
import * as React from "react";

type ChartLegendVariant =
  | "square"
  | "circle"
  | "circle-outline"
  | "rounded-square"
  | "rounded-square-outline"
  | "vertical-bar"
  | "horizontal-bar";

function ChartLegendContent({
  className,
  hideIcon = false,
  nameKey,
  payload,
  verticalAlign,
  align = "right",
  selected,
  onSelectChange,
  isClickable,
  variant = "rounded-square",
}: React.ComponentProps<"div"> & {
  hideIcon?: boolean;
  nameKey?: string;
  selected?: string | null;
  isClickable?: boolean;
  onSelectChange?: (selected: string | null) => void;
  variant?: ChartLegendVariant;
} & RechartsPrimitive.DefaultLegendContentProps) {
  const { config } = useChart();

  if (!payload?.length) {
    return null;
  }

  return (
    <div
      className={cn(
        "flex items-center gap-4 select-none",
        align === "left" && "justify-start",
        align === "center" && "justify-center",
        align === "right" && "justify-end",
        verticalAlign === "top" ? "pb-4" : "pt-4",
        className,
      )}
    >
      {payload
        .filter((item) => item.type !== "none")
        .map((item) => {
          // For pie charts, item.value contains the sector name (e.g., "chrome")
          // For radial charts, the name is in item.payload[nameKey]
          // For other charts, item.dataKey contains the series name (e.g., "desktop")
          const payloadName =
            nameKey && item.payload
              ? (item.payload as Record<string, unknown>)[nameKey]
              : undefined;
          const key = `${payloadName ?? item.value ?? item.dataKey ?? "value"}`;
          const itemConfig = getPayloadConfigFromPayload(config, item, key);
          const isSelected = selected === null || selected === key;

          // Get colors count for this item to determine gradient vs solid
          const colorsCount = itemConfig ? getColorsCount(itemConfig) : 1;

          return (
            <div
              key={key}
              className={cn(
                "[&>svg]:text-muted-foreground flex items-center gap-1.5 transition-opacity [&>svg]:h-3 [&>svg]:w-3",
                !isSelected && "opacity-30",
                isClickable && "cursor-pointer",
              )}
              onClick={() => {
                if (!isClickable) return;

                onSelectChange?.(selected === key ? null : key);
              }}
            >
              {itemConfig?.icon && !hideIcon ? (
                <itemConfig.icon />
              ) : (
                <LegendIndicator
                  variant={variant}
                  dataKey={key}
                  colorsCount={colorsCount}
                />
              )}
              {itemConfig?.label}
            </div>
          );
        })}
    </div>
  );
}

// ---------------------------------------------------------------------------
// Legend indicator — each variant gets its own branch so future variants
// can diverge freely in markup & style.
// ---------------------------------------------------------------------------

function LegendIndicator({
  variant,
  dataKey,
  colorsCount,
}: {
  variant: ChartLegendVariant;
  dataKey: string;
  colorsCount: number;
}) {
  const fillStyle = getLegendFillStyle(dataKey, colorsCount);
  const outlineStyle = getLegendOutlineStyle(dataKey, colorsCount);

  switch (variant) {
    case "square":
      return <div className="h-2 w-2 shrink-0" style={fillStyle} />;

    case "circle":
      return <div className="h-2 w-2 shrink-0 rounded-full" style={fillStyle} />;

    case "circle-outline":
      return (
        <div
          className="h-2.5 w-2.5 shrink-0 rounded-full p-[1.5px]"
          style={outlineStyle}
        />
      );

    case "vertical-bar":
      return <div className="h-3 w-1 shrink-0 rounded-[2px]" style={fillStyle} />;

    case "horizontal-bar":
      return <div className="h-1 w-3 shrink-0 rounded-[2px]" style={fillStyle} />;

    case "rounded-square-outline":
      return (
        <div
          className="h-2.5 w-2.5 shrink-0 rounded-[3px] p-[1.5px]"
          style={outlineStyle}
        />
      );

    case "rounded-square":
    default:
      return <div className="h-2 w-2 shrink-0 rounded-[2px]" style={fillStyle} />;
  }
}

// ---------------------------------------------------------------------------
// Style helpers
// ---------------------------------------------------------------------------

/** Solid fill / gradient background for filled variants. */
function getLegendFillStyle(dataKey: string, colorsCount: number): React.CSSProperties {
  if (colorsCount <= 1) {
    return { backgroundColor: `var(--color-${dataKey}-0)` };
  }

  const stops = Array.from({ length: colorsCount }, (_, i) => {
    const offset = (i / (colorsCount - 1)) * 100;
    return `var(--color-${dataKey}-${i}) ${offset}%`;
  }).join(", ");

  return { background: `linear-gradient(to right, ${stops})` };
}

/**
 * Outline style for stroke variants.
 * Uses background + mask-composite to punch out the center, leaving only the
 * "border" visible. Works with both solid colors and gradients, and respects
 * border-radius — unlike plain `border-color`.
 */
function getLegendOutlineStyle(dataKey: string, colorsCount: number): React.CSSProperties {
  const maskStyle: React.CSSProperties = {
    WebkitMask:
      "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)",
    WebkitMaskComposite: "xor",
    mask: "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)",
    maskComposite: "exclude",
  };

  if (colorsCount <= 1) {
    return {
      backgroundColor: `var(--color-${dataKey}-0)`,
      ...maskStyle,
    };
  }

  const stops = Array.from({ length: colorsCount }, (_, i) => {
    const offset = (i / (colorsCount - 1)) * 100;
    return `var(--color-${dataKey}-${i}) ${offset}%`;
  }).join(", ");

  return {
    background: `linear-gradient(to right, ${stops})`,
    ...maskStyle,
  };
}

const ChartLegend = RechartsPrimitive.Legend;

export { ChartLegend, ChartLegendContent, type ChartLegendVariant };

```
        
      
    
  


## Usage

```tsx
import { EvilRadarChart } from "@/components/evilcharts/charts/radar-chart";
```

```tsx
const data = [
  { skill: "JavaScript", desktop: 186, mobile: 80 },
  { skill: "TypeScript", desktop: 305, mobile: 200 },
  { skill: "React", desktop: 237, mobile: 120 },
  { skill: "Node.js", desktop: 173, mobile: 190 },
  { skill: "CSS", desktop: 209, mobile: 130 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    colors: { light: ["#3b82f6"], dark: ["#60a5fa"] },
  },
  mobile: {
    label: "Mobile",
    colors: { light: ["#10b981"], dark: ["#34d399"] },
  },
} satisfies ChartConfig;

<EvilRadarChart  
  data={data}
  dataKey="skill"
  chartConfig={chartConfig}
/>
```

### Interactive Selection

When `isClickable` is enabled, you can use the `onSelectionChange` callback to handle selection events:

```tsx
<EvilRadarChart
  data={data}
  dataKey="skill"
  chartConfig={chartConfig}
  isClickable={true}
  onSelectionChange={(selectedDataKey) => {
    if (selectedDataKey) {
      console.log("Selected:", selectedDataKey);
    } else {
      console.log("Deselected");
    }
  }}
/>
```

### Loading State

### isLoading='true'

```tsx
"use client";

import { EvilRadarChart } from "@/components/evilcharts/charts/radar-chart";
import { type ChartConfig } from "@/components/evilcharts/ui/chart";

const data = [
  { skill: "JavaScript", desktop: 186, mobile: 80 },
  { skill: "TypeScript", desktop: 305, mobile: 200 },
  { skill: "React", desktop: 237, mobile: 120 },
  { skill: "Node.js", desktop: 173, mobile: 190 },
  { skill: "CSS", desktop: 209, mobile: 130 },
  { skill: "Python", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    colors: {
      light: ["#3b82f6"],
      dark: ["#60a5fa"],
    },
  },
  mobile: {
    label: "Mobile",
    colors: {
      light: ["#10b981"],
      dark: ["#34d399"],
    },
  },
} satisfies ChartConfig;

export function EvilExampleRadarChart() {
  return (
    <EvilRadarChart
      className="h-full w-full p-4"
      data={data}
      dataKey="skill"
      hideDots
      chartConfig={chartConfig}
      isLoading // [!code highlight]
    />
  );
}

```
>  
  
    The radar chart supports loading state with animated data. You can pass the `isLoading` prop to show a loading animation while your data is being fetched.
  


## Examples

Below are some examples of the radar chart with different configurations.

### Lines Variant

### variant='lines'

```tsx
"use client";

import { EvilRadarChart } from "@/components/evilcharts/charts/radar-chart";
import { type ChartConfig } from "@/components/evilcharts/ui/chart";

const data = [
  { skill: "JavaScript", desktop: 186, mobile: 80 },
  { skill: "TypeScript", desktop: 305, mobile: 200 },
  { skill: "React", desktop: 237, mobile: 120 },
  { skill: "Node.js", desktop: 173, mobile: 190 },
  { skill: "CSS", desktop: 209, mobile: 130 },
  { skill: "Python", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    colors: {
      light: ["#3b82f6"],
      dark: ["#60a5fa"],
    },
  },
  mobile: {
    label: "Mobile",
    colors: {
      light: ["#10b981"],
      dark: ["#34d399"],
    },
  },
} satisfies ChartConfig;

export function EvilExampleRadarChart() {
  return (
    <EvilRadarChart
      className="h-full w-full p-4"
      data={data}
      dataKey="skill"
      dotVariant="colored-border"
      activeDotVariant="default"
      chartConfig={chartConfig}
      variant="lines" // [!code highlight]
    />
  );
}

```
>  
  
    Set `variant="lines"` to show only the outline without fill. This is useful for comparing multiple datasets more clearly.
  


### Circle Grid

### gridType='circle'

```tsx
"use client";

import { EvilRadarChart } from "@/components/evilcharts/charts/radar-chart";
import { type ChartConfig } from "@/components/evilcharts/ui/chart";

const data = [
  { skill: "JavaScript", desktop: 186, mobile: 80 },
  { skill: "TypeScript", desktop: 305, mobile: 200 },
  { skill: "React", desktop: 237, mobile: 120 },
  { skill: "Node.js", desktop: 173, mobile: 190 },
  { skill: "CSS", desktop: 209, mobile: 130 },
  { skill: "Python", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    colors: {
      light: ["#3b82f6"],
      dark: ["#60a5fa"],
    },
  },
  mobile: {
    label: "Mobile",
    colors: {
      light: ["#10b981"],
      dark: ["#34d399"],
    },
  },
} satisfies ChartConfig;

export function EvilExampleRadarChart() {
  return (
    <EvilRadarChart
      className="h-full w-full p-4"
      data={data}
      dataKey="skill"
      dotVariant="colored-border"
      activeDotVariant="default"
      chartConfig={chartConfig}
      gridType="circle" // [!code highlight]
    />
  );
}

```
>  
  
    Set `gridType="circle"` to use circular grid lines instead of the default polygon grid.
  


### Gradient Colors

### gradient colors

```tsx
"use client";

import { EvilRadarChart } from "@/components/evilcharts/charts/radar-chart";
import { type ChartConfig } from "@/components/evilcharts/ui/chart";

const data = [
  { skill: "JavaScript", desktop: 186, mobile: 80 },
  { skill: "TypeScript", desktop: 305, mobile: 200 },
  { skill: "React", desktop: 237, mobile: 120 },
  { skill: "Node.js", desktop: 173, mobile: 190 },
  { skill: "CSS", desktop: 209, mobile: 130 },
  { skill: "Python", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    colors: {
      light: ["#6366f1", "#a855f7", "#ec4899"], // Indigo -> Purple -> Pink // [!code highlight]
      dark: ["red", "orange", "pink"], // [!code highlight]
    },
  },
  mobile: {
    label: "Mobile",
    colors: {
      light: ["#14b8a6", "#06b6d4", "#3b82f6"], // Teal -> Cyan -> Blue // [!code highlight]
      dark: ["#2dd4bf", "#22d3ee", "#60a5fa"], // [!code highlight]
    },
  },
} satisfies ChartConfig;

export function EvilExampleRadarChart() {
  return (
    <EvilRadarChart
      className="h-full w-full p-4"
      data={data}
      dataKey="skill"
      dotVariant="colored-border"
      activeDotVariant="default"
      chartConfig={chartConfig}
    />
  );
}

```

### Glowing Radars

### glowingRadars={['desktop']}

```tsx
"use client";

import { EvilRadarChart } from "@/components/evilcharts/charts/radar-chart";
import { type ChartConfig } from "@/components/evilcharts/ui/chart";

const data = [
  { skill: "JavaScript", desktop: 186, mobile: 80 },
  { skill: "TypeScript", desktop: 305, mobile: 200 },
  { skill: "React", desktop: 237, mobile: 120 },
  { skill: "Node.js", desktop: 173, mobile: 190 },
  { skill: "CSS", desktop: 209, mobile: 130 },
  { skill: "Python", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    colors: {
      light: ["#3b82f6"],
      dark: ["#60a5fa"],
    },
  },
  mobile: {
    label: "Mobile",
    colors: {
      light: ["#10b981"],
      dark: ["#34d399"],
    },
  },
} satisfies ChartConfig;

export function EvilExampleRadarChart() {
  return (
    <EvilRadarChart
      className="h-full w-full p-4"
      data={data}
      dataKey="skill"
      dotVariant="colored-border"
      activeDotVariant="default"
      chartConfig={chartConfig}
      glowingRadars={["desktop"]} // [!code highlight]
    />
  );
}

```
>  
  
    Add a subtle glow effect to specific radars using `glowingRadars` prop. Pass an array of data keys to specify which radars should glow.
  


## API Reference


  
    ### `data`
    
      Data used to display the chart. An array of objects where each object represents a data point on the radar.
      
      type: `TData[]` where `TData extends Record<string, unknown>`
    
  
  
  
    ### `dataKey`
    
      The key from your data objects to use for the polar angle axis labels (e.g., categories, skills, months).
      
      type: `keyof TData & string`
    
  

  
    ### `chartConfig`
    
      Configuration object that defines the chart's radar series. Each key should match a data key in your data array (the numeric values), with corresponding colors.
      
      type: `Record<string, ChartConfig[string]>`
    
  

  
    ### `className`
    
      Additional CSS classes to apply to the chart container.
      
      type: `string`
    
  

  
    ### `variant`
    
      The visual style variant for the radar. `"filled"` shows filled areas, `"lines"` shows only the outline.
      
      type: `"filled" | "lines"`
      
      default: `"filled"`
    
  

  
    ### `fillOpacity`
    
      The opacity of the filled area when using `variant="filled"`.
      
      type: `number`
      
      default: `0.3`
    
  

  
    ### `hideAngleAxis`
    
      Whether to hide the polar angle axis (the labels around the perimeter).
      
      type: `boolean`
      
      default: `false`
    
  

  
    ### `hideRadiusAxis`
    
      Whether to hide the polar radius axis (the scale values from center outward).
      
      type: `boolean`
      
      default: `true`
    
  

  
    ### `hideGrid`
    
      Whether to hide the polar grid lines.
      
      type: `boolean`
      
      default: `false`
    
  

  
    ### `backgroundVariant`
    
      Background pattern variant to display behind the chart.
      
      type: `BackgroundVariant`
    
  

  
    ### `gridType`
    
      The shape of the grid lines. `"polygon"` creates angular grid lines, `"circle"` creates circular grid lines.
      
      type: `"polygon" | "circle"`
      
      default: `"polygon"`
    
  

  
    ### `hideTooltip`
    
      Whether to hide the tooltip on hover.
      
      type: `boolean`
      
      default: `false`
    
  

  
    ### `tooltipRoundness`
    
      Controls the border-radius of the tooltip.
      
      type: `"sm" | "md" | "lg" | "xl"`
      
      default: `"lg"`
    
  

  
    ### `tooltipVariant`
    
      Controls the visual style of the tooltip.
      
      type: `"default" | "frosted-glass"`
      
      default: `"default"`
    
  

  
    ### `tooltipDefaultIndex`
    
      When set, the tooltip will be visible by default at the specified data point index.
      
      type: `number`
    
  

  
    ### `legendVariant`
    
      The visual style variant for the legend indicators.
      
      type: `"square" | "circle" | "circle-outline" | "rounded-square" | "rounded-square-outline" | "vertical-bar" | "horizontal-bar"`
    
  

  
    ### `hideLegend`
    
      Whether to hide the chart legend.
      
      type: `boolean`
      
      default: `false`
    
  

  
    ### `hideDots`
    
      Whether to hide the dots at each data point on the radar.
      
      type: `boolean`
      
      default: `false`
    
  

  
    ### `dotVariant`
    
      The visual style for dots at data points on the radar.
      
      type: `"default" | "border" | "colored-border"`
    
  

  
    ### `activeDotVariant`
    
      The visual style for the active/hovered dot at data points.
      
      type: `"default" | "border" | "colored-border"`
    
  

  
    ### `isClickable`
    
      Enables interactive clicking on radar areas to select/deselect them. When a radar is selected, unselected radars become semi-transparent.
      
      type: `boolean`
      
      default: `false`
    
  

  
    ### `onSelectionChange`
    
      Callback function that is called when a radar is selected or deselected. Receives the selected data key as a parameter, or `null` when deselected.
      
      **Note:** This prop is only available when `isClickable` is set to `true`.
      
      type: `(selectedDataKey: string | null) => void`
    
  

  
    ### `isLoading`
    
      Shows a loading animation with animated data when data is being fetched.
      
      type: `boolean`
      
      default: `false`
    
  

  
    ### `glowingRadars`
    
      Array of data keys that should have a glowing effect applied. Creates a smooth outer glow around the specified radar areas.
      
      type: `NumericDataKeys<TData>[]` (array of keys where the value is a number)
      
      default: `[]`
    
  

  
    ### `hideDots`
    
      Array of data keys that should have a neon effect applied. Creates a multi-layered glow with a bright white core for the specified radar areas. Neon effect takes priority over glow effect.
      
      type: `NumericDataKeys<TData>[]` (array of keys where the value is a number)
      
      default: `[]`
    
  

  
    ### `chartProps`
    
      Additional props to pass to the underlying Recharts RadarChart component.
      
      type: `ComponentProps<typeof RadarChart>`
      
      Read the [Recharts RadarChart documentation](https://recharts.github.io/en-US/api/RadarChart/) for available props.
    
  

  
    ### `radarProps`
    
      Additional props to pass to the underlying Recharts Radar component.
      
      type: `Omit<ComponentProps<typeof Radar>, "dataKey">`
      
      Read the [Recharts Radar documentation](https://recharts.github.io/en-US/api/Radar/) for available props.
    
  

  
    ### `polarGridProps`
    
      Additional props to pass to the underlying Recharts PolarGrid component.
      
      type: `ComponentProps<typeof PolarGrid>`
      
      Read the [Recharts PolarGrid documentation](https://recharts.github.io/en-US/api/PolarGrid/) for available props.
    
  

