]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Initial check in of the scale service and the scale constructor registration. An...
authorEvert Timberg <evert.timberg@gmail.com>
Sat, 16 May 2015 23:40:52 +0000 (19:40 -0400)
committerEvert Timberg <evert.timberg@gmail.com>
Sat, 16 May 2015 23:40:52 +0000 (19:40 -0400)
src/Chart.Scale.js [new file with mode: 0644]

diff --git a/src/Chart.Scale.js b/src/Chart.Scale.js
new file mode 100644 (file)
index 0000000..3a2495c
--- /dev/null
@@ -0,0 +1,655 @@
+(function() {
+       "use strict";
+       
+        var root = this,
+        Chart = root.Chart,
+        helpers = Chart.helpers;
+       
+       // The scale service is used to resize charts along with all of their axes. We make this as
+       // a service where scales are registered with their respective charts so that changing the 
+       // scales does not require 
+       Chart.scaleService = {
+               registeredCharts: [],
+               getWrapperForChart: function(chartInstance) {
+                       var wrapper = helpers.findNextWhere(this.registeredCharts, function(charScaleWrapper) {
+                               return charScaleWrapper.chartInstance == chartInstance;
+                       });
+                       
+                       return wrapper;
+               },
+               registerChartScale: function(chartInstance, scaleInstance) {
+                       var chartScaleWrapper = this.getWrapperForChart(chartInstance);
+                       
+                       if (!chartScaleWrapper) {
+                               chartScaleWrapper = {
+                                       scales: [],
+                                       chartInstance: chartInstance,
+                               };
+                               
+                               this.registeredCharts.push(chartScaleWrapper);
+                       }
+                       
+                       chartScaleWrapper.scales.push(scaleInstance);
+               },
+               removeChartScale: function(chartInstance, scaleInstance) {
+                       var chartScaleWrapper = this.getWrapperForChart(chartInstance);
+                       
+                       if (chartScaleWrapper) {
+                               var scaleIndex = helpers.indexOf(scaleWrapper.scales, scaleInstance);
+                               
+                               if (scaleIndex) {
+                                       scaleWrapper.scales.splice(scaleIndex, 1);
+                               }
+                       }
+               },
+               // Remove a chart instance from the scale service. Useful when a chart is destroyed
+               removeChartInstance: function(chartInstance) {
+                       var index = helpers.findNextWhere(this.registeredCharts, function(scaleWrapper) {
+                               return scaleWrapper.chartInstance == chartInstance;
+                       });
+                       
+                       if (index) {
+                               this.registeredCharts.splice(index, 1);
+                       }
+               },
+               // The interesting function
+               fitScalesForChart: function(chartInstance, width, height) {
+                       var chartScaleWrapper = this.getWrapperForChart(chartInstance);
+                       
+                       if (chartScaleWrapper) {
+                               var leftScales = helpers.where(chartScaleWrapper.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "left";
+                               });
+                               var rightScales = helpers.where(chartScaleWrapper.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "right";
+                               });
+                               var topScales = helpers.where(chartScaleWrapper.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "top";
+                               });
+                               var bottomScales = helpers.where(chartScaleWrapper.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "bottom";
+                               });
+                               
+                               // 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 
+                               // B1 is the bottom axis
+                               // |------------------------------------------------------|
+                               // |              |                             T1                                              |         |
+                               // |----|-----|-------------------------------------|-----|
+                               // |    |     |                                                                     |     |
+                               // | L1 |  L2 |                 Chart area                                      |  R1 |
+                               // |    |         |                                                                             |     |
+                               // |    |         |                                                                             |     |
+                               // |----|-----|-------------------------------------|-----|
+                               // |              |                             B1                                              |         |
+                               // |              |                                                                             |         |
+                               // |------------------------------------------------------|
+                               
+                               // What we do to find the best sizing, we do the following
+                               // 1. Determine the minimum size of the chart area. 
+                               // 2. Split the remaining width equally between each vertical axis
+                               // 3. Split the remaining height equally between each horizontal axis
+                               // 4. Give each scale the maximum size it can be. The scale will return it's minimum size
+                               // 5. Adjust the sizes of each axis based on it's minimum reported size. 
+                               // 6. Refit each axis
+                               // 7. Position each axis in the final location
+                               // 8. Tell the chart the final location of the chart area
+                               
+                               // Step 1
+                               var chartWidth = width / 2; // min 50%
+                               var chartHeight = height / 2; // min 50%
+                               var aspectRatio = chartHeight / chartWidth;
+                               var screenAspectRatio;
+                               
+                               if (chartInstance.options.maintainAspectRatio) {
+                                       screenAspectRatio = height / width;
+                                       
+                                       if (aspectRatio != screenAspectRatio) {
+                                               chartHeight = chartWidth * screenAspectRatio;
+                                               aspectRatio = screenAspectRatio;
+                                       }
+                               }
+                               
+                               // Step 2
+                               var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length);
+                               
+                               // Step 3
+                               var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length);
+                               
+                               // Step 4;
+                               var scalesToMinSize = {};
+                               
+                               var verticalScaleMinSizeFunction = function(scaleInstance) {
+                                       var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight);
+                                       scalesToMinSize[scaleInstance] = minSize;
+                               };
+                               
+                               var horizontalScaleMinSizeFunction = function(scaleInstance) {
+                                       var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight);
+                                       scalesToMinSize[scaleInstance] = minSize;
+                               };
+                               
+                               // vertical scales
+                               helpers.each(leftScales, verticalScaleMinSizeFunction);
+                               helpers.each(rightScales, verticalScaleMinSizeFunction);
+                               
+                               // horizontal scales
+                               helpers.each(topScales, horizontalScaleMinSizeFunction);
+                               helpers.each(bottomScales, horizontalScaleMinSizeFunction);
+                               
+                               // Step 5
+                               var maxChartHeight = height;
+                               var maxChartWidth = width;
+                               
+                               var chartWidthReduceFunction = function(scaleInstance) {
+                                       maxChartWidth -= scalesToMinSize[scaleInstance].width;
+                               };
+                               
+                               var chartHeightReduceFunction = function(scaleInstance) {
+                                       maxChartHeight -= scalesToMinSize[scaleInstance].height;
+                               };
+                               
+                               helpers.each(leftScales, chartWidthReduceFunction);
+                               helpers.each(rightScales, chartWidthReduceFunction);
+                               helpers.each(topScales, chartHeightReduceFunction);
+                               helpers.each(bottomScales, chartHeightReduceFunction);
+                               
+                               // At this point, maxChartHeight and maxChartWidth are the size the chart area could
+                               // be if the axes are drawn at their minimum sizes.
+                               if (chartInstance.options.maintainAspectRatio) {
+                                       // Figure out what the real max size will be
+                                       var maxAspectRatio = maxChartHeight / maxChartWidth;
+                                       
+                                       if (maxAspectRatio != screenAspectRatio) {
+                                               // Need to adjust
+                                               if (maxChartHeight < maxChartWidth) {
+                                                       maxChartWidth = maxChartHeight / screenAspectRatio;
+                                               }
+                                               else {
+                                                       maxChartHeight = maxChartWidth * screenAspectRatio;
+                                               }
+                                       }
+                               }
+                               
+                               // Step 6
+                               var verticalScaleFitFunction = function(scaleInstance) {
+                                       var minSize = scalesToMinSize[scaleInstance];
+                                       scaleInstance.fit(minSize.width, maxChartHeight);
+                               };
+                               
+                               var horizontalScaleFitFunction = function(scaleInstance) {
+                                       var minSize = scalesToMinSize[scaleInstance];
+                                       scaleInstance.fit(maxChartWidth, minSize.width);
+                               };
+                               
+                               helpers.each(leftScales, verticalScaleFitFunction);
+                               helpers.each(rightScales, verticalScaleFitFunction);
+                               helpers.each(topScales, horizontalScaleFitFunction);
+                               helpers.each(bottomScales, horizontalScaleFitFunction);
+                               
+                               // Step 7 
+                               var totalLeftWidth = 0;
+                               var totalTopHeight = 0;
+                               
+                               // Calculate total width of all left axes
+                               helpers.each(leftScales, function(scaleInstance) {
+                                       totalLeftWidth += scaleInstance.width;
+                               });
+                               
+                               // Calculate total height of all top axes
+                               helpers.each(topScales, function(scaleInstance) {
+                                       totalTopHeight += scaleInstance.height;
+                               });
+                               
+                               // Position the scales
+                               var left = 0;
+                               var top = 0;
+                               var right = 0;
+                               var bottom = 0;
+                               
+                               var verticalScalePlacer = function(scaleInstance) {
+                                       scaleInstance.left = left;
+                                       scaleInstance.right = left + scaleInstance.width;
+                                       scaleInstance.top = totalTopHeight;
+                                       scaleInstance.bottom = totalTopHeight + maxChartHeight;
+                                       
+                                       // Move to next point
+                                       left = scaleInstance.right;
+                               };
+                               
+                               var horizontalScalePlacer = function(scaleInstance) {
+                                       scaleInstance.left = totalLeftWidth;
+                                       scaleInstance.right = totalLeftWidth + maxChartWidth;
+                                       scaleInstance.top = top;
+                                       scaleInstance.bottom = top + scaleInstance.height;
+                                       
+                                       // Move to next point 
+                                       top = scaleInstance.bottom;
+                               };
+                               
+                               helpers.each(leftScales, verticalScalePlacer);
+                               helpers.each(topScales, horizontalScalePlacer);
+                               
+                               // Account for chart width and height
+                               left += maxChartWidth;
+                               top  += maxChartHeight;
+                               
+                               helpers.each(rightScales, verticalScalePlacer);
+                               helpers.each(bottomScales, horizontalScalePlacer);
+                               
+                               // Step 8
+                               chartScaleWrapper.chartInstance.chartArea = {
+                                       left: totalLeftWidth,
+                                       top: totalTopHeight,
+                                       right: totalLeftWidth + maxChartWidth,
+                                       bottom: totalTopHeight + maxChartHeight,
+                               };
+                       }
+               }
+       };
+       
+       // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
+       // use the new chart options to grab the correct scale
+       Chart.scales = {
+               constructors: {}, 
+               // Use a registration function so that we can move to an ES6 map when we no longer need to support
+               // old browsers
+               registerScaleType: function(scaleType, scaleConstructor) {
+                       this.constructors[scaleType] = scaleConstructor;
+               },
+               getScaleConstructor: function(scaleType) {
+                       return this.constructors.hasOwnProperty(scaleType) ? this.constructors[scaleType] : undefined;
+               }
+       };
+       
+       var LinearScale = Chart.Element.extend({
+               calculateRange: helpers.noop, // overridden in the chart. Will set min and max as properties of the scale for later use
+               isHorizontal: function() {
+                       return this.options.position == "top" || this.options.position == "bottom";
+               },
+               generateTicks: function(width, height) {
+                       // 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);
+                                       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;
+                               
+                               if (this.isHorizontal()) {
+                                       maxTicks = Math.min(11, Math.ceil(width / 50));
+                               } else {
+                                       maxTicks = Math.min(11, Math.ceil(height / 50));
+                               }
+                               
+                               // 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) {
+                                       this.min = Math.min(this.min, 0);
+                               }
+                               
+                               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);
+                               }
+                       }
+                       
+                       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();
+                       }
+               },
+               buildLabels: function() {
+                       // We assume that this has been run after ticks have been generated. We try to figure out
+                       // a label for each tick. 
+                       this.labels = [];
+                       
+                       helpers.each(this.ticks, function(tick, index, ticks) {
+                               var label; 
+                               
+                               if (this.options.labelCallback) {
+                                       // If the user provided a callback for label generation, use that as first priority
+                                       label = this.options.labelCallback(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.labels.push(label ? label : ""); // empty string will not render so we're good
+                       }, this);
+               },
+               getPixelForValue: function(value) {
+                       // This must be called after fit has been run so that 
+                       //              this.left, this.top, this.right, and this.bottom have been defined
+                       var pixel;
+                       var range = this.max - this.min;
+                       
+                       if (this.isHorizontal()) {
+                               pixel = this.left + (this.width / range * (value - this.min));
+                       } else {
+                               // Bottom - top since pixels increase downard on a screen
+                               pixel = this.bottom - (this.height / range * (value - this.min));
+                       }
+                       
+                       return pixel;
+               },
+               // Fit this axis to the given size
+               // @param {number} maxWidth : the max width the axis can be
+               // @param {number} maxHeight: the max height the axis can be
+               // @return {object} minSize : the minimum size needed to draw the axis
+               fit: function(maxWidth, maxHeight) {
+                       this.calculateRange();
+                       this.generateTicks(maxWidth, maxHeight);
+                       this.buildLabels();
+                       
+                       var minSize = {
+                               width: 0,
+                               height: 0,
+                       };
+                       
+                       if (this.isHorizontal()) {
+                               minSize.width = maxWidth; // fill all the width
+                               
+                               // In a horizontal axis, we need some room for the scale to be drawn
+                               //
+                               //              -----------------------------------------------------
+                               //                      |                       |                       |                       |                       |
+                               //
+                               minSize.height = this.options.gridLines.show ? 25 : 0;
+                       } else {
+                               minSize.height = maxHeight; // fill all the height
+                               
+                               // In a vertical axis, we need some room for the scale to be drawn.
+                               // The actual grid lines will be drawn on the chart area, however, we need to show 
+                               // ticks where the axis actually is.
+                               // We will allocate 25px for this width
+                               //              |
+                               //         -|
+                               //          |
+                               //              |
+                               //         -|
+                               //          |
+                               //              |
+                               //         -|
+                               minSize.width = this.options.gridLines.show ? 25 : 0;
+                       }
+                       
+                       if (this.options.labels.show) {
+                               // Don't bother fitting the labels if we are not showing them
+                               var labelFont = helpers.fontString(this.options.labels.fontSize, 
+                                       this.options.labels.fontStyle, this.options.labels.fontFamily);
+                               
+                               if (this.isHorizontal()) {
+                                       // A horizontal axis is more constrained by the height.
+                                       var maxLabelHeight = maxHeight - minSize.height;
+                                       
+                                       // Calculate the label rotation
+                                       var labelHeight = this.calculateLabelRotation(minSize.width, maxLabelHeight);
+                                       minSize.height = Math.min(maxHeight, minSize.height + labelHeight);
+                               } else {
+                                       // A vertical axis is more constrained by the width. Labels are the dominant factor 
+                                       // here, so get that length first
+                                       var maxLabelWidth = maxWidth - minSize.width;
+                                       var largestTextWidth = helpers.longestText(this.ctx, labelFont, this.labels);
+                                       
+                                       if (largestTextWidth < maxLabelWidth) {
+                                               // We don't need all the room
+                                               minSize.width += largestTextWidth;
+                                       } else {
+                                               // Expand to max size
+                                               minSize.width = maxWidth;
+                                       }
+                               }
+                       }
+                       
+                       this.width = minSize.width;
+                       this.height = minSize.height;
+                       return minSize;
+               },
+               // Function calculate the needed rotation of the labels. Should only be used in horizontal mode
+               // @param {number} width : the available width
+               // @param {number} height: the available height
+               // @return {number} : the height needed by the labels
+               calculateLabelRotation : function(width, height){
+                       //Get the width of each grid by calculating the difference
+                       //between x offsets between 0 and 1.
+
+                       var labelFont = helpers.fontString(this.options.labels.fontSize, 
+                               this.options.labels.fontStyle, this.options.labels.fontFamily);
+                       
+                       this.labelRotation = 0; // reset
+                       
+                       // Steps
+                       // 1. determine if we need to overlap
+                       // 2. if overlap, determine max rotation
+                       // 3. Rotate until no overlap
+                       // 4. Save rotation
+                       // 5. Return height needed for rotation
+                       var longestTextWidth = helpers.longestText(this.ctx, labelFont, this.lables);
+                       var maxAvailableWidth = (width / (this.ticks.length - 1)) - 6;
+                       
+                       // 6 adds 3px of padding on each end of the label
+                       if (longestTextWidth > maxAvailableWidth) {
+                               // Ok, we need to rotate. Do steps 2-4
+                               var idealRotation = Math.floor(helpers.toDegrees(Math.asin(height / longestTextWidth)));
+                               var maxRotation = Math.min(90, idealRotation);
+                               
+                               // Increment the rotation in 1 degree steps (step 3)
+                               for (var rotation = 1; rotation < maxRotation; ++rotation) {
+                                       var cosRotation = Math.cos(helpers.toRadians(rotation));
+                                       this.labelRotation = rotation; // step 4
+                                       
+                                       if (cosRotation * longestTextWidth <= maxAvailableWidth) {
+                                               // Rotated enough
+                                               break;
+                                       }
+                               }
+                               
+                               // step 5
+                               return Math.min(height, longestTextWidth * Math.sin(this.labelRotation));
+                       } else {
+                               // Height only constrained by text font size and padding
+                               var idealHeight = this.options.labels.fontSize + 10; // add 10 for padding
+                               return Math.min(height, idealHeight);
+                       }
+               },
+               
+               // Actualy draw the scale on the canvas
+               // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
+               draw: function(chartArea) {
+                       if (this.options.show) {
+                               
+                               var setContextLineSettings;
+                               var hasZero;
+                               
+                               if (this.isHorizontal()) {
+                                       if (this.options.gridLines.show) {
+                                               // Draw the horizontal line
+                                               setContextLineSettings = true;
+                                               hasZero = helpers.findNextWhere(this.ticks, function(tick) { return tick === 0; }) !== undefined;
+                                               var yTickStart = this.options.position == "bottom" ? this.top : this.bottom - 10;
+                                               var yTickEnd = this.options.position == "bottom" ? this.top + 10 : this.bottom;
+                                               
+                                               this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
+                                               this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
+                                               
+                                               this.ctx.beginPath();
+                                               
+                                               if (this.options.position == "top") {
+                                                       this.ctx.moveTo(this.left, this.bottom - 5);
+                                                       this.ctx.lineTo(this.right, this.bottom - 5);
+                                               } else {
+                                                       // On bottom, so draw horizontal line on the top
+                                                       this.ctx.moveTo(this.left, this.top + 5);
+                                                       this.ctx.lineTo(this.right, this.top + 5);
+                                               }
+
+                                               helpers.each(this.ticks, function(tick, index) {
+                                                       // Grid lines are vertical
+                                                       var xValue = this.getPixelForValue(tick);
+                                                       
+                                                       if (tick === 0 || (!hasZero && index === 0)) {
+                                                               // Draw the 0 point specially or the left if there is no 0
+                                                               this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
+                                                               this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
+                                                               setContextLineSettings = true; // reset next time
+                                                       } else if (setContextLineSettings) {
+                                                               this.ctx.lineWidth = this.options.gridLines.lineWidth;
+                                                               this.ctx.strokeStyle = this.options.gridLines.color;
+                                                               setContextLineSettings = false;
+                                                       }
+                                                       
+                                                       xValue += helpers.aliasPixel(this.ctx.lineWidth);
+                                                       
+                                                       // Draw the label area
+                                                       this.ctx.moveTo(xValue, yTickStart);
+                                                       this.ctx.lineTo(xValue, yTickEnd);
+                                                       
+                                                       // Draw the chart area
+                                                       if (this.options.gridLines.drawOnChartArea) {
+                                                               this.ctx.moveTo(xValue, chartArea.top);
+                                                               this.ctx.lineTo(xValue, chartArea.bottom);
+                                                       }
+                                               }, this);
+                                               
+                                               this.ctx.stroke();
+                                       }
+
+                                       if (this.options.labels.show) {
+                                               // Draw the labels
+                                               
+                                               var labelStartY;
+                                               
+                                               if (this.options.position == "top") {
+                                                       labelStartY = this.top;
+                                               } else {
+                                                       // bottom side
+                                                       labelStartY = this.top + 20;
+                                               }
+                                               
+                                               this.ctx.textAlign = "center";
+                                               this.ctx.textBaseline = "top";
+                                               this.ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
+                                               
+                                               helpers.each(this.labels, function(label, index) {
+                                                       var xValue = this.getPixelForValue(this.ticks[index]);
+                                                       this.ctx.fillText(label, xValue, labelStartY);
+                                               }, this);
+                                       }
+                               } else {
+                                       // Vertical
+                                       if (this.options.gridLines.show) {
+                                               
+                                               // Draw the vertical line
+                                               setContextLineSettings = true;
+                                               hasZero = helpers.findNextWhere(this.ticks, function(tick) { return tick === 0; }) !== undefined;
+                                               var xTickStart = this.options.position == "left" ? this.left : this.right - 10;
+                                               var xTickEnd = this.options.position == "left" ? this.left + 10 : this.right;
+                                               
+                                               this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
+                                               this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
+                                               
+                                               this.ctx.beginPath();
+                                               
+                                               if (this.options.position == "left") {
+                                                       this.ctx.moveTo(this.right - 5, this.top);
+                                                       this.ctx.lineTo(this.right - 5, this.bottom);
+                                               } else {
+                                                       // On right, so draw vertical line on left size of axis block
+                                                       this.ctx.moveTo(this.left + 5, this.top);
+                                                       this.ctx.lineTo(this.left + 5, this.bottom);
+                                               }
+                                               
+                                               helpers.each(this.ticks, function(tick, index) {
+                                                       // Grid lines are horizontal
+                                                       var yValue = this.getPixelForValue(tick);
+                                                       
+                                                       if (tick === 0 || (!hasZero && index === 0)) {
+                                                               // Draw the 0 point specially or the bottom if there is no 0
+                                                               this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
+                                                               this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
+                                                               setContextLineSettings = true; // reset next time
+                                                       } else if (setContextLineSettings) {
+                                                               this.ctx.lineWidth = this.options.gridLines.lineWidth;
+                                                               this.ctx.strokeStyle = this.options.gridLines.color;
+                                                               setContextLineSettings = false;
+                                                       }
+                                                       
+                                                       // Draw the label area
+                                                       this.ctx.moveTo(xTickStart, yValue);
+                                                       this.ctx.lineTo(xTickEnd, yValue);
+                                                       
+                                                       // Draw the chart area
+                                                       if (this.options.gridLines.drawOnChartArea) {
+                                                               this.ctx.moveTo(chartArea.left, yValue);
+                                                               this.ctx.lineTo(chartArea.right, yValue);
+                                                       }
+                                               }, this);
+                                               
+                                               this.ctx.stroke();
+                                       }
+                                       
+                                       if (this.options.labels.show) {
+                                               // Draw the labels
+                                               
+                                               var labelStartX;
+                                               var maxLabelWidth = this.width - 25;
+                                               
+                                               if (this.options.position == "left") {
+                                                       labelStartX = this.left;
+                                               } else {
+                                                       // right side
+                                                       labelStartX = this.left + 20;
+                                               }
+                                               
+                                               this.ctx.textAlign = "left";
+                                               this.ctx.textBaseline = "middle";
+                                               this.ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
+                                               
+                                               helpers.each(this.labels, function(label, index) {
+                                                       var yValue = this.getPixelForValue(this.ticks[index]);
+                                                       this.ctx.fillText(label, labelStartX, yValue, maxLabelWidth);
+                                               }, this);
+                                       }
+                               }
+                       }
+               }
+       });
+       Chart.scales.registerScaleType("linear", LinearScale);
+}).call(this);
\ No newline at end of file