diff --git a/docs/05-Radar-Chart.md b/docs/05-Radar-Chart.md index 161f787683c..e974d2a19c5 100644 --- a/docs/05-Radar-Chart.md +++ b/docs/05-Radar-Chart.md @@ -98,6 +98,7 @@ scale | Object | [See Scales](#scales) and [Defaults for Radial Linear Scale](#s *scale*.type | String |"radialLinear" | As defined in ["Radial Linear"](#scales-radial-linear-scale). *elements*.line | Object | | Options for all line elements used on the chart, as defined in the global elements, duplicated here to show Radar chart specific defaults. *elements.line*.lineTension | Number | 0 | Tension exhibited by lines when calculating splineCurve. Setting to 0 creates straight lines. +startAngle | Number | 0 | The number of degrees to rotate the chart clockwise. You can override these for your `Chart` instance by passing a second argument into the `Radar` method as an object with the keys you want to override. diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index c69974f60ae..d0cb7f2c6d0 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -171,8 +171,13 @@ module.exports = function(Chart) { // 5px to space the text slightly out - similar to what we do in the draw function. pointPosition = this.getPointPosition(i, largestPossibleRadius); textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5; - if (i === 0 || i === this.getValueCount() / 2) { - // If we're at index zero, or exactly the middle, we're at exactly the top/bottom + + // Add quarter circle to make degree 0 mean top of circle + var angleRadians = this.getIndexAngle(i) + (Math.PI / 2); + var angle = (angleRadians * 360 / (2 * Math.PI)) % 360; + + if (angle === 0 || angle === 180) { + // At angle 0 and 180, we're at exactly the top/bottom // of the radar chart, so text will be aligned centrally, so we'll half it and compare // w/left and right text sizes halfTextWidth = textWidth / 2; @@ -184,13 +189,13 @@ module.exports = function(Chart) { furthestLeft = pointPosition.x - halfTextWidth; furthestLeftIndex = i; } - } else if (i < this.getValueCount() / 2) { + } else if (angle < 180) { // Less than half the values means we'll left align the text if (pointPosition.x + textWidth > furthestRight) { furthestRight = pointPosition.x + textWidth; furthestRightIndex = i; } - } else if (i > this.getValueCount() / 2) { + } else { // More than half the values means we'll right align the text if (pointPosition.x - textWidth < furthestLeft) { furthestLeft = pointPosition.x - textWidth; @@ -227,9 +232,14 @@ module.exports = function(Chart) { getIndexAngle: function(index) { var angleMultiplier = (Math.PI * 2) / this.getValueCount(); - // Start from the top instead of right, so remove a quarter of the circle + var startAngle = this.chart.options && this.chart.options.startAngle ? + this.chart.options.startAngle : + 0; + + var startAngleRadians = startAngle * Math.PI * 2 / 360; - return index * angleMultiplier - (Math.PI / 2); + // Start from the top instead of right, so remove a quarter of the circle + return index * angleMultiplier - (Math.PI / 2) + startAngleRadians; }, getDistanceFromCenterForValue: function(value) { var me = this; @@ -373,26 +383,24 @@ module.exports = function(Chart) { ctx.font = pointLabeFont; ctx.fillStyle = pointLabelFontColor; - var pointLabels = me.pointLabels, - labelsCount = pointLabels.length, - halfLabelsCount = pointLabels.length / 2, - quarterLabelsCount = halfLabelsCount / 2, - upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount), - exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount); - if (i === 0) { - ctx.textAlign = 'center'; - } else if (i === halfLabelsCount) { + var pointLabels = me.pointLabels; + + // Add quarter circle to make degree 0 mean top of circle + var angleRadians = this.getIndexAngle(i) + (Math.PI / 2); + var angle = (angleRadians * 360 / (2 * Math.PI)) % 360; + + if (angle === 0 || angle === 180) { ctx.textAlign = 'center'; - } else if (i < halfLabelsCount) { + } else if (angle < 180) { ctx.textAlign = 'left'; } else { ctx.textAlign = 'right'; } // Set the correct text baseline based on outer positioning - if (exactQuarter) { + if (angle === 90 || angle === 270) { ctx.textBaseline = 'middle'; - } else if (upperHalf) { + } else if (angle > 270 || angle < 90) { ctx.textBaseline = 'bottom'; } else { ctx.textBaseline = 'top'; diff --git a/test/scale.radialLinear.tests.js b/test/scale.radialLinear.tests.js index d7b61695eb2..bb84181e1a2 100644 --- a/test/scale.radialLinear.tests.js +++ b/test/scale.radialLinear.tests.js @@ -420,4 +420,43 @@ describe('Test the radial linear scale', function() { expect(chartInstance.scale.getDistanceFromCenterForValue(chartInstance.scale.min)).toBe(225); expect(chartInstance.scale.getDistanceFromCenterForValue(chartInstance.scale.max)).toBe(0); }); + + it('should correctly get angles for all points', function() { + chartInstance = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5'] + }, + options: { + scale: { + pointLabels: { + callback: function(value, index) { + return index.toString(); + } + } + }, + startAngle: 15 + } + }); + + var radToNearestDegree = function(rad) { + return Math.round((360 * rad) / (2 * Math.PI)); + } + + var slice = 72; // (360 / 5) + + for(var i = 0; i < 5; i++) { + expect(radToNearestDegree(chartInstance.scale.getIndexAngle(i))).toBe(15 + (slice * i) - 90); + } + + chartInstance.options.startAngle = 0; + chartInstance.update(); + + for(var i = 0; i < 5; i++) { + expect(radToNearestDegree(chartInstance.scale.getIndexAngle(i))).toBe((slice * i) - 90); + } + }); });