Skip to content

feat(donut-s2): add AdvancedLabel (swatch + name/value/detail block) - #901

Open
madelineluke wants to merge 5 commits into
feat/donut-summary-delta-linefrom
feat/donut-advanced-labels
Open

feat(donut-s2): add AdvancedLabel (swatch + name/value/detail block)#901
madelineluke wants to merge 5 commits into
feat/donut-summary-delta-linefrom
feat/donut-advanced-labels

Conversation

@madelineluke

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a new AdvancedLabel Donut child component - a color swatch + segment name row, optional bold value/percent row, and optional "X out of Y" detail row, positioned around the ring, per planning/specs/donut/implemented/donut-advanced-labels.json.
  • Extends the direct-label hemisphere-mirrored anchor technique (from SegmentLabel) to a 4-part swatch+text block, including manually replicating Vega's polar-to-cartesian math for the swatch since symbol marks have no radius/theta/dx encode channels.
  • Reserves ring-gap radius when an AdvancedLabel is present (previously only SegmentLabel triggered this), and caps the block's vertical row-stacking height using the same existing horizontal-reach ratio, so a segment anchored near the ring's top/bottom never overflows without shrinking the ring further than direct labels already do.
  • Generalizes getSegmentLabelValueFill into a shared getLabelValueFill(donutOptions, restColor) so both direct and advanced labels reuse the same hover-color-switch logic, and extends isInteractive() to treat a value/percent-showing AdvancedLabel as interactive.
  • Fixes a sanitizeChildren allowlist bug found during implementation: it maintains its own independent validDisplayNames set separate from childrenAdapter.ts's switch, which was silently dropping AdvancedLabel children before they ever reached the adapter.
  • Collision avoidance between adjacent advanced-label blocks (donut-direct-labels-collision) was explicitly deferred and is not integrated in this PR - documented as a known limitation in the reconciled spec.

Test plan

  • yarn test - 4224 passed (41 new in advancedLabelUtils.test.tsx, plus new coverage in markUtils.test.ts and donutSpecBuilder.test.ts)
  • yarn tsc --noEmit - no new errors
  • yarn lint - clean
  • Visually verified in Storybook across the Basic, NameOnly, and a new responsive WithDetail story (width-slider, mirroring DirectLabels.story.tsx) spanning all size tiers (60px-674px) - confirmed the vertical-overflow fix holds (no label escapes its reserved margin) and the ring stays full-size; XS/S-tier adjacent-label overlap is a known, separately-tracked limitation (no collision avoidance yet)

🤖 Generated with Claude Code

@madelineluke

Copy link
Copy Markdown
Collaborator Author

Follow-up from live testing: confirmed two real layout issues not caught by unit tests:

  • A right-hemisphere label block wider than the reserved horizontal margin overflows the container's edge outright (repro'd at container width 338px / 184px outer diameter, L tier) - there's no horizontal equivalent of the vertical row-stacking cap.
  • The vertical row-stacking cap (getAdvancedLabelRowDy) does prevent vertical overflow, but as a side effect it compresses the gaps between name/value/detail rows, visibly "smooshing" them together at small sizes when content is oversized.

Tried a horizontal fix mirroring the left hemisphere's capped pull-back - it kept content on-canvas, but did so by shrinking the fixed 20px ring-gap on oversized right-hemisphere labels, which isn't acceptable. Reverted.

Decision: defer both issues to donut-direct-labels-collision as a single unit of work, since a correct fix for either likely needs a continuous font-size shrink factor applied to the whole block when it doesn't fit, not a position or spacing adjustment (which is what both attempts here amounted to, each trading away a different invariant). Documented in planning/specs/donut/implemented/donut-advanced-labels.json.

🤖 Generated with Claude Code

Comment thread packages/vega-spec-builder-s2/src/donut/advancedLabelUtils.ts
@madelineluke
madelineluke force-pushed the feat/donut-advanced-labels branch from 7e50417 to cec9eef Compare September 3, 2026 16:15
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

madelineluke and others added 5 commits September 8, 2026 15:36
Adds the AdvancedLabel Donut child: a color swatch + segment name row,
optional bold value/percent row, and optional "X out of Y" detail row,
positioned around the ring using the same hemisphere-mirrored anchor
technique as SegmentLabel but extended to a 4-part block. Reuses the
existing ring-gap/horizontal-reach reservation ratio to also cap the
block's vertical row-stacking height, so a segment anchored near the
ring's top/bottom never overflows without shrinking the ring further.

Collision avoidance between adjacent label blocks (donut-direct-labels-
collision) was explicitly deferred and is not integrated here - tracked
as a known limitation in the reconciled spec.

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

Live testing surfaced two real issues not caught by unit tests: a
right-hemisphere label block wider than the reserved margin overflows
the container edge outright (no horizontal protection existed), and
the existing vertical row-stacking cap compresses row spacing at small
sizes as a side effect. A horizontal fix was attempted but reverted -
it kept content on-canvas by shrinking the fixed 20px ring-gap, which
isn't acceptable. Both issues are deferred to donut-direct-labels-
collision as a single unit of work, since the real fix likely needs a
continuous font-size shrink factor rather than position/spacing tricks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Advanced-label name/value/detail sizes were one tier too large, and the
segment-name row's "same size as Direct label - value" aliasing was
wrong. Give segment name its own dedicated per-tier array
(DONUT_ADVANCED_LABEL_NAME_FONT_SIZES) and shift all three arrays
(name/value/detail) down one tier, deriving XS by extending the
corrected S->M step downward - same correction methodology used for
Direct labels.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… code smells in advancedLabelUtils

Extracts getAdvancedLabelRowDy's nested ternary/template-literal
constructs (totalHeightExpr, bottomDetailDy, topValueDy, topNameDy)
into named consts and if/else chains, and switches the percent+value
signal concatenation to String.raw to avoid the escaped backslash. No
behavior change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Not present in the Figma spec and not needed. value/percent were
almost always OR'd together (hasValue = value || percent), so most
sites simplify to just percent. The one real behavior change is
getAdvancedLabelValueText, which previously supported a combined
"percent + raw value" display (e.g. "60.8%  6,458") - that combined
mode is removed along with the standalone value-only mode, leaving
percent-only. valueFormat is kept, re-scoped to the detail row's
"{value} out of {total}" number formatting, which is unrelated to the
removed value boolean.

Updates isInteractive() (markUtils.ts), the AdvancedLabel component
default, stories, and tests accordingly, and corrects the two
now-inaccurate prop-signature references in the implemented feature
spec.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@madelineluke
madelineluke force-pushed the feat/donut-advanced-labels branch from e5615bc to 5882d3c Compare September 8, 2026 21:49
@sonarqubecloud

sonarqubecloud Bot commented Sep 8, 2026

Copy link
Copy Markdown

@madelineluke
madelineluke removed this pull request from stack #898 September 9, 2026 22:41
@madelineluke
madelineluke added this pull request to stack #908 September 9, 2026 22:42
@madelineluke
madelineluke removed this pull request from stack #908 September 9, 2026 22:42
@madelineluke
madelineluke added this pull request to stack #909 September 9, 2026 22:43
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.

2 participants