Polar geometry is available only from @tanstack/charts/polar. The container owns responsive center, angle, and radius ranges; granular D3 modules still own pie layout, configured scales, and curve factories.
import {
angleGrid,
polar,
radialArc,
radialArea,
radialDot,
radialGrid,
radialLine,
radialRule,
radialText,
} from '@tanstack/charts/polar'The package root stays Cartesian-sized when this subpath is not imported.
Use d3-shape's pie transform to turn totals into angular intervals. radialArc renders the intervals. A zero inner radius is a pie; a responsive nonzero inner radius is a donut.
import { defineChart } from '@tanstack/charts'
import { polar, radialArc } from '@tanstack/charts/polar'
import { pie } from 'd3-shape'
interface Part {
id: string
value: number
fill: string
}
const parts: Part[] = [
{ id: 'ingest', value: 42, fill: '#2563eb' },
{ id: 'query', value: 28, fill: '#7c3aed' },
{ id: 'alerts', value: 18, fill: '#db2777' },
{ id: 'other', value: 12, fill: '#f59e0b' },
]
const slices = pie<Part>()
.sort(null)
.value((part) => part.value)(parts)
function ring(innerRatio: number) {
return polar({
inset: 8,
radiusRatio: 0.82,
marks: [
radialArc(slices, {
key: (slice) => slice.data.id,
startAngle: 'startAngle',
endAngle: 'endAngle',
padAngle: 'padAngle',
innerRadius: ({ radius }) => radius * innerRatio,
cornerRadius: 4,
fill: (slice) => slice.data.fill,
}),
],
})
}
const pieChart = defineChart({
marks: [ring(0)],
guides: false,
x: null,
y: null,
})
const donutChart = defineChart({
marks: [ring(0.58)],
guides: false,
x: null,
y: null,
})The same primitives cover labels, center content, padding, rounded corners, and concentric rings:
Keep pie().sort(null) when source order is semantic. Stable arc keys must come from the original row, not the generated slice index.
A gauge is the same composition over a restricted D3 pie interval. It is not a separate geometry implementation.
import { defineChart } from '@tanstack/charts'
import { polar, radialArc } from '@tanstack/charts/polar'
import { pie } from 'd3-shape'
const value = 72
const gaugeParts = [
{ id: 'value', value, fill: '#ef4444' },
{ id: 'remainder', value: 100 - value, fill: '#e2e8f0' },
]
const gaugeSlices = pie<(typeof gaugeParts)[number]>()
.sort(null)
.value((part) => part.value)
.startAngle(-Math.PI * 0.75)
.endAngle(Math.PI * 0.75)(gaugeParts)
const gauge = defineChart({
marks: [
polar({
radiusRatio: 0.84,
marks: [
radialArc(gaugeSlices, {
key: (slice) => slice.data.id,
startAngle: 'startAngle',
endAngle: 'endAngle',
innerRadius: ({ radius }) => radius * 0.72,
cornerRadius: 999,
fill: (slice) => slice.data.fill,
}),
],
}),
],
guides: false,
x: null,
y: null,
})Bound the input before layout and expose the exact value outside the arc. Arc length is useful for a compact status summary, not fine comparison.
Radar combines an inferred angle factory and a fixed radius instance with polar guides and radial marks. TanStack supplies both responsive ranges.
import { defineChart } from '@tanstack/charts'
import {
angleGrid,
polar,
radialArea,
radialDot,
radialGrid,
radialLine,
} from '@tanstack/charts/polar'
import { scaleBand, scaleLinear } from 'd3-scale'
import { curveLinearClosed } from 'd3-shape'
const metrics = [
'Latency',
'Errors',
'Traffic',
'Saturation',
'Availability',
] as const
const profile = metrics.map((metric, index) => ({
metric,
score: [74, 46, 88, 61, 93][index] ?? 0,
}))
const radar = defineChart({
marks: [
polar({
radiusRatio: 0.72,
angle: { scale: scaleBand<string> },
radius: { scale: scaleLinear().domain([0, 100]).nice(4) },
guides: [
radialGrid({ ticks: 4, shape: 'polygon', labels: false }),
angleGrid({
labels: true,
labelDx: ({ x }) => (x < -1 ? -3 : x > 1 ? 3 : 0),
labelDy: ({ y }) => (y < -1 ? -2 : y > 1 ? 2 : 0),
}),
],
marks: [
radialArea(profile, {
angle: 'metric',
radius: 'score',
curve: curveLinearClosed,
fill: '#7c3aed',
fillOpacity: 0.22,
}),
radialLine(profile, {
angle: 'metric',
radius: 'score',
curve: curveLinearClosed,
stroke: '#8b5cf6',
strokeWidth: 2,
}),
radialDot(profile, {
angle: 'metric',
radius: 'score',
key: 'metric',
r: 3,
fill: '#8b5cf6',
}),
],
}),
],
guides: false,
x: null,
y: null,
})Use radar for a small, fixed set of compatible dimensions. Keep every domain and direction explicit, and do not rank profiles by apparent filled area.
Continuous D3 scales map numeric angle and radius values without changing the mark API. Repeat the first row at the angular endpoint when a line should close; independent scatter observations need no seam row.
import { defineChart } from '@tanstack/charts'
import {
angleGrid,
polar,
radialDot,
radialGrid,
radialLine,
} from '@tanstack/charts/polar'
import { scaleLinear } from 'd3-scale'
const trajectory = [
{ id: '0', angle: 0, radius: 52 },
{ id: '90', angle: 90, radius: 76 },
{ id: '180', angle: 180, radius: 43 },
{ id: '270', angle: 270, radius: 68 },
{ id: '360', angle: 360, radius: 52 },
]
const observations = [
{ id: 'a', angle: 34, radius: 61 },
{ id: 'b', angle: 142, radius: 82 },
{ id: 'c', angle: 248, radius: 47 },
]
const numericPolar = defineChart({
marks: [
polar({
angle: { scale: scaleLinear().domain([0, 360]) },
radius: { scale: scaleLinear().domain([0, 100]) },
guides: [
radialGrid({ values: [25, 50, 75, 100] }),
angleGrid({ values: [0, 90, 180, 270], labels: false }),
],
marks: [
radialLine(trajectory, {
angle: 'angle',
radius: 'radius',
key: 'id',
stroke: '#0f766e',
}),
radialDot(observations, {
angle: 'angle',
radius: 'radius',
key: 'id',
r: 4.5,
fill: '#e11d48',
}),
],
}),
],
guides: false,
x: null,
y: null,
})radialArc.generator accepts responsive D3 arc accessors, so variable-radius sectors, concentric radial bars, and hierarchy rings remain one primitive.
polar() is a positionless container mark. It resolves one center and radius, copies configured angle/radius scales, paints guide backgrounds, child marks, then guide foreground labels, and emits ordinary scene nodes and focus points. The outer chart therefore uses x: null, y: null, and guides: false.
The polar entry uses D3 arc and radial path generators internally. Application source imports pie, configured scales, and curve factories directly from d3-shape or d3-scale. See Polar Marks for the complete API and Bundle Size and Performance for the isolated consumer budgets.