From: Evert Timberg Date: Sat, 3 Oct 2020 20:47:39 +0000 (-0400) Subject: Enable per-dataset circumference and rotation for pie/doughnut charts (#7833) X-Git-Tag: v3.0.0-beta.4~26 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9b204eb278b9a1d82867da81f2e5e10758c28474;p=thirdparty%2FChart.js.git Enable per-dataset circumference and rotation for pie/doughnut charts (#7833) * Enable per-dataset circumference and rotation for pie/doughnut charts * Convert `circumference` and `rotation` options to degrees --- diff --git a/docs/docs/charts/doughnut.mdx b/docs/docs/charts/doughnut.mdx index ab1b15d69..451fb52fc 100644 --- a/docs/docs/charts/doughnut.mdx +++ b/docs/docs/charts/doughnut.mdx @@ -100,6 +100,7 @@ The doughnut/pie chart allows a number of properties to be specified for each da | [`borderAlign`](#border-alignment) | `string` | Yes | Yes | `'center'` | [`borderColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'#fff'` | [`borderWidth`](#styling) | `number` | Yes | Yes | `2` +| ['circumference`](#general) | `number` | - | - | `undefined` | [`clip`](#general) | number|object | - | - | `undefined` | [`data`](#data-structure) | `number[]` | - | - | **required** | [`hoverBackgroundColor`](#interations) | [`Color`](../general/colors.md) | Yes | Yes | `undefined` @@ -107,13 +108,16 @@ The doughnut/pie chart allows a number of properties to be specified for each da | [`hoverBorderWidth`](#interactions) | `number` | Yes | Yes | `undefined` | [`hoverOffset`](#interactions) | `number` | Yes | Yes | `0` | [`offset`](#styling) | `number` | Yes | Yes | `0` +| [`rotation`](#general) | `number` | - | - | `undefined` | [`weight`](#styling) | `number` | - | - | `1` ### General | Name | Description | ---- | ---- +| `circumference` | Per-dataset override for the sweep that the arcs cover | `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}` +| `rotation` | Per-dataset override for the starting angle to draw arcs from ### Styling diff --git a/docs/docs/getting-started/v3-migration.md b/docs/docs/getting-started/v3-migration.md index 09a3dd118..d80c078e0 100644 --- a/docs/docs/getting-started/v3-migration.md +++ b/docs/docs/getting-started/v3-migration.md @@ -66,6 +66,8 @@ A number of changes were made to the configuration options passed to the `Chart` * `hover.animationDuration` is now configured in `animation.active.duration` * `responsiveAnimationDuration` is now configured in `animation.resize.duration` * 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 `0`. * `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` * `scales.[x/y]Axes.barThickness` was moved to dataset option `barThickness` diff --git a/src/controllers/controller.doughnut.js b/src/controllers/controller.doughnut.js index 389e46979..8856ceef9 100644 --- a/src/controllers/controller.doughnut.js +++ b/src/controllers/controller.doughnut.js @@ -1,5 +1,6 @@ import DatasetController from '../core/core.datasetController'; import {isArray, valueOrDefault} from '../helpers/helpers.core'; +import {toRadians} from '../helpers/helpers.math'; /** * @typedef { import("../core/core.controller").default } Chart @@ -78,6 +79,35 @@ export default class DoughnutController extends DatasetController { return ringIndex; } + /** + * Get the maximal rotation & circumference extents + * across all visible datasets. + */ + _getRotationExtents() { + let min = DOUBLE_PI; + let max = -DOUBLE_PI; + + const me = this; + const opts = me.chart.options; + + for (let i = 0; i < me.chart.data.datasets.length; ++i) { + if (me.chart.isDatasetVisible(i)) { + const dataset = me.chart.data.datasets[i]; + const rotation = toRadians(valueOrDefault(dataset.rotation, opts.rotation) - 90); + const circumference = toRadians(valueOrDefault(dataset.circumference, opts.circumference)); + + min = Math.min(min, rotation); + max = Math.max(max, rotation + circumference); + } + } + + + return { + rotation: min, + circumference: max - min, + }; + } + /** * @param {string} mode */ @@ -89,7 +119,12 @@ export default class DoughnutController extends DatasetController { const arcs = meta.data; const cutout = options.cutoutPercentage / 100 || 0; const chartWeight = me._getRingWeight(me.index); - const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(options.rotation, options.circumference, cutout); + + // Compute the maximal rotation & circumference limits. + // If we only consider our dataset, this can cause problems when two datasets + // 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; @@ -114,7 +149,8 @@ export default class DoughnutController extends DatasetController { const me = this; const opts = me.chart.options; const meta = me._cachedMeta; - return reset && opts.animation.animateRotate ? 0 : this.chart.getDataVisibility(i) ? me.calculateCircumference(meta._parsed[i] * opts.circumference / DOUBLE_PI) : 0; + const circumference = toRadians(valueOrDefault(me._config.circumference, opts.circumference)); + return reset && opts.animation.animateRotate ? 0 : this.chart.getDataVisibility(i) ? me.calculateCircumference(meta._parsed[i] * circumference / DOUBLE_PI) : 0; } updateElements(arcs, start, count, mode) { @@ -132,7 +168,7 @@ export default class DoughnutController extends DatasetController { const firstOpts = me.resolveDataElementOptions(start, mode); const sharedOptions = me.getSharedOptions(firstOpts); const includeOptions = me.includeOptions(mode, sharedOptions); - let startAngle = opts.rotation; + let startAngle = toRadians(valueOrDefault(me._config.rotation, opts.rotation) - 90); let i; for (i = 0; i < start; ++i) { @@ -334,10 +370,10 @@ DoughnutController.defaults = { cutoutPercentage: 50, // The rotation of the chart, where the first data arc begins. - rotation: -HALF_PI, + rotation: 0, // The total circumference of the chart. - circumference: DOUBLE_PI, + circumference: 360, // Need to override these to give a nice default tooltips: { diff --git a/test/fixtures/controller.doughnut/doughnut-circumference-over-2pi.json b/test/fixtures/controller.doughnut/doughnut-circumference-over-2pi.json index e2c427517..c7f37d485 100644 --- a/test/fixtures/controller.doughnut/doughnut-circumference-over-2pi.json +++ b/test/fixtures/controller.doughnut/doughnut-circumference-over-2pi.json @@ -15,7 +15,7 @@ }] }, "options": { - "circumference": 7, + "circumference": 400, "responsive": false, "legend": false, "title": false diff --git a/test/fixtures/controller.doughnut/doughnut-circumference-per-dataset.js b/test/fixtures/controller.doughnut/doughnut-circumference-per-dataset.js new file mode 100644 index 000000000..677c14a5e --- /dev/null +++ b/test/fixtures/controller.doughnut/doughnut-circumference-per-dataset.js @@ -0,0 +1,33 @@ +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)' + ], + borderWidth: 1, + borderColor: [ + 'rgb(255, 99, 132)', + 'rgb(54, 162, 235)', + 'rgb(255, 206, 86)', + 'rgb(75, 192, 192)', + 'rgb(153, 102, 255)' + ], + circumference: 180 + }] + }, + options: { + circumference: 57.32, + responsive: false, + legend: false, + title: false + } + } +} diff --git a/test/fixtures/controller.doughnut/doughnut-circumference-per-dataset.png b/test/fixtures/controller.doughnut/doughnut-circumference-per-dataset.png new file mode 100644 index 000000000..7965426bb Binary files /dev/null and b/test/fixtures/controller.doughnut/doughnut-circumference-per-dataset.png differ diff --git a/test/fixtures/controller.doughnut/doughnut-circumference.json b/test/fixtures/controller.doughnut/doughnut-circumference.json index 427079f50..c6073a4f9 100644 --- a/test/fixtures/controller.doughnut/doughnut-circumference.json +++ b/test/fixtures/controller.doughnut/doughnut-circumference.json @@ -23,7 +23,7 @@ }] }, "options": { - "circumference": 1, + "circumference": 57.32, "responsive": false, "legend": false, "title": false diff --git a/test/fixtures/controller.doughnut/doughnut-rotation-per-dataset.js b/test/fixtures/controller.doughnut/doughnut-rotation-per-dataset.js new file mode 100644 index 000000000..03ddaebab --- /dev/null +++ b/test/fixtures/controller.doughnut/doughnut-rotation-per-dataset.js @@ -0,0 +1,51 @@ +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)' + ], + borderWidth: 1, + borderColor: [ + 'rgb(255, 99, 132)', + 'rgb(54, 162, 235)', + 'rgb(255, 206, 86)', + 'rgb(75, 192, 192)', + 'rgb(153, 102, 255)' + ], + rotation: -90 + }, { + 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)' + ], + borderWidth: 1, + borderColor: [ + 'rgb(255, 99, 132)', + 'rgb(54, 162, 235)', + 'rgb(255, 206, 86)', + 'rgb(75, 192, 192)', + 'rgb(153, 102, 255)' + ], + rotation: 0 + }] + }, + options: { + circumference: 180, + responsive: false, + legend: false, + title: false + } + } +} diff --git a/test/fixtures/controller.doughnut/doughnut-rotation-per-dataset.png b/test/fixtures/controller.doughnut/doughnut-rotation-per-dataset.png new file mode 100644 index 000000000..081367d1f Binary files /dev/null and b/test/fixtures/controller.doughnut/doughnut-rotation-per-dataset.png differ diff --git a/test/fixtures/controller.doughnut/pie-circumference.json b/test/fixtures/controller.doughnut/pie-circumference.json index b892707fe..328718645 100644 --- a/test/fixtures/controller.doughnut/pie-circumference.json +++ b/test/fixtures/controller.doughnut/pie-circumference.json @@ -23,7 +23,7 @@ }] }, "options": { - "circumference": 1, + "circumference": 57.32, "responsive": false, "legend": false, "title": false diff --git a/test/specs/controller.doughnut.tests.js b/test/specs/controller.doughnut.tests.js index 32c992dcd..c6bd73ab7 100644 --- a/test/specs/controller.doughnut.tests.js +++ b/test/specs/controller.doughnut.tests.js @@ -69,8 +69,8 @@ describe('Chart.controllers.doughnut', function() { animateScale: false }, cutoutPercentage: 50, - rotation: Math.PI * -0.5, - circumference: Math.PI * 2.0, + rotation: 0, + circumference: 360, elements: { arc: { backgroundColor: 'rgb(255, 0, 0)', @@ -160,14 +160,14 @@ describe('Chart.controllers.doughnut', function() { }, { data: [1, 0] }], - labels: ['label0', 'label1'] + labels: ['label0', 'label1', 'label2'] }, options: { legend: false, title: false, cutoutPercentage: 50, - rotation: Math.PI, - circumference: Math.PI * 0.5, + rotation: 270, + circumference: 90, elements: { arc: { backgroundColor: 'rgb(255, 0, 0)', @@ -210,8 +210,8 @@ describe('Chart.controllers.doughnut', function() { legend: false, title: false, cutoutPercentage: 50, - rotation: Math.PI, - circumference: Math.PI * 0.5, + rotation: 270, + circumference: 90, elements: { arc: { backgroundColor: 'rgb(255, 0, 0)', diff --git a/types/controllers/index.d.ts b/types/controllers/index.d.ts index 6482d11c8..b24728cd8 100644 --- a/types/controllers/index.d.ts +++ b/types/controllers/index.d.ts @@ -177,6 +177,19 @@ export interface IDoughnutControllerDatasetOptions extends IControllerDatasetOptions, ScriptableAndArrayOptions, ScriptableAndArrayOptions { + + /** + * Sweep to allow arcs to cover. + * @default 2 * Math.PI + */ + circumference: number; + + /** + * Starting angle to draw this dataset from. + * @default -0.5 * Math.PI + */ + rotation: number; + /** * The relative thickness of the dataset. Providing a value for weight will cause the pie or doughnut dataset to be drawn with a thickness relative to the sum of all the dataset weight values. * @default 1