Skip to content

feat: s2 donut ring width and slice gap - #891

Open
madelineluke wants to merge 4 commits into
feat/donut-summary-responsive-sizingfrom
fix/donut-ring-width-per-tier
Open

feat: s2 donut ring width and slice gap#891
madelineluke wants to merge 4 commits into
feat/donut-summary-responsive-sizingfrom
fix/donut-ring-width-per-tier

Conversation

@madelineluke

@madelineluke madelineluke commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Description

Fixes getArcMark/getEmptyStateArcMark's ring geometry: innerRadius was computed as holeRatio * DONUT_RADIUS (a fixed proportion of the outer radius), but the S2 design system specifies a near-fixed ring width per named size tier (16/18/20/22/24px for XS/S/M/L/XL) that grows far more slowly than diameter. Confirmed by pixel-measuring the Figma reference: the ring was roughly half as thick as intended at the S tier, worsening at larger sizes.

Ring width now snaps to the nearest named tier whenever holeRatio is left at its default (DEFAULT_HOLE_RATIO); an explicitly customized holeRatio is still honored as a proportional ring, preserving backward compatibility. getArcMark, getEmptyStateArcMark, and DonutSummary's truncation/fit-gate formulas all share one getDonutInnerRadiusExpr() helper so they can't drift out of sync again.

Discovered mid-fix: the corrected (thicker) ring at the S tier drops its inner radius below the old DONUT_SUMMARY_MIN_RADIUS fit threshold, which would have hidden center text that used to render. Introduces a new s2-only DONUT_SUMMARY_MIN_RADIUS_S2 to keep S-tier text visible, without touching s1's shared constant.

Also fixes the arc segment padAngle: previously a flat 0.01 (too thin at every size). Now converts the per-tier chart.donut.size.slice-gap token (1/2/2/2/4px for XS/S/M/L/XL, per llm/skills/s2-donut/donut-chart-tokens/SKILL.md) to an angle at the outer radius, via the same tier-snapping pattern as ring width.

Related Issue

Fixes planning/specs/donut/issues/implemented/donut-ring-width-holeratio-mismatch.json. No GitHub issue tracked.

Motivation and Context

The donut's rendered ring didn't match the Figma design at any size tier, and the gap between adjacent segments was too thin and didn't scale per tier as the design specifies.

How Has This Been Tested?

  • yarn test --testPathPattern="react-spectrum-charts-s2|vega-spec-builder-s2" - 2122/2122 passing
  • yarn lint - clean
  • yarn tsc --noEmit - no new errors (pre-existing unrelated errors confirmed present on main)
  • Verified visually in Storybook (Donut/Features/Donut Summary/Size Tiers) that ring width and segment gaps scale correctly per tier

Screenshots (if appropriate):

N/A - verified in Storybook, see test plan above.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@madelineluke
madelineluke marked this pull request as draft August 19, 2026 22:05
@madelineluke madelineluke changed the title Donut ring width & slice gap — calibrate to per-tier design tokens feat: s2 donut ring width and slice gap Aug 19, 2026
@madelineluke
madelineluke force-pushed the fix/donut-ring-width-per-tier branch from adaa194 to aa6f478 Compare August 27, 2026 20:23
@madelineluke
madelineluke changed the base branch from main to feat/donut-summary-responsive-sizing August 27, 2026 20:23
madelineluke and others added 4 commits August 27, 2026 14:52
Confirmed by pixel-measuring the Figma "Donut sizes" reference: the
actual ring width per named size tier (S/M/L) matches the documented
chart.donut.size.ring-width tokens almost exactly, while the current
holeRatio=0.85-based innerRadius calculation renders a ring roughly
half as thick at S, worsening at larger tiers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… of holeRatio

getArcMark/getEmptyStateArcMark computed innerRadius as a fixed
proportion of the outer radius (holeRatio * DONUT_RADIUS), but the S2
design system specifies a near-fixed ring width per named size tier
(16/18/20/22/24px for XS/S/M/L/XL) that grows far more slowly than
diameter. Confirmed by pixel-measuring the Figma reference: the actual
ring was roughly half as thick as intended at the S tier, worsening at
larger sizes.

Ring width now snaps to the nearest named tier (matching the pattern
already used for DonutSummary font sizing) whenever holeRatio is left
at its default - an explicitly customized holeRatio is still honored
as a proportional ring. getArcMark, getEmptyStateArcMark, and
DonutSummary's truncation/fit-gate formulas all share one
getDonutInnerRadiusExpr() helper so they can't drift out of sync again.

Discovered mid-fix: the corrected (thicker) ring at the S tier drops
its inner radius below the old DONUT_SUMMARY_MIN_RADIUS fit threshold,
which would have hidden center text that used to render - a new
s2-only DONUT_SUMMARY_MIN_RADIUS_S2 keeps S-tier text visible without
touching s1's shared constant.

Also recalibrates the arc segment padAngle to a constant ~1px visual
gap (1 / DONUT_RADIUS) regardless of donut size, replacing a fixed
0.01 radian value that was too thin at every size and confirmed
against the Figma reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous commit's padAngle fix used a single constant-pixel-gap
heuristic. The S2 donut chart tokens actually specify a per-named-tier
slice gap (chart.donut.size.slice-gap: XS=1px/S=2px/M=2px/L=2px/XL=4px)
- the same discrete tier-lookup shape already used for ring width and
DonutSummary font sizing, not a single flat value.

Adds getSliceGapScale/getSliceGapSignal (mirroring
getRingWidthScale/getRingWidthSignal) and converts the resolved px gap
to an angle at the outer radius for the arc mark's padAngle. Unlike
ring width, slice gap doesn't depend on holeRatio, so it's registered
unconditionally for every donut.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…egments

A donut with many segments of highly skewed sizes (e.g. sliveredDonutData's
15 browsers, from Chrome's 34K down to SlimBrowser's 120) could have its
smallest segments' entire angular width consumed by the fixed per-tier
slice gap, rendering them as degenerate zero-area paths instead of a
smaller gap. Cap padAngle to a fraction of each segment's own arcLength
(already computed by the pie transform) rather than an average across all
segments - an average gets dragged up by the larger segments and wouldn't
meaningfully shrink the gap for the specific tiny ones actually at risk.

Verified in Storybook: all 15 arcs in the Slivers story now render with
proper (non-degenerate) SVG arc commands.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@madelineluke
madelineluke force-pushed the fix/donut-ring-width-per-tier branch from aa6f478 to 3e382ba Compare August 27, 2026 20:55
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

@madelineluke
madelineluke marked this pull request as ready for review August 27, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant