Skip to content

Commit

Permalink
Rename cutoutPercentage to cutout + chores (#8514)
Browse files Browse the repository at this point in the history
  • Loading branch information
kurkle authored Feb 24, 2021
1 parent 2edd07d commit b06cd36
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 39 deletions.
8 changes: 4 additions & 4 deletions docs/docs/charts/doughnut.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -163,16 +163,16 @@ 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.
| `animation.animateScale` | `boolean` | `false` | If true, will animate scaling the chart from the center outwards.

## 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

Expand Down
1 change: 1 addition & 0 deletions docs/docs/getting-started/v3-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `` 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`
Expand Down
6 changes: 3 additions & 3 deletions samples/scriptable/pie.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@

// 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();
}
Expand Down
37 changes: 17 additions & 20 deletions src/controllers/controller.doughnut.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,31 @@
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;
let offsetX = 0;
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;
Expand Down Expand Up @@ -127,19 +123,20 @@ 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.
// 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 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;
Expand Down Expand Up @@ -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,
Expand All @@ -354,7 +351,7 @@ DoughnutController.defaults = {
circumference: 360,

// The outr radius of the chart
outerRadius: '100%'
radius: '100%'
},

indexAxis: 'r',
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/controller.pie.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ PieController.defaults = {
circumference: 360,

// The outr radius of the chart
outerRadius: '100%'
radius: '100%'
}
};
7 changes: 6 additions & 1 deletion src/helpers/helpers.core.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = {
}]
},
options: {
outerRadius: '30%',
radius: '30%',
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = {
}]
},
options: {
outerRadius: 150,
radius: 150,
}
}
};
Original file line number Diff line number Diff line change
@@ -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();
});
}
}
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions test/specs/controller.doughnut.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe('Chart.controllers.doughnut', function() {
animateRotate: true,
animateScale: false
},
cutoutPercentage: 50,
cutout: '50%',
rotation: 0,
circumference: 360,
elements: {
Expand Down Expand Up @@ -169,7 +169,7 @@ describe('Chart.controllers.doughnut', function() {
legend: false,
title: false,
},
cutoutPercentage: 50,
cutout: '50%',
rotation: 270,
circumference: 90,
elements: {
Expand Down Expand Up @@ -215,7 +215,7 @@ describe('Chart.controllers.doughnut', function() {
legend: false,
title: false
},
cutoutPercentage: 50,
cutout: '50%',
rotation: 270,
circumference: 90,
elements: {
Expand Down Expand Up @@ -325,7 +325,7 @@ describe('Chart.controllers.doughnut', function() {
}]
},
options: {
cutoutPercentage: 0,
cutout: '50%',
elements: {
arc: {
backgroundColor: 'rgb(100, 150, 200)',
Expand Down
7 changes: 4 additions & 3 deletions types/index.esm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion types/tests/controllers/doughnut_outer_radius.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ const chart = new Chart('id', {
}]
},
options: {
outerRadius: () => Math.random() > 0.5 ? 50 : '50%',
radius: () => Math.random() > 0.5 ? 50 : '50%',
}
});

0 comments on commit b06cd36

Please sign in to comment.