]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Rename cutoutPercentage to cutout + chores (#8514)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Wed, 24 Feb 2021 21:58:01 +0000 (23:58 +0200)
committerGitHub <noreply@github.com>
Wed, 24 Feb 2021 21:58:01 +0000 (16:58 -0500)
13 files changed:
docs/docs/charts/doughnut.mdx
docs/docs/getting-started/v3-migration.md
samples/scriptable/pie.html
src/controllers/controller.doughnut.js
src/controllers/controller.pie.js
src/helpers/helpers.core.js
test/fixtures/controller.doughnut/doughnut-outer-radius-percent.js
test/fixtures/controller.doughnut/doughnut-outer-radius-pixels.js
test/fixtures/controller.doughnut/doughnut-rotation-circumference-8x8.js [new file with mode: 0644]
test/fixtures/controller.doughnut/doughnut-rotation-circumference-8x8.png [new file with mode: 0644]
test/specs/controller.doughnut.tests.js
types/index.esm.d.ts
types/tests/controllers/doughnut_outer_radius.ts

index 63abd025f90139516f8028f93fdb4de0fee44e48..d34e296b010592d574d3d4ca751839eb5d24cf03 100644 (file)
@@ -6,7 +6,7 @@ Pie and doughnut charts are probably the most commonly used charts. They are div
 
 They are excellent at showing the relational proportions between data.
 
-Pie and doughnut charts are effectively the same class in Chart.js, but have one different default value - their `cutoutPercentage`. This equates to what percentage of the inner should be cut out. This defaults to `0` for pie charts, and `50` for doughnuts.
+Pie and doughnut charts are effectively the same class in Chart.js, but have one different default value - their `cutout`. This equates to what portion of the inner should be cut out. This defaults to `0` for pie charts, and `'50%'` for doughnuts.
 
 They are also registered under two aliases in the `Chart` core. Other than their different default value, and different alias, they are exactly the same.
 
@@ -163,8 +163,8 @@ These are the customisation options specific to Pie & Doughnut charts. These opt
 
 | Name | Type | Default | Description
 | ---- | ---- | ------- | -----------
-| `cutoutPercentage` | `number` | `50` - for doughnut, `0` - for pie | The percentage of the chart that is cut out of the middle.
-| `outerRadius` | `number`\|`string` | `100%` | The outer radius of the chart. If `string` and ending with '%', percentage of the maximum radius. `number` is considered to be pixels.
+| `cutout` | `number`\|`string` | `50%` - for doughnut, `0` - for pie | The portion of the chart that is cut out of the middle. If `string` and ending with '%', percentage of the chart radius. `number` is considered to be pixels.
+| `radius` | `number`\|`string` | `100%` | The outer radius of the chart. If `string` and ending with '%', percentage of the maximum radius. `number` is considered to be pixels.
 | `rotation` | `number` | 0 | Starting angle to draw arcs from.
 | `circumference` | `number` | 360 | Sweep to allow arcs to cover.
 | `animation.animateRotate` | `boolean` | `true` | If true, the chart will animate in with a rotation animation. This property is in the `options.animation` object.
@@ -172,7 +172,7 @@ These are the customisation options specific to Pie & Doughnut charts. These opt
 
 ## Default Options
 
-We can also change these default values for each Doughnut type that is created, this object is available at `Chart.defaults.controllers.doughnut`. Pie charts also have a clone of these defaults available to change at `Chart.defaults.controllers.pie`, with the only difference being `cutoutPercentage` being set to 0.
+We can also change these default values for each Doughnut type that is created, this object is available at `Chart.defaults.controllers.doughnut`. Pie charts also have a clone of these defaults available to change at `Chart.defaults.controllers.pie`, with the only difference being `cutout` being set to 0.
 
 ## Data Structure
 
index 332779e9a34817a9fb387ccab5c0da760ff23dec..6b9137911cf71db60c4c43b64a5fc40eac754f6f 100644 (file)
@@ -77,6 +77,7 @@ A number of changes were made to the configuration options passed to the `Chart`
 * Polar area `startAngle` option is now consistent with `Radar`, 0 is at top and value is in degrees. Default is changed from `-½π` to  `0`.
 * Doughnut `rotation` option is now in degrees and 0 is at top. Default is changed from `-½π` to  `0`.
 * Doughnut `circumference` option is now in degrees. Default is changed from `2π` to `360`.
+* Doughnut `cutoutPercentage` was renamed to `cutout`and accepts pixels as numer and percent as string ending with `%`.
 * `scale` option was removed in favor of `options.scales.r` (or any other scale id, with `axis: 'r'`)
 * `scales.[x/y]Axes` arrays were removed. Scales are now configured directly to `options.scales` object with the object key being the scale Id.
 * `scales.[x/y]Axes.barPercentage` was moved to dataset option `barPercentage`
index 2b5b26c619bc99f9b4545442848ff2aa054ff49e..1cc10366e39a3deaa3648f519464943864893916 100644 (file)
 
                // eslint-disable-next-line no-unused-vars
                function togglePieDoughnut() {
-                       if (chart.options.cutoutPercentage) {
-                               chart.options.cutoutPercentage = 0;
+                       if (chart.options.cutout) {
+                               chart.options.cutout = 0;
                        } else {
-                               chart.options.cutoutPercentage = 50;
+                               chart.options.cutout = '50%';
                        }
                        chart.update();
                }
index 2f013c0cff87143383d5f737f7ca52b8108687e8..d2b75f32a97fb32bc6c6ab48a57b64e5a287cf12 100644 (file)
@@ -1,13 +1,12 @@
 import DatasetController from '../core/core.datasetController';
 import {formatNumber} from '../core/core.intl';
-import {isArray, numberOrPercentageOf, valueOrDefault} from '../helpers/helpers.core';
-import {toRadians, PI, TAU, HALF_PI} from '../helpers/helpers.math';
+import {isArray, toPercentage, toPixels, valueOrDefault} from '../helpers/helpers.core';
+import {toRadians, PI, TAU, HALF_PI, _angleBetween} from '../helpers/helpers.math';
 
 /**
  * @typedef { import("../core/core.controller").default } Chart
  */
 
-
 function getRatioAndOffset(rotation, circumference, cutout) {
   let ratioX = 1;
   let ratioY = 1;
@@ -15,21 +14,18 @@ function getRatioAndOffset(rotation, circumference, cutout) {
   let offsetY = 0;
   // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc
   if (circumference < TAU) {
-    let startAngle = rotation % TAU;
-    startAngle += startAngle >= PI ? -TAU : startAngle < -PI ? TAU : 0;
+    const startAngle = rotation;
     const endAngle = startAngle + circumference;
     const startX = Math.cos(startAngle);
     const startY = Math.sin(startAngle);
     const endX = Math.cos(endAngle);
     const endY = Math.sin(endAngle);
-    const contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= TAU;
-    const contains90 = (startAngle <= HALF_PI && endAngle >= HALF_PI) || endAngle >= TAU + HALF_PI;
-    const contains180 = startAngle === -PI || endAngle >= PI;
-    const contains270 = (startAngle <= -HALF_PI && endAngle >= -HALF_PI) || endAngle >= PI + HALF_PI;
-    const minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout);
-    const minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout);
-    const maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout);
-    const maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout);
+    const calcMax = (angle, a, b) => _angleBetween(angle, startAngle, endAngle) ? 1 : Math.max(a, a * cutout, b, b * cutout);
+    const calcMin = (angle, a, b) => _angleBetween(angle, startAngle, endAngle) ? -1 : Math.min(a, a * cutout, b, b * cutout);
+    const maxX = calcMax(0, startX, endX);
+    const maxY = calcMax(HALF_PI, startY, endY);
+    const minX = calcMin(PI, startX, endX);
+    const minY = calcMin(PI + HALF_PI, startY, endY);
     ratioX = (maxX - minX) / 2;
     ratioY = (maxY - minY) / 2;
     offsetX = -(maxX + minX) / 2;
@@ -127,7 +123,9 @@ export default class DoughnutController extends DatasetController {
     const {chartArea} = chart;
     const meta = me._cachedMeta;
     const arcs = meta.data;
-    const cutout = me.options.cutoutPercentage / 100 || 0;
+    const spacing = me.getMaxBorderWidth() + me.getMaxOffset(arcs);
+    const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
+    const cutout = Math.min(toPercentage(me.options.cutout, maxSize), 1);
     const chartWeight = me._getRingWeight(me.index);
 
     // Compute the maximal rotation & circumference limits.
@@ -135,11 +133,10 @@ export default class DoughnutController extends DatasetController {
     // are both less than a circle with different rotations (starting angles)
     const {circumference, rotation} = me._getRotationExtents();
     const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(rotation, circumference, cutout);
-    const spacing = me.getMaxBorderWidth() + me.getMaxOffset(arcs);
-    const maxWidth = (chartArea.right - chartArea.left - spacing) / ratioX;
-    const maxHeight = (chartArea.bottom - chartArea.top - spacing) / ratioY;
+    const maxWidth = (chartArea.width - spacing) / ratioX;
+    const maxHeight = (chartArea.height - spacing) / ratioY;
     const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
-    const outerRadius = numberOrPercentageOf(me.options.outerRadius, maxRadius);
+    const outerRadius = toPixels(me.options.radius, maxRadius);
     const innerRadius = Math.max(outerRadius * cutout, 0);
     const radiusLength = (outerRadius - innerRadius) / me._getVisibleDatasetWeightTotal();
     me.offsetX = offsetX * outerRadius;
@@ -345,7 +342,7 @@ DoughnutController.defaults = {
 
   datasets: {
     // The percentage of the chart that we cut out of the middle.
-    cutoutPercentage: 50,
+    cutout: '50%',
 
     // The rotation of the chart, where the first data arc begins.
     rotation: 0,
@@ -354,7 +351,7 @@ DoughnutController.defaults = {
     circumference: 360,
 
     // The outr radius of the chart
-    outerRadius: '100%'
+    radius: '100%'
   },
 
   indexAxis: 'r',
index f0162ef2d04269b7acc1b9f9ea47d4e8d3fbb07c..a53b8d5f9eda297507dceb00ec08e24094886b44 100644 (file)
@@ -22,6 +22,6 @@ PieController.defaults = {
     circumference: 360,
 
     // The outr radius of the chart
-    outerRadius: '100%'
+    radius: '100%'
   }
 };
index f670065c36f0152deda34fe789343187e5a7de38..78ce4740970b4d429c4b8338ff6198f59abc4296 100644 (file)
@@ -85,7 +85,12 @@ export function valueOrDefault(value, defaultValue) {
   return typeof value === 'undefined' ? defaultValue : value;
 }
 
-export const numberOrPercentageOf = (value, dimension) =>
+export const toPercentage = (value, dimension) =>
+  typeof value === 'string' && value.endsWith('%') ?
+    parseFloat(value) / 100
+    : value / dimension;
+
+export const toPixels = (value, dimension) =>
   typeof value === 'string' && value.endsWith('%') ?
     parseFloat(value) / 100 * dimension
     : +value;
index 28dd46da09b778cf70f0c6568beed08f54cd6faa..46cf4b7200b914ade9a00ad83e7499f385c880aa 100644 (file)
@@ -22,7 +22,7 @@ module.exports = {
       }]
     },
     options: {
-      outerRadius: '30%',
+      radius: '30%',
     }
   }
 };
