From e305f1f54db4a79ea983d84d1a80443cd94b90e4 Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Tue, 27 Oct 2026 08:43:02 +0530 Subject: [PATCH 1/4] Fix radial auto-label rectangle intersection Detect contained and intersecting radial point-label rectangles while retaining the existing half-pixel margin. Add regression coverage. Co-authored-by: generaltony007 <144252170+GeneralTony007@users.noreply.github.com> --- src/scales/scale.radialLinear.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 0a2c61f722b..73e5626f782 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -1,5 +1,5 @@ import defaults from '../core/core.defaults.js'; -import {_longestText, addRoundedRectPath, renderText, _isPointInArea} from '../helpers/helpers.canvas.js'; +import {_longestText, addRoundedRectPath, renderText} from '../helpers/helpers.canvas.js'; import {HALF_PI, TAU, toDegrees, toRadians, _normalizeAngle, PI} from '../helpers/helpers.math.js'; import LinearScaleBase from './scale.linearbase.js'; import Ticks from '../core/core.ticks.js'; @@ -167,10 +167,9 @@ function isNotOverlapped(item, area) { if (!area) { return true; } - const {left, top, right, bottom} = item; - const apexesInArea = _isPointInArea({x: left, y: top}, area) || _isPointInArea({x: left, y: bottom}, area) || - _isPointInArea({x: right, y: top}, area) || _isPointInArea({x: right, y: bottom}, area); - return !apexesInArea; + // Match the half-pixel margin previously used for corner checks. + return item.left >= area.right + 0.5 || item.right <= area.left - 0.5 || + item.top >= area.bottom + 0.5 || item.bottom <= area.top - 0.5; } function buildPointLabelItems(scale, labelSizes, padding) { From 252f179515905a04964d89278e73a3de2b0da382 Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Tue, 27 Oct 2026 08:43:17 +0530 Subject: [PATCH 2/4] Fix radial auto-label rectangle intersection Detect contained and intersecting radial point-label rectangles while retaining the existing half-pixel margin. Add regression coverage. Co-authored-by: generaltony007 <144252170+GeneralTony007@users.noreply.github.com> --- test/specs/scale.radialLinear.tests.js | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index 451f6fb8db8..76f60a368da 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -654,3 +654,61 @@ describe('Test the radial linear scale', function() { }); }); + +describe('Radial auto point labels', function() { + [false, true].forEach(function(centerPointLabels) { + [0, 180].forEach(function(startAngle) { + [20, 50, 100].forEach(function(count) { + it('avoids intersections with ' + count + ' labels, angle ' + startAngle + ', centered ' + centerPointLabels, function() { + var chart = window.acquireChart({ + type: 'polarArea', + data: { + labels: Array.from({length: count}, (_, i) => 'Label '.repeat(i % 4 + 1) + i), + datasets: [{data: Array(count).fill(1)}] + }, + options: { + animation: false, + plugins: {legend: false}, + scales: {r: {startAngle, pointLabels: {display: 'auto', centerPointLabels}}} + } + }); + var items = chart.scales.r._pointLabelItems.filter(item => item.visible); + expect(items.length).toBeGreaterThan(1); + var overlaps = []; + items.forEach(function(a, i) { + items.slice(i + 1).forEach(function(b) { + if (a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top) { + overlaps.push([i, items.indexOf(b)]); + } + }); + }); + expect(overlaps).toEqual([]); + }); + }); + }); + }); + + [true, false].forEach(function(display) { + it('preserves explicit display ' + display, function() { + var chart = window.acquireChart({ + type: 'polarArea', + data: { + labels: Array.from({length: 50}, (_, i) => 'Label '.repeat(i % 4 + 1) + i), + datasets: [{data: Array(50).fill(1)}] + }, + options: { + animation: false, + plugins: {legend: false}, + scales: {r: {pointLabels: {display}}} + } + }); + var items = chart.scales.r._pointLabelItems; + if (display) { + expect(items.length).toBe(50); + expect(items.every(item => item.visible)).toBe(true); + } else { + expect(items.length).toBe(0); + } + }); + }); +}); From cb08f72b83f6df580a18d1d68f21a7ff4eded09e Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Tue, 27 Oct 2026 08:58:19 +0530 Subject: [PATCH 3/4] Improve label visibility check in radial scale Compare the first retained label as well as the previous label so the circular traversal cannot retain colliding labels across its seam. Add rotated multiline regression cases. Co-authored-by: generaltony007 <144252170+GeneralTony007@users.noreply.github.com> --- src/scales/scale.radialLinear.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 73e5626f782..f822914274e 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -182,6 +182,7 @@ function buildPointLabelItems(scale, labelSizes, padding) { additionalAngle: centerPointLabels ? PI / valueCount : 0 }; let area; + let first; for (let i = 0; i < valueCount; i++) { itemOpts.padding = padding[i]; @@ -190,8 +191,10 @@ function buildPointLabelItems(scale, labelSizes, padding) { const item = createPointLabelItem(scale, i, itemOpts); items.push(item); if (display === 'auto') { - item.visible = isNotOverlapped(item, area); + // The traversal wraps around the scale, so also check its first label. + item.visible = isNotOverlapped(item, area) && isNotOverlapped(item, first); if (item.visible) { + first = first || item; area = item; } } From 30185e46e0d58ad583b846066f26e32575c9c241 Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Tue, 27 Oct 2026 08:58:38 +0530 Subject: [PATCH 4/4] Check radial auto-label overlap across the circular seam Compare the first retained label as well as the previous label so the circular traversal cannot retain colliding labels across its seam. Add rotated multiline regression cases. Co-authored-by: generaltony007 <144252170+GeneralTony007@users.noreply.github.com> --- test/specs/scale.radialLinear.tests.js | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index 76f60a368da..ba85a375895 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -712,3 +712,33 @@ describe('Radial auto point labels', function() { }); }); }); +describe('Radial auto-label circular seam', function() { + [false, true].forEach(function(centerPointLabels) { + [45, 90, 135, 270].forEach(function(startAngle) { + it('retained boxes do not intersect at angle ' + startAngle + ', centered ' + centerPointLabels, function() { + var count = 100; + var chart = window.acquireChart({ + type: 'polarArea', + data: { + labels: Array.from({length: count}, (_, i) => Array(i % 3 + 1).fill('Label '.repeat(i % 4 + 1) + i)), + datasets: [{data: Array(count).fill(1)}] + }, + options: { + animation: false, plugins: {legend: false}, + scales: {r: {startAngle, pointLabels: {display: 'auto', centerPointLabels}}} + } + }); + var items = chart.scales.r._pointLabelItems.filter(item => item.visible); + var overlaps = []; + items.forEach(function(a, i) { + items.slice(i + 1).forEach(function(b) { + if (a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top) { + overlaps.push([chart.scales.r._pointLabelItems.indexOf(a), chart.scales.r._pointLabelItems.indexOf(b)]); + } + }); + }); + expect(overlaps).toEqual([]); + }); + }); + }); +});