Add textAlign for legend labels (#8665)

* Add textAlign for legend labels

* Update tests
This commit is contained in:
Jukka Kurkela 2021-03-18 13:37:03 +02:00 committed by GitHub
parent f10b510890
commit 97136d0cbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 248 additions and 15 deletions

View File

@ -62,6 +62,7 @@ Namespace: `options.plugins.legend.labels`
| `filter` | `function` | `null` | Filters legend items out of the legend. Receives 2 parameters, a [Legend Item](#legend-item-interface) and the chart data.
| `sort` | `function` | `null` | Sorts legend items. Receives 3 parameters, two [Legend Items](#legend-item-interface) and the chart data.
| `pointStyle` | | | If specified, this style of point is used for the legend. Only used if `usePointStyle` is true.
| `textAlign` | `string` | `'center'` | Horizontal alignment of the label text. Options are: `'left'`, `'right'` or `'center'`.
| `usePointStyle` | `boolean` | `false` | Label style will match corresponding point style (size is based on the minimum value between boxWidth and font.size).
## Legend Title Configuration

View File

@ -68,10 +68,19 @@ export function debounce(fn, delay) {
export const _toLeftRightCenter = (align) => align === 'start' ? 'left' : align === 'end' ? 'right' : 'center';
/**
* Returns `start`, `end` or `(start + end) / 2` depending on `align`
* Returns `start`, `end` or `(start + end) / 2` depending on `align`. Defaults to `center`
* @param {string} align start, end, center
* @param {number} start value for start
* @param {number} end value for end
* @private
*/
export const _alignStartEnd = (align, start, end) => align === 'start' ? start : align === 'end' ? end : (start + end) / 2;
/**
* Returns `left`, `right` or `(left + right) / 2` depending on `align`. Defaults to `left`
* @param {string} align start, end, center
* @param {number} left value for start
* @param {number} right value for end
* @private
*/
export const _textX = (align, left, right) => align === 'right' ? right : align === 'center' ? (left + right) / 2 : left;

View File

@ -3,11 +3,11 @@ import Element from '../core/core.element';
import layouts from '../core/core.layouts';
import {drawPoint, renderText} from '../helpers/helpers.canvas';
import {
callback as call, valueOrDefault, toFont, isObject,
callback as call, valueOrDefault, toFont,
toPadding, getRtlAdapter, overrideTextDirection, restoreTextDirection,
clipArea, unclipArea
} from '../helpers/index';
import {_toLeftRightCenter, _alignStartEnd} from '../helpers/helpers.extras';
import {_toLeftRightCenter, _alignStartEnd, _textX} from '../helpers/helpers.extras';
/**
* @typedef { import("../platform/platform.base").ChartEvent } ChartEvent
*/
@ -244,6 +244,7 @@ export class Legend extends Element {
const labelFont = toFont(labelOpts.font);
const {color: fontColor, padding} = labelOpts;
const fontSize = labelFont.size;
const halfFontSize = fontSize / 2;
let cursor;
me.drawTitle();
@ -287,7 +288,7 @@ export class Legend extends Element {
borderWidth: lineWidth
};
const centerX = rtlHelper.xPlus(x, boxWidth / 2);
const centerY = y + fontSize / 2;
const centerY = y + halfFontSize;
// Draw pointStyle as legend symbol
drawPoint(ctx, drawOptions, centerX, centerY);
@ -306,9 +307,10 @@ export class Legend extends Element {
};
const fillText = function(x, y, legendItem) {
const halfFontSize = fontSize / 2;
const xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize);
renderText(ctx, legendItem.text, xLeft, y + (itemHeight / 2), labelFont, {strikethrough: legendItem.hidden});
renderText(ctx, legendItem.text, x, y + (itemHeight / 2), labelFont, {
strikethrough: legendItem.hidden,
textAlign: legendItem.textAlign
});
};
// Horizontal
@ -333,6 +335,7 @@ export class Legend extends Element {
const lineHeight = itemHeight + padding;
me.legendItems.forEach((legendItem, i) => {
const textWidth = ctx.measureText(legendItem.text).width;
const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
const width = boxWidth + (fontSize / 2) + textWidth;
let x = cursor.x;
let y = cursor.y;
@ -358,8 +361,10 @@ export class Legend extends Element {
legendHitBoxes[i].left = rtlHelper.leftForLtr(realX, legendHitBoxes[i].width);
legendHitBoxes[i].top = y;
x = _textX(textAlign, x + boxWidth + halfFontSize, me.right);
// Fill the actual label
fillText(realX, y, legendItem);
fillText(rtlHelper.x(x), y, legendItem);
if (isHorizontal) {
cursor.x += width + padding;
@ -577,13 +582,11 @@ export default {
// lineWidth :
generateLabels(chart) {
const datasets = chart.data.datasets;
const {labels} = chart.legend.options;
const usePointStyle = labels.usePointStyle;
const overrideStyle = labels.pointStyle;
const {labels: {usePointStyle, pointStyle, textAlign}} = chart.legend.options;
return chart._getSortedDatasetMetas().map((meta) => {
const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
const borderWidth = isObject(style.borderWidth) ? (valueOrDefault(style.borderWidth.top, 0) + valueOrDefault(style.borderWidth.left, 0) + valueOrDefault(style.borderWidth.bottom, 0) + valueOrDefault(style.borderWidth.right, 0)) / 4 : style.borderWidth;
const borderWidth = toPadding(style.borderWidth);
return {
text: datasets[meta.index].label,
@ -593,10 +596,11 @@ export default {
lineDash: style.borderDash,
lineDashOffset: style.borderDashOffset,
lineJoin: style.borderJoinStyle,
lineWidth: borderWidth,
lineWidth: (borderWidth.width + borderWidth.height) / 4,
strokeStyle: style.borderColor,
pointStyle: overrideStyle || style.pointStyle,
pointStyle: pointStyle || style.pointStyle,
rotation: style.rotation,
textAlign: textAlign || style.textAlign,
// Below is extra data used for toggling the datasets
datasetIndex: meta.index

View File

@ -0,0 +1,30 @@
module.exports = {
config: {
type: 'pie',
data: {
labels: ['aaaa', 'bb', 'c'],
datasets: [
{
data: [1, 2, 3]
}
]
},
options: {
plugins: {
legend: {
position: 'right',
labels: {
textAlign: 'center'
}
}
}
}
},
options: {
spriteText: true,
canvas: {
width: 256,
height: 256
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,30 @@
module.exports = {
config: {
type: 'pie',
data: {
labels: ['aaaa', 'bb', 'c'],
datasets: [
{
data: [1, 2, 3]
}
]
},
options: {
plugins: {
legend: {
position: 'right',
labels: {
textAlign: 'left'
}
}
}
}
},
options: {
spriteText: true,
canvas: {
width: 256,
height: 256
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,30 @@
module.exports = {
config: {
type: 'pie',
data: {
labels: ['aaaa', 'bb', 'c'],
datasets: [
{
data: [1, 2, 3]
}
]
},
options: {
plugins: {
legend: {
position: 'right',
labels: {
textAlign: 'right'
}
}
}
}
},
options: {
spriteText: true,
canvas: {
width: 256,
height: 256
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,31 @@
module.exports = {
config: {
type: 'pie',
data: {
labels: ['aaaa', 'bb', 'c'],
datasets: [
{
data: [1, 2, 3]
}
]
},
options: {
plugins: {
legend: {
position: 'right',
rtl: true,
labels: {
textAlign: 'center'
}
}
}
}
},
options: {
spriteText: true,
canvas: {
width: 256,
height: 256
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,31 @@
module.exports = {
config: {
type: 'pie',
data: {
labels: ['aaaa', 'bb', 'c'],
datasets: [
{
data: [1, 2, 3]
}
]
},
options: {
plugins: {
legend: {
position: 'right',
rtl: true,
labels: {
textAlign: 'left'
}
}
}
}
},
options: {
spriteText: true,
canvas: {
width: 256,
height: 256
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,31 @@
module.exports = {
config: {
type: 'pie',
data: {
labels: ['aaaa', 'bb', 'c'],
datasets: [
{
data: [1, 2, 3]
}
]
},
options: {
plugins: {
legend: {
rtl: true,
position: 'right',
labels: {
textAlign: 'right'
}
}
}
}
},
options: {
spriteText: true,
canvas: {
width: 256,
height: 256
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -102,6 +102,7 @@ describe('Default Configs', function() {
hidden: false,
index: 0,
strokeStyle: '#000',
textAlign: undefined,
lineWidth: 2
}, {
text: 'label2',
@ -109,6 +110,7 @@ describe('Default Configs', function() {
hidden: false,
index: 1,
strokeStyle: '#000',
textAlign: undefined,
lineWidth: 2
}, {
text: 'label3',
@ -116,6 +118,7 @@ describe('Default Configs', function() {
hidden: false,
index: 2,
strokeStyle: '#000',
textAlign: undefined,
lineWidth: 2
}];
expect(chart.legend.legendItems).toEqual(expected);
@ -193,6 +196,7 @@ describe('Default Configs', function() {
hidden: false,
index: 0,
strokeStyle: '#000',
textAlign: undefined,
lineWidth: 2
}, {
text: 'label2',
@ -200,6 +204,7 @@ describe('Default Configs', function() {
hidden: false,
index: 1,
strokeStyle: '#000',
textAlign: undefined,
lineWidth: 2
}, {
text: 'label3',
@ -207,6 +212,7 @@ describe('Default Configs', function() {
hidden: false,
index: 2,
strokeStyle: '#000',
textAlign: undefined,
lineWidth: 2
}];
expect(chart.legend.legendItems).toEqual(expected);

View File

@ -71,6 +71,7 @@ describe('Legend block tests', function() {
strokeStyle: 'rgba(0,0,0,0.1)',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 0
}, {
text: 'dataset2',
@ -84,6 +85,7 @@ describe('Legend block tests', function() {
strokeStyle: 'rgba(0,0,0,0.1)',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 1
}, {
text: 'dataset3',
@ -97,6 +99,7 @@ describe('Legend block tests', function() {
strokeStyle: 'green',
pointStyle: 'crossRot',
rotation: undefined,
textAlign: undefined,
datasetIndex: 2
}]);
});
@ -141,6 +144,7 @@ describe('Legend block tests', function() {
strokeStyle: 'rgba(0,0,0,0.1)',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 0
}, {
text: 'dataset2',
@ -154,6 +158,7 @@ describe('Legend block tests', function() {
strokeStyle: 'rgba(0,0,0,0.1)',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 1
}, {
text: 'dataset3',
@ -167,6 +172,7 @@ describe('Legend block tests', function() {
strokeStyle: 'green',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 2
}]);
});
@ -218,6 +224,7 @@ describe('Legend block tests', function() {
strokeStyle: 'green',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 2
}, {
text: 'dataset2',
@ -231,6 +238,7 @@ describe('Legend block tests', function() {
strokeStyle: 'rgba(0,0,0,0.1)',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 1
}, {
text: 'dataset1',
@ -244,6 +252,7 @@ describe('Legend block tests', function() {
strokeStyle: 'rgba(0,0,0,0.1)',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 0
}]);
});
@ -300,6 +309,7 @@ describe('Legend block tests', function() {
strokeStyle: 'rgba(0,0,0,0.1)',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 0
}, {
text: 'dataset3',
@ -313,6 +323,7 @@ describe('Legend block tests', function() {
strokeStyle: 'green',
pointStyle: 'crossRot',
rotation: undefined,
textAlign: undefined,
datasetIndex: 2
}]);
});
@ -368,6 +379,7 @@ describe('Legend block tests', function() {
strokeStyle: 'green',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 2
}, {
text: 'dataset2',
@ -381,6 +393,7 @@ describe('Legend block tests', function() {
strokeStyle: 'rgba(0,0,0,0.1)',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 1
}, {
text: 'dataset1',
@ -394,6 +407,7 @@ describe('Legend block tests', function() {
strokeStyle: 'rgba(0,0,0,0.1)',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 0
}]);
});
@ -524,6 +538,7 @@ describe('Legend block tests', function() {
strokeStyle: 'red',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 0
}]);
});
@ -565,6 +580,7 @@ describe('Legend block tests', function() {
strokeStyle: 'rgb(205, 0, 0)',
pointStyle: undefined,
rotation: undefined,
textAlign: undefined,
datasetIndex: 0
}]);
});
@ -621,6 +637,7 @@ describe('Legend block tests', function() {
strokeStyle: 'green',
pointStyle: 'crossRot',
rotation: 0,
textAlign: undefined,
datasetIndex: 0
}, {
text: 'dataset2',
@ -634,6 +651,7 @@ describe('Legend block tests', function() {
strokeStyle: '#f31',
pointStyle: 'crossRot',
rotation: 15,
textAlign: undefined,
datasetIndex: 1
}]);
});
@ -691,6 +709,7 @@ describe('Legend block tests', function() {
strokeStyle: 'green',
pointStyle: 'star',
rotation: 0,
textAlign: undefined,
datasetIndex: 0
}, {
text: 'dataset2',
@ -704,6 +723,7 @@ describe('Legend block tests', function() {
strokeStyle: '#f31',
pointStyle: 'star',
rotation: 15,
textAlign: undefined,
datasetIndex: 1
}]);
});

12
types/index.esm.d.ts vendored
View File

@ -2057,6 +2057,11 @@ export interface LegendItem {
* Rotation of the point in degrees (only used if usePointStyle is true)
*/
rotation?: number;
/**
* Text alignment
*/
textAlign?: TextAlign;
}
export interface LegendElement extends Element, LayoutItem {}
@ -2076,7 +2081,7 @@ export interface LegendOptions {
* Alignment of the legend.
* @default 'center'
*/
align: TextAlign;
align: 'start' | 'center' | 'end';
/**
* Marks that this box should take the full width/height of the canvas (moving other boxes). This is unlikely to need to be changed in day-to-day use.
* @default true
@ -2146,6 +2151,11 @@ export interface LegendOptions {
*/
pointStyle: PointStyle;
/**
* Text alignment
*/
textAlign?: TextAlign;
/**
* Label style will match corresponding point style (size is based on the minimum value between boxWidth and font.size).
* @default false