]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Convert the radialLinear scale to derive from Core.scale. Radial linear scale is...
authorEvert Timberg <evert.timberg@gmail.com>
Sun, 27 Sep 2015 15:58:20 +0000 (11:58 -0400)
committerEvert Timberg <evert.timberg@gmail.com>
Sun, 27 Sep 2015 15:58:20 +0000 (11:58 -0400)
src/controllers/controller.polarArea.js
src/controllers/controller.radar.js
src/core/core.controller.js
src/core/core.scaleService.js
src/scales/scale.radialLinear.js
test/mockContext.js
test/scale.radialLinear.tests.js [new file with mode: 0644]

index af3657ad12b6721e7f1fe17ac4ab00af09a9be20..c962b9c3870287561a0198ff17963286face26ae 100644 (file)
                },
 
                update: function update(reset) {
-
-                       Chart.scaleService.update(this, this.chart.width, this.chart.height);
-                       //this.chart.scale.setScaleSize();
-                       this.chart.scale.calculateRange();
-                       this.chart.scale.generateTicks();
-                       this.chart.scale.buildYLabels();
-
                        this.chart.outerRadius = Math.max((helpers.min([this.chart.chart.width, this.chart.chart.height]) - this.chart.options.elements.arc.borderWidth / 2) / 2, 0);
                        this.chart.innerRadius = Math.max(this.chart.options.cutoutPercentage ? (this.chart.outerRadius / 100) * (this.chart.options.cutoutPercentage) : 1, 0);
                        this.chart.radiusLength = (this.chart.outerRadius - this.chart.innerRadius) / this.chart.data.datasets.length;
index 85cf5a459f9ff8072907d390a7530c57d93360d4..40fc4a7abda3dfb542902da9de57f6f8f41cd2af 100644 (file)
                        var scale = this.chart.scale;
                        var scaleBase;
 
-                       scale.setScaleSize();
-                       scale.calculateRange();
-                       scale.generateTicks();
-                       scale.buildYLabels();
-
                        if (scale.min < 0 && scale.max < 0) {
                                scaleBase = scale.getPointPositionForValue(0, scale.max);
                        } else if (scale.min > 0 && scale.max > 0) {
index 81818475e6cdc42167bf608359723298145fb161..6d8d5e25e2b922ba2b022aef5c13bd5a3f057f9f 100644 (file)
                                });
 
                                this.scale = scale;
+
+                               this.scales['radialScale'] = scale;
                        }
 
                        Chart.scaleService.update(this, this.chart.width, this.chart.height);
index bd100fed2c5a0252c98d6d3c2ec521f90f0b9f3c..d60a4b66d40606be05d062051317776897558025 100644 (file)
                                        return scaleInstance.options.position == "bottom";
                                });
 
+                               // Scales that overlay the chartarea such as the radialLinear scale
+                               var chartAreaScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "chartArea";
+                               });
+
                                // Essentially we now have any number of scales on each of the 4 sides.
                                // Our canvas looks like the following.
                                // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and 
@@ -71,6 +76,7 @@
                                // 6. Refit each axis
                                // 7. Position each axis in the final location
                                // 8. Tell the chart the final location of the chart area
+                               // 9. Tell any axes that overlay the chart area the positions of the chart area
 
                                // Step 1
                                var chartWidth = width / 2; // min 50%
                                        right: totalLeftWidth + maxChartWidth,
                                        bottom: totalTopHeight + maxChartHeight,
                                };
+
+                               // Step 9
+                               helpers.each(chartAreaScales, function(scaleInstance) {
+                                       scaleInstance.left = chartInstance.chartArea.left;
+                                       scaleInstance.top = chartInstance.chartArea.top;
+                                       scaleInstance.right = chartInstance.chartArea.right;
+                                       scaleInstance.bottom = chartInstance.chartArea.bottom;
+                                       
+                                       scaleInstance.update(maxChartWidth, maxChartHeight);
+                               });
                        }
                }
        };
index 5c24bd5c668753e9aa48b290393a87abcdb795d2..b9fae20a7b65fe54882920529701a1c58344ea96 100644 (file)
 
                //Boolean - Whether to animate scaling the chart from the centre
                animate: true,
-
                lineArc: false,
-
-               // grid line settings
-               gridLines: {
-                       show: true,
-                       color: "rgba(0, 0, 0, 0.1)",
-                       lineWidth: 1,
-               },
+               position: "chartArea",
 
                angleLines: {
                        show: true,
-                       color: "rgba(0,0,0, 0.1)",
+                       color: "rgba(0, 0, 0, 0.1)",
                        lineWidth: 1
                },
 
-               // scale numbers
-               reverse: false,
-               beginAtZero: true,
-
                // label settings
-               labels: {
-                       show: true,
-                       template: "<%=value.toLocaleString()%>",
-                       fontSize: 12,
-                       fontStyle: "normal",
-                       fontColor: "#666",
-                       fontFamily: "Helvetica Neue",
-
+               ticks: {
                        //Boolean - Show a backdrop to the scale label
                        showLabelBackdrop: true,
 
                },
        };
 
-       var LinearRadialScale = Chart.Element.extend({
-               initialize: function() {
-                       this.height = this.chart.height;
-                       this.width = this.chart.width;
-                       this.xCenter = this.chart.width / 2;
-                       this.yCenter = this.chart.height / 2;
-                       this.size = helpers.min([this.height, this.width]);
-                       this.labels = this.data.labels;
-                       this.drawingArea = (this.options.display) ? (this.size / 2) - (this.options.labels.fontSize / 2 + this.options.labels.backdropPaddingY) : (this.size / 2);
-               },
+       var LinearRadialScale = Chart.Scale.extend({
                getValueCount: function() {
                        return this.data.labels.length;
                },
-               update: function() {
-                       if (!this.options.lineArc) {
-                               this.setScaleSize();
-                       } else {
-                               this.drawingArea = (this.options.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2);
-                       }
-
-                       this.buildYLabels();
+               setDimensions: function() {
+                       // Set the unconstrained dimension before label rotation
+                       this.width = this.maxWidth;
+                       this.height = this.maxHeight;
+                       this.xCenter = Math.round(this.width / 2);
+                       this.yCenter = Math.round(this.height / 2);
+                       
+                       var minSize = helpers.min([this.height, this.width]);
+                       this.drawingArea = (this.options.display) ? (minSize / 2) - (this.options.ticks.fontSize / 2 + this.options.ticks.backdropPaddingY) : (minSize / 2);
                },
-               calculateRange: function() {
+               buildTicks: function() {
                        this.min = null;
                        this.max = null;
 
                                        }
                                }, this);
                        }, this);
-               },
-               generateTicks: function() {
-                       // We need to decide how many ticks we are going to have. Each tick draws a grid line.
-                       // There are two possibilities. The first is that the user has manually overridden the scale
-                       // calculations in which case the job is easy. The other case is that we have to do it ourselves
-                       // 
-                       // We assume at this point that the scale object has been updated with the following values
-                       // by the chart.
-                       //  min: this is the minimum value of the scale
-                       //  max: this is the maximum value of the scale
-                       //  options: contains the options for the scale. This is referenced from the user settings
-                       //      rather than being cloned. This ensures that updates always propogate to a redraw
-
-                       // Reset the ticks array. Later on, we will draw a grid line at these positions
-                       // The array simply contains the numerical value of the spots where ticks will be
-                       this.ticks = [];
 
-                       if (this.options.override) {
-                               // The user has specified the manual override. We use <= instead of < so that 
-                               // we get the final line
-                               for (var i = 0; i <= this.options.override.steps; ++i) {
-                                       var value = this.options.override.start + (i * this.options.override.stepWidth);
-                                       this.ticks.push(value);
-                               }
-                       } else {
-                               // Figure out what the max number of ticks we can support it is based on the size of
-                               // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
-                               // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on 
-                               // the graph
-
-                               var maxTicks = Math.min(11, Math.ceil(this.drawingArea / (2 * this.options.labels.fontSize)));
-
-                               // Make sure we always have at least 2 ticks 
-                               maxTicks = Math.max(2, maxTicks);
-
-                               // To get a "nice" value for the tick spacing, we will use the appropriately named 
-                               // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
-                               // for details.
-
-                               // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
-                               // do nothing since that would make the chart weird. If the user really wants a weird chart
-                               // axis, they can manually override it
-                               if (this.options.beginAtZero) {
-                                       var minSign = helpers.sign(this.min);
-                                       var maxSign = helpers.sign(this.max);
-
-                                       if (minSign < 0 && maxSign < 0) {
-                                               // move the top up to 0
-                                               this.max = 0;
-                                       } else if (minSign > 0 && maxSign > 0) {
-                                               // move the botttom down to 0
-                                               this.min = 0;
-                                       }
-                               }
+                       if (this.min === this.max) {
+                               this.min--;
+                               this.max++;
+                       }
 
-                               var niceRange = helpers.niceNum(this.max - this.min, false);
-                               var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
-                               var niceMin = Math.floor(this.min / spacing) * spacing;
-                               var niceMax = Math.ceil(this.max / spacing) * spacing;
+                       this.ticks = [];
 
-                               // Put the values into the ticks array
-                               for (var j = niceMin; j <= niceMax; j += spacing) {
-                                       this.ticks.push(j);
+                       // Figure out what the max number of ticks we can support it is based on the size of
+                       // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
+                       // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on 
+                       // the graph
+                       var maxTicks = Math.min(11, Math.ceil(this.drawingArea / (1.5 * this.options.ticks.fontSize)));
+                       maxTicks = Math.max(2, maxTicks); // Make sure we always have at least 2 ticks 
+
+                       // To get a "nice" value for the tick spacing, we will use the appropriately named 
+                       // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
+                       // for details.
+
+                       // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
+                       // do nothing since that would make the chart weird. If the user really wants a weird chart
+                       // axis, they can manually override it
+                       if (this.options.ticks.beginAtZero) {
+                               var minSign = helpers.sign(this.min);
+                               var maxSign = helpers.sign(this.max);
+
+                               if (minSign < 0 && maxSign < 0) {
+                                       // move the top up to 0
+                                       this.max = 0;
+                               } else if (minSign > 0 && maxSign > 0) {
+                                       // move the botttom down to 0
+                                       this.min = 0;
                                }
                        }
 
-                       if (this.options.position == "left" || this.options.position == "right") {
-                               // We are in a vertical orientation. The top value is the highest. So reverse the array
-                               this.ticks.reverse();
+                       var niceRange = helpers.niceNum(this.max - this.min, false);
+                       var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
+                       var niceMin = Math.floor(this.min / spacing) * spacing;
+                       var niceMax = Math.ceil(this.max / spacing) * spacing;
+
+                       // Put the values into the ticks array
+                       for (var j = niceMin; j <= niceMax; j += spacing) {
+                               this.ticks.push(j);
                        }
 
                        // At this point, we need to update our max and min given the tick values since we have expanded the
                        // range of the scale
                        this.max = helpers.max(this.ticks);
                        this.min = helpers.min(this.ticks);
-               },
-               buildYLabels: function() {
-                       this.yLabels = [];
-
-                       helpers.each(this.ticks, function(tick, index, ticks) {
-                               var label;
-
-                               if (this.options.labels.userCallback) {
-                                       // If the user provided a callback for label generation, use that as first priority
-                                       label = this.options.labels.userCallback(tick, index, ticks);
-                               } else if (this.options.labels.template) {
-                                       // else fall back to the template string
-                                       label = helpers.template(this.options.labels.template, {
-                                               value: tick
-                                       });
-                               }
 
-                               this.yLabels.push(label ? label : "");
-                       }, this);
+                       if (this.options.ticks.reverse) {
+                               this.ticks.reverse();
+
+                               this.start = this.max;
+                               this.end = this.min;
+                       } else {
+                               this.start = this.min;
+                               this.end = this.max;
+                       }
+
+                       this.zeroLineIndex = this.ticks.indexOf(0);
                },
                getCircumference: function() {
                        return ((Math.PI * 2) / this.getValueCount());
                },
-               setScaleSize: function() {
+               fit: function() {
                        /*
                         * Right, this is really confusing and there is a lot of maths going on here
                         * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
                        for (i = 0; i < this.getValueCount(); i++) {
                                // 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(helpers.template(this.options.labels.template, {
-                                       value: this.labels[i]
+                               textWidth = this.ctx.measureText(helpers.template(this.options.ticks.template, {
+                                       value: this.data.labels[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
                        }
 
                        xProtrusionLeft = furthestLeft;
-
                        xProtrusionRight = Math.ceil(furthestRight - this.width);
 
                        furthestRightAngle = this.getIndexAngle(furthestRightIndex);
-
                        furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
 
                        radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
-
                        radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
 
                        // Ensure we actually need to reduce the size of the chart
                        radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
                        radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
 
-                       this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2;
-
-                       //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
+                       this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2);
                        this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
-
                },
                setCenterPoint: function(leftMovement, rightMovement) {
 
                        var maxRight = this.width - rightMovement - this.drawingArea,
                                maxLeft = leftMovement + this.drawingArea;
 
-                       this.xCenter = (maxLeft + maxRight) / 2;
+                       this.xCenter = Math.round(((maxLeft + maxRight) / 2) + this.left);
                        // Always vertically in the centre as the text height doesn't change
-                       this.yCenter = (this.height / 2);
+                       this.yCenter = Math.round((this.height / 2) + this.top);
                },
 
                getIndexAngle: function(index) {
                draw: function() {
                        if (this.options.display) {
                                var ctx = this.ctx;
-                               helpers.each(this.yLabels, function(label, index) {
+                               helpers.each(this.ticks, function(label, index) {
                                        // Don't draw a centre value (if it is minimum)
                                        if (index > 0 || this.options.reverse) {
                                                var yCenterOffset = this.getDistanceFromCenterForValue(this.ticks[index]);
                                                        }
                                                }
 
-                                               if (this.options.labels.show) {
-                                                       ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
+                                               if (this.options.ticks.show) {
+                                                       ctx.font = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
 
-                                                       if (this.options.labels.showLabelBackdrop) {
+                                                       if (this.options.ticks.showLabelBackdrop) {
                                                                var labelWidth = ctx.measureText(label).width;
-                                                               ctx.fillStyle = this.options.labels.backdropColor;
+                                                               ctx.fillStyle = this.options.ticks.backdropColor;
                                                                ctx.fillRect(
-                                                                       this.xCenter - labelWidth / 2 - this.options.labels.backdropPaddingX,
-                                                                       yHeight - this.fontSize / 2 - this.options.labels.backdropPaddingY,
-                                                                       labelWidth + this.options.labels.backdropPaddingX * 2,
-                                                                       this.options.labels.fontSize + this.options.labels.backdropPaddingY * 2
+                                                                       this.xCenter - labelWidth / 2 - this.options.ticks.backdropPaddingX,
+                                                                       yHeight - this.options.ticks.fontSize / 2 - this.options.ticks.backdropPaddingY,
+                                                                       labelWidth + this.options.ticks.backdropPaddingX * 2,
+                                                                       this.options.ticks.fontSize + this.options.ticks.backdropPaddingY * 2
                                                                );
                                                        }
 
                                                        ctx.textAlign = 'center';
                                                        ctx.textBaseline = "middle";
-                                                       ctx.fillStyle = this.options.labels.fontColor;
+                                                       ctx.fillStyle = this.options.ticks.fontColor;
                                                        ctx.fillText(label, this.xCenter, yHeight);
                                                }
                                        }
                                                ctx.font = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily);
                                                ctx.fillStyle = this.options.pointLabels.fontColor;
 
-                                               var labelsCount = this.labels.length,
-                                                       halfLabelsCount = this.labels.length / 2,
+                                               var labelsCount = this.data.labels.length,
+                                                       halfLabelsCount = this.data.labels.length / 2,
                                                        quarterLabelsCount = halfLabelsCount / 2,
                                                        upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
                                                        exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
                                                        ctx.textBaseline = 'top';
                                                }
 
-                                               ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
+                                               ctx.fillText(this.data.labels[i], pointLabelPosition.x, pointLabelPosition.y);
                                        }
                                }
                        }
index 56b28ffe3ab14b4c3480bbb559382994d68f1740..6559bc7b468a2926f32684dd27d2ac09cc4f0f50 100644 (file)
@@ -68,6 +68,7 @@
                        clearRect: function() {},
                        closePath: function() {},
                        fill: function() {},
+                       fillRect: function() {},
                        fillText: function() {},
                        lineTo: function(x, y) {},
                        measureText: function(text) {
diff --git a/test/scale.radialLinear.tests.js b/test/scale.radialLinear.tests.js
new file mode 100644 (file)
index 0000000..cfbf9d4
--- /dev/null
@@ -0,0 +1,773 @@
+// Tests for the radial linear scale used by the polar area and radar charts
+describe('Test the radial linear scale', function() {
+       it('Should register the constructor with the scale service', function() {
+               var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
+               expect(Constructor).not.toBe(undefined);
+               expect(typeof Constructor).toBe('function');
+       });
+
+       it('Should have the correct default config', function() {
+               var defaultConfig = Chart.scaleService.getScaleDefaults('radialLinear');
+               expect(defaultConfig).toEqual({
+                       angleLines: {
+                               show: true,
+                               color: "rgba(0, 0, 0, 0.1)",
+                               lineWidth: 1
+                       },
+                       animate: true,
+                       display: true,
+                       gridLines: {
+                               color: "rgba(0, 0, 0, 0.1)",
+                               drawOnChartArea: true,
+                               drawTicks: true,
+                               lineWidth: 1,
+                               offsetGridLines: false,
+                               show: true,
+                               zeroLineColor: "rgba(0,0,0,0.25)",
+                               zeroLineWidth: 1,
+                       },
+                       lineArc: false,
+                       pointLabels: {
+                               fontColor: "#666",
+                               fontFamily: "'Arial'",
+                               fontSize: 10,
+                               fontStyle: "normal",
+                       },
+                       position: "chartArea",
+                       scaleLabel: {
+                               fontColor: '#666',
+                               fontFamily: 'Helvetica Neue',
+                               fontSize: 12,
+                               fontStyle: 'normal',
+                               labelString: '',
+                               show: false,
+                       },
+                       ticks: {
+                               backdropColor: "rgba(255,255,255,0.75)",
+                               backdropPaddingY: 2,
+                               backdropPaddingX: 2,
+                               beginAtZero: false,
+                               fontColor: "#666",
+                               fontFamily: "Helvetica Neue",
+                               fontSize: 12,
+                               fontStyle: "normal",
+                               maxRotation: 90,
+                               minRotation: 20,
+                               mirror: false,
+                               padding: 10,
+                               reverse: false,
+                               showLabelBackdrop: true,
+                               show: true,
+                               template: "<%=value%>",
+
+                       },
+               });
+       });
+
+       it('Should correctly determine the max & min data values', function() {
+               var scaleID = 'myScale';
+
+               var mockData = {
+                       datasets: [{
+                               yAxisID: scaleID,
+                               data: [10, 5, 0, -5, 78, -100]
+                       }, {
+                               yAxisID: scaleID,
+                               data: [150]
+                       }],
+                       labels: ['lablel1', 'label2', 'label3', 'label4', 'label5', 'label6']
+               };
+
+               var mockContext = window.createMockContext();
+               var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
+               var scale = new Constructor({
+                       ctx: mockContext,
+                       options: Chart.scaleService.getScaleDefaults('radialLinear'), // use default config for scale
+                       data: mockData,
+                       id: scaleID,
+               });
+
+               scale.update(200, 300);
+               expect(scale.min).toBe(-100);
+               expect(scale.max).toBe(200);
+       });
+
+       it('Should ensure that the scale has a max and min that are not equal', function() {
+               var scaleID = 'myScale';
+
+               var mockData = {
+                       datasets: [],
+                       labels: []
+               };
+
+               var mockContext = window.createMockContext();
+               var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
+               var scale = new Constructor({
+                       ctx: mockContext,
+                       options: Chart.scaleService.getScaleDefaults('radialLinear'), // use default config for scale
+                       data: mockData,
+                       id: scaleID,
+               });
+
+               scale.update(200, 300);
+               expect(scale.min).toBe(-1);
+               expect(scale.max).toBe(1);
+       });
+
+       it('should forcibly include 0 in the range if the beginAtZero option is used', function() {
+               var scaleID = 'myScale';
+
+               var mockData = {
+                       datasets: [{
+                               yAxisID: scaleID,
+                               data: [20, 30, 40, 50]
+                       }],
+                       labels: [],
+               };
+
+               var mockContext = window.createMockContext();
+               var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('radialLinear'));
+               var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
+               var scale = new Constructor({
+                       ctx: mockContext,
+                       options: config,
+                       data: mockData,
+                       id: scaleID,
+               });
+
+               config.ticks.beginAtZero = false;
+               scale.update(400, 400);
+               expect(scale.ticks).toEqual(['20', '25', '30', '35', '40', '45', '50']);
+
+               config.ticks.beginAtZero = true;
+               scale.update(400, 400);
+               expect(scale.ticks).toEqual(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45', '50']);
+
+               mockData.datasets[0].data = [-20, -30, -40, -50];
+               scale.update(400, 400);
+               expect(scale.ticks).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']);
+
+               config.ticks.beginAtZero = false;
+               scale.update(400, 400);
+               expect(scale.ticks).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20']);
+       });
+
+       it('Should generate tick marks in the correct order in reversed mode', function() {
+               var scaleID = 'myScale';
+
+               var mockData = {
+                       datasets: [{
+                               yAxisID: scaleID,
+                               data: [10, 5, 0, 25, 78]
+                       }],
+                       labels: []
+               };
+
+               var mockContext = window.createMockContext();
+               var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('radialLinear'));
+               config.ticks.reverse = true;
+               var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
+               var scale = new Constructor({
+                       ctx: mockContext,
+                       options: config,
+                       data: mockData,
+                       id: scaleID,
+               });
+
+               scale.update(200, 300);
+
+               // Reverse mode makes this count up
+               expect(scale.ticks).toEqual(['80', '60', '40', '20', '0']);
+               expect(scale.start).toBe(80);
+               expect(scale.end).toBe(0);
+       });
+
+       it('Should build labels using the user supplied callback', function() {
+               var scaleID = 'myScale';
+
+               var mockData = {
+                       datasets: [{
+                               yAxisID: scaleID,
+                               data: [10, 5, 0, 25, 78]
+                       }],
+                       labels: []
+               };
+
+               var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('radialLinear'));
+               config.ticks.userCallback = function(value, index) {
+                       return index.toString();
+               };
+
+               var mockContext = window.createMockContext();
+               var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
+               var scale = new Constructor({
+                       ctx: mockContext,
+                       options: config,
+                       data: mockData,
+                       id: scaleID,
+               });
+
+               scale.update(200, 300);
+
+               // Just the index
+               expect(scale.ticks).toEqual(['0', '1', '2', '3', '4']);
+       });
+
+       it('should correctly set the center point', function() {
+               var scaleID = 'myScale';
+
+               var mockData = {
+                       datasets: [{
+                               yAxisID: scaleID,
+                               data: [10, 5, 0, 25, 78]
+                       }],
+                       labels: ['point1', 'point2', 'point3', 'point4', 'point5'] // used in radar charts which use the same scales
+               };
+
+               var mockContext = window.createMockContext();
+               var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('radialLinear'));
+               var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
+               var scale = new Constructor({
+                       ctx: mockContext,
+                       options: config,
+                       data: mockData,
+                       id: scaleID,
+               });
+
+               scale.left = 10;
+               scale.right = 210;
+               scale.top = 5;
+               scale.bottom = 305;
+               scale.update(200, 300);
+
+               expect(scale.drawingArea).toBe(36);
+               expect(scale.xCenter).toBe(110);
+               expect(scale.yCenter).toBe(155);
+       });
+
+       it('should get the correct distance from the center point', function() {
+               var scaleID = 'myScale';
+
+               var mockData = {
+                       datasets: [{
+                               yAxisID: scaleID,
+                               data: [10, 5, 0, 25, 78]
+                       }],
+                       labels: ['point1', 'point2', 'point3', 'point4', 'point5'] // used in radar charts which use the same scales
+               };
+
+               var mockContext = window.createMockContext();
+               var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('radialLinear'));
+               var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
+               var scale = new Constructor({
+                       ctx: mockContext,
+                       options: config,
+                       data: mockData,
+                       id: scaleID,
+               });
+
+               scale.left = 0;
+               scale.right = 200;
+               scale.top = 0;
+               scale.bottom = 300;
+               scale.update(200, 300);
+
+               expect(scale.getDistanceFromCenterForValue(scale.min)).toBe(0);
+               expect(scale.getDistanceFromCenterForValue(scale.max)).toBe(36);
+               expect(scale.getPointPositionForValue(1, 5)).toEqual({
+                       x: 102.13987716166409,
+                       y: 149.30471176265638,
+               });
+
+               config.reverse = true;
+
+               scale.update(200, 300);
+
+               expect(scale.getDistanceFromCenterForValue(scale.min)).toBe(36);
+               expect(scale.getDistanceFromCenterForValue(scale.max)).toBe(0);
+       });
+
+       it('should draw correctly when there are no point labels', function() {
+               var scaleID = 'myScale';
+
+               var mockData = {
+                       datasets: [{
+                               yAxisID: scaleID,
+                               data: [10, 5, 0, 25, 78]
+                       }, ],
+                       labels: ['point1', 'point2', 'point3', 'point4', 'point5'] // used in radar charts which use the same scales
+               };
+
+               var mockContext = window.createMockContext();
+               var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('radialLinear'));
+               config.lineArc = true;
+               var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
+               var scale = new Constructor({
+                       ctx: mockContext,
+                       options: config,
+                       data: mockData,
+                       id: scaleID,
+               });
+
+               scale.left = 0;
+               scale.right = 200;
+               scale.top = 0;
+               scale.bottom = 300;
+               scale.update(200, 300);
+
+               scale.draw();
+
+               var expected = [{
+                       "name": "measureText",
+                       "args": ["0"]
+               }, {
+                       "name": "measureText",
+                       "args": ["80"]
+               }, {
+                       "name": "measureText",
+                       "args": ["point1"]
+               }, {
+                       "name": "measureText",
+                       "args": ["point2"]
+               }, {
+                       "name": "measureText",
+                       "args": ["point3"]
+               }, {
+                       "name": "measureText",
+                       "args": ["point4"]
+               }, {
+                       "name": "measureText",
+                       "args": ["point5"]
+               }, {
+                       "name": "setStrokeStyle",
+                       "args": ["rgba(0, 0, 0, 0.1)"]
+               }, {
+                       "name": "setLineWidth",
+                       "args": [1]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "arc",
+                       "args": [100, 150, 9, 0, 6.283185307179586]
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "measureText",
+                       "args": ["20"]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["rgba(255,255,255,0.75)"]
+               }, {
+                       "name": "fillRect",
+                       "args": [88, 133, 24, 16]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["20", 100, 141]
+               }, {
+                       "name": "setStrokeStyle",
+                       "args": ["rgba(0, 0, 0, 0.1)"]
+               }, {
+                       "name": "setLineWidth",
+                       "args": [1]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "arc",
+                       "args": [100, 150, 18, 0, 6.283185307179586]
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "measureText",
+                       "args": ["40"]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["rgba(255,255,255,0.75)"]
+               }, {
+                       "name": "fillRect",
+                       "args": [88, 124, 24, 16]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["40", 100, 132]
+               }, {
+                       "name": "setStrokeStyle",
+                       "args": ["rgba(0, 0, 0, 0.1)"]
+               }, {
+                       "name": "setLineWidth",
+                       "args": [1]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "arc",
+                       "args": [100, 150, 27, 0, 6.283185307179586]
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "measureText",
+                       "args": ["60"]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["rgba(255,255,255,0.75)"]
+               }, {
+                       "name": "fillRect",
+                       "args": [88, 115, 24, 16]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["60", 100, 123]
+               }, {
+                       "name": "setStrokeStyle",
+                       "args": ["rgba(0, 0, 0, 0.1)"]
+               }, {
+                       "name": "setLineWidth",
+                       "args": [1]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "arc",
+                       "args": [100, 150, 36, 0, 6.283185307179586]
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "measureText",
+                       "args": ["80"]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["rgba(255,255,255,0.75)"]
+               }, {
+                       "name": "fillRect",
+                       "args": [88, 106, 24, 16]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["80", 100, 114]
+               }];
+               expect(mockContext.getCalls()).toEqual(expected);
+
+               mockContext.resetCalls();
+               config.lineArc = false;
+               scale.draw();
+
+               expect(mockContext.getCalls()).toEqual([{
+                       "name": "setStrokeStyle",
+                       "args": ["rgba(0, 0, 0, 0.1)"]
+               }, {
+                       "name": "setLineWidth",
+                       "args": [1]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "moveTo",
+                       "args": [100, 141]
+               }, {
+                       "name": "lineTo",
+                       "args": [108.55950864665638, 147.21884705062547]
+               }, {
+                       "name": "lineTo",
+                       "args": [105.29006727063226, 157.28115294937453]
+               }, {
+                       "name": "lineTo",
+                       "args": [94.70993272936774, 157.28115294937453]
+               }, {
+                       "name": "lineTo",
+                       "args": [91.44049135334362, 147.21884705062547]
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "measureText",
+                       "args": ["20"]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["rgba(255,255,255,0.75)"]
+               }, {
+                       "name": "fillRect",
+                       "args": [88, 133, 24, 16]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["20", 100, 141]
+               }, {
+                       "name": "setStrokeStyle",
+                       "args": ["rgba(0, 0, 0, 0.1)"]
+               }, {
+                       "name": "setLineWidth",
+                       "args": [1]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "moveTo",
+                       "args": [100, 132]
+               }, {
+                       "name": "lineTo",
+                       "args": [117.11901729331277, 144.43769410125094]
+               }, {
+                       "name": "lineTo",
+                       "args": [110.58013454126451, 164.56230589874906]
+               }, {
+                       "name": "lineTo",
+                       "args": [89.41986545873549, 164.56230589874906]
+               }, {
+                       "name": "lineTo",
+                       "args": [82.88098270668723, 144.43769410125094]
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "measureText",
+                       "args": ["40"]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["rgba(255,255,255,0.75)"]
+               }, {
+                       "name": "fillRect",
+                       "args": [88, 124, 24, 16]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["40", 100, 132]
+               }, {
+                       "name": "setStrokeStyle",
+                       "args": ["rgba(0, 0, 0, 0.1)"]
+               }, {
+                       "name": "setLineWidth",
+                       "args": [1]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "moveTo",
+                       "args": [100, 123]
+               }, {
+                       "name": "lineTo",
+                       "args": [125.67852593996915, 141.6565411518764]
+               }, {
+                       "name": "lineTo",
+                       "args": [115.87020181189678, 171.8434588481236]
+               }, {
+                       "name": "lineTo",
+                       "args": [84.12979818810322, 171.8434588481236]
+               }, {
+                       "name": "lineTo",
+                       "args": [74.32147406003085, 141.6565411518764]
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "measureText",
+                       "args": ["60"]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["rgba(255,255,255,0.75)"]
+               }, {
+                       "name": "fillRect",
+                       "args": [88, 115, 24, 16]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["60", 100, 123]
+               }, {
+                       "name": "setStrokeStyle",
+                       "args": ["rgba(0, 0, 0, 0.1)"]
+               }, {
+                       "name": "setLineWidth",
+                       "args": [1]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "moveTo",
+                       "args": [100, 114]
+               }, {
+                       "name": "lineTo",
+                       "args": [134.23803458662553, 138.87538820250188]
+               }, {
+                       "name": "lineTo",
+                       "args": [121.16026908252903, 179.12461179749812]
+               }, {
+                       "name": "lineTo",
+                       "args": [78.83973091747097, 179.12461179749812]
+               }, {
+                       "name": "lineTo",
+                       "args": [65.76196541337447, 138.8753882025019]
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "measureText",
+                       "args": ["80"]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["rgba(255,255,255,0.75)"]
+               }, {
+                       "name": "fillRect",
+                       "args": [88, 106, 24, 16]
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["80", 100, 114]
+               }, {
+                       "name": "setLineWidth",
+                       "args": [1]
+               }, {
+                       "name": "setStrokeStyle",
+                       "args": ["rgba(0, 0, 0, 0.1)"]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "moveTo",
+                       "args": [100, 150]
+               }, {
+                       "name": "lineTo",
+                       "args": [65.76196541337447, 138.8753882025019]
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["point5", 61.0066828318987, 137.33030323062715]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "moveTo",
+                       "args": [100, 150]
+               }, {
+                       "name": "lineTo",
+                       "args": [78.83973091747097, 179.12461179749812]
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["point4", 75.9008046560086, 183.16969676937285]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "moveTo",
+                       "args": [100, 150]
+               }, {
+                       "name": "lineTo",
+                       "args": [121.16026908252903, 179.12461179749812]
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["point3", 124.0991953439914, 183.16969676937285]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "moveTo",
+                       "args": [100, 150]
+               }, {
+                       "name": "lineTo",
+                       "args": [134.23803458662553, 138.87538820250188]
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["point2", 138.9933171681013, 137.33030323062715]
+               }, {
+                       "name": "beginPath",
+                       "args": []
+               }, {
+                       "name": "moveTo",
+                       "args": [100, 150]
+               }, {
+                       "name": "lineTo",
+                       "args": [100, 114]
+               }, {
+                       "name": "stroke",
+                       "args": []
+               }, {
+                       "name": "closePath",
+                       "args": []
+               }, {
+                       "name": "setFillStyle",
+                       "args": ["#666"]
+               }, {
+                       "name": "fillText",
+                       "args": ["point1", 100, 109]
+               }]);
+       });
+});
\ No newline at end of file