index a4a50df2387a84f3c843a5ccc843e16d88ebb6ce..8fabaa935ac62362983227bbd7967502b857e88a 100644 (file)
@@ -22,7 +22,7 @@ module.exports = {
       }]
     },
     options: {
-      outerRadius: 150,
+      radius: 150,
     }
   }
 };
diff --git a/test/fixtures/controller.doughnut/doughnut-rotation-circumference-8x8.js b/test/fixtures/controller.doughnut/doughnut-rotation-circumference-8x8.js
new file mode 100644 (file)
index 0000000..4a0481e
--- /dev/null
@@ -0,0 +1,67 @@
+const canvas = document.createElement('canvas');
+canvas.width = 512;
+canvas.height = 512;
+const ctx = canvas.getContext('2d');
+
+module.exports = {
+  config: {
+    type: 'doughnut',
+    data: {
+      labels: ['A', 'B', 'C', 'D', 'E'],
+      datasets: [{
+        data: [1, 5, 10, 50, 100],
+        backgroundColor: [
+          'rgba(255, 99, 132, 0.8)',
+          'rgba(54, 162, 235, 0.8)',
+          'rgba(255, 206, 86, 0.8)',
+          'rgba(75, 192, 192, 0.8)',
+          'rgba(153, 102, 255, 0.8)'
+        ],
+        borderColor: [
+          'rgb(255, 99, 132)',
+          'rgb(54, 162, 235)',
+          'rgb(255, 206, 86)',
+          'rgb(75, 192, 192)',
+          'rgb(153, 102, 255)'
+        ]
+      }]
+    },
+    options: {
+      rotation: -360,
+      circumference: 180,
+      events: []
+    }
+  },
+  options: {
+    canvas: {
+      height: 512,
+      width: 512
+    },
+    run: function(chart) {
+      return new Promise((resolve) => {
+        for (let i = 0; i < 64; i++) {
+          const col = i % 8;
+          const row = Math.floor(i / 8);
+          const evenodd = row % 2 ? 1 : -1;
+          chart.options.rotation = col * 45 * evenodd;
+          chart.options.circumference = 360 - row * 45;
+          chart.update();
+          ctx.drawImage(chart.canvas, col * 64, row * 64, 64, 64);
+        }
+        ctx.strokeStyle = 'red';
+        ctx.lineWidth = 0.5;
+        ctx.beginPath();
+        for (let i = 1; i < 8; i++) {
+          ctx.moveTo(i * 64, 0);
+          ctx.lineTo(i * 64, 511);
+          ctx.moveTo(0, i * 64);
+          ctx.lineTo(511, i * 64);
+        }
+        ctx.stroke();
+        Chart.helpers.clearCanvas(chart.canvas);
+        chart.ctx.drawImage(canvas, 0, 0);
+        resolve();
+      });
+    }
+  }
+};
diff --git a/test/fixtures/controller.doughnut/doughnut-rotation-circumference-8x8.png b/test/fixtures/controller.doughnut/doughnut-rotation-circumference-8x8.png
new file mode 100644 (file)
index 0000000..ec18f13
Binary files /dev/null and b/test/fixtures/controller.doughnut/doughnut-rotation-circumference-8x8.png differ
index 9be7c86f20a907fbca1475a2fcc9ab1d4d25f994..c3bb1d9f2be5287443a1ac758998061b16f739d2 100644 (file)
@@ -70,7 +70,7 @@ describe('Chart.controllers.doughnut', function() {
           animateRotate: true,
           animateScale: false
         },
-        cutoutPercentage: 50,
+        cutout: '50%',
         rotation: 0,
         circumference: 360,
         elements: {
@@ -169,7 +169,7 @@ describe('Chart.controllers.doughnut', function() {
           legend: false,
           title: false,
         },
-        cutoutPercentage: 50,
+        cutout: '50%',
         rotation: 270,
         circumference: 90,
         elements: {
@@ -215,7 +215,7 @@ describe('Chart.controllers.doughnut', function() {
           legend: false,
           title: false
         },
-        cutoutPercentage: 50,
+        cutout: '50%',
         rotation: 270,
         circumference: 90,
         elements: {
@@ -325,7 +325,7 @@ describe('Chart.controllers.doughnut', function() {
           }]
         },
         options: {
-          cutoutPercentage: 0,
+          cutout: '50%',
           elements: {
             arc: {
               backgroundColor: 'rgb(100, 150, 200)',
index 7a3d28df561e2769f69720b2151ab57457774879..5231d1f34ddc13b0aab45cb0e2e0cb9ce9d7c0f9 100644 (file)
@@ -285,16 +285,17 @@ export interface DoughnutControllerChartOptions {
        circumference: number;
 
   /**
-        * The percentage of the chart that is cut out of the middle. (50 - for doughnut, 0 - for pie)
+        * The portion of the chart that is cut out of the middle. ('50%' - for doughnut, 0 - for pie)
+   * String ending with '%' means percentage, number means pixels.
         * @default 50
         */
-  cutoutPercentage: number;
+  cutout: Scriptable<number | string, ScriptableContext<number>>;
 
   /**
    * The outer radius of the chart. String ending with '%' means percentage of maximum radius, number means pixels.
    * @default '100%'
    */
-  outerRadius: Scriptable<number | string, ScriptableContext<number>>;
+  radius: Scriptable<number | string, ScriptableContext<number>>;
 
        /**
         * Starting angle to draw arcs from.
index 2c1dd73b75104489e849537b021878b16ba2cedc..e1074f39cad69eeb37107cf19f46d148bad9f51e 100644 (file)
@@ -9,6 +9,6 @@ const chart = new Chart('id', {
     }]
   },
   options: {
-    outerRadius: () => Math.random() > 0.5 ? 50 : '50%',
+    radius: () => Math.random() > 0.5 ? 50 : '50%',
   }
 });