Enable point labels hiding when overlapped (#11055)

* Enable point labels hiding when overlapped

* fix cc

* fallback CC updates

* fixes CC
This commit is contained in:
stockiNail 2023-04-28 00:28:55 +02:00 committed by GitHub
parent ee7e928cfe
commit eff39c0769
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 168 additions and 55 deletions

View File

@ -154,7 +154,7 @@ Namespace: `options.scales[scaleId].pointLabels`
| `backdropColor` | [`Color`](../../general/colors.md) | `true` | `undefined` | Background color of the point label.
| `backdropPadding` | [`Padding`](../../general/padding.md) | | `2` | Padding of label backdrop.
| `borderRadius` | `number`\|`object` | `true` | `0` | Border radius of the point label
| `display` | `boolean` | | `true` | If true, point labels are shown.
| `display` | `boolean`\|`string` | | `true` | If true, point labels are shown. When `display: 'auto'`, the label is hidden if it overlaps with another label.
| `callback` | `function` | | | Callback function to transform data labels to point labels. The default implementation simply returns the current string.
| `color` | [`Color`](../../general/colors.md) | Yes | `Chart.defaults.color` | Color of label.
| `font` | `Font` | Yes | `Chart.defaults.font` | See [Fonts](../../general/fonts.md)

View File

@ -1,5 +1,5 @@
import defaults from '../core/core.defaults.js';
import {_longestText, addRoundedRectPath, renderText} from '../helpers/helpers.canvas.js';
import {_longestText, addRoundedRectPath, renderText, _isPointInArea} 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';
@ -136,23 +136,18 @@ function updateLimits(limits, orig, angle, hLimits, vLimits) {
}
}
function buildPointLabelItems(scale, labelSizes, padding) {
const items = [];
const valueCount = scale._pointLabels.length;
const opts = scale.options;
const extra = getTickBackdropHeight(opts) / 2;
function createPointLabelItem(scale, index, itemOpts) {
const outerDistance = scale.drawingArea;
const additionalAngle = opts.pointLabels.centerPointLabels ? PI / valueCount : 0;
for (let i = 0; i < valueCount; i++) {
const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + padding[i], additionalAngle);
const {extra, additionalAngle, padding, size} = itemOpts;
const pointLabelPosition = scale.getPointPosition(index, outerDistance + extra + padding, additionalAngle);
const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI)));
const size = labelSizes[i];
const y = yForAngle(pointLabelPosition.y, size.h, angle);
const textAlign = getTextAlignForAngle(angle);
const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);
return {
// if to draw or overlapped
visible: true,
items.push({
// Text position
x: pointLabelPosition.x,
y,
@ -165,7 +160,42 @@ function buildPointLabelItems(scale, labelSizes, padding) {
top: y,
right: left + size.w,
bottom: y + size.h
});
};
}
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;
}
function buildPointLabelItems(scale, labelSizes, padding) {
const items = [];
const valueCount = scale._pointLabels.length;
const opts = scale.options;
const {centerPointLabels, display} = opts.pointLabels;
const itemOpts = {
extra: getTickBackdropHeight(opts) / 2,
additionalAngle: centerPointLabels ? PI / valueCount : 0
};
let area;
for (let i = 0; i < valueCount; i++) {
itemOpts.padding = padding[i];
itemOpts.size = labelSizes[i];
const item = createPointLabelItem(scale, i, itemOpts);
items.push(item);
if (display === 'auto') {
item.visible = isNotOverlapped(item, area);
if (item.visible) {
area = item;
}
}
}
return items;
}
@ -198,18 +228,13 @@ function yForAngle(y, h, angle) {
return y;
}
function drawPointLabels(scale, labelCount) {
const {ctx, options: {pointLabels}} = scale;
for (let i = labelCount - 1; i >= 0; i--) {
const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
const plFont = toFont(optsAtIndex.font);
const {x, y, textAlign, left, top, right, bottom} = scale._pointLabelItems[i];
const {backdropColor} = optsAtIndex;
function drawPointLabelBox(ctx, opts, item) {
const {left, top, right, bottom} = item;
const {backdropColor} = opts;
if (!isNullOrUndef(backdropColor)) {
const borderRadius = toTRBLCorners(optsAtIndex.borderRadius);
const padding = toPadding(optsAtIndex.backdropPadding);
const borderRadius = toTRBLCorners(opts.borderRadius);
const padding = toPadding(opts.backdropPadding);
ctx.fillStyle = backdropColor;
const backdropLeft = left - padding.left;
@ -231,6 +256,21 @@ function drawPointLabels(scale, labelCount) {
ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight);
}
}
}
function drawPointLabels(scale, labelCount) {
const {ctx, options: {pointLabels}} = scale;
for (let i = labelCount - 1; i >= 0; i--) {
const item = scale._pointLabelItems[i];
if (!item.visible) {
// overlapping
continue;
}
const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
drawPointLabelBox(ctx, optsAtIndex, item);
const plFont = toFont(optsAtIndex.font);
const {x, y, textAlign} = item;
renderText(
ctx,

View File

@ -3500,10 +3500,10 @@ export type RadialLinearScaleOptions = CoreScaleOptions & {
borderRadius: Scriptable<number | BorderRadius, ScriptableScalePointLabelContext>;
/**
* if true, point labels are shown.
* if true, point labels are shown. When `display: 'auto'`, the label is hidden if it overlaps with another label.
* @default true
*/
display: boolean;
display: boolean | 'auto';
/**
* Color of label
* @see Defaults.color

View File

@ -0,0 +1,25 @@
module.exports = {
config: {
type: 'polarArea',
data: {
datasets: [{
data: new Array(50).fill(5),
backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']
}],
labels: new Array(50).fill(0).map((el, i) => ['label ' + i, 'line 2'])
},
options: {
scales: {
r: {
startAngle: 180,
pointLabels: {
display: 'auto',
}
}
}
}
},
options: {
spriteText: true
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View File

@ -0,0 +1,24 @@
module.exports = {
config: {
type: 'polarArea',
data: {
datasets: [{
data: new Array(50).fill(5),
backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']
}],
labels: new Array(50).fill(0).map((el, i) => ['label ' + i, 'line 2'])
},
options: {
scales: {
r: {
pointLabels: {
display: 'auto',
}
}
}
}
},
options: {
spriteText: true
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View File

@ -0,0 +1,24 @@
module.exports = {
config: {
type: 'polarArea',
data: {
datasets: [{
data: new Array(50).fill(5),
backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']
}],
labels: new Array(50).fill(0).map((el, i) => ['label ' + i, 'line 2'])
},
options: {
scales: {
r: {
pointLabels: {
display: true,
}
}
}
}
},
options: {
spriteText: true
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB