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
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