| [`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) | <code>number|object</code> | - | - | `undefined`
| [`data`](#data-structure) | `number[]` | - | - | **required**
| [`hoverBackgroundColor`](#interations) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`
| [`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
* `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`
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
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
*/
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;
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) {
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) {
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: {
}]
},
"options": {
- "circumference": 7,
+ "circumference": 400,
"responsive": false,
"legend": false,
"title": false
--- /dev/null
+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
+ }
+ }
+}
}]
},
"options": {
- "circumference": 1,
+ "circumference": 57.32,
"responsive": false,
"legend": false,
"title": false
--- /dev/null
+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
+ }
+ }
+}
}]
},
"options": {
- "circumference": 1,
+ "circumference": 57.32,
"responsive": false,
"legend": false,
"title": false
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)',
}, {
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)',
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)',
extends IControllerDatasetOptions,
ScriptableAndArrayOptions<IArcOptions>,
ScriptableAndArrayOptions<IArcHoverOptions> {
+
+ /**
+ * 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