From: Jukka Kurkela Date: Thu, 18 Mar 2021 11:37:03 +0000 (+0200) Subject: Add textAlign for legend labels (#8665) X-Git-Tag: v3.0.0-rc~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=97136d0cbfa245752adf0b3f6317f6333b3e2ad7;p=thirdparty%2FChart.js.git Add textAlign for legend labels (#8665) * Add textAlign for legend labels * Update tests --- diff --git a/docs/docs/configuration/legend.md b/docs/docs/configuration/legend.md index e7c4f101e..750b7f772 100644 --- a/docs/docs/configuration/legend.md +++ b/docs/docs/configuration/legend.md @@ -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 diff --git a/src/helpers/helpers.extras.js b/src/helpers/helpers.extras.js index 703fbf208..89ffa418c 100644 --- a/src/helpers/helpers.extras.js +++ b/src/helpers/helpers.extras.js @@ -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; diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index 5cbe41215..7913d8fa2 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -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 diff --git a/test/fixtures/plugin.legend/label-textAlign/center.js b/test/fixtures/plugin.legend/label-textAlign/center.js new file mode 100644 index 000000000..e46fbfdc6 --- /dev/null +++ b/test/fixtures/plugin.legend/label-textAlign/center.js @@ -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 + } + } +}; diff --git a/test/fixtures/plugin.legend/label-textAlign/center.png b/test/fixtures/plugin.legend/label-textAlign/center.png new file mode 100644 index 000000000..e0fb8ff11 Binary files /dev/null and b/test/fixtures/plugin.legend/label-textAlign/center.png differ diff --git a/test/fixtures/plugin.legend/label-textAlign/left.js b/test/fixtures/plugin.legend/label-textAlign/left.js new file mode 100644 index 000000000..c587f9385 --- /dev/null +++ b/test/fixtures/plugin.legend/label-textAlign/left.js @@ -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 + } + } +}; diff --git a/test/fixtures/plugin.legend/label-textAlign/left.png b/test/fixtures/plugin.legend/label-textAlign/left.png new file mode 100644 index 000000000..fd737d28c Binary files /dev/null and b/test/fixtures/plugin.legend/label-textAlign/left.png differ diff --git a/test/fixtures/plugin.legend/label-textAlign/right.js b/test/fixtures/plugin.legend/label-textAlign/right.js new file mode 100644 index 000000000..b745f9624 --- /dev/null +++ b/test/fixtures/plugin.legend/label-textAlign/right.js @@ -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 + } + } +}; diff --git a/test/fixtures/plugin.legend/label-textAlign/right.png b/test/fixtures/plugin.legend/label-textAlign/right.png new file mode 100644 index 000000000..cf5feff64 Binary files /dev/null and b/test/fixtures/plugin.legend/label-textAlign/right.png differ diff --git a/test/fixtures/plugin.legend/label-textAlign/rtl-center.js b/test/fixtures/plugin.legend/label-textAlign/rtl-center.js new file mode 100644 index 000000000..dd86154bf --- /dev/null +++ b/test/fixtures/plugin.legend/label-textAlign/rtl-center.js @@ -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 + } + } +}; diff --git a/test/fixtures/plugin.legend/label-textAlign/rtl-center.png b/test/fixtures/plugin.legend/label-textAlign/rtl-center.png new file mode 100644 index 000000000..10d42be4b Binary files /dev/null and b/test/fixtures/plugin.legend/label-textAlign/rtl-center.png differ diff --git a/test/fixtures/plugin.legend/label-textAlign/rtl-left.js b/test/fixtures/plugin.legend/label-textAlign/rtl-left.js new file mode 100644 index 000000000..96c0bcdf5 --- /dev/null +++ b/test/fixtures/plugin.legend/label-textAlign/rtl-left.js @@ -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 + } + } +}; diff --git a/test/fixtures/plugin.legend/label-textAlign/rtl-left.png b/test/fixtures/plugin.legend/label-textAlign/rtl-left.png new file mode 100644 index 000000000..80789d65f Binary files /dev/null and b/test/fixtures/plugin.legend/label-textAlign/rtl-left.png differ diff --git a/test/fixtures/plugin.legend/label-textAlign/rtl-right.js b/test/fixtures/plugin.legend/label-textAlign/rtl-right.js new file mode 100644 index 000000000..a2f342af7 --- /dev/null +++ b/test/fixtures/plugin.legend/label-textAlign/rtl-right.js @@ -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 + } + } +}; diff --git a/test/fixtures/plugin.legend/label-textAlign/rtl-right.png b/test/fixtures/plugin.legend/label-textAlign/rtl-right.png new file mode 100644 index 000000000..294d61dd6 Binary files /dev/null and b/test/fixtures/plugin.legend/label-textAlign/rtl-right.png differ diff --git a/test/specs/global.defaults.tests.js b/test/specs/global.defaults.tests.js index ddf21e787..b7fa9c8a2 100644 --- a/test/specs/global.defaults.tests.js +++ b/test/specs/global.defaults.tests.js @@ -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); diff --git a/test/specs/plugin.legend.tests.js b/test/specs/plugin.legend.tests.js index 096a002d6..7f0c26f60 100644 --- a/test/specs/plugin.legend.tests.js +++ b/test/specs/plugin.legend.tests.js @@ -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 }]); }); diff --git a/types/index.esm.d.ts b/types/index.esm.d.ts index 2d3f4e2cb..65b1d2b88 100644 --- a/types/index.esm.d.ts +++ b/types/index.esm.d.ts @@ -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