Skip to content

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

Open
madelineluke wants to merge 4 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 4 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

* @param donutOptions
* @returns Signal[]
*/
export const getAdvancedLabelSignals = (donutOptions: DonutSpecOptions): Signal[] => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we decide to base size scaling on diameter rather than chart width?

madelineluke and others added 2 commits September 3, 2026 10:11
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>
@madelineluke
madelineluke force-pushed the feat/donut-advanced-labels branch from 7e50417 to cec9eef Compare September 3, 2026 16:15
madelineluke and others added 2 commits September 3, 2026 11:01
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>
@sonarqubecloud

sonarqubecloud Bot commented Sep 4, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

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