Skip to content

Add angular focus for radial charts - #90

Merged
tannerlinsley merged 3 commits into
mainfrom
taren/radial-tooltip-focus
Aug 12, 2026
Merged

Add angular focus for radial charts#90
tannerlinsley merged 3 commits into
mainfrom
taren/radial-tooltip-focus

Conversation

@tannerlinsley

@tannerlinsley tannerlinsley commented Aug 12, 2026

Copy link
Copy Markdown
Member

Summary

  • add focusGroupAngle as the polar equivalent of group-x
  • resolve radialArc focus against painted arc geometry, including donut holes
  • document the polar focus strategy and refresh generated release evidence

Validation

  • pnpm validate

Release

Minor release for @tanstack/charts; the fixed package set advances to 0.12.0.

Summary by CodeRabbit

  • New Features

    • Added angular focus for radial charts, grouping points by semantic angle and prioritizing the nearest radial distance.
    • Added angle-based keyboard navigation for radial chart interactions.
    • Improved radial arc tooltips and focus detection using painted arc boundaries for more accurate selection.
  • Bug Fixes

    • Stabilized pointer interaction timing to prevent transient inactive states from triggering prematurely.
  • Documentation

    • Added guidance for angular focus, radial interactions, and focus-distance settings.
  • Tests

    • Added coverage for radial focus grouping, navigation, and geometry-based arc selection.

@nx-cloud

nx-cloud Bot commented Aug 12, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 948a884

Command Status Duration Result
nx run charts-workspace:ci-distributed ✅ Succeeded 4m 55s View ↗
nx run charts-workspace:package-check ✅ Succeeded <1s View ↗
nx run charts-workspace:benchmark-check ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-12 23:16:39 UTC

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Polar marks now carry focus geometry. The new focusGroupAngle strategy groups radial points by semantic angle and supports angular navigation. Radial arcs use sampled painted boundaries for nearest-focus behavior. Pointer probes now require two consecutive inactive frames.

Changes

Polar focus interaction

