Skip to content

feat(donut-s2): Direct Labels - base implementation, positioning and scaling - #894

Open
madelineluke wants to merge 4 commits into
fix/donut-ring-width-per-tierfrom
feat/donut-direct-labels-ring-gap-sizing-anchor
Open

feat(donut-s2): Direct Labels - base implementation, positioning and scaling #894
madelineluke wants to merge 4 commits into
fix/donut-ring-width-per-tierfrom
feat/donut-direct-labels-ring-gap-sizing-anchor

Conversation

@madelineluke

Copy link
Copy Markdown
Collaborator

Description

Implements the ring-gap sizing/hemisphere-anchor portion of the donut-direct-labels spec, and fixes a real resize-reactivity bug found while building the responsive Storybook example for it.

  • Adds getDonutOuterRadiusExpr to deterministically reserve room for the direct-label ring gap and hemisphere pull-back within the donut's own radius, so the ring and its labels always fit within a given container size in a single pass, without relying on Vega's autosize growing beyond it. This is threaded through the arc mark, the empty-state ring, and DonutSummary's radius/font-size/truncation calculations so they all agree on the same effective radius.
  • Fixes a bug where radius/dx/fontSize/dy/align/baseline on the segment label text marks lived in Vega's enter encode set, which only evaluates once when a mark is first created. On resize, only the shared x/y anchor (in update) tracked the new container size, so labels were uniformly translated by the container's center shift instead of properly rescaling - causing them to visually detach from (or overlap into) the ring at certain sizes. Moved these encodings into update.
  • Replaces the CSS-resize-based Storybook example with a width-slider-driven Responsive story (matching the existing Line DirectLabelSizeScaling pattern, with visible size-tier breakpoint markers), plus a ManySegmentsResponsive variant using the existing sliveredDonutData fixture to stress-test label crowding with 15 segments.

Related Issue

planning/specs/donut/donut-direct-labels.json (approved)

Motivation and Context

Direct labels on a donut need a ring gap and can pull back horizontally to avoid overlapping the ring near the 9/3 o'clock cardinal points. Without reserving space for that up front, labels could end up demanding more room than the container had, either forcing the chart to grow beyond its given size or causing the ring to shrink disproportionately. Fixing the resize-reactivity bug was necessary because the size-tier-driven font/anchor calculations only take effect on the very first render otherwise, making the ring and its labels visibly drift apart on any subsequent resize.

How Has This Been Tested?

  • Unit tests updated in segmentLabelUtils.test.tsx and donutSummaryUtils.test.tsx for the new radius-reservation formula and the enterupdate encode move.
  • Full vega-spec-builder-s2/react-spectrum-charts-s2 test suite passes (131 suites, 2125 tests).
  • yarn lint and yarn tsc --noEmit are clean (no new errors; two pre-existing unrelated failures confirmed present on the base commit too).
  • Manually verified in Storybook via Playwright screenshots across the full slider range (80px-500px, XS-XL tiers) on both the Responsive and ManySegmentsResponsive stories: the ring and its labels now scale together in real time with no overlap, including immediately after a resize (previously frozen from the initial render).

Screenshots (if appropriate):

See the Responsive and ManySegmentsResponsive stories under React Spectrum Charts 2/Donut/Features/Direct Label in Storybook.

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 26, 2026 21:49
@madelineluke
madelineluke changed the base branch from main to feat/donut-summary-responsive-sizing August 27, 2026 18:16
@madelineluke
madelineluke force-pushed the feat/donut-direct-labels-ring-gap-sizing-anchor branch from 38b9e40 to b471c34 Compare August 27, 2026 20:23
@madelineluke
madelineluke changed the base branch from feat/donut-summary-responsive-sizing to fix/donut-ring-width-per-tier August 27, 2026 20:23
madelineluke and others added 2 commits August 27, 2026 14:53
…ize reactivity

- Add getDonutOuterRadiusExpr to reserve room for the direct-label ring
  gap and hemisphere pull-back within the donut's own radius, so the ring
  and its labels deterministically fit any container size without
  relying on Vega's autosize growing beyond it. Threaded through the arc,
  empty-state ring, and DonutSummary radius/font-size calculations.
- Move segment label radius/dx/fontSize/dy/align/baseline encodings from
  Vega's `enter` set (evaluated once at mark creation) into `update`,
  since they depend on width/height-derived signals that must recompute
  on resize. Previously labels only translated along with the
  container's center shift while the ring itself resized correctly,
  causing labels to visually detach from the ring at certain sizes.
- Add a width-slider-driven Responsive story (matching Line's
  DirectLabelSizeScaling pattern) plus a ManySegmentsResponsive variant
  using the existing sliveredDonutData fixture, replacing the earlier
  CSS-resize-based story.

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

The story computed size tiers from raw container width, but the real spec
keys tiers off the label-reserved outer diameter (getDonutOuterRadiusExpr),
so the displayed "Size tier" text and threshold-line positions didn't match
what actually rendered. Also fixes XL being structurally unreachable because
the fixed chart height capped min(width, height) before the width slider
could reach it.
@madelineluke
madelineluke force-pushed the feat/donut-direct-labels-ring-gap-sizing-anchor branch from b471c34 to 33d3b7c Compare August 27, 2026 20:55
…der, segmentLabelUtils

Replace length-1 indexing with .at(-1) (guarded against undefined, matching
the existing pattern in barUtils.ts), consolidate consecutive Array#push()
calls, and guard getTextRuleExpr's field access against Field's non-string
variants to avoid stringifying an object as "[object Object]".
Export getTextRuleExpr and add direct unit tests for its field, value, and
empty-rule fallback branches, plus the empty-array guard - none of these
were reachable through getSegmentLabelValueText, the only real caller,
which always produces signal-shaped rules.
@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:53
@madelineluke madelineluke changed the title feat(donut-s2): reserve ring-gap radius for direct labels and fix resize reactivity feat(donut-s2): Direct Labels - base implementation, positioning and scaling Aug 27, 2026
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