1 GitHub star = 1 new component.

Star the repo
Charts & Stats chartsvgzero-dependencypolarseasonalityradialcyclical

Polar Area Chart

Equal-angle wedges with area-correct radius for cyclical categories like months or hours.

Live Preview

Orders by month

Seasonality at a glance

Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
View data
ItemValue
Jan320
Feb280
Mar360
Apr410
May470
Jun520
Jul490
Aug450
Sep430
Oct510
Nov690
Dec820

Usage

Copy and paste this code into your Astro page:

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

<PolarAreaChart
  items={[
    { label: 'Q1', value: 320 },
    { label: 'Q2', value: 410 },
    { label: 'Q3', value: 380 },
    { label: 'Q4', value: 690 },
  ]}
/>
--- import { PolarAreaChart } from 'astro-component-kit/charts'; --- <PolarAreaChart items={[ { label: 'Q1', value: 320 }, { label: 'Q2', value: 410 }, { label: 'Q3', value: 380 }, { label: 'Q4', value: 690 }, ]} />

Manual Installation

If you are not using the npm package, create a new file and paste the following code: src/components/lib/PolarAreaChart.astro:

astro
---
/**
 * PolarAreaChart — Equal-angle wedges whose radius encodes value (area-correct
 * via square-root scaling). Good for cyclical categories like months or hours.
 *
 * @param {Array<{label: string, value: number}>} items - Wedges (max 12).
 * @param {string} color - Wedge hue (single series).
 * @param {string} suffix - Value suffix.
 * @param {string} variant - Card style (glass|solid|ghost).
 * @param {string} size - Density (sm|md|lg).
 */
import ChartFrame from './ChartFrame.astro';
import { arcPath, polar, formatValue } from './chart-utils';

interface Props {
  title?: string;
  subtitle?: string;
  items?: Array<{ label: string; value: number }>;
  color?: string;
  prefix?: string;
  suffix?: string;
  variant?: 'glass' | 'solid' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

const {
  title = 'Orders by month',
  subtitle = 'Seasonality at a glance',
  items = [
    { label: 'Jan', value: 320 }, { label: 'Feb', value: 280 }, { label: 'Mar', value: 360 },
    { label: 'Apr', value: 410 }, { label: 'May', value: 470 }, { label: 'Jun', value: 520 },
    { label: 'Jul', value: 490 }, { label: 'Aug', value: 450 }, { label: 'Sep', value: 430 },
    { label: 'Oct', value: 510 }, { label: 'Nov', value: 690 }, { label: 'Dec', value: 820 },
  ],
  color = 'var(--chart-7, #9085e9)',
  prefix = '',
  suffix = '',
  variant = 'glass',
  size = 'md',
  className = '',
} = Astro.props;

const S = 320;
const c = S / 2;
const R = S / 2 - 26;
const max = Math.max(...items.map((i) => i.value)) || 1;
const n = Math.min(items.length, 12);
const sweep = (Math.PI * 2) / n;
const rings = [0.25, 0.5, 0.75, 1];
const fmt = (v: number) => formatValue(v, prefix, suffix);
---

<ChartFrame
  title={title}
  subtitle={subtitle}
  variant={variant}
  size={size}
  className={className}
  table={{ headers: ['Item', 'Value'], rows: items.map((i) => [i.label, fmt(i.value)]) }}
>
  <div class="pa">
    <svg viewBox={`0 0 ${S} ${S}`} role="img" aria-label={`${title}: polar area chart of ${n} categories`}>
      {rings.map((f) => <circle class="cf-grid" cx={c} cy={c} r={R * Math.sqrt(f)} fill="none" />)}
      {items.slice(0, n).map((it, i) => {
        const r = R * Math.sqrt(it.value / max);
        const a0 = i * sweep + 0.012;
        const a1 = (i + 1) * sweep - 0.012;
        const [lx, ly] = polar(c, c, R + 14, i * sweep + sweep / 2);
        const isMax = it.value === max;
        return (
          <g>
            <path class="cf-mark cf-dim" d={arcPath(c, c, r, 0, a0, a1)} fill={color} fill-opacity={0.35 + 0.6 * (it.value / max)}
              tabindex="0" role="img" aria-label={`${it.label}: ${fmt(it.value)}`} data-tip-title={it.label} data-tip={`${color}::${fmt(it.value)}`} />
            <text class={isMax ? 'cf-label cf-label--strong' : 'cf-label'} x={lx} y={ly + 4} text-anchor="middle">{it.label}</text>
          </g>
        );
      })}
    </svg>
  </div>
</ChartFrame>

<style>
  .pa { max-width: 360px; margin: 0 auto; }
</style>
--- /** * PolarAreaChart — Equal-angle wedges whose radius encodes value (area-correct * via square-root scaling). Good for cyclical categories like months or hours. * * @param {Array<{label: string, value: number}>} items - Wedges (max 12). * @param {string} color - Wedge hue (single series). * @param {string} suffix - Value suffix. * @param {string} variant - Card style (glass|solid|ghost). * @param {string} size - Density (sm|md|lg). */ import ChartFrame from './ChartFrame.astro'; import { arcPath, polar, formatValue } from './chart-utils'; interface Props { title?: string; subtitle?: string; items?: Array<{ label: string; value: number }>; color?: string; prefix?: string; suffix?: string; variant?: 'glass' | 'solid' | 'ghost'; size?: 'sm' | 'md' | 'lg'; className?: string; } const { title = 'Orders by month', subtitle = 'Seasonality at a glance', items = [ { label: 'Jan', value: 320 }, { label: 'Feb', value: 280 }, { label: 'Mar', value: 360 }, { label: 'Apr', value: 410 }, { label: 'May', value: 470 }, { label: 'Jun', value: 520 }, { label: 'Jul', value: 490 }, { label: 'Aug', value: 450 }, { label: 'Sep', value: 430 }, { label: 'Oct', value: 510 }, { label: 'Nov', value: 690 }, { label: 'Dec', value: 820 }, ], color = 'var(--chart-7, #9085e9)', prefix = '', suffix = '', variant = 'glass', size = 'md', className = '', } = Astro.props; const S = 320; const c = S / 2; const R = S / 2 - 26; const max = Math.max(...items.map((i) => i.value)) || 1; const n = Math.min(items.length, 12); const sweep = (Math.PI * 2) / n; const rings = [0.25, 0.5, 0.75, 1]; const fmt = (v: number) => formatValue(v, prefix, suffix); --- <ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className} table={{ headers: ['Item', 'Value'], rows: items.map((i) => [i.label, fmt(i.value)]) }} > <div class="pa"> <svg viewBox={`0 0 ${S} ${S}`} role="img" aria-label={`${title}: polar area chart of ${n} categories`}> {rings.map((f) => <circle class="cf-grid" cx={c} cy={c} r={R * Math.sqrt(f)} fill="none" />)} {items.slice(0, n).map((it, i) => { const r = R * Math.sqrt(it.value / max); const a0 = i * sweep + 0.012; const a1 = (i + 1) * sweep - 0.012; const [lx, ly] = polar(c, c, R + 14, i * sweep + sweep / 2); const isMax = it.value === max; return ( <g> <path class="cf-mark cf-dim" d={arcPath(c, c, r, 0, a0, a1)} fill={color} fill-opacity={0.35 + 0.6 * (it.value / max)} tabindex="0" role="img" aria-label={`${it.label}: ${fmt(it.value)}`} data-tip-title={it.label} data-tip={`${color}::${fmt(it.value)}`} /> <text class={isMax ? 'cf-label cf-label--strong' : 'cf-label'} x={lx} y={ly + 4} text-anchor="middle">{it.label}</text> </g> ); })} </svg> </div> </ChartFrame> <style> .pa { max-width: 360px; margin: 0 auto; } </style>

Quick Info

Category
Charts & Stats
Filename
PolarAreaChart.astro
Dependencies
apexcharts (peer)
Tags
chartsvgzero-dependencypolarseasonalityradialcyclical