diff --git a/src/controllers/controller.doughnut.js b/src/controllers/controller.doughnut.js index a6d4a63775a..688368edac4 100644 --- a/src/controllers/controller.doughnut.js +++ b/src/controllers/controller.doughnut.js @@ -8,6 +8,10 @@ var helpers = require('../helpers/index'); var resolve = helpers.options.resolve; var valueOrDefault = helpers.valueOrDefault; +var PI = Math.PI; +var DOUBLE_PI = PI * 2; +var HALF_PI = PI / 2; + defaults._set('doughnut', { animation: { // Boolean - Whether we animate the rotation of the Doughnut @@ -91,10 +95,10 @@ defaults._set('doughnut', { cutoutPercentage: 50, // The rotation of the chart, where the first data arc begins. - rotation: Math.PI * -0.5, + rotation: -HALF_PI, // The total circumference of the chart. - circumference: Math.PI * 2.0, + circumference: DOUBLE_PI, // Need to override these to give a nice default tooltips: { @@ -145,34 +149,38 @@ module.exports = DatasetController.extend({ var chart = me.chart; var chartArea = chart.chartArea; var opts = chart.options; - var availableWidth = chartArea.right - chartArea.left; - var availableHeight = chartArea.bottom - chartArea.top; - var minSize = Math.min(availableWidth, availableHeight); - var offset = {x: 0, y: 0}; + var ratioX = 1; + var ratioY = 1; + var offsetX = 0; + var offsetY = 0; var meta = me.getMeta(); var arcs = meta.data; - var cutoutPercentage = opts.cutoutPercentage; + var cutout = opts.cutoutPercentage / 100 || 0; var circumference = opts.circumference; var chartWeight = me._getRingWeight(me.index); - var i, ilen; + var maxWidth, maxHeight, i, ilen; - // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc - if (circumference < Math.PI * 2.0) { - var startAngle = opts.rotation % (Math.PI * 2.0); - startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 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 < DOUBLE_PI) { + var startAngle = opts.rotation % DOUBLE_PI; + startAngle += startAngle >= PI ? -DOUBLE_PI : startAngle < -PI ? DOUBLE_PI : 0; var endAngle = startAngle + circumference; - var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; - var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; - var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); - var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); - var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); - var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); - var cutout = cutoutPercentage / 100.0; - var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))}; - var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))}; - var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; - minSize = Math.min(availableWidth / size.width, availableHeight / size.height); - offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; + var startX = Math.cos(startAngle); + var startY = Math.sin(startAngle); + var endX = Math.cos(endAngle); + var endY = Math.sin(endAngle); + var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI; + var contains90 = (startAngle <= HALF_PI && endAngle >= HALF_PI) || endAngle >= DOUBLE_PI + HALF_PI; + var contains180 = startAngle === -PI || endAngle >= PI; + var contains270 = (startAngle <= -HALF_PI && endAngle >= -HALF_PI) || endAngle >= PI + HALF_PI; + var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout); + var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout); + var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout); + var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout); + ratioX = (maxX - minX) / 2; + ratioY = (maxY - minY) / 2; + offsetX = -(maxX + minX) / 2; + offsetY = -(maxY + minY) / 2; } for (i = 0, ilen = arcs.length; i < ilen; ++i) { @@ -180,11 +188,13 @@ module.exports = DatasetController.extend({ } chart.borderWidth = me.getMaxBorderWidth(); - chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); - chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); + maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX; + maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY; + chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); + chart.innerRadius = Math.max(chart.outerRadius * cutout, 0); chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1); - chart.offsetX = offset.x * chart.outerRadius; - chart.offsetY = offset.y * chart.outerRadius; + chart.offsetX = offsetX * chart.outerRadius; + chart.offsetY = offsetY * chart.outerRadius; meta.total = me.calculateTotal(); @@ -207,7 +217,7 @@ module.exports = DatasetController.extend({ var startAngle = opts.rotation; // non reset case handled later var endAngle = opts.rotation; // non reset case handled later var dataset = me.getDataset(); - var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)); + var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI); var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; var options = arc._options || {}; @@ -273,7 +283,7 @@ module.exports = DatasetController.extend({ calculateCircumference: function(value) { var total = this.getMeta().total; if (total > 0 && !isNaN(value)) { - return (Math.PI * 2.0) * (Math.abs(value) / total); + return DOUBLE_PI * (Math.abs(value) / total); } return 0; }, diff --git a/test/fixtures/controller.doughnut/doughnut-circumference.json b/test/fixtures/controller.doughnut/doughnut-circumference.json new file mode 100644 index 00000000000..427079f505b --- /dev/null +++ b/test/fixtures/controller.doughnut/doughnut-circumference.json @@ -0,0 +1,32 @@ +{ + "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": 20, + "borderColor": [ + "rgb(255, 99, 132)", + "rgb(54, 162, 235)", + "rgb(255, 206, 86)", + "rgb(75, 192, 192)", + "rgb(153, 102, 255)" + ] + }] + }, + "options": { + "circumference": 1, + "responsive": false, + "legend": false, + "title": false + } + } +} diff --git a/test/fixtures/controller.doughnut/doughnut-circumference.png b/test/fixtures/controller.doughnut/doughnut-circumference.png new file mode 100644 index 00000000000..e5bf2074091 Binary files /dev/null and b/test/fixtures/controller.doughnut/doughnut-circumference.png differ diff --git a/test/fixtures/controller.doughnut/pie-circumference.json b/test/fixtures/controller.doughnut/pie-circumference.json new file mode 100644 index 00000000000..b892707fe34 --- /dev/null +++ b/test/fixtures/controller.doughnut/pie-circumference.json @@ -0,0 +1,32 @@ +{ + "config": { + "type": "pie", + "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": 20, + "borderColor": [ + "rgb(255, 99, 132)", + "rgb(54, 162, 235)", + "rgb(255, 206, 86)", + "rgb(75, 192, 192)", + "rgb(153, 102, 255)" + ] + }] + }, + "options": { + "circumference": 1, + "responsive": false, + "legend": false, + "title": false + } + } +} diff --git a/test/fixtures/controller.doughnut/pie-circumference.png b/test/fixtures/controller.doughnut/pie-circumference.png new file mode 100644 index 00000000000..7b4a631dbec Binary files /dev/null and b/test/fixtures/controller.doughnut/pie-circumference.png differ