]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Add textAlign for legend labels (#8665)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Thu, 18 Mar 2021 11:37:03 +0000 (13:37 +0200)
committerGitHub <noreply@github.com>
Thu, 18 Mar 2021 11:37:03 +0000 (07:37 -0400)
* Add textAlign for legend labels

* Update tests

18 files changed:
docs/docs/configuration/legend.md
src/helpers/helpers.extras.js
src/plugins/plugin.legend.js
test/fixtures/plugin.legend/label-textAlign/center.js [new file with mode: 0644]
test/fixtures/plugin.legend/label-textAlign/center.png [new file with mode: 0644]
test/fixtures/plugin.legend/label-textAlign/left.js [new file with mode: 0644]
test/fixtures/plugin.legend/label-textAlign/left.png [new file with mode: 0644]
test/fixtures/plugin.legend/label-textAlign/right.js [new file with mode: 0644]
test/fixtures/plugin.legend/label-textAlign/right.png [new file with mode: 0644]
test/fixtures/plugin.legend/label-textAlign/rtl-center.js [new file with mode: 0644]
test/fixtures/plugin.legend/label-textAlign/rtl-center.png [new file with mode: 0644]
test/fixtures/plugin.legend/label-textAlign/rtl-left.js [new file with mode: 0644]
test/fixtures/plugin.legend/label-textAlign/rtl-left.png [new file with mode: 0644]
test/fixtures/plugin.legend/label-textAlign/rtl-right.js [new file with mode: 0644]
test/fixtures/plugin.legend/label-textAlign/rtl-right.png [new file with mode: 0644]
test/specs/global.defaults.tests.js
test/specs/plugin.legend.tests.js
types/index.esm.d.ts

index e7c4f101e3dc6193ad7b12bef292859a48803315..750b7f7728a4fe894d7862993aeb08eec23eb292 100644 (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
index 703fbf208d483886775daf5ccf678997eafc1811..89ffa418c57af0ee6b004ac361622363dd20bd31 100644 (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;
index 5cbe412152047366ffabb98b4cdcc0bc14ae5ec2..7913d8fa2589f765780349d785b76fc821e4d4a0 100644 (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
diff --git a/test/fixtures/plugin.legend/label-textAlign/center.js b/test/fixtures/plugin.legend/label-textAlign/center.js
new file mode 100644 (file)
index 0000000..e46fbfd
--- /dev/null
@@ -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 (file)
index 0000000..e0fb8ff
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 (file)
index 0000000..c587f93
--- /dev/null
@@ -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 (file)
index 0000000..fd737d2
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 (file)
index 0000000..b745f96
--- /dev/null
@@ -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 (file)
index 0000000..cf5feff
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 (file)
index 0000000..dd86154
--- /dev/null
@@ -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 (file)
index 0000000..10d42be
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 (file)
index 0000000..96c0bcd
--- /dev/null
@@ -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 (file)
index 0000000..80789d6
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 (file)
index 0000000..a2f342a
--- /dev/null
@@ -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 (file)
index 0000000..294d61d
Binary files /dev/null and b/test/fixtures/plugin.legend/label-textAlign/rtl-right.png differ
index ddf21e78755ca79959cea7c4602a6d7d4b824629..b7fa9c8a27624893be80900ccdbacd29c53daeef 100644 (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);
index 096a002d63cf8651a85cb5c4decdba0f79c9a3c5..7f0c26f604f18e92ef12c5befc0f16eba2dd9a28 100644 (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
     }]);
   });
index 2d3f4e2cba75160dbcbe3ab7e88d1519809257e3..65b1d2b88ceffa7a851f30c2b59b51d24c3a6017 100644 (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