]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Implement layers (z-index) for layout items (#6241)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Thu, 9 May 2019 13:54:05 +0000 (16:54 +0300)
committerSimon Brunel <simonbrunel@users.noreply.github.com>
Thu, 9 May 2019 13:54:05 +0000 (15:54 +0200)
docs/axes/styling.md
src/core/core.controller.js
src/core/core.layouts.js
src/core/core.scale.js
src/scales/scale.radialLinear.js
test/fixtures/scale.radialLinear/gridlines-no-z.json [new file with mode: 0644]
test/fixtures/scale.radialLinear/gridlines-no-z.png [new file with mode: 0644]
test/fixtures/scale.radialLinear/gridlines-z.json [new file with mode: 0644]
test/fixtures/scale.radialLinear/gridlines-z.png [new file with mode: 0644]
test/specs/core.scale.tests.js

index 23b212348dab921a5dec54a0218fd87b5ea13627..b058498b652f33198ad47c818c28d7f0e52ddb92 100644 (file)
@@ -23,6 +23,7 @@ The grid line configuration is nested under the scale configuration in the `grid
 | `zeroLineBorderDash` | `number[]` | `[]` | Length and spacing of dashes of the grid line for the first index (index 0). See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).
 | `zeroLineBorderDashOffset` | `number` | `0.0` | Offset for line dashes of the grid line for the first index (index 0). See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).
 | `offsetGridLines` | `boolean` | `false` | If true, grid lines will be shifted to be between labels. This is set to `true` for a category scale in a bar chart by default.
+| `z` | `number` | `0` | z-index of gridline layer. Values &lt;= 0 are drawn under datasets, &gt; 0 on top.
 
 ## Tick Configuration
 The tick configuration is nested under the scale configuration in the `ticks` key. It defines options for the tick marks that are generated by the axis.
@@ -40,6 +41,7 @@ The tick configuration is nested under the scale configuration in the `ticks` ke
 | `minor` | `object` | `{}` | Minor ticks configuration. Omitted options are inherited from options above.
 | `major` | `object` | `{}` | Major ticks configuration. Omitted options are inherited from options above.
 | `padding` | `number` | `0` | Sets the offset of the tick labels from the axis
+| `z` | `number` | `0` | z-index of tick layer. Useful when ticks are drawn on chart area. Values &lt;= 0 are drawn under datasets, &gt; 0 on top.
 
 ## Minor Tick Configuration
 The minorTick configuration is nested under the ticks configuration in the `minor` key. It defines options for the minor tick marks that are generated by the axis. Omitted options are inherited from `ticks` configuration.
index 8abe3978e1af617dd0d975a6ff74d4103ef63f1e..168d40b494057833910853a0e284bf48da26f917 100644 (file)
@@ -169,6 +169,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
                me.aspectRatio = height ? width / height : null;
                me.options = config.options;
                me._bufferedRender = false;
+               me._layers = [];
 
                /**
                 * Provided for backward compatibility, Chart and Chart.Controller have been merged,
@@ -495,6 +496,12 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
                // Do this before render so that any plugins that need final scale updates can use it
                plugins.notify(me, 'afterUpdate');
 
+               me._layers.sort(function(a, b) {
+                       return a.z === b.z
+                               ? a._idx - b._idx
+                               : a.z - b.z;
+               });
+
                if (me._bufferedRender) {
                        me._bufferedRequest = {
                                duration: config.duration,
@@ -520,6 +527,15 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
 
                layouts.update(this, this.width, this.height);
 
+               me._layers = [];
+               helpers.each(me.boxes, function(box) {
+                       me._layers.push.apply(me._layers, box._layers());
+               }, me);
+
+               me._layers.forEach(function(item, index) {
+                       item._idx = index;
+               });
+
                /**
                 * Provided for backward compatibility, use `afterLayout` instead.
                 * @method IPlugin#afterScaleUpdate
@@ -626,6 +642,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
 
        draw: function(easingValue) {
                var me = this;
+               var i, layers;
 
                me.clear();
 
@@ -643,12 +660,21 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
                        return;
                }
 
-               // Draw all the scales
-               helpers.each(me.boxes, function(box) {
-                       box.draw(me.chartArea);
-               }, me);
+               // Because of plugin hooks (before/afterDatasetsDraw), datasets can't
+               // currently be part of layers. Instead, we draw
+               // layers <= 0 before(default, backward compat), and the rest after
+               layers = me._layers;
+               for (i = 0; i < layers.length && layers[i].z <= 0; ++i) {
+                       layers[i].draw(me.chartArea);
+               }
 
                me.drawDatasets(easingValue);
+
+               // Rest of layers
+               for (; i < layers.length; ++i) {
+                       layers[i].draw(me.chartArea);
+               }
+
                me._drawTooltip(easingValue);
 
                plugins.notify(me, 'afterDraw', [easingValue]);
index fbaf96a952dd9e42ae28baf64afe56db48879067..4a0969f62ba4c85be559254a1ecc8ffb89b5690f 100644 (file)
@@ -103,6 +103,14 @@ module.exports = {
                item.fullWidth = item.fullWidth || false;
                item.position = item.position || 'top';
                item.weight = item.weight || 0;
+               item._layers = item._layers || function() {
+                       return [{
+                               z: 0,
+                               draw: function() {
+                                       item.draw.apply(item, arguments);
+                               }
+                       }];
+               };
 
                chart.boxes.push(item);
        },
index a4cd62317ccaa8d38076be6658fb3ff38ef0c400..cf1fb281e88a218ec27b117e60649c26ca65e6b8 100644 (file)
@@ -189,7 +189,7 @@ function parseTickFontOptions(options) {
        return {minor: minor, major: major};
 }
 
-module.exports = Element.extend({
+var Scale = Element.extend({
        /**
         * Get the padding needed for the scale
         * @method getPadding
@@ -252,6 +252,7 @@ module.exports = Element.extend({
                me._maxLabelLines = 0;
                me.longestLabelWidth = 0;
                me.longestTextCache = me.longestTextCache || {};
+               me._itemsToDraw = null;
 
                // Dimensions
                me.beforeSetDimensions();
@@ -780,22 +781,14 @@ module.exports = Element.extend({
        },
 
        /**
-        * Actually draw the scale on the canvas
-        * @param {object} chartArea - the area of the chart to draw full grid lines on
+        * @private
         */
-       draw: function(chartArea) {
+       _computeItemsToDraw: function(chartArea) {
                var me = this;
-               var options = me.options;
-
-               if (!me._isVisible()) {
-                       return;
-               }
-
                var chart = me.chart;
-               var context = me.ctx;
+               var options = me.options;
                var optionTicks = options.ticks;
                var gridLines = options.gridLines;
-               var scaleLabel = options.scaleLabel;
                var position = options.position;
 
                var isRotated = me.labelRotation !== 0;
@@ -809,12 +802,11 @@ module.exports = Element.extend({
 
                var tl = getTickMarkLength(gridLines);
 
-               var scaleLabelFontColor = valueOrDefault(scaleLabel.fontColor, defaults.global.defaultFontColor);
-               var scaleLabelFont = helpers.options._parseFont(scaleLabel);
-               var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
                var labelRotationRadians = helpers.toRadians(me.labelRotation);
 
-               var itemsToDraw = [];
+               var items = [];
+
+               var epsilon = 0.0000001; // 0.0000001 is margin in pixels for Accumulated error.
 
                var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
                var alignPixel = helpers._alignPixel;
@@ -838,8 +830,6 @@ module.exports = Element.extend({
                        tickEnd = me.left + tl;
                }
 
-               var epsilon = 0.0000001; // 0.0000001 is margin in pixels for Accumulated error.
-
                helpers.each(ticks, function(tick, index) {
                        // autoskipper skipped this tick (#4635)
                        if (helpers.isNullOrUndef(tick.label)) {
@@ -919,7 +909,7 @@ module.exports = Element.extend({
                                }
                        }
 
-                       itemsToDraw.push({
+                       items.push({
                                tx1: tx1,
                                ty1: ty1,
                                tx2: tx2,
@@ -937,107 +927,74 @@ module.exports = Element.extend({
                                rotation: -1 * labelRotationRadians,
                                label: label,
                                major: tick.major,
+                               font: tick.major ? tickFonts.major : tickFonts.minor,
                                textOffset: textOffset,
                                textAlign: textAlign
                        });
                });
 
-               // Draw all of the tick labels, tick marks, and grid lines at the correct places
-               helpers.each(itemsToDraw, function(itemToDraw) {
-                       var glWidth = itemToDraw.glWidth;
-                       var glColor = itemToDraw.glColor;
-
-                       if (gridLines.display && glWidth && glColor) {
-                               context.save();
-                               context.lineWidth = glWidth;
-                               context.strokeStyle = glColor;
-                               if (context.setLineDash) {
-                                       context.setLineDash(itemToDraw.glBorderDash);
-                                       context.lineDashOffset = itemToDraw.glBorderDashOffset;
+               items.ticksLength = ticks.length;
+               items.borderValue = borderValue;
+
+               return items;
+       },
+
+       /**
+        * @private
+        */
+       _drawGrid: function(chartArea) {
+               var me = this;
+               var ctx = me.ctx;
+               var chart = me.chart;
+               var gridLines = me.options.gridLines;
+
+               if (!gridLines.display) {
+                       return;
+               }
+
+               var alignPixel = helpers._alignPixel;
+               var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
+               var items = me._itemsToDraw || (me._itemsToDraw = me._computeItemsToDraw(chartArea));
+               var glWidth, glColor;
+
+               helpers.each(items, function(item) {
+                       glWidth = item.glWidth;
+                       glColor = item.glColor;
+
+                       if (glWidth && glColor) {
+                               ctx.save();
+                               ctx.lineWidth = glWidth;
+                               ctx.strokeStyle = glColor;
+                               if (ctx.setLineDash) {
+                                       ctx.setLineDash(item.glBorderDash);
+                                       ctx.lineDashOffset = item.glBorderDashOffset;
                                }
 
-                               context.beginPath();
+                               ctx.beginPath();
 
                                if (gridLines.drawTicks) {
-                                       context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
-                                       context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
+                                       ctx.moveTo(item.tx1, item.ty1);
+                                       ctx.lineTo(item.tx2, item.ty2);
                                }
 
                                if (gridLines.drawOnChartArea) {
-                                       context.moveTo(itemToDraw.x1, itemToDraw.y1);
-                                       context.lineTo(itemToDraw.x2, itemToDraw.y2);
+                                       ctx.moveTo(item.x1, item.y1);
+                                       ctx.lineTo(item.x2, item.y2);
                                }
 
-                               context.stroke();
-                               context.restore();
-                       }
-
-                       if (optionTicks.display) {
-                               var tickFont = itemToDraw.major ? tickFonts.major : tickFonts.minor;
-
-                               // Make sure we draw text in the correct color and font
-                               context.save();
-                               context.translate(itemToDraw.labelX, itemToDraw.labelY);
-                               context.rotate(itemToDraw.rotation);
-                               context.font = tickFont.string;
-                               context.fillStyle = tickFont.color;
-                               context.textBaseline = 'middle';
-                               context.textAlign = itemToDraw.textAlign;
-
-                               var label = itemToDraw.label;
-                               var y = itemToDraw.textOffset;
-                               if (helpers.isArray(label)) {
-                                       for (var i = 0; i < label.length; ++i) {
-                                               // We just make sure the multiline element is a string here..
-                                               context.fillText('' + label[i], 0, y);
-                                               y += tickFont.lineHeight;
-                                       }
-                               } else {
-                                       context.fillText(label, 0, y);
-                               }
-                               context.restore();
+                               ctx.stroke();
+                               ctx.restore();
                        }
                });
 
-               if (scaleLabel.display) {
-                       // Draw the scale label
-                       var scaleLabelX;
-                       var scaleLabelY;
-                       var rotation = 0;
-                       var halfLineHeight = scaleLabelFont.lineHeight / 2;
-
-                       if (isHorizontal) {
-                               scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
-                               scaleLabelY = position === 'bottom'
-                                       ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
-                                       : me.top + halfLineHeight + scaleLabelPadding.top;
-                       } else {
-                               var isLeft = position === 'left';
-                               scaleLabelX = isLeft
-                                       ? me.left + halfLineHeight + scaleLabelPadding.top
-                                       : me.right - halfLineHeight - scaleLabelPadding.top;
-                               scaleLabelY = me.top + ((me.bottom - me.top) / 2);
-                               rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
-                       }
-
-                       context.save();
-                       context.translate(scaleLabelX, scaleLabelY);
-                       context.rotate(rotation);
-                       context.textAlign = 'center';
-                       context.textBaseline = 'middle';
-                       context.fillStyle = scaleLabelFontColor; // render in correct colour
-                       context.font = scaleLabelFont.string;
-                       context.fillText(scaleLabel.labelString, 0, 0);
-                       context.restore();
-               }
-
                if (axisWidth) {
                        // Draw the line at the edge of the axis
                        var firstLineWidth = axisWidth;
-                       var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, ticks.length - 1, 0);
+                       var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, items.ticksLength - 1, 0);
+                       var borderValue = items.borderValue;
                        var x1, x2, y1, y2;
 
-                       if (isHorizontal) {
+                       if (me.isHorizontal()) {
                                x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2;
                                x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2;
                                y1 = y2 = borderValue;
@@ -1047,12 +1004,149 @@ module.exports = Element.extend({
                                x1 = x2 = borderValue;
                        }
 
-                       context.lineWidth = axisWidth;
-                       context.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0);
-                       context.beginPath();
-                       context.moveTo(x1, y1);
-                       context.lineTo(x2, y2);
-                       context.stroke();
+                       ctx.lineWidth = axisWidth;
+                       ctx.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0);
+                       ctx.beginPath();
+                       ctx.moveTo(x1, y1);
+                       ctx.lineTo(x2, y2);
+                       ctx.stroke();
+               }
+       },
+
+       /**
+        * @private
+        */
+       _drawLabels: function(chartArea) {
+               var me = this;
+               var ctx = me.ctx;
+               var optionTicks = me.options.ticks;
+
+               if (!optionTicks.display) {
+                       return;
+               }
+
+               var items = me._itemsToDraw || (me._itemsToDraw = me._computeItemsToDraw(chartArea));
+               var tickFont;
+
+               helpers.each(items, function(item) {
+                       tickFont = item.font;
+
+                       // Make sure we draw text in the correct color and font
+                       ctx.save();
+                       ctx.translate(item.labelX, item.labelY);
+                       ctx.rotate(item.rotation);
+                       ctx.font = tickFont.string;
+                       ctx.fillStyle = tickFont.color;
+                       ctx.textBaseline = 'middle';
+                       ctx.textAlign = item.textAlign;
+
+                       var label = item.label;
+                       var y = item.textOffset;
+                       if (helpers.isArray(label)) {
+                               for (var i = 0; i < label.length; ++i) {
+                                       // We just make sure the multiline element is a string here..
+                                       ctx.fillText('' + label[i], 0, y);
+                                       y += tickFont.lineHeight;
+                               }
+                       } else {
+                               ctx.fillText(label, 0, y);
+                       }
+                       ctx.restore();
+               });
+       },
+
+       /**
+        * @private
+        */
+       _drawTitle: function() {
+               var me = this;
+               var ctx = me.ctx;
+               var options = me.options;
+               var scaleLabel = options.scaleLabel;
+
+               if (!scaleLabel.display) {
+                       return;
                }
+
+               var scaleLabelFontColor = valueOrDefault(scaleLabel.fontColor, defaults.global.defaultFontColor);
+               var scaleLabelFont = helpers.options._parseFont(scaleLabel);
+               var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
+               var halfLineHeight = scaleLabelFont.lineHeight / 2;
+               var position = options.position;
+               var rotation = 0;
+               var scaleLabelX, scaleLabelY;
+
+               if (me.isHorizontal()) {
+                       scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
+                       scaleLabelY = position === 'bottom'
+                               ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
+                               : me.top + halfLineHeight + scaleLabelPadding.top;
+               } else {
+                       var isLeft = position === 'left';
+                       scaleLabelX = isLeft
+                               ? me.left + halfLineHeight + scaleLabelPadding.top
+                               : me.right - halfLineHeight - scaleLabelPadding.top;
+                       scaleLabelY = me.top + ((me.bottom - me.top) / 2);
+                       rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
+               }
+
+               ctx.save();
+               ctx.translate(scaleLabelX, scaleLabelY);
+               ctx.rotate(rotation);
+               ctx.textAlign = 'center';
+               ctx.textBaseline = 'middle';
+               ctx.fillStyle = scaleLabelFontColor; // render in correct colour
+               ctx.font = scaleLabelFont.string;
+               ctx.fillText(scaleLabel.labelString, 0, 0);
+               ctx.restore();
+       },
+
+       draw: function(chartArea) {
+               var me = this;
+
+               if (!me._isVisible()) {
+                       return;
+               }
+
+               me._drawGrid(chartArea);
+               me._drawTitle(chartArea);
+               me._drawLabels(chartArea);
+       },
+
+       /**
+        * @private
+        */
+       _layers: function() {
+               var me = this;
+               var opts = me.options;
+               var tz = opts.ticks && opts.ticks.z || 0;
+               var gz = opts.gridLines && opts.gridLines.z || 0;
+
+               if (!me._isVisible() || tz === gz || me.draw !== me._draw) {
+                       // backward compatibility: draw has been overridden by custom scale
+                       return [{
+                               z: tz,
+                               draw: function() {
+                                       me.draw.apply(me, arguments);
+                               }
+                       }];
+               }
+
+               return [{
+                       z: gz,
+                       draw: function() {
+                               me._drawGrid.apply(me, arguments);
+                               me._drawTitle.apply(me, arguments);
+                       }
+               }, {
+                       z: tz,
+                       draw: function() {
+                               me._drawLabels.apply(me, arguments);
+                       }
+               }];
        }
 });
+
+Scale.prototype._draw = Scale.prototype.draw;
+
+module.exports = Scale;
index f75e594ad7ffde065e8abb0f5242561fab2dfd9a..47b1089d93de8dbf6b941bea9667fe86e3210d37 100644 (file)
@@ -224,53 +224,30 @@ function adjustPointPositionForLabelHeight(angle, textSize, position) {
 function drawPointLabels(scale) {
        var ctx = scale.ctx;
        var opts = scale.options;
-       var angleLineOpts = opts.angleLines;
-       var gridLineOpts = opts.gridLines;
        var pointLabelOpts = opts.pointLabels;
-       var lineWidth = valueOrDefault(angleLineOpts.lineWidth, gridLineOpts.lineWidth);
-       var lineColor = valueOrDefault(angleLineOpts.color, gridLineOpts.color);
        var tickBackdropHeight = getTickBackdropHeight(opts);
-
-       ctx.save();
-       ctx.lineWidth = lineWidth;
-       ctx.strokeStyle = lineColor;
-       if (ctx.setLineDash) {
-               ctx.setLineDash(resolve([angleLineOpts.borderDash, gridLineOpts.borderDash, []]));
-               ctx.lineDashOffset = resolve([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]);
-       }
-
        var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
-
-       // Point Label Font
        var plFont = helpers.options._parseFont(pointLabelOpts);
 
+       ctx.save();
+
        ctx.font = plFont.string;
        ctx.textBaseline = 'middle';
 
        for (var i = getValueCount(scale) - 1; i >= 0; i--) {
-               if (angleLineOpts.display && lineWidth && lineColor) {
-                       var outerPosition = scale.getPointPosition(i, outerDistance);
-                       ctx.beginPath();
-                       ctx.moveTo(scale.xCenter, scale.yCenter);
-                       ctx.lineTo(outerPosition.x, outerPosition.y);
-                       ctx.stroke();
-               }
-
-               if (pointLabelOpts.display) {
-                       // Extra pixels out for some label spacing
-                       var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
-                       var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
+               // Extra pixels out for some label spacing
+               var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
+               var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
 
-                       // Keep this in loop since we may support array properties here
-                       var pointLabelFontColor = valueAtIndexOrDefault(pointLabelOpts.fontColor, i, defaults.global.defaultFontColor);
-                       ctx.fillStyle = pointLabelFontColor;
+               // Keep this in loop since we may support array properties here
+               var pointLabelFontColor = valueAtIndexOrDefault(pointLabelOpts.fontColor, i, defaults.global.defaultFontColor);
+               ctx.fillStyle = pointLabelFontColor;
 
-                       var angleRadians = scale.getIndexAngle(i);
-                       var angle = helpers.toDegrees(angleRadians);
-                       ctx.textAlign = getTextAlignForAngle(angle);
-                       adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
-                       fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.lineHeight);
-               }
+               var angleRadians = scale.getIndexAngle(i);
+               var angle = helpers.toDegrees(angleRadians);
+               ctx.textAlign = getTextAlignForAngle(angle);
+               adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
+               fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.lineHeight);
        }
        ctx.restore();
 }
@@ -473,60 +450,109 @@ module.exports = LinearScaleBase.extend({
                        0);
        },
 
-       draw: function() {
+       /**
+        * @private
+        */
+       _drawGrid: function() {
                var me = this;
+               var ctx = me.ctx;
                var opts = me.options;
                var gridLineOpts = opts.gridLines;
-               var tickOpts = opts.ticks;
+               var angleLineOpts = opts.angleLines;
+               var lineWidth = valueOrDefault(angleLineOpts.lineWidth, gridLineOpts.lineWidth);
+               var lineColor = valueOrDefault(angleLineOpts.color, gridLineOpts.color);
+               var i, offset, position;
+
+               if (opts.pointLabels.display) {
+                       drawPointLabels(me);
+               }
 
-               if (opts.display) {
-                       var ctx = me.ctx;
-                       var startAngle = this.getIndexAngle(0);
-                       var tickFont = helpers.options._parseFont(tickOpts);
+               if (gridLineOpts.display) {
+                       helpers.each(me.ticks, function(label, index) {
+                               if (index !== 0) {
+                                       offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
+                                       drawRadiusLine(me, gridLineOpts, offset, index);
+                               }
+                       });
+               }
 
-                       if (opts.angleLines.display || opts.pointLabels.display) {
-                               drawPointLabels(me);
+               if (angleLineOpts.display && lineWidth && lineColor) {
+                       ctx.save();
+                       ctx.lineWidth = lineWidth;
+                       ctx.strokeStyle = lineColor;
+                       if (ctx.setLineDash) {
+                               ctx.setLineDash(resolve([angleLineOpts.borderDash, gridLineOpts.borderDash, []]));
+                               ctx.lineDashOffset = resolve([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]);
                        }
 
-                       helpers.each(me.ticks, function(label, index) {
-                               // Don't draw a centre value (if it is minimum)
-                               if (index > 0 || tickOpts.reverse) {
-                                       var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
+                       for (i = getValueCount(me) - 1; i >= 0; i--) {
+                               offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max);
+                               position = me.getPointPosition(i, offset);
+                               ctx.beginPath();
+                               ctx.moveTo(me.xCenter, me.yCenter);
+                               ctx.lineTo(position.x, position.y);
+                               ctx.stroke();
+                       }
 
-                                       // Draw circular lines around the scale
-                                       if (gridLineOpts.display && index !== 0) {
-                                               drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
-                                       }
+                       ctx.restore();
+               }
+       },
 
-                                       if (tickOpts.display) {
-                                               var tickFontColor = valueOrDefault(tickOpts.fontColor, defaults.global.defaultFontColor);
-                                               ctx.font = tickFont.string;
-
-                                               ctx.save();
-                                               ctx.translate(me.xCenter, me.yCenter);
-                                               ctx.rotate(startAngle);
-
-                                               if (tickOpts.showLabelBackdrop) {
-                                                       var labelWidth = ctx.measureText(label).width;
-                                                       ctx.fillStyle = tickOpts.backdropColor;
-                                                       ctx.fillRect(
-                                                               -labelWidth / 2 - tickOpts.backdropPaddingX,
-                                                               -yCenterOffset - tickFont.size / 2 - tickOpts.backdropPaddingY,
-                                                               labelWidth + tickOpts.backdropPaddingX * 2,
-                                                               tickFont.size + tickOpts.backdropPaddingY * 2
-                                                       );
-                                               }
-
-                                               ctx.textAlign = 'center';
-                                               ctx.textBaseline = 'middle';
-                                               ctx.fillStyle = tickFontColor;
-                                               ctx.fillText(label, 0, -yCenterOffset);
-                                               ctx.restore();
-                                       }
-                               }
-                       });
+       /**
+        * @private
+        */
+       _drawLabels: function() {
+               var me = this;
+               var ctx = me.ctx;
+               var opts = me.options;
+               var tickOpts = opts.ticks;
+
+               if (!tickOpts.display) {
+                       return;
                }
-       }
+
+               var startAngle = me.getIndexAngle(0);
+               var tickFont = helpers.options._parseFont(tickOpts);
+               var tickFontColor = valueOrDefault(tickOpts.fontColor, defaults.global.defaultFontColor);
+               var offset, width;
+
+               ctx.save();
+               ctx.font = tickFont.string;
+               ctx.translate(me.xCenter, me.yCenter);
+               ctx.rotate(startAngle);
+               ctx.textAlign = 'center';
+               ctx.textBaseline = 'middle';
+
+               helpers.each(me.ticks, function(label, index) {
+                       if (index === 0 && !tickOpts.reverse) {
+                               return;
+                       }
+
+                       offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
+
+                       if (tickOpts.showLabelBackdrop) {
+                               width = ctx.measureText(label).width;
+                               ctx.fillStyle = tickOpts.backdropColor;
+
+                               ctx.fillRect(
+                                       -width / 2 - tickOpts.backdropPaddingX,
+                                       -offset - tickFont.size / 2 - tickOpts.backdropPaddingY,
+                                       width + tickOpts.backdropPaddingX * 2,
+                                       tickFont.size + tickOpts.backdropPaddingY * 2
+                               );
+                       }
+
+                       ctx.fillStyle = tickFontColor;
+                       ctx.fillText(label, 0, -offset);
+               });
+
+               ctx.restore();
+       },
+
+       /**
+        * @private
+        */
+       _drawTitle: helpers.noop
 });
 
 // INTERNAL: static default options, registered in src/index.js
diff --git a/test/fixtures/scale.radialLinear/gridlines-no-z.json b/test/fixtures/scale.radialLinear/gridlines-no-z.json
new file mode 100644 (file)
index 0000000..362c519
--- /dev/null
@@ -0,0 +1,33 @@
+{
+    "config": {
+        "type": "radar",
+        "data": {
+            "labels": ["A", "B", "C", "D", "E"],
+            "datasets": [{
+                "backgroundColor": "rgba(255, 0, 0, 1)",
+                "data": [1,2,3,3,3]
+            }]
+        },
+        "options": {
+            "responsive": false,
+            "legend": false,
+            "title": false,
+            "scale": {
+                "gridLines": {
+                    "color": "rgba(0, 0, 0, 1)",
+                    "lineWidth": 1
+                },
+                "angleLines": {
+                    "color": "rgba(0, 0, 255, 1)",
+                    "lineWidth": 1
+                },
+                "pointLabels": {
+                    "display": false
+                },
+                "ticks": {
+                    "display": false
+                }
+            }
+        }
+    }
+}
diff --git a/test/fixtures/scale.radialLinear/gridlines-no-z.png b/test/fixtures/scale.radialLinear/gridlines-no-z.png
new file mode 100644 (file)
index 0000000..c545c56
Binary files /dev/null and b/test/fixtures/scale.radialLinear/gridlines-no-z.png differ
diff --git a/test/fixtures/scale.radialLinear/gridlines-z.json b/test/fixtures/scale.radialLinear/gridlines-z.json
new file mode 100644 (file)
index 0000000..add497b
--- /dev/null
@@ -0,0 +1,34 @@
+{
+    "config": {
+        "type": "radar",
+        "data": {
+            "labels": ["A", "B", "C", "D", "E"],
+            "datasets": [{
+                "backgroundColor": "rgba(255, 0, 0, 1)",
+                "data": [1,2,3,3,3]
+            }]
+        },
+        "options": {
+            "responsive": false,
+            "legend": false,
+            "title": false,
+            "scale": {
+                "gridLines": {
+                    "color": "rgba(0, 0, 0, 1)",
+                    "lineWidth": 1,
+                    "z": 1
+                },
+                "angleLines": {
+                    "color": "rgba(0, 0, 255, 1)",
+                    "lineWidth": 1
+                },
+                "pointLabels": {
+                    "display": false
+                },
+                "ticks": {
+                    "display": false
+                }
+            }
+        }
+    }
+}
diff --git a/test/fixtures/scale.radialLinear/gridlines-z.png b/test/fixtures/scale.radialLinear/gridlines-z.png
new file mode 100644 (file)
index 0000000..61485f3
Binary files /dev/null and b/test/fixtures/scale.radialLinear/gridlines-z.png differ
index 2f2c3b1dece3c40ef33ecd04cb887afba42be1a1..605707eb3c170fa992dafa6d6c77153b4d18b6dc 100644 (file)
@@ -473,4 +473,137 @@ describe('Core.scale', function() {
                        expect(scale.ticks.length).toBe(0);
                });
        });
+
+       describe('_layers', function() {
+               it('should default to one layer', function() {
+                       var chart = window.acquireChart({
+                               type: 'line',
+                               options: {
+                                       scales: {
+                                               xAxes: [{
+                                                       id: 'x',
+                                                       type: 'linear',
+                                               }]
+                                       }
+                               }
+                       });
+
+                       var scale = chart.scales.x;
+                       expect(scale._layers().length).toEqual(1);
+               });
+
+               it('should default to one layer for custom scales', function() {
+                       var customScale = Chart.Scale.extend({
+                               draw: function() {},
+                               convertTicksToLabels: function() {
+                                       return ['tick'];
+                               }
+                       });
+                       Chart.scaleService.registerScaleType('customScale', customScale, {});
+
+                       var chart = window.acquireChart({
+                               type: 'line',
+                               options: {
+                                       scales: {
+                                               xAxes: [{
+                                                       id: 'x',
+                                                       type: 'customScale',
+                                                       gridLines: {
+                                                               z: 10
+                                                       },
+                                                       ticks: {
+                                                               z: 20
+                                                       }
+                                               }]
+                                       }
+                               }
+                       });
+
+                       var scale = chart.scales.x;
+                       expect(scale._layers().length).toEqual(1);
+                       expect(scale._layers()[0].z).toEqual(20);
+               });
+
+               it('should default to one layer when z is equal between ticks and grid', function() {
+                       var chart = window.acquireChart({
+                               type: 'line',
+                               options: {
+                                       scales: {
+                                               xAxes: [{
+                                                       id: 'x',
+                                                       type: 'linear',
+                                                       ticks: {
+                                                               z: 10
+                                                       },
+                                                       gridLines: {
+                                                               z: 10
+                                                       }
+                                               }]
+                                       }
+                               }
+                       });
+
+                       var scale = chart.scales.x;
+                       expect(scale._layers().length).toEqual(1);
+               });
+
+
+               it('should return 2 layers when z is not equal between ticks and grid', function() {
+                       var chart = window.acquireChart({
+                               type: 'line',
+                               options: {
+                                       scales: {
+                                               xAxes: [{
+                                                       id: 'x',
+                                                       type: 'linear',
+                                                       ticks: {
+                                                               z: 10
+                                                       }
+                                               }]
+                                       }
+                               }
+                       });
+
+                       expect(chart.scales.x._layers().length).toEqual(2);
+
+                       chart = window.acquireChart({
+                               type: 'line',
+                               options: {
+                                       scales: {
+                                               xAxes: [{
+                                                       id: 'x',
+                                                       type: 'linear',
+                                                       gridLines: {
+                                                               z: 11
+                                                       }
+                                               }]
+                                       }
+                               }
+                       });
+
+                       expect(chart.scales.x._layers().length).toEqual(2);
+
+                       chart = window.acquireChart({
+                               type: 'line',
+                               options: {
+                                       scales: {
+                                               xAxes: [{
+                                                       id: 'x',
+                                                       type: 'linear',
+                                                       ticks: {
+                                                               z: 10
+                                                       },
+                                                       gridLines: {
+                                                               z: 11
+                                                       }
+                                               }]
+                                       }
+                               }
+                       });
+
+                       expect(chart.scales.x._layers().length).toEqual(2);
+
+               });
+
+       });
 });