chartRadialGrid
A Charts block/component from shadcn/ui — clean-room reimplemented for Domphy (see methodology). Call chartRadialGrid() with no arguments for a working demo, or edit the code below live.
Props
| Prop | Type | Description |
|---|---|---|
data | RadialSeriesDatum[] | — |
title | string | — |
description | string | — |
trendText | string | — |
trendDirection | ChartTrendDirection | — |
captionText | string | — |
spokeCount | number | Radial spoke lines (recharts PolarGrid radialLines), evenly spaced. |
gridCircleCount | number | Concentric gridline count. Defaults to one ring per category band. |
outerRadius | number | — |
Implementation notes
Same five-ring base (renderRadialRingsChart) as chartRadialSimple with showBackgroundTrack and showGridCircles — solid per-ring tracks are replaced with a small set of thin, evenly-spaced concentric <circle> gridlines (default 4, configurable) drawn once behind all rings, and outerRadius defaults slightly smaller (82 vs 90) to leave margin for the grid, per spec. Same hover tooltip and grow-in sweep as chartRadialSimple. tsc --noEmit clean; 2/2 tests pass.
Status: ported · Reference: shadcn/ui original
// shadcn/ui "chart-radial-grid" recipe — clean-room reimplementation.
//
// The same five-ring radial bar chart as chartRadialSimple, but the solid
// per-ring background track is swapped for recharts' <PolarGrid
// gridType="circle" />: one thin concentric gridline per category band
// (aligned to the ring radii) plus, from recharts' default radialLines={true},
// evenly spaced radial spoke lines running from the inner radius out to the
// outer radius. The hover tooltip mirrors <ChartTooltipContent hideLabel
// nameKey="browser" /> — a color swatch, the series name, and its numeric
// value (e.g. "Chrome 275").
//
// The rings/grid/spokes are composed from the same shared radial primitives
// the other recipes use; only this file's element tree is bespoke.
import type { DomphyElement, Listener } from "@domphy/core";
import { themeColor, themeSpacing } from "@domphy/theme";
import {
type ChartTrendDirection,
chartTrendFooter,
} from "./chart-area-shared.js";
import {
computeRingLayout,
createRadialTooltip,
polarPoint,
RADIAL_CENTER,
RADIAL_CHANNEL_DATA,
RADIAL_VIEW_SIZE,
type RadialSeriesDatum,
radialArcPath,
radialCardShell,
radialSeriesTone,
radialThinCircle,
radialTooltipLayer,
} from "./chart-radial-shared.js";
export interface ChartRadialGridProps {
data?: RadialSeriesDatum[];
title?: string;
description?: string;
trendText?: string;
trendDirection?: ChartTrendDirection;
captionText?: string;
/** Radial spoke lines (recharts PolarGrid radialLines), evenly spaced. */
spokeCount?: number;
/** Concentric gridline count. Defaults to one ring per category band. */
gridCircleCount?: number;
outerRadius?: number;
}
/**
* shadcn/ui "chart-radial-grid" recipe — five rings over a polar grid
* (concentric band circles + radial spokes) instead of solid background
* tracks. Call with no arguments for a working demo.
*/
function chartRadialGrid(
props: ChartRadialGridProps = {},
): DomphyElement<"div"> {
const {
data = RADIAL_CHANNEL_DATA,
title = "Radial Chart - Grid",
description = "January - June 2026",
trendText = "Trending up by 5.2% this month",
trendDirection = "up",
captionText = "Showing total sessions by acquisition channel",
spokeCount = 4,
gridCircleCount,
outerRadius = 82,
} = props;
const innerRadius = outerRadius * 0.25;
const ringGap = 2;
const cx = RADIAL_CENTER;
const cy = RADIAL_CENTER;
const rings = computeRingLayout(
data.length,
outerRadius,
innerRadius,
ringGap,
);
const maxValue = Math.max(...data.map((point) => point.value), 1);
const tooltip = createRadialTooltip();
const gridElements: DomphyElement[] = [];
// <PolarGrid gridType="circle" />: concentric gridlines. By default one per
// category band, sitting on each ring radius so the grid aligns with the
// bars; an explicit gridCircleCount overrides with that many evenly spaced.
const gridRadii =
gridCircleCount == null
? rings.map((ring) => ring.radius)
: Array.from({ length: gridCircleCount }, (_, index) => {
const denominator = Math.max(1, gridCircleCount - 1);
return (
innerRadius + ((outerRadius - innerRadius) / denominator) * index
);
});
gridRadii.forEach((radius, index) => {
gridElements.push({
...radialThinCircle(cx, cy, radius, "muted"),
_key: `grid-ring-${index}`,
});
});
// radialLines={true} default: evenly spaced spokes from inner to outer radius.
for (let index = 0; index < spokeCount; index++) {
const angle = (360 / spokeCount) * index;
const start = polarPoint(cx, cy, innerRadius, angle);
const end = polarPoint(cx, cy, outerRadius, angle);
gridElements.push({
line: null,
x1: start.x,
y1: start.y,
x2: end.x,
y2: end.y,
fill: "none",
stroke: (listener: Listener) => themeColor(listener, "shift-3"),
strokeWidth: 1,
_key: `grid-spoke-${index}`,
} as DomphyElement<"line">);
}
const arcElements: DomphyElement[] = data.map((point, index) => {
const ring = rings[index];
const tone = radialSeriesTone(index, point.tone);
const sweep = (point.value / maxValue) * 360;
return radialArcPath({
cx,
cy,
radius: ring.radius,
thickness: ring.thickness,
startAngleDeg: 0,
endAngleDeg: sweep,
tone,
tooltip,
// ChartTooltipContent (hideLabel nameKey="browser") = swatch + name +
// right-aligned numeric value; the shared tooltip now threads the value
// through and renders it after the label.
tooltipLabel: point.label,
tooltipValue: point.value,
seriesKey: point.key,
});
});
const chart: DomphyElement<"div"> = {
div: [
{
svg: [...gridElements, ...arcElements],
viewBox: `0 0 ${RADIAL_VIEW_SIZE} ${RADIAL_VIEW_SIZE}`,
style: {
width: "100%",
height: "100%",
display: "block",
overflow: "visible",
},
} as DomphyElement<"svg">,
radialTooltipLayer(tooltip),
],
style: {
position: "relative",
width: "100%",
aspectRatio: "1 / 1",
maxHeight: themeSpacing(72),
marginInline: "auto",
},
_onMount(node) {
tooltip.bindContainer(node.domElement as HTMLElement);
},
_onRemove() {
tooltip.bindContainer(null);
},
} as DomphyElement<"div">;
return radialCardShell({
title,
description,
content: { div: [chart] },
footer: chartTrendFooter({
trendText,
direction: trendDirection,
captionText,
}),
});
}
export { chartRadialGrid };