]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
FIX: render multiline legend items without overlapping (#10532) (#10641)
authorKartik <58913047+kartik-madhak@users.noreply.github.com>
Fri, 16 Sep 2022 13:46:53 +0000 (19:16 +0530)
committerGitHub <noreply@github.com>
Fri, 16 Sep 2022 13:46:53 +0000 (09:46 -0400)
* FIX: render multiline legend items without overlapping (#10532)

Co-authored-by: Nirav Chavda <nmchavda99@gmail.com>
* CLN: Extract method to fix codeclimate line count

Co-authored-by: Nirav Chavda <nmchavda99@gmail.com>
* CLN: Shift helper methods from class to module scope

Co-authored-by: Nirav Chavda <nmchavda99@gmail.com>
* TST: Add test with fixtures

Co-authored-by: kartik <codebull707@gmail.com>
* FIX: Fix test case for multiline label

Co-authored-by: kartik <codebull707@gmail.com>
* 10532-ENH: Calculate legend item width for multiline labels

Co-authored-by: Nirav Chavda <nmchavda99@gmail.com>
* 10532-TST: use spriteText and non-empty labels for test

Co-authored-by: Nirav Chavda <nmchavda99@gmail.com>
* 10532-FIX: failing test case due to legendItem.text being undefined

Co-authored-by: Nirav Chavda <nmchavda99@gmail.com>
* 10532-FIX: Update compression size

Co-authored-by: kartik <codebull707@gmail.com>
Co-authored-by: Nirav Chavda <nmchavda99@gmail.com>
.size-limit.cjs
src/plugins/plugin.legend.js
test/fixtures/plugin.legend/legend-doughnut-right-center-mulitiline-labels.json [new file with mode: 0644]
test/fixtures/plugin.legend/legend-doughnut-right-center-mulitiline-labels.png [new file with mode: 0644]
test/specs/plugin.legend.tests.js

index c9b0b3fef144745eddd6a6d031f588b4a336ee82..11673e8836dd49ae2bad0220c810d9b96d58ff38 100644 (file)
@@ -34,7 +34,7 @@ module.exports = [
   },
   {
     path: 'dist/chart.js',
-    limit: '27.1 KB',
+    limit: '27.5 KB',
     import: '{ Decimation, Filler, Legend, SubTitle, Title, Tooltip }',
     running: false,
     modifyWebpackConfig
index a1045efde5fc57214a1326691c1dff934faf8be7..573c07755982753d65c846426303e10a5de33741 100644 (file)
@@ -3,12 +3,20 @@ import Element from '../core/core.element';
 import layouts from '../core/core.layouts';
 import {addRoundedRectPath, drawPointLegend, renderText} from '../helpers/helpers.canvas';
 import {
-  callback as call, valueOrDefault, toFont,
-  toPadding, getRtlAdapter, overrideTextDirection, restoreTextDirection,
-  clipArea, unclipArea, _isBetween
+  _isBetween,
+  callback as call,
+  clipArea,
+  getRtlAdapter,
+  overrideTextDirection,
+  restoreTextDirection,
+  toFont,
+  toPadding,
+  unclipArea,
+  valueOrDefault,
 } from '../helpers/index';
-import {_toLeftRightCenter, _alignStartEnd, _textX} from '../helpers/helpers.extras';
+import {_alignStartEnd, _textX, _toLeftRightCenter} from '../helpers/helpers.extras';
 import {toTRBLCorners} from '../helpers/helpers.options';
+
 /**
  * @typedef { import("../../types").ChartEvent } ChartEvent
  */
@@ -139,7 +147,7 @@ export class Legend extends Element {
       height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
     } else {
       height = this.maxHeight; // fill all the height
-      width = this._fitCols(titleHeight, fontSize, boxWidth, itemHeight) + 10;
+      width = this._fitCols(titleHeight, labelFont, boxWidth, itemHeight) + 10;
     }
 
     this.width = Math.min(width, options.maxWidth || this.maxWidth);
@@ -180,7 +188,7 @@ export class Legend extends Element {
     return totalHeight;
   }
 
-  _fitCols(titleHeight, fontSize, boxWidth, itemHeight) {
+  _fitCols(titleHeight, labelFont, boxWidth, _itemHeight) {
     const {ctx, maxHeight, options: {labels: {padding}}} = this;
     const hitboxes = this.legendHitBoxes = [];
     const columnSizes = this.columnSizes = [];
@@ -194,7 +202,7 @@ export class Legend extends Element {
     let col = 0;
 
     this.legendItems.forEach((legendItem, i) => {
-      const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
+      const {itemWidth, itemHeight} = calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight);
 
       // If too tall, go to new column
       if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {
@@ -418,6 +426,9 @@ export class Legend extends Element {
 
       if (isHorizontal) {
         cursor.x += width + padding;
+      } else if (typeof legendItem.text !== 'string') {
+        const fontLineHeight = labelFont.lineHeight;
+        cursor.y += calculateLegendItemHeight(legendItem, fontLineHeight);
       } else {
         cursor.y += lineHeight;
       }
@@ -541,6 +552,33 @@ export class Legend extends Element {
   }
 }
 
+function calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight) {
+  const itemWidth = calculateItemWidth(legendItem, boxWidth, labelFont, ctx);
+  const itemHeight = calculateItemHeight(_itemHeight, legendItem, labelFont.lineHeight);
+  return {itemWidth, itemHeight};
+}
+
+function calculateItemWidth(legendItem, boxWidth, labelFont, ctx) {
+  let legendItemText = legendItem.text;
+  if (legendItemText && typeof legendItemText !== 'string') {
+    legendItemText = legendItemText.reduce((a, b) => a.length > b.length ? a : b);
+  }
+  return boxWidth + (labelFont.size / 2) + ctx.measureText(legendItemText).width;
+}
+
+function calculateItemHeight(_itemHeight, legendItem, fontLineHeight) {
+  let itemHeight = _itemHeight;
+  if (typeof legendItem.text !== 'string') {
+    itemHeight = calculateLegendItemHeight(legendItem, fontLineHeight);
+  }
+  return itemHeight;
+}
+
+function calculateLegendItemHeight(legendItem, fontLineHeight) {
+  const labelHeight = legendItem.text ? legendItem.text.length + 0.5 : 0;
+  return fontLineHeight * labelHeight;
+}
+
 function isListened(type, opts) {
   if ((type === 'mousemove' || type === 'mouseout') && (opts.onHover || opts.onLeave)) {
     return true;
diff --git a/test/fixtures/plugin.legend/legend-doughnut-right-center-mulitiline-labels.json b/test/fixtures/plugin.legend/legend-doughnut-right-center-mulitiline-labels.json
new file mode 100644 (file)
index 0000000..4c27159
--- /dev/null
@@ -0,0 +1,28 @@
+{
+       "config": {
+               "type": "doughnut",
+               "data": {
+                       "labels": ["Example Label", ["I like these colors", "Red", "Green", "Blue", "Yellow"], "Example Label", "Example Label", "Example Label"],
+                       "datasets": [{
+                               "data": [10, 20, 30, 40, 50],
+                               "backgroundColor": "#00ff00",
+                               "borderWidth": 0
+                       }]
+               },
+               "options": {
+                       "plugins": {
+                               "legend": {
+                                       "position": "right",
+                                       "align": "center"
+                               }
+                       }
+               }
+       },
+       "options": {
+    "spriteText": true,
+               "canvas": {
+                       "height": 256,
+                       "width": 512
+               }
+       }
+}
diff --git a/test/fixtures/plugin.legend/legend-doughnut-right-center-mulitiline-labels.png b/test/fixtures/plugin.legend/legend-doughnut-right-center-mulitiline-labels.png
new file mode 100644 (file)
index 0000000..6be6973
Binary files /dev/null and b/test/fixtures/plugin.legend/legend-doughnut-right-center-mulitiline-labels.png differ
index aa1b65ec82ec98ed54d24dbeeb19e6ed02183b2c..e0bed42c2635f368bfdfab347adf50f39546ab59 100644 (file)
@@ -507,6 +507,70 @@ describe('Legend block tests', function() {
     });
   });
 
+  it('should draw legend with multiline labels', function() {
+    const chart = window.acquireChart({
+      type: 'doughnut',
+      data: {
+        labels: [
+          'ABCDE',
+          [
+            'ABCDE',
+            'ABCDE',
+          ],
+          [
+            'Some Text',
+            'Some Text',
+            'Some Text',
+          ],
+          'ABCDE',
+        ],
+        datasets: [
+          {
+            label: 'test',
+            data: [
+              73.42,
+              18.13,
+              7.54,
+              0.9,
+              0.0025,
+              1.8e-5,
+            ],
+            backgroundColor: [
+              '#0078C2',
+              '#56CAF5',
+              '#B1E3F9',
+              '#FBBC8D',
+              '#F6A3BE',
+              '#4EC2C1',
+            ],
+          },
+        ],
+      },
+      options: {
+        plugins: {
+          legend: {
+            labels: {
+              usePointStyle: true,
+              pointStyle: 'rect',
+            },
+            position: 'right',
+            align: 'center',
+            maxWidth: 860,
+          },
+        },
+        aspectRatio: 3,
+      },
+    });
+
+    // Check some basic assertions about the test setup
+    expect(chart.legend.legendHitBoxes.length).toBe(4);
+
+    // Check whether any legend items reach outside the established bounds
+    chart.legend.legendHitBoxes.forEach(function(item) {
+      expect(item.left + item.width).toBeLessThanOrEqual(chart.width);
+    });
+  });
+
   it('should draw items with a custom boxHeight', function() {
     var chart = window.acquireChart(
       {