mirror of
https://github.com/chartjs/Chart.js.git
synced 2024-10-06 04:09:08 +02:00
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:
parent
ee7e928cfe
commit
eff39c0769
@ -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)
|
||||
|
@ -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,
|
||||
|
4
src/types/index.d.ts
vendored
4
src/types/index.d.ts
vendored
@ -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
|
||||
|
25
test/fixtures/controller.polarArea/pointLabels/displayAuto-180.js
vendored
Normal file
25
test/fixtures/controller.polarArea/pointLabels/displayAuto-180.js
vendored
Normal 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
|
||||
}
|
||||
};
|
BIN
test/fixtures/controller.polarArea/pointLabels/displayAuto-180.png
vendored
Normal file
BIN
test/fixtures/controller.polarArea/pointLabels/displayAuto-180.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 174 KiB |
24
test/fixtures/controller.polarArea/pointLabels/displayAuto.js
vendored
Normal file
24
test/fixtures/controller.polarArea/pointLabels/displayAuto.js
vendored
Normal 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
|
||||
}
|
||||
};
|
BIN
test/fixtures/controller.polarArea/pointLabels/displayAuto.png
vendored
Normal file
BIN
test/fixtures/controller.polarArea/pointLabels/displayAuto.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 174 KiB |
24
test/fixtures/controller.polarArea/pointLabels/overlapping.js
vendored
Normal file
24
test/fixtures/controller.polarArea/pointLabels/overlapping.js
vendored
Normal 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
|
||||
}
|
||||
};
|
BIN
test/fixtures/controller.polarArea/pointLabels/overlapping.png
vendored
Normal file
BIN
test/fixtures/controller.polarArea/pointLabels/overlapping.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 186 KiB |
Loading…
Reference in New Issue
Block a user