Polar

Polar charts place values by angle and distance from a center, which suits cyclic data like the seasons or compass directions.

LLMs

Code

Polar charts are drawn with TanStack Charts, through the kit's Chart components. They paint the chart with the theme's colors, grid and type, and add three palette props. The chart itself is a TanStack definition, written the way TanStack documents it.

Install

npx antuikit@latest init
npx antuikit@latest add tanstack-chart
The first command installs AntuikitProvider and the theme tokens. The second adds the chart components and TanStack Charts itself.

Your first rose chart

Draw the wedges with radialBarRadius inside a polar container. angle names the category, one equal wedge each, on a band scale. radius names the value, on a linear scale.
import { defineChart } from "@tanstack/charts"
import { polar, radialBarRadius } from "@tanstack/charts/polar"
import { scaleBand } from "@tanstack/charts/scales/band"
import { scaleLinear } from "@tanstack/charts/scales/linear"
import { tooltip } from "@tanstack/charts/tooltip"
import { Chart, chartColors } from "@/components/ui/tanstack-chart"
const weekdays = [
{ day: "Monday", orders: 412 },
{ day: "Tuesday", orders: 386 },
{ day: "Wednesday", orders: 441 },
{ day: "Thursday", orders: 398 },
{ day: "Friday", orders: 520 },
{ day: "Saturday", orders: 610 },
{ day: "Sunday", orders: 344 },
]
const definition = defineChart(
{
marks: [
polar({
radiusRatio: 0.8,
scales: {
angle: { scale: () => scaleBand<string>() },
radius: { scale: scaleLinear().domain([0, 610]) },
},
marks: [
radialBarRadius(weekdays, {
id: "Orders",
angle: "day",
radius: "orders",
key: "day",
color: "day",
stroke: chartColors.surface,
strokeWidth: 1,
}),
],
}),
],
scales: { x: null, y: null },
color: { domain: weekdays.map((row) => row.day) },
margin: 0,
},
{
tooltip: {
use: tooltip,
format: ({ datum }) => `${datum.day} · ${datum.orders} orders`,
},
},
)
export function WeekdayChart() {
return (
<Chart
definition={definition}
ariaLabel="Orders by weekday, last 4 weeks"
height={260}
/>
)
}
The chart fills its container's width, so give it a height. ariaLabel is required.

Which component

All three take the same definition and the same palette props.
Chart components

Which mark

  • radialBarRadius: the value grows outward from the center, one wedge per category. A rose.
  • radialBarAngle: the value sweeps around the circle, one ring per category. Radial bars.
  • radialLine and radialDot: numbers on both channels, for a value followed around a cycle or points placed by direction and size.

Radius, area and the inner hole

The wedge's length follows the radius scale, so its area grows with the square of the value. To make area match the value, map the radius with a square root scale, such as scaleSqrt from d3-scale, and keep the radius range starting at the center.
A range that starts away from the center leaves a hole and moves zero to its edge. Lengths from the hole then match the values, but areas do not.
radius: {
scale: scaleLinear().domain([0, 610]),
range: [({ radius }) => radius * 0.3, ({ radius }) => radius],
}
With no radius1, a wedge starts at the physical center. For signed values, set radius1: 0 so zero is mapped through the scale.

Numbers around the circle

For a cycle such as the days of a year, put a linear scale on angle and compute the position with a function. A domain of 0 to 360 reads as degrees, starting at 12 o'clock and going clockwise.
import { angleGrid, polar, radialGrid, radialLine } from "@tanstack/charts/polar"
const yearStart = Date.UTC(2012, 0, 1)
const yearEnd = Date.UTC(2013, 0, 1)
const dayOfYearAngle = (row: Row) =>
((row.date.getTime() - yearStart) / (yearEnd - yearStart)) * 360
polar({
radiusRatio: 0.72,
scales: {
angle: { scale: scaleLinear().domain([0, 360]) },
radius: { scale: scaleLinear().domain([-10, 40]) },
},
guides: [
radialGrid({ values: [0, 10, 20, 30, 40], stroke: chartColors.grid }),
angleGrid({ values: [0, 90, 180, 270], labels: false, stroke: chartColors.grid }),
],
marks: [
radialLine(days, {
angle: dayOfYearAngle,
radius: "temp_max",
stroke: chartColors.series[0],
strokeWidth: 2.5,
}),
],
})
The radius domain starts at -10, so the center is not zero. Label the rings when the center is not zero, or readers take distance for an amount.

Points by direction

radialDot takes the same channels. Compute the direction and the size from your fields with functions, and leave the source rows unchanged for the tooltip.
const windDirection = (row: Row) =>
(Math.atan2(row.v, row.u) * (180 / Math.PI) + 360) % 360
const windSpeed = (row: Row) => Math.hypot(row.u, row.v)
radialDot(rows, {
angle: windDirection,
radius: windSpeed,
r: 4.5,
fill: chartColors.series[0],
stroke: chartColors.surface,
strokeWidth: 1,
})

Grid rings and spokes

Pass radialGrid and angleGrid in the guides of polar. List the rings and spokes with values. Ring values are hidden by default. Turn them on with labels: true when the reader needs to read a distance. Guides draw behind the marks and their labels on top.

Colors

The chart takes the theme's chart colors without any setup. Change the palette on the chart itself, or paint one mark with fill and chartColors. The Color guideline covers which palette fits which data.
<Chart definition={definition} ariaLabel="..." palette="mono" />
// One color for every wedge: the position already names the day.
radialBarRadius(weekdays, { angle: "day", radius: "orders", fill: chartColors.series[0] })
Palette props
Taken by Chart, RendererChart and CanvasChart. Rows marked antuikit in the Version column are added by the kit.

Tooltip and focus

Each wedge, dot or line point is a focus point, and the arrow keys move through them in order. To group the points that share an angle, such as several lines at the same day, pass focus: focusGroupAngle. In the tooltip, point.datum is your own row, so format the original fields, not the computed angle.

Animation

Use RendererChart with TanStack's motion() renderer to animate marks in and between data updates. It skips the animation when the reader has asked for reduced motion.
import { motion } from "@tanstack/charts/motion"
import { RendererChart } from "@/components/ui/tanstack-chart"
const renderer = motion({ initial: "always" })
<RendererChart definition={definition} renderer={renderer} ariaLabel="..." />

API

The options a polar chart uses most. TanStack Charts' documentation has the full reference.
radialBarRadius
radialLine and radialDot
polar
Angle and radius scales
radialGrid and angleGrid