Skip to content

[charts] Save full-circle flag on rotation axis#22162

Merged
alexfauquette merged 5 commits intomui:masterfrom
Anexus5919:fix/polar-axis-full-circle-flag
Apr 24, 2026
Merged

[charts] Save full-circle flag on rotation axis#22162
alexfauquette merged 5 commits intomui:masterfrom
Anexus5919:fix/polar-axis-full-circle-flag

Conversation

@Anexus5919
Copy link
Copy Markdown
Contributor

Closes #22135

Follow-up to #22134

Summary

When a rotation axis uses a point scale and covers a full circle, the polar plugin's getRange() intentionally shortens the angular range by 2π / N to avoid overlapping the first and last data points. #22134 patched ChartsRadialGrid to still draw closed circles in that case by re-deriving the decision with a floating-point EPSILON comparison on the already-shortened range.

As @JCQuintas pointed out in #22134 (comment):

If we ourselves decide that the default range is 2*PI * (N - 1) / N can't we simply detect that we are using the defaults without having to calculate anything?

This PR records that decision at its source in getRange() and exposes it as an internal flag on the computed polar axis, so consumers can read a boolean instead of reconstructing the verdict from shortened ranges.

Changes

packages/x-charts/src/internals/plugins/featurePlugins/useChartPolarAxis/computeAxisValue.ts

  • getRange() now returns { range, isPointScaleFullCircle } for all three branches (rotation+point, rotation+non-point, radius).
  • isPointScaleFullCircle is true only in the existing rotation+point branch when the pre-shortening diff > 2π − 0.1, the exact condition that was already triggering the range shortening. It is false (and irrelevant) everywhere else.
  • The flag is attached to the completed axis inside the isPointScaleConfig(axis) branch, where it has meaning.

packages/x-charts/src/models/axis.ts

  • Added isPointScaleFullCircle?: boolean to PolarAxisDefaultized (where the polar plugin assigns it) and ComputedAxis (what useRotationAxes() returns at the type level, since [charts-premium] Create a 'radialLine' series type #22066 unified the return type). Marked @ignore - internal on both.
  • Optional and additive - no existing consumer is affected.

packages/x-charts/src/ChartsRadialGrid/ChartsRadialGrid.tsx

  • Replaced the EPSILON-based reconstruction (dataRotationGap / angleGap / Math.abs(...) < EPSILON) with a direct flag read.
  • Dropped the unused EPSILON import.
  • As a small side benefit, rotationAxisConfig?.isPointScaleFullCircle uses optional chaining, so the code no longer has the rotationAxisConfig.scaleType === 'point' non-null access that was introduced by [charts] Fix radius grid lines when axis uses point scale #22134.

Behavior

For every canonical scenario the output is byte-identical to current master. Verified against #22134's two tests:

  1. Default angles + scaleType: 'point' → closed circles rendered (circle elements). ✅
  2. startAngle: 0, endAngle: Math.PI + scaleType: 'point' → open arcs rendered (path elements). ✅

One subtle behavior change (arguably an internal-consistency fix)

The threshold that triggers range shortening in getRange() is diff > 2π − 0.1 (≈ 354.28°). The old EPSILON check in ChartsRadialGrid, after algebra, only fires when the user-specified diff is within EPSILON · N/(N−1) of (≈ 0.06°–0.08° in typical cases). That created a narrow window where a user specifying e.g. endAngle: 357° with a point scale and 5 data points would see:

  • The scale's range shortened (as if "full circle") - data points placed accordingly
  • The radius grid drawn as an open arc - getRange and the grid renderer disagreeing with each other

After this PR, the single source of truth (the shortening decision in getRange()) drives both. In that narrow window, the grid now closes consistently with how the scale is already laid out.

This is a defensible consistency fix, but worth calling out explicitly in case it surprises anyone.

Why not just cast in ChartsRadialGrid?

Considered. Because #22066 intentionally typed the polar selector's return via ComputedAxis/ComputedAxisConfig (unified with cartesian to enable the radial line series), a cast would have been needed to surface the flag. An optional internal field on ComputedAxis is the minimal, non-fragile way; the field is ? and @ignore - internal, so cartesian consumers are unaffected.

Testing

Everything run against this branch. Failures called out below are pre-existing on upstream/master (verified independently by stashing and re-running).

Command Result
pnpm --filter "@mui/x-charts*" run typescript (x-charts, x-charts-pro, x-charts-premium) ✅ clean
pnpm test:unit --project "x-charts" --run 485 passed, 107 skipped (including all 7 ChartsRadialGrid tests)
pnpm test:unit --project "x-charts-pro" --run ✅ 135 passed
pnpm test:unit --project "x-charts-premium" --run ✅ 7 passed
pnpm test:browser --project "x-charts" --run src/ChartsRadialGrid ✅ 7 passed
pnpm test:browser --project "x-charts" --run (full) ✅ 579 passed. 2 failures in src/utils/niceDomain.test.tsx are pre-existing timezone artifacts (also fail on clean upstream/master — unrelated to this PR)
pnpm eslint ✅ clean (--max-warnings 0)
pnpm proptypes ✅ no diffs from this PR's changes
pnpm generate:exports ✅ no diffs

Test plan

  • Unit tests pass
  • Browser tests pass (excluding pre-existing unrelated failures)
  • TypeScript clean across x-charts / x-charts-pro / x-charts-premium
  • ESLint clean
  • pnpm proptypes produces no diff (no public API change - ChartsRadialGridProps is untouched)
  • Visual verification: the two behavioral scenarios pinned by [charts] Fix radius grid lines when axis uses point scale #22134 render identically (closed circles for default angles; open arcs for custom half-circle)

…ion axis

Record the decision made in getRange() as `isPointScaleFullCircle` on the
computed polar axis so ChartsRadialGrid reads a flag instead of recomputing
the full-circle check with floating-point math.

Closes mui#22135
@Anexus5919
Copy link
Copy Markdown
Contributor Author

@alexfauquette @JCQuintas I'd appreciate a review on this PR whenever you get a chance. Thanks!

@code-infra-dashboard
Copy link
Copy Markdown

code-infra-dashboard Bot commented Apr 22, 2026

Bundle size

Bundle Parsed size Gzip size
@mui/x-data-grid 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-pro 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-premium 0B(0.00%) 0B(0.00%)
@mui/x-charts ▼-32B(-0.01%) 🔺+13B(+0.01%)
@mui/x-charts-pro ▼-44B(-0.01%) 🔺+25B(+0.02%)
@mui/x-charts-premium ▼-59B(-0.01%) 🔺+35B(+0.02%)
@mui/x-date-pickers 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers-pro 0B(0.00%) 0B(0.00%)
@mui/x-tree-view 0B(0.00%) 0B(0.00%)
@mui/x-tree-view-pro 0B(0.00%) 0B(0.00%)

Details of bundle changes

Deploy preview

https://deploy-preview-22162--material-ui-x.netlify.app/


Check out the code infra dashboard for more information about this PR.

@alexfauquette
Copy link
Copy Markdown
Member

What about renaming it isFullCircle and computing it for all cases. This would help in other places of the codebase

@Anexus5919
Copy link
Copy Markdown
Contributor Author

What about renaming it isFullCircle and computing it for all cases. This would help in other places of the codebase

Good call - will do. I'll rename to isFullCircle, compute it for every rotation axis (point/band/continuous) using the same >= 2π - EPSILON threshold already used in ChartsRadiusGrid and useTicks, and wire those two consumers to read the flag directly. Pushing shortly.

… axes

Address review feedback: rename the flag to `isFullCircle` and compute it
for every rotation axis (point/band/continuous), not only the point-scale
branch. Wire the flag through to `ChartsRadiusGrid` as a prop so it drops
its local `EPSILON`-based derivation. The point-scale shortening keeps its
existing `2π - 0.1` threshold as a local decision.
@Anexus5919
Copy link
Copy Markdown
Contributor Author

Done in bdd6fcc. isFullCircle is now computed for every rotation scale (point/band/continuous) using diff >= 2π - EPSILON, and wired through to ChartsRadiusGrid as a prop - its local Math.abs(...) derivation is gone. Also dropped the inflated startAngle + 2π workaround in ChartsRadialGrid; endAngle is now just scale.range()[1].

I left useTicks's isInsideRotation alone - that's a per-tick aliasing filter (rejects ticks ≥ 2π from start so they don't overlap the start position), which is a different concern from "is the axis a full circle" and the EPSILON check needs to stay there regardless. Happy to revisit if you see a cleaner way.

Comment on lines 611 to 614
triggerTooltip?: boolean;
/** @ignore - internal. True when a rotation axis covers a full circle. */
isFullCircle?: boolean;
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this one necessary?

The isFullCircle is only added in the computed axis

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I tried removing it and TypeScript breaks at the three write sites in computeAxisValue.ts (the completeAxis[axis.id] = { ..., isFullCircle } literals in the band, point, and continuous branches). completeAxis is typed DefaultizedAxisConfig<ChartsAxisProps> -> PolarAxisDefaultized, so the object literal hits the excess-property check if the field isn't declared there.

The reads go through ComputedAxis and the writes go through PolarAxisDefaultized, so both need it. Happy to remove it from PolarAxisDefaultized if you'd prefer to widen the write site with a cast though I'd lean toward keeping both since the @ignore - internal keeps it out of the public surface either way.

];
const diff = angles[1] - angles[0];
const isFullCircle = diff >= Math.PI * 2 - EPSILON;
if (axis.scaleType === 'point' && diff > Math.PI * 2 - 0.1) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if (axis.scaleType === 'point' && diff > Math.PI * 2 - 0.1) {
if (axis.scaleType === 'point' && isFullCircle) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied in 9f9196917. Thanks for catching it.

@zannager zannager added the scope: charts Changes related to the charts. label Apr 23, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 23, 2026

Merging this PR will improve performance by 9.02%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 13 untouched benchmarks

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation ScatterChartPro with big data amount (single renderer) 473.1 ms 434 ms +9.02%

Comparing Anexus5919:fix/polar-axis-full-circle-flag (8a98f2d) with master (2e2d35a)1

Open in CodSpeed

Footnotes

  1. No successful run was found on master (c16ac09) during the generation of this report, so 2e2d35a was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

…ircle

Apply review suggestion: use `isFullCircle` (the `2π - EPSILON` check) as
the condition for the point-scale range shortening instead of the looser
`2π - 0.1`. Removes the remaining threshold discrepancy between the flag
and the decision that triggers the shortening.
@alexfauquette alexfauquette added the type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature. label Apr 24, 2026
alexfauquette and others added 2 commits April 24, 2026 14:58
Co-authored-by: Copilot <copilot@github.com>
Copy link
Copy Markdown
Member

@alexfauquette alexfauquette left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution

@alexfauquette alexfauquette merged commit 91752ff into mui:master Apr 24, 2026
22 checks passed
@alexfauquette alexfauquette changed the title [charts] Save full-circle flag on polar point-scale rotation axis [charts] Save full-circle flag on rotation axis Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: charts Changes related to the charts. type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[charts] Save if point scale are for full circle or not

3 participants