Layer / File(s) Summary
Polar interaction geometry
packages/charts-core/src/polar-focus-internal.ts, packages/charts-core/src/polar-sector-internal.ts, packages/charts-core/src/polar.ts
Polar marks attach focus geometry. Radial arcs expose sampled painted boundaries, including custom generator inputs.
Angular focus strategy and validation
packages/charts-core/src/polar-focus-internal.ts, packages/charts-core/src/polar.ts, packages/charts-core/src/polar.test.ts, packages/charts-core/src/exports.test.ts, scripts/public-callback-contract.mjs
focusGroupAngle resolves bounded radial rays, groups points by angle and series, and orders navigation by angle and radius. Tests verify radial ordering, arc geometry, and polar-only exports.
Focus behavior documentation
.changeset/*, API-FRICTION.md, docs/guides/*, docs/reference/*, packages/charts-core/docs/guides/*, packages/charts-core/docs/reference/*, packages/charts-core/skills/*
Documentation describes angular focus configuration, tooltip grouping, navigation, distance limits, and painted arc boundaries.
Benchmark and comparison baselines
benchmarks/*, docs/comparison.md, packages/charts-core/docs/comparison.md, scripts/measure-bundles.mjs
Bundle budgets, source revisions, preview hashes, package metadata, and comparison dates were refreshed.

Pointer activation stability

Layer / File(s) Summary
Consecutive inactive-frame activation gate
scripts/stress-chart-libraries.mjs, benchmarks/comparison/stress/README.md, API-FRICTION.md
Pointer activation timing starts after two consecutive inactive animation frames. Active frames reset the counter. The stress documentation and friction record describe the updated behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Mergeability Score: 🟡 Moderate · up to acb27

The PR adds angular focus and radial-arc hit testing, but its stress-validation helper can begin timing too early when the tooltip is already inactive, producing unreliable benchmark or interaction measurements. Merge should wait for that bounded validation issue to be fixed or explicitly accepted; the documented tie-breaker also needs a minor correction.

Sequence Diagram(s)

sequenceDiagram
  participant Pointer
  participant focusGroupAngle
  participant PolarMarks
  participant Tooltip
  Pointer->>focusGroupAngle: provide pointer position
  focusGroupAngle->>PolarMarks: inspect polar focus geometry
  PolarMarks-->>focusGroupAngle: return candidate points
  focusGroupAngle-->>Tooltip: return angularly grouped points
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding angular focus support for radial charts.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch taren/radial-tooltip-focus

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
API-FRICTION.md (1)

7972-7975: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the documented tie-breaker.

focusGroupAngle does not use radius distance for the primary-point tie-breaker. In packages/charts-core/src/polar-focus-internal.ts:49-119, resolve compares Euclidean pointer-to-point distance after the ray-distance comparison. Update F-271 to document the actual behavior.

Proposed wording
-  selects the nearest bounded radial ray, uses radius distance as the primary-point
-  tie-breaker, groups one point per series at the same semantic angle, and
+  selects the nearest bounded radial ray, uses Euclidean pointer-to-point distance
+  as the tie-breaker, groups one point per series at the same semantic angle, and
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@API-FRICTION.md` around lines 7972 - 7975, Update the F-271 decision text
describing focusGroupAngle so its primary-point tie-breaker is documented as
Euclidean pointer-to-point distance after ray-distance comparison, replacing the
incorrect radius-distance description. Preserve the remaining behavior and
export-path wording.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/stress-chart-libraries.mjs`:
- Around line 894-900: Update the inactive-frame loop in
__stressPointerWaitInactive so it awaits one animation frame before checking
__stressPointerActive, ensuring two completed consecutive inactive frames are
observed even when initially inactive. Preserve the existing 120-frame timeout
and reset behavior when activity is detected.

---

Outside diff comments:
In `@API-FRICTION.md`:
- Around line 7972-7975: Update the F-271 decision text describing
focusGroupAngle so its primary-point tie-breaker is documented as Euclidean
pointer-to-point distance after ray-distance comparison, replacing the incorrect
radius-distance description. Preserve the remaining behavior and export-path
wording.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 373277f5-cdff-4911-832e-fcab096159f4

📥 Commits

Reviewing files that changed from the base of the PR and between 948a884 and acb2722.

📒 Files selected for processing (3)
  • API-FRICTION.md
  • benchmarks/comparison/stress/README.md
  • scripts/stress-chart-libraries.mjs

Comment on lines +894 to +900
let inactiveFrames = 0
for (let frame = 0; frame < 120; frame++) {
if (!globalThis.__stressPointerActive()) return true
if (globalThis.__stressPointerActive()) {
inactiveFrames = 0
} else if (++inactiveFrames >= 2) {
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wait for two completed inactive frames.

If __stressPointerWaitInactive starts while the tooltip is already inactive, Lines 894-900 count the entry-time state as frame one and return after only one requestAnimationFrame cycle. This does not enforce the documented two consecutive inactive animation frames and can start activation timing too early. Await the frame before checking the state, while keeping the 120-frame timeout.

Proposed fix
       globalThis.__stressPointerWaitInactive = async () => {
         let inactiveFrames = 0
         for (let frame = 0; frame < 120; frame++) {
+          await nextFrame()
           if (globalThis.__stressPointerActive()) {
             inactiveFrames = 0
           } else if (++inactiveFrames >= 2) {
             return true
           }
-          await nextFrame()
         }
         throw new Error('Pointer tooltip did not return to an inactive state.')
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let inactiveFrames = 0
for (let frame = 0; frame < 120; frame++) {
if (!globalThis.__stressPointerActive()) return true
if (globalThis.__stressPointerActive()) {
inactiveFrames = 0
} else if (++inactiveFrames >= 2) {
return true
}
globalThis.__stressPointerWaitInactive = async () => {
let inactiveFrames = 0
for (let frame = 0; frame < 120; frame++) {
await nextFrame()
if (globalThis.__stressPointerActive()) {
inactiveFrames = 0
} else if (++inactiveFrames >= 2) {
return true
}
}
throw new Error('Pointer tooltip did not return to an inactive state.')
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/stress-chart-libraries.mjs` around lines 894 - 900, Update the
inactive-frame loop in __stressPointerWaitInactive so it awaits one animation
frame before checking __stressPointerActive, ensuring two completed consecutive
inactive frames are observed even when initially inactive. Preserve the existing
120-frame timeout and reset behavior when activity is detected.

@tannerlinsley
tannerlinsley merged commit 639904c into main Aug 12, 2026
18 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 12, 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