]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Change spaces to tabs throughout
authorEvert Timberg <evert.timberg@gmail.com>
Sat, 13 Jun 2015 14:15:21 +0000 (10:15 -0400)
committerEvert Timberg <evert.timberg@gmail.com>
Sat, 13 Jun 2015 14:15:21 +0000 (10:15 -0400)
src/charts/chart.bar.js
src/charts/chart.doughnut.js
src/core/core.animation.js
src/core/core.js
src/core/core.scale.js
src/core/core.tooltip.js
src/elements/element.arc.js
src/elements/element.line.js
src/elements/element.point.js
src/elements/element.rectangle.js

index e16161bb220bb9ea9ebc771c7de7f1cd2bd4c910..b77fe97fe836068a96f25db5d1cab47f2e557dcb 100644 (file)
 (function() {
-    "use strict";
-
-    var root = this,
-        Chart = root.Chart,
-        helpers = Chart.helpers;
-
-    var defaultConfig = {
-        hover: {
-            mode: "label"
-        },
-
-        scales: {
-            xAxes: [{
-                type: "category", // scatter should not use a dataset axis
-                display: true,
-                position: "bottom",
-                id: "x-axis-1", // need an ID so datasets can reference the scale
-
-                categorySpacing: 10,
-                spacing: 1,
-
-                // grid line settings
-                gridLines: {
-                    show: true,
-                    color: "rgba(0, 0, 0, 0.05)",
-                    lineWidth: 1,
-                    drawOnChartArea: true,
-                    drawTicks: true,
-                    zeroLineWidth: 1,
-                    zeroLineColor: "rgba(0,0,0,0.25)",
-                    offsetGridLines: true,
-                },
-
-                // label settings
-                labels: {
-                    show: true,
-                    template: "<%=value%>",
-                    fontSize: 12,
-                    fontStyle: "normal",
-                    fontColor: "#666",
-                    fontFamily: "Helvetica Neue",
-                },
-            }],
-            yAxes: [{
-                type: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
-                display: true,
-                position: "left",
-                id: "y-axis-1",
-
-                spacing: 1,
-
-                // grid line settings
-                gridLines: {
-                    show: true,
-                    color: "rgba(0, 0, 0, 0.05)",
-                    lineWidth: 1,
-                    drawOnChartArea: true,
-                    drawTicks: true, // draw ticks extending towards the label
-                    zeroLineWidth: 1,
-                    zeroLineColor: "rgba(0,0,0,0.25)",
-                },
-
-                // scale numbers
-                beginAtZero: false,
-                override: null,
-
-                // label settings
-                labels: {
-                    show: true,
-                    template: "<%=value%>",
-                    fontSize: 12,
-                    fontStyle: "normal",
-                    fontColor: "#666",
-                    fontFamily: "Helvetica Neue",
-                }
-            }],
-        },
-
-    };
-
-
-    Chart.Type.extend({
-        name: "Bar",
-        defaults: defaultConfig,
-        initialize: function() {
-
-            var _this = this;
-
-            // Events
-            helpers.bindEvents(this, this.options.events, this.events);
-
-            //Create a new bar for each piece of data
-            helpers.each(this.data.datasets, function(dataset, datasetIndex) {
-                dataset.metaData = [];
-                helpers.each(dataset.data, function(dataPoint, index) {
-                    dataset.metaData.push(new Chart.Rectangle({
-                        _chart: this.chart,
-                        _datasetIndex: datasetIndex,
-                        _index: index,
-                    }));
-                }, this);
-
-                // The bar chart only supports a single x axis because the x axis is always a dataset axis
-                dataset.xAxisID = this.options.scales.xAxes[0].id;
-
-                if (!dataset.yAxisID) {
-                    dataset.yAxisID = this.options.scales.yAxes[0].id;
-                }
-            }, this);
-
-            // Build and fit the scale. Needs to happen after the axis IDs have been set
-            this.buildScale();
-
-            // Create tooltip instance exclusively for this chart with some defaults.
-            this.tooltip = new Chart.Tooltip({
-                _chart: this.chart,
-                _data: this.data,
-                _options: this.options,
-            }, this);
-
-            // Need to fit scales before we reset elements. 
-            Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
-
-            // So that we animate from the baseline
-            this.resetElements();
-
-            // Update the chart with the latest data.
-            this.update();
-        },
-        resetElements: function() {
-            // Update the points
-            this.eachElement(function(bar, index, dataset, datasetIndex) {
-                var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID];
-                var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID];
-
-                var yScalePoint;
-
-                if (yScale.min < 0 && yScale.max < 0) {
-                    // all less than 0. use the top
-                    yScalePoint = yScale.getPixelForValue(yScale.max);
-                } else if (yScale.min > 0 && yScale.max > 0) {
-                    yScalePoint = yScale.getPixelForValue(yScale.min);
-                } else {
-                    yScalePoint = yScale.getPixelForValue(0);
-                }
-
-                helpers.extend(bar, {
-                    // Utility
-                    _chart: this.chart,
-                    _xScale: xScale,
-                    _yScale: yScale,
-                    _datasetIndex: datasetIndex,
-                    _index: index,
-
-                    // Desired view properties
-                    _model: {
-                        x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index),
-                        y: yScalePoint,
-
-                        // Appearance
-                        base: yScale.calculateBarBase(datasetIndex, index),
-                        width: xScale.calculateBarWidth(this.data.datasets.length),
-                        backgroundColor: bar.custom && bar.custom.backgroundColor ? bar.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].backgroundColor, index, this.options.elements.rectangle.backgroundColor),
-                        borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.rectangle.borderColor),
-                        borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.rectangle.borderWidth),
-
-                        // Tooltip
-                        label: this.data.labels[index],
-                        datasetLabel: this.data.datasets[datasetIndex].label,
-                    },
-                });
-                bar.pivot();
-            }, this);
-        },
-        update: function(animationDuration) {
-            // Update the scale sizes
-            Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
-
-            // Update the points
-            this.eachElement(function(bar, index, dataset, datasetIndex) {
-                var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID];
-                var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID];
-
-                helpers.extend(bar, {
-                    // Utility
-                    _chart: this.chart,
-                    _xScale: xScale,
-                    _yScale: yScale,
-                    _datasetIndex: datasetIndex,
-                    _index: index,
-
-                    // Desired view properties
-                    _model: {
-                        x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index),
-                        y: yScale.calculateBarY(datasetIndex, index),
-
-                        // Appearance
-                        base: yScale.calculateBarBase(datasetIndex, index),
-                        width: xScale.calculateBarWidth(this.data.datasets.length),
-                        backgroundColor: bar.custom && bar.custom.backgroundColor ? bar.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].backgroundColor, index, this.options.elements.rectangle.backgroundColor),
-                        borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.rectangle.borderColor),
-                        borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.rectangle.borderWidth),
-
-                        // Tooltip
-                        label: this.data.labels[index],
-                        datasetLabel: this.data.datasets[datasetIndex].label,
-                    },
-                });
-                bar.pivot();
-            }, this);
-
-
-            this.render(animationDuration);
-        },
-        buildScale: function(labels) {
-            var self = this;
-
-            // Map of scale ID to scale object so we can lookup later 
-            this.scales = {};
-
-            // Build the x axis. The line chart only supports a single x axis
-            var ScaleClass = Chart.scaleService.getScaleConstructor(this.options.scales.xAxes[0].type);
-            var xScale = new ScaleClass({
-                ctx: this.chart.ctx,
-                options: this.options.scales.xAxes[0],
-                id: this.options.scales.xAxes[0].id,
-                data: this.data,
-            });
-            this.scales[xScale.id] = xScale;
-
-            // Build up all the y scales
-            helpers.each(this.options.scales.yAxes, function(yAxisOptions) {
-                var ScaleClass = Chart.scaleService.getScaleConstructor(yAxisOptions.type);
-                var scale = new ScaleClass({
-                    ctx: this.chart.ctx,
-                    options: yAxisOptions,
-                    data: this.data,
-                    id: yAxisOptions.id,
-                });
-
-                this.scales[scale.id] = scale;
-            }, this);
-        },
-        draw: function(ease) {
-
-            var easingDecimal = ease || 1;
-            this.clear();
-
-            // Draw all the scales
-            helpers.each(this.scales, function(scale) {
-                scale.draw(this.chartArea);
-            }, this);
-
-            //Draw all the bars for each dataset
-            this.eachElement(function(bar, index, datasetIndex) {
-                bar.transition(easingDecimal).draw();
-            }, this);
-
-            // Finally draw the tooltip
-            this.tooltip.transition(easingDecimal).draw();
-        },
-        events: function(e) {
-
-
-
-            this.lastActive = this.lastActive || [];
-
-            // Find Active Elements
-            if (e.type == 'mouseout') {
-                this.active = [];
-            } else {
-                this.active = function() {
-                    switch (this.options.hover.mode) {
-                        case 'single':
-                            return this.getElementAtEvent(e);
-                        case 'label':
-                            return this.getElementsAtEvent(e);
-                        case 'dataset':
-                            return this.getDatasetAtEvent(e);
-                        default:
-                            return e;
-                    }
-                }.call(this);
-            }
-
-            // On Hover hook
-            if (this.options.hover.onHover) {
-                this.options.hover.onHover.call(this, this.active);
-            }
-
-            if (e.type == 'mouseup' || e.type == 'click') {
-                if (this.options.onClick) {
-                    this.options.onClick.call(this, e, this.active);
-                }
-            }
-
-            var dataset;
-            var index;
-            // Remove styling for last active (even if it may still be active)
-            if (this.lastActive.length) {
-                switch (this.options.hover.mode) {
-                    case 'single':
-                        dataset = this.data.datasets[this.lastActive[0]._datasetIndex];
-                        index = this.lastActive[0]._index;
-
-                        this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.rectangle.backgroundColor);
-                        this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.rectangle.borderColor);
-                        this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.rectangle.borderWidth);
-                        break;
-                    case 'label':
-                        for (var i = 0; i < this.lastActive.length; i++) {
-                            dataset = this.data.datasets[this.lastActive[i]._datasetIndex];
-                            index = this.lastActive[i]._index;
-
-                            this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.rectangle.backgroundColor);
-                            this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.rectangle.borderColor);
-                            this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.rectangle.borderWidth);
-                        }
-                        break;
-                    case 'dataset':
-                        break;
-                    default:
-                        // Don't change anything
-                }
-            }
-
-            // Built in hover styling
-            if (this.active.length && this.options.hover.mode) {
-                switch (this.options.hover.mode) {
-                    case 'single':
-                        dataset = this.data.datasets[this.active[0]._datasetIndex];
-                        index = this.active[0]._index;
-
-                        this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
-                        this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(this.active[0]._model.borderColor).saturate(0.5).darken(0.1).rgbString());
-                        this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[0]._model.borderWidth);
-                        break;
-                    case 'label':
-                        for (var i = 0; i < this.active.length; i++) {
-                            dataset = this.data.datasets[this.active[i]._datasetIndex];
-                            index = this.active[i]._index;
-
-                            this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
-                            this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(this.active[i]._model.borderColor).saturate(0.5).darken(0.1).rgbString());
-                            this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[i]._model.borderWidth);
-                        }
-                        break;
-                    case 'dataset':
-                        break;
-                    default:
-                        // Don't change anything
-                }
-            }
-
-
-            // Built in Tooltips
-            if (this.options.tooltips.enabled) {
-
-                // The usual updates
-                this.tooltip.initialize();
-
-                // Active
-                if (this.active.length) {
-                    this.tooltip._model.opacity = 1;
-
-                    helpers.extend(this.tooltip, {
-                        _active: this.active,
-                    });
-
-                    this.tooltip.update();
-                } else {
-                    // Inactive
-                    this.tooltip._model.opacity = 0;
-                }
-            }
-
-
-            this.tooltip.pivot();
-
-            // Hover animations
-            if (!this.animating) {
-                var changed;
-
-                helpers.each(this.active, function(element, index) {
-                    if (element !== this.lastActive[index]) {
-                        changed = true;
-                    }
-                }, this);
-
-                // If entering, leaving, or changing elements, animate the change via pivot
-                if ((!this.lastActive.length && this.active.length) ||
-                    (this.lastActive.length && !this.active.length) ||
-                    (this.lastActive.length && this.active.length && changed)) {
-
-                    this.stop();
-                    this.render(this.options.hoverAnimationDuration);
-                }
-            }
-
-            // Remember Last Active
-            this.lastActive = this.active;
-            return this;
-        },
-    });
+       "use strict";
+
+       var root = this,
+               Chart = root.Chart,
+               helpers = Chart.helpers;
+
+       var defaultConfig = {
+               hover: {
+                       mode: "label"
+               },
+
+               scales: {
+                       xAxes: [{
+                               type: "category", // scatter should not use a dataset axis
+                               display: true,
+                               position: "bottom",
+                               id: "x-axis-1", // need an ID so datasets can reference the scale
+
+                               categorySpacing: 10,
+                               spacing: 1,
+
+                               // grid line settings
+                               gridLines: {
+                                       show: true,
+                                       color: "rgba(0, 0, 0, 0.05)",
+                                       lineWidth: 1,
+                                       drawOnChartArea: true,
+                                       drawTicks: true,
+                                       zeroLineWidth: 1,
+                                       zeroLineColor: "rgba(0,0,0,0.25)",
+                                       offsetGridLines: true,
+                               },
+
+                               // label settings
+                               labels: {
+                                       show: true,
+                                       template: "<%=value%>",
+                                       fontSize: 12,
+                                       fontStyle: "normal",
+                                       fontColor: "#666",
+                                       fontFamily: "Helvetica Neue",
+                               },
+                       }],
+                       yAxes: [{
+                               type: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
+                               display: true,
+                               position: "left",
+                               id: "y-axis-1",
+
+                               spacing: 1,
+
+                               // grid line settings
+                               gridLines: {
+                                       show: true,
+                                       color: "rgba(0, 0, 0, 0.05)",
+                                       lineWidth: 1,
+                                       drawOnChartArea: true,
+                                       drawTicks: true, // draw ticks extending towards the label
+                                       zeroLineWidth: 1,
+                                       zeroLineColor: "rgba(0,0,0,0.25)",
+                               },
+
+                               // scale numbers
+                               beginAtZero: false,
+                               override: null,
+
+                               // label settings
+                               labels: {
+                                       show: true,
+                                       template: "<%=value%>",
+                                       fontSize: 12,
+                                       fontStyle: "normal",
+                                       fontColor: "#666",
+                                       fontFamily: "Helvetica Neue",
+                               }
+                       }],
+               },
+
+       };
+
+
+       Chart.Type.extend({
+               name: "Bar",
+               defaults: defaultConfig,
+               initialize: function() {
+
+                       var _this = this;
+
+                       // Events
+                       helpers.bindEvents(this, this.options.events, this.events);
+
+                       //Create a new bar for each piece of data
+                       helpers.each(this.data.datasets, function(dataset, datasetIndex) {
+                               dataset.metaData = [];
+                               helpers.each(dataset.data, function(dataPoint, index) {
+                                       dataset.metaData.push(new Chart.Rectangle({
+                                               _chart: this.chart,
+                                               _datasetIndex: datasetIndex,
+                                               _index: index,
+                                       }));
+                               }, this);
+
+                               // The bar chart only supports a single x axis because the x axis is always a dataset axis
+                               dataset.xAxisID = this.options.scales.xAxes[0].id;
+
+                               if (!dataset.yAxisID) {
+                                       dataset.yAxisID = this.options.scales.yAxes[0].id;
+                               }
+                       }, this);
+
+                       // Build and fit the scale. Needs to happen after the axis IDs have been set
+                       this.buildScale();
+
+                       // Create tooltip instance exclusively for this chart with some defaults.
+                       this.tooltip = new Chart.Tooltip({
+                               _chart: this.chart,
+                               _data: this.data,
+                               _options: this.options,
+                       }, this);
+
+                       // Need to fit scales before we reset elements. 
+                       Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
+
+                       // So that we animate from the baseline
+                       this.resetElements();
+
+                       // Update the chart with the latest data.
+                       this.update();
+               },
+               resetElements: function() {
+                       // Update the points
+                       this.eachElement(function(bar, index, dataset, datasetIndex) {
+                               var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID];
+                               var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID];
+
+                               var yScalePoint;
+
+                               if (yScale.min < 0 && yScale.max < 0) {
+                                       // all less than 0. use the top
+                                       yScalePoint = yScale.getPixelForValue(yScale.max);
+                               } else if (yScale.min > 0 && yScale.max > 0) {
+                                       yScalePoint = yScale.getPixelForValue(yScale.min);
+                               } else {
+                                       yScalePoint = yScale.getPixelForValue(0);
+                               }
+
+                               helpers.extend(bar, {
+                                       // Utility
+                                       _chart: this.chart,
+                                       _xScale: xScale,
+                                       _yScale: yScale,
+                                       _datasetIndex: datasetIndex,
+                                       _index: index,
+
+                                       // Desired view properties
+                                       _model: {
+                                               x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index),
+                                               y: yScalePoint,
+
+                                               // Appearance
+                                               base: yScale.calculateBarBase(datasetIndex, index),
+                                               width: xScale.calculateBarWidth(this.data.datasets.length),
+                                               backgroundColor: bar.custom && bar.custom.backgroundColor ? bar.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].backgroundColor, index, this.options.elements.rectangle.backgroundColor),
+                                               borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.rectangle.borderColor),
+                                               borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.rectangle.borderWidth),
+
+                                               // Tooltip
+                                               label: this.data.labels[index],
+                                               datasetLabel: this.data.datasets[datasetIndex].label,
+                                       },
+                               });
+                               bar.pivot();
+                       }, this);
+               },
+               update: function(animationDuration) {
+                       // Update the scale sizes
+                       Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
+
+                       // Update the points
+                       this.eachElement(function(bar, index, dataset, datasetIndex) {
+                               var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID];
+                               var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID];
+
+                               helpers.extend(bar, {
+                                       // Utility
+                                       _chart: this.chart,
+                                       _xScale: xScale,
+                                       _yScale: yScale,
+                                       _datasetIndex: datasetIndex,
+                                       _index: index,
+
+                                       // Desired view properties
+                                       _model: {
+                                               x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index),
+                                               y: yScale.calculateBarY(datasetIndex, index),
+
+                                               // Appearance
+                                               base: yScale.calculateBarBase(datasetIndex, index),
+                                               width: xScale.calculateBarWidth(this.data.datasets.length),
+                                               backgroundColor: bar.custom && bar.custom.backgroundColor ? bar.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].backgroundColor, index, this.options.elements.rectangle.backgroundColor),
+                                               borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.rectangle.borderColor),
+                                               borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.rectangle.borderWidth),
+
+                                               // Tooltip
+                                               label: this.data.labels[index],
+                                               datasetLabel: this.data.datasets[datasetIndex].label,
+                                       },
+                               });
+                               bar.pivot();
+                       }, this);
+
+
+                       this.render(animationDuration);
+               },
+               buildScale: function(labels) {
+                       var self = this;
+
+                       // Map of scale ID to scale object so we can lookup later 
+                       this.scales = {};
+
+                       // Build the x axis. The line chart only supports a single x axis
+                       var ScaleClass = Chart.scaleService.getScaleConstructor(this.options.scales.xAxes[0].type);
+                       var xScale = new ScaleClass({
+                               ctx: this.chart.ctx,
+                               options: this.options.scales.xAxes[0],
+                               id: this.options.scales.xAxes[0].id,
+                               data: this.data,
+                       });
+                       this.scales[xScale.id] = xScale;
+
+                       // Build up all the y scales
+                       helpers.each(this.options.scales.yAxes, function(yAxisOptions) {
+                               var ScaleClass = Chart.scaleService.getScaleConstructor(yAxisOptions.type);
+                               var scale = new ScaleClass({
+                                       ctx: this.chart.ctx,
+                                       options: yAxisOptions,
+                                       data: this.data,
+                                       id: yAxisOptions.id,
+                               });
+
+                               this.scales[scale.id] = scale;
+                       }, this);
+               },
+               draw: function(ease) {
+
+                       var easingDecimal = ease || 1;
+                       this.clear();
+
+                       // Draw all the scales
+                       helpers.each(this.scales, function(scale) {
+                               scale.draw(this.chartArea);
+                       }, this);
+
+                       //Draw all the bars for each dataset
+                       this.eachElement(function(bar, index, datasetIndex) {
+                               bar.transition(easingDecimal).draw();
+                       }, this);
+
+                       // Finally draw the tooltip
+                       this.tooltip.transition(easingDecimal).draw();
+               },
+               events: function(e) {
+
+
+
+                       this.lastActive = this.lastActive || [];
+
+                       // Find Active Elements
+                       if (e.type == 'mouseout') {
+                               this.active = [];
+                       } else {
+                               this.active = function() {
+                                       switch (this.options.hover.mode) {
+                                               case 'single':
+                                                       return this.getElementAtEvent(e);
+                                               case 'label':
+                                                       return this.getElementsAtEvent(e);
+                                               case 'dataset':
+                                                       return this.getDatasetAtEvent(e);
+                                               default:
+                                                       return e;
+                                       }
+                               }.call(this);
+                       }
+
+                       // On Hover hook
+                       if (this.options.hover.onHover) {
+                               this.options.hover.onHover.call(this, this.active);
+                       }
+
+                       if (e.type == 'mouseup' || e.type == 'click') {
+                               if (this.options.onClick) {
+                                       this.options.onClick.call(this, e, this.active);
+                               }
+                       }
+
+                       var dataset;
+                       var index;
+                       // Remove styling for last active (even if it may still be active)
+                       if (this.lastActive.length) {
+                               switch (this.options.hover.mode) {
+                                       case 'single':
+                                               dataset = this.data.datasets[this.lastActive[0]._datasetIndex];
+                                               index = this.lastActive[0]._index;
+
+                                               this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.rectangle.backgroundColor);
+                                               this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.rectangle.borderColor);
+                                               this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.rectangle.borderWidth);
+                                               break;
+                                       case 'label':
+                                               for (var i = 0; i < this.lastActive.length; i++) {
+                                                       dataset = this.data.datasets[this.lastActive[i]._datasetIndex];
+                                                       index = this.lastActive[i]._index;
+
+                                                       this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.rectangle.backgroundColor);
+                                                       this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.rectangle.borderColor);
+                                                       this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.rectangle.borderWidth);
+                                               }
+                                               break;
+                                       case 'dataset':
+                                               break;
+                                       default:
+                                               // Don't change anything
+                               }
+                       }
+
+                       // Built in hover styling
+                       if (this.active.length && this.options.hover.mode) {
+                               switch (this.options.hover.mode) {
+                                       case 'single':
+                                               dataset = this.data.datasets[this.active[0]._datasetIndex];
+                                               index = this.active[0]._index;
+
+                                               this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
+                                               this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(this.active[0]._model.borderColor).saturate(0.5).darken(0.1).rgbString());
+                                               this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[0]._model.borderWidth);
+                                               break;
+                                       case 'label':
+                                               for (var i = 0; i < this.active.length; i++) {
+                                                       dataset = this.data.datasets[this.active[i]._datasetIndex];
+                                                       index = this.active[i]._index;
+
+                                                       this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
+                                                       this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(this.active[i]._model.borderColor).saturate(0.5).darken(0.1).rgbString());
+                                                       this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[i]._model.borderWidth);
+                                               }
+                                               break;
+                                       case 'dataset':
+                                               break;
+                                       default:
+                                               // Don't change anything
+                               }
+                       }
+
+
+                       // Built in Tooltips
+                       if (this.options.tooltips.enabled) {
+
+                               // The usual updates
+                               this.tooltip.initialize();
+
+                               // Active
+                               if (this.active.length) {
+                                       this.tooltip._model.opacity = 1;
+
+                                       helpers.extend(this.tooltip, {
+                                               _active: this.active,
+                                       });
+
+                                       this.tooltip.update();
+                               } else {
+                                       // Inactive
+                                       this.tooltip._model.opacity = 0;
+                               }
+                       }
+
+
+                       this.tooltip.pivot();
+
+                       // Hover animations
+                       if (!this.animating) {
+                               var changed;
+
+                               helpers.each(this.active, function(element, index) {
+                                       if (element !== this.lastActive[index]) {
+                                               changed = true;
+                                       }
+                               }, this);
+
+                               // If entering, leaving, or changing elements, animate the change via pivot
+                               if ((!this.lastActive.length && this.active.length) ||
+                                       (this.lastActive.length && !this.active.length) ||
+                                       (this.lastActive.length && this.active.length && changed)) {
+
+                                       this.stop();
+                                       this.render(this.options.hoverAnimationDuration);
+                               }
+                       }
+
+                       // Remember Last Active
+                       this.lastActive = this.active;
+                       return this;
+               },
+       });
 
 
 }).call(this);
index 17b0802d819c19bc0d580ec316aac30cf2ca9438..47884f29831392138e105b35f224305a15a41fda 100644 (file)
 (function() {
-    "use strict";
-
-    var root = this,
-        Chart = root.Chart,
-        //Cache a local reference to Chart.helpers
-        helpers = Chart.helpers;
-
-    var defaultConfig = {
-
-        animation: {
-            //Boolean - Whether we animate the rotation of the Doughnut
-            animateRotate: true,
-
-            //Boolean - Whether we animate scaling the Doughnut from the centre
-            animateScale: false,
-        },
-
-        hover: {
-            mode: 'single'
-        },
-
-        //The percentage of the chart that we cut out of the middle.
-
-        cutoutPercentage: 50,
-
-    };
-
-    Chart.Type.extend({
-        //Passing in a name registers this chart in the Chart namespace
-        name: "Doughnut",
-        //Providing a defaults will also register the deafults in the chart namespace
-        defaults: defaultConfig,
-        //Initialize is fired when the chart is initialized - Data is passed in as a parameter
-        //Config is automatically merged by the core of Chart.js, and is available at this.options
-        initialize: function() {
-
-            //Set up tooltip events on the chart
-            helpers.bindEvents(this, this.options.events, this.events);
-
-            //Create a new bar for each piece of data
-            helpers.each(this.data.datasets, function(dataset, datasetIndex) {
-                dataset.metaData = [];
-                helpers.each(dataset.data, function(dataPoint, index) {
-                    dataset.metaData.push(new Chart.Arc({
-                        _chart: this.chart,
-                        _datasetIndex: datasetIndex,
-                        _index: index,
-                        _model: {}
-                    }));
-                }, this);
-            }, this);
-
-            // Create tooltip instance exclusively for this chart with some defaults.
-            this.tooltip = new Chart.Tooltip({
-                _chart: this.chart,
-                _data: this.data,
-                _options: this.options,
-            }, this);
-
-            this.resetElements();
-
-            // Update the chart with the latest data.
-            this.update();
-
-        },
-
-        calculateCircumference: function(dataset, value) {
-            if (dataset.total > 0) {
-                return (Math.PI * 2) * (value / dataset.total);
-            } else {
-                return 0;
-            }
-        },
-        resetElements: function() {
-            this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.arc.borderWidth / 2) / 2;
-            this.innerRadius = this.options.cutoutPercentage ? (this.outerRadius / 100) * (this.options.cutoutPercentage) : 1;
-            this.radiusLength = (this.outerRadius - this.innerRadius) / this.data.datasets.length;
-
-            // Update the points
-            helpers.each(this.data.datasets, function(dataset, datasetIndex) {
-                // So that calculateCircumference works
-                dataset.total = 0;
-                helpers.each(dataset.data, function(value) {
-                    dataset.total += Math.abs(value);
-                }, this);
-
-                dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex);
-                dataset.innerRadius = dataset.outerRadius - this.radiusLength;
-
-                helpers.each(dataset.metaData, function(slice, index) {
-                    helpers.extend(slice, {
-                        _model: {
-                            x: this.chart.width / 2,
-                            y: this.chart.height / 2,
-                            startAngle: Math.PI * -0.5, // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function
-                            circumference: (this.options.animation.animateRotate) ? 0 : this.calculateCircumference(metaSlice.value),
-                            outerRadius: (this.options.animation.animateScale) ? 0 : dataset.outerRadius,
-                            innerRadius: (this.options.animation.animateScale) ? 0 : dataset.innerRadius,
-
-                            backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor),
-                            hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor),
-                            borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth),
-                            borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor),
-
-                            label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index])
-                        },
-                    });
-
-                    slice.pivot();
-                }, this);
-
-            }, this);
-        },
-        update: function(animationDuration) {
-
-            this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.arc.borderWidth / 2) / 2;
-            this.innerRadius = this.options.cutoutPercentage ? (this.outerRadius / 100) * (this.options.cutoutPercentage) : 1;
-            this.radiusLength = (this.outerRadius - this.innerRadius) / this.data.datasets.length;
-
-
-            // Update the points
-            helpers.each(this.data.datasets, function(dataset, datasetIndex) {
-
-                dataset.total = 0;
-                helpers.each(dataset.data, function(value) {
-                    dataset.total += Math.abs(value);
-                }, this);
-
-
-                dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex);
-
-                dataset.innerRadius = dataset.outerRadius - this.radiusLength;
-
-                helpers.each(dataset.metaData, function(slice, index) {
-
-                    helpers.extend(slice, {
-                        // Utility
-                        _chart: this.chart,
-                        _datasetIndex: datasetIndex,
-                        _index: index,
-
-                        // Desired view properties
-                        _model: {
-                            x: this.chart.width / 2,
-                            y: this.chart.height / 2,
-                            circumference: this.calculateCircumference(dataset, dataset.data[index]),
-                            outerRadius: dataset.outerRadius,
-                            innerRadius: dataset.innerRadius,
-
-                            backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor),
-                            hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor),
-                            borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth),
-                            borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor),
-
-                            label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index])
-                        },
-                    });
-
-                    if (index === 0) {
-                        slice._model.startAngle = Math.PI * -0.5; // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function
-                    } else {
-                        slice._model.startAngle = dataset.metaData[index - 1]._model.endAngle;
-                    }
-
-                    slice._model.endAngle = slice._model.startAngle + slice._model.circumference;
-
-
-                    //Check to see if it's the last slice, if not get the next and update its start angle
-                    if (index < dataset.data.length - 1) {
-                        dataset.metaData[index + 1]._model.startAngle = slice._model.endAngle;
-                    }
-
-                    slice.pivot();
-                }, this);
-
-            }, this);
-
-            this.render(animationDuration);
-        },
-        draw: function(easeDecimal) {
-            easeDecimal = easeDecimal || 1;
-            this.clear();
-
-            this.eachElement(function(slice) {
-                slice.transition(easeDecimal).draw();
-            }, this);
-
-            this.tooltip.transition(easeDecimal).draw();
-        },
-        events: function(e) {
-
-            this.lastActive = this.lastActive || [];
-
-            // Find Active Elements
-            if (e.type == 'mouseout') {
-                this.active = [];
-            } else {
-
-                this.active = function() {
-                    switch (this.options.hover.mode) {
-                        case 'single':
-                            return this.getSliceAtEvent(e);
-                        case 'label':
-                            return this.getSlicesAtEvent(e);
-                        case 'dataset':
-                            return this.getDatasetAtEvent(e);
-                        default:
-                            return e;
-                    }
-                }.call(this);
-            }
-
-            // On Hover hook
-            if (this.options.hover.onHover) {
-                this.options.hover.onHover.call(this, this.active);
-            }
-
-            if (e.type == 'mouseup' || e.type == 'click') {
-                if (this.options.onClick) {
-                    this.options.onClick.call(this, e, this.active);
-                }
-            }
-
-            var dataset;
-            var index;
-            // Remove styling for last active (even if it may still be active)
-            if (this.lastActive.length) {
-                switch (this.options.hover.mode) {
-                    case 'single':
-                        dataset = this.data.datasets[this.lastActive[0]._datasetIndex];
-                        index = this.lastActive[0]._index;
-
-                        this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor);
-                        this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor);
-                        this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth);
-                        break;
-                    case 'label':
-                        for (var i = 0; i < this.lastActive.length; i++) {
-                            dataset = this.data.datasets[this.lastActive[i]._datasetIndex];
-                            index = this.lastActive[i]._index;
-
-                            this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor);
-                            this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor);
-                            this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth);
-                        }
-                        break;
-                    case 'dataset':
-                        break;
-                    default:
-                        // Don't change anything
-                }
-            }
-
-            // Built in hover styling
-            if (this.active.length && this.options.hover.mode) {
-                switch (this.options.hover.mode) {
-                    case 'single':
-                        dataset = this.data.datasets[this.active[0]._datasetIndex];
-                        index = this.active[0]._index;
-
-                        this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
-                        this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, this.active[0]._model.borderColor);
-                        this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, this.active[0]._model.borderWidth);
-                        break;
-                    case 'label':
-                        for (var i = 0; i < this.active.length; i++) {
-                            dataset = this.data.datasets[this.active[i]._datasetIndex];
-                            index = this.active[i]._index;
-
-                            this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
-                            this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, this.active[0]._model.borderColor);
-                            this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, this.active[i]._model.borderWidth);
-                        }
-                        break;
-                    case 'dataset':
-                        break;
-                    default:
-                        // Don't change anything
-                }
-            }
-
-
-            // Built in Tooltips
-            if (this.options.tooltips.enabled) {
-
-                // The usual updates
-                this.tooltip.initialize();
-
-                // Active
-                if (this.active.length) {
-                    this.tooltip._model.opacity = 1;
-
-                    helpers.extend(this.tooltip, {
-                        _active: this.active,
-                    });
-
-                    this.tooltip.update();
-                } else {
-                    // Inactive
-                    this.tooltip._model.opacity = 0;
-                }
-            }
-
-
-            // Hover animations
-            this.tooltip.pivot();
-
-            if (!this.animating) {
-                var changed;
-
-                helpers.each(this.active, function(element, index) {
-                    if (element !== this.lastActive[index]) {
-                        changed = true;
-                    }
-                }, this);
-
-                // If entering, leaving, or changing elements, animate the change via pivot
-                if ((!this.lastActive.length && this.active.length) ||
-                    (this.lastActive.length && !this.active.length) ||
-                    (this.lastActive.length && this.active.length && changed)) {
-
-                    this.stop();
-                    this.render(this.options.hover.animationDuration);
-                }
-            }
-
-            // Remember Last Active
-            this.lastActive = this.active;
-            return this;
-        },
-        getSliceAtEvent: function(e) {
-            var elements = [];
-
-            var location = helpers.getRelativePosition(e);
-
-            this.eachElement(function(slice, index) {
-                if (slice.inRange(location.x, location.y)) {
-                    elements.push(slice);
-                }
-            }, this);
-            return elements;
-        },
-        /*getSlicesAtEvent: function(e) {
-            var elements = [];
-
-            var location = helpers.getRelativePosition(e);
-
-            this.eachElement(function(slice, index) {
-                if (slice.inGroupRange(location.x, location.y)) {
-                    elements.push(slice);
-                }
-            }, this);
-            return elements;
-        },*/
-    });
-
-    Chart.types.Doughnut.extend({
-        name: "Pie",
-        defaults: helpers.merge(defaultConfig, {
-            cutoutPercentage: 0
-        })
-    });
+       "use strict";
+
+       var root = this,
+               Chart = root.Chart,
+               //Cache a local reference to Chart.helpers
+               helpers = Chart.helpers;
+
+       var defaultConfig = {
+
+               animation: {
+                       //Boolean - Whether we animate the rotation of the Doughnut
+                       animateRotate: true,
+
+                       //Boolean - Whether we animate scaling the Doughnut from the centre
+                       animateScale: false,
+               },
+
+               hover: {
+                       mode: 'single'
+               },
+
+               //The percentage of the chart that we cut out of the middle.
+
+               cutoutPercentage: 50,
+
+       };
+
+       Chart.Type.extend({
+               //Passing in a name registers this chart in the Chart namespace
+               name: "Doughnut",
+               //Providing a defaults will also register the deafults in the chart namespace
+               defaults: defaultConfig,
+               //Initialize is fired when the chart is initialized - Data is passed in as a parameter
+               //Config is automatically merged by the core of Chart.js, and is available at this.options
+               initialize: function() {
+
+                       //Set up tooltip events on the chart
+                       helpers.bindEvents(this, this.options.events, this.events);
+
+                       //Create a new bar for each piece of data
+                       helpers.each(this.data.datasets, function(dataset, datasetIndex) {
+                               dataset.metaData = [];
+                               helpers.each(dataset.data, function(dataPoint, index) {
+                                       dataset.metaData.push(new Chart.Arc({
+                                               _chart: this.chart,
+                                               _datasetIndex: datasetIndex,
+                                               _index: index,
+                                               _model: {}
+                                       }));
+                               }, this);
+                       }, this);
+
+                       // Create tooltip instance exclusively for this chart with some defaults.
+                       this.tooltip = new Chart.Tooltip({
+                               _chart: this.chart,
+                               _data: this.data,
+                               _options: this.options,
+                       }, this);
+
+                       this.resetElements();
+
+                       // Update the chart with the latest data.
+                       this.update();
+
+               },
+
+               calculateCircumference: function(dataset, value) {
+                       if (dataset.total > 0) {
+                               return (Math.PI * 2) * (value / dataset.total);
+                       } else {
+                               return 0;
+                       }
+               },
+               resetElements: function() {
+                       this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.arc.borderWidth / 2) / 2;
+                       this.innerRadius = this.options.cutoutPercentage ? (this.outerRadius / 100) * (this.options.cutoutPercentage) : 1;
+                       this.radiusLength = (this.outerRadius - this.innerRadius) / this.data.datasets.length;
+
+                       // Update the points
+                       helpers.each(this.data.datasets, function(dataset, datasetIndex) {
+                               // So that calculateCircumference works
+                               dataset.total = 0;
+                               helpers.each(dataset.data, function(value) {
+                                       dataset.total += Math.abs(value);
+                               }, this);
+
+                               dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex);
+                               dataset.innerRadius = dataset.outerRadius - this.radiusLength;
+
+                               helpers.each(dataset.metaData, function(slice, index) {
+                                       helpers.extend(slice, {
+                                               _model: {
+                                                       x: this.chart.width / 2,
+                                                       y: this.chart.height / 2,
+                                                       startAngle: Math.PI * -0.5, // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function
+                                                       circumference: (this.options.animation.animateRotate) ? 0 : this.calculateCircumference(metaSlice.value),
+                                                       outerRadius: (this.options.animation.animateScale) ? 0 : dataset.outerRadius,
+                                                       innerRadius: (this.options.animation.animateScale) ? 0 : dataset.innerRadius,
+
+                                                       backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor),
+                                                       hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor),
+                                                       borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth),
+                                                       borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor),
+
+                                                       label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index])
+                                               },
+                                       });
+
+                                       slice.pivot();
+                               }, this);
+
+                       }, this);
+               },
+               update: function(animationDuration) {
+
+                       this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.arc.borderWidth / 2) / 2;
+                       this.innerRadius = this.options.cutoutPercentage ? (this.outerRadius / 100) * (this.options.cutoutPercentage) : 1;
+                       this.radiusLength = (this.outerRadius - this.innerRadius) / this.data.datasets.length;
+
+
+                       // Update the points
+                       helpers.each(this.data.datasets, function(dataset, datasetIndex) {
+
+                               dataset.total = 0;
+                               helpers.each(dataset.data, function(value) {
+                                       dataset.total += Math.abs(value);
+                               }, this);
+
+
+                               dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex);
+
+                               dataset.innerRadius = dataset.outerRadius - this.radiusLength;
+
+                               helpers.each(dataset.metaData, function(slice, index) {
+
+                                       helpers.extend(slice, {
+                                               // Utility
+                                               _chart: this.chart,
+                                               _datasetIndex: datasetIndex,
+                                               _index: index,
+
+                                               // Desired view properties
+                                               _model: {
+                                                       x: this.chart.width / 2,
+                                                       y: this.chart.height / 2,
+                                                       circumference: this.calculateCircumference(dataset, dataset.data[index]),
+                                                       outerRadius: dataset.outerRadius,
+                                                       innerRadius: dataset.innerRadius,
+
+                                                       backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor),
+                                                       hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor),
+                                                       borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth),
+                                                       borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor),
+
+                                                       label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index])
+                                               },
+                                       });
+
+                                       if (index === 0) {
+                                               slice._model.startAngle = Math.PI * -0.5; // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function
+                                       } else {
+                                               slice._model.startAngle = dataset.metaData[index - 1]._model.endAngle;
+                                       }
+
+                                       slice._model.endAngle = slice._model.startAngle + slice._model.circumference;
+
+
+                                       //Check to see if it's the last slice, if not get the next and update its start angle
+                                       if (index < dataset.data.length - 1) {
+                                               dataset.metaData[index + 1]._model.startAngle = slice._model.endAngle;
+                                       }
+
+                                       slice.pivot();
+                               }, this);
+
+                       }, this);
+
+                       this.render(animationDuration);
+               },
+               draw: function(easeDecimal) {
+                       easeDecimal = easeDecimal || 1;
+                       this.clear();
+
+                       this.eachElement(function(slice) {
+                               slice.transition(easeDecimal).draw();
+                       }, this);
+
+                       this.tooltip.transition(easeDecimal).draw();
+               },
+               events: function(e) {
+
+                       this.lastActive = this.lastActive || [];
+
+                       // Find Active Elements
+                       if (e.type == 'mouseout') {
+                               this.active = [];
+                       } else {
+
+                               this.active = function() {
+                                       switch (this.options.hover.mode) {
+                                               case 'single':
+                                                       return this.getSliceAtEvent(e);
+                                               case 'label':
+                                                       return this.getSlicesAtEvent(e);
+                                               case 'dataset':
+                                                       return this.getDatasetAtEvent(e);
+                                               default:
+                                                       return e;
+                                       }
+                               }.call(this);
+                       }
+
+                       // On Hover hook
+                       if (this.options.hover.onHover) {
+                               this.options.hover.onHover.call(this, this.active);
+                       }
+
+                       if (e.type == 'mouseup' || e.type == 'click') {
+                               if (this.options.onClick) {
+                                       this.options.onClick.call(this, e, this.active);
+                               }
+                       }
+
+                       var dataset;
+                       var index;
+                       // Remove styling for last active (even if it may still be active)
+                       if (this.lastActive.length) {
+                               switch (this.options.hover.mode) {
+                                       case 'single':
+                                               dataset = this.data.datasets[this.lastActive[0]._datasetIndex];
+                                               index = this.lastActive[0]._index;
+
+                                               this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor);
+                                               this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor);
+                                               this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth);
+                                               break;
+                                       case 'label':
+                                               for (var i = 0; i < this.lastActive.length; i++) {
+                                                       dataset = this.data.datasets[this.lastActive[i]._datasetIndex];
+                                                       index = this.lastActive[i]._index;
+
+                                                       this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor);
+                                                       this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor);
+                                                       this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth);
+                                               }
+                                               break;
+                                       case 'dataset':
+                                               break;
+                                       default:
+                                               // Don't change anything
+                               }
+                       }
+
+                       // Built in hover styling
+                       if (this.active.length && this.options.hover.mode) {
+                               switch (this.options.hover.mode) {
+                                       case 'single':
+                                               dataset = this.data.datasets[this.active[0]._datasetIndex];
+                                               index = this.active[0]._index;
+
+                                               this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
+                                               this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, this.active[0]._model.borderColor);
+                                               this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, this.active[0]._model.borderWidth);
+                                               break;
+                                       case 'label':
+                                               for (var i = 0; i < this.active.length; i++) {
+                                                       dataset = this.data.datasets[this.active[i]._datasetIndex];
+                                                       index = this.active[i]._index;
+
+                                                       this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
+                                                       this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, this.active[0]._model.borderColor);
+                                                       this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, this.active[i]._model.borderWidth);
+                                               }
+                                               break;
+                                       case 'dataset':
+                                               break;
+                                       default:
+                                               // Don't change anything
+                               }
+                       }
+
+
+                       // Built in Tooltips
+                       if (this.options.tooltips.enabled) {
+
+                               // The usual updates
+                               this.tooltip.initialize();
+
+                               // Active
+                               if (this.active.length) {
+                                       this.tooltip._model.opacity = 1;
+
+                                       helpers.extend(this.tooltip, {
+                                               _active: this.active,
+                                       });
+
+                                       this.tooltip.update();
+                               } else {
+                                       // Inactive
+                                       this.tooltip._model.opacity = 0;
+                               }
+                       }
+
+
+                       // Hover animations
+                       this.tooltip.pivot();
+
+                       if (!this.animating) {
+                               var changed;
+
+                               helpers.each(this.active, function(element, index) {
+                                       if (element !== this.lastActive[index]) {
+                                               changed = true;
+                                       }
+                               }, this);
+
+                               // If entering, leaving, or changing elements, animate the change via pivot
+                               if ((!this.lastActive.length && this.active.length) ||
+                                       (this.lastActive.length && !this.active.length) ||
+                                       (this.lastActive.length && this.active.length && changed)) {
+
+                                       this.stop();
+                                       this.render(this.options.hover.animationDuration);
+                               }
+                       }
+
+                       // Remember Last Active
+                       this.lastActive = this.active;
+                       return this;
+               },
+               getSliceAtEvent: function(e) {
+                       var elements = [];
+
+                       var location = helpers.getRelativePosition(e);
+
+                       this.eachElement(function(slice, index) {
+                               if (slice.inRange(location.x, location.y)) {
+                                       elements.push(slice);
+                               }
+                       }, this);
+                       return elements;
+               },
+               /*getSlicesAtEvent: function(e) {
+                       var elements = [];
+
+                       var location = helpers.getRelativePosition(e);
+
+                       this.eachElement(function(slice, index) {
+                               if (slice.inGroupRange(location.x, location.y)) {
+                                       elements.push(slice);
+                               }
+                       }, this);
+                       return elements;
+               },*/
+       });
+
+       Chart.types.Doughnut.extend({
+               name: "Pie",
+               defaults: helpers.merge(defaultConfig, {
+                       cutoutPercentage: 0
+               })
+       });
 
 }).call(this);
index 1794febd7e9b93a00e8f3fe16f078674ffe64fc6..f8812b5d940d80022b29980a12307b35e9cfba28 100644 (file)
 
 (function() {
 
-    "use strict";
-
-    var root = this,
-        Chart = root.Chart,
-        helpers = Chart.helpers;
-
-    Chart.defaults.global.animation = {
-        duration: 1000,
-        easing: "easeOutQuart",
-        onProgress: function() {},
-        onComplete: function() {},
-    };
-
-    Chart.Animation = Chart.Element.extend({
-        currentStep: null, // the current animation step
-        numSteps: 60, // default number of steps
-        easing: "", // the easing to use for this animation
-        render: null, // render function used by the animation service
-
-        onAnimationProgress: null, // user specified callback to fire on each step of the animation 
-        onAnimationComplete: null, // user specified callback to fire when the animation finishes
-    });
-
-    Chart.animationService = {
-        frameDuration: 17,
-        animations: [],
-        dropFrames: 0,
-        addAnimation: function(chartInstance, animationObject, duration) {
-
-            if (!duration) {
-                chartInstance.animating = true;
-            }
-
-            for (var index = 0; index < this.animations.length; ++index) {
-                if (this.animations[index].chartInstance === chartInstance) {
-                    // replacing an in progress animation
-                    this.animations[index].animationObject = animationObject;
-                    return;
-                }
-            }
-
-            this.animations.push({
-                chartInstance: chartInstance,
-                animationObject: animationObject
-            });
-
-            // If there are no animations queued, manually kickstart a digest, for lack of a better word
-            if (this.animations.length == 1) {
-                helpers.requestAnimFrame.call(window, this.digestWrapper);
-            }
-        },
-        // Cancel the animation for a given chart instance
-        cancelAnimation: function(chartInstance) {
-            var index = helpers.findNextWhere(this.animations, function(animationWrapper) {
-                return animationWrapper.chartInstance === chartInstance;
-            });
-
-            if (index) {
-                this.animations.splice(index, 1);
-                chartInstance.animating = false;
-            }
-        },
-        // calls startDigest with the proper context
-        digestWrapper: function() {
-            Chart.animationService.startDigest.call(Chart.animationService);
-        },
-        startDigest: function() {
-
-            var startTime = Date.now();
-            var framesToDrop = 0;
-
-            if (this.dropFrames > 1) {
-                framesToDrop = Math.floor(this.dropFrames);
-                this.dropFrames -= framesToDrop;
-            }
-
-            for (var i = 0; i < this.animations.length; i++) {
-
-                if (this.animations[i].animationObject.currentStep === null) {
-                    this.animations[i].animationObject.currentStep = 0;
-                }
-
-                this.animations[i].animationObject.currentStep += 1 + framesToDrop;
-                if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) {
-                    this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps;
-                }
-
-                this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject);
-
-                if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps) {
-                    // executed the last frame. Remove the animation.
-                    this.animations[i].chartInstance.animating = false;
-                    this.animations.splice(i, 1);
-                    // Keep the index in place to offset the splice
-                    i--;
-                }
-            }
-
-            var endTime = Date.now();
-            var delay = endTime - startTime - this.frameDuration;
-            var frameDelay = delay / this.frameDuration;
-
-            if (frameDelay > 1) {
-                this.dropFrames += frameDelay;
-            }
-
-            // Do we have more stuff to animate?
-            if (this.animations.length > 0) {
-                helpers.requestAnimFrame.call(window, this.digestWrapper);
-            }
-        }
-    };
+       "use strict";
+
+       var root = this,
+               Chart = root.Chart,
+               helpers = Chart.helpers;
+
+       Chart.defaults.global.animation = {
+               duration: 1000,
+               easing: "easeOutQuart",
+               onProgress: function() {},
+               onComplete: function() {},
+       };
+
+       Chart.Animation = Chart.Element.extend({
+               currentStep: null, // the current animation step
+               numSteps: 60, // default number of steps
+               easing: "", // the easing to use for this animation
+               render: null, // render function used by the animation service
+
+               onAnimationProgress: null, // user specified callback to fire on each step of the animation 
+               onAnimationComplete: null, // user specified callback to fire when the animation finishes
+       });
+
+       Chart.animationService = {
+               frameDuration: 17,
+               animations: [],
+               dropFrames: 0,
+               addAnimation: function(chartInstance, animationObject, duration) {
+
+                       if (!duration) {
+                               chartInstance.animating = true;
+                       }
+
+                       for (var index = 0; index < this.animations.length; ++index) {
+                               if (this.animations[index].chartInstance === chartInstance) {
+                                       // replacing an in progress animation
+                                       this.animations[index].animationObject = animationObject;
+                                       return;
+                               }
+                       }
+
+                       this.animations.push({
+                               chartInstance: chartInstance,
+                               animationObject: animationObject
+                       });
+
+                       // If there are no animations queued, manually kickstart a digest, for lack of a better word
+                       if (this.animations.length == 1) {
+                               helpers.requestAnimFrame.call(window, this.digestWrapper);
+                       }
+               },
+               // Cancel the animation for a given chart instance
+               cancelAnimation: function(chartInstance) {
+                       var index = helpers.findNextWhere(this.animations, function(animationWrapper) {
+                               return animationWrapper.chartInstance === chartInstance;
+                       });
+
+                       if (index) {
+                               this.animations.splice(index, 1);
+                               chartInstance.animating = false;
+                       }
+               },
+               // calls startDigest with the proper context
+               digestWrapper: function() {
+                       Chart.animationService.startDigest.call(Chart.animationService);
+               },
+               startDigest: function() {
+
+                       var startTime = Date.now();
+                       var framesToDrop = 0;
+
+                       if (this.dropFrames > 1) {
+                               framesToDrop = Math.floor(this.dropFrames);
+                               this.dropFrames -= framesToDrop;
+                       }
+
+                       for (var i = 0; i < this.animations.length; i++) {
+
+                               if (this.animations[i].animationObject.currentStep === null) {
+                                       this.animations[i].animationObject.currentStep = 0;
+                               }
+
+                               this.animations[i].animationObject.currentStep += 1 + framesToDrop;
+                               if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) {
+                                       this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps;
+                               }
+
+                               this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject);
+
+                               if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps) {
+                                       // executed the last frame. Remove the animation.
+                                       this.animations[i].chartInstance.animating = false;
+                                       this.animations.splice(i, 1);
+                                       // Keep the index in place to offset the splice
+                                       i--;
+                               }
+                       }
+
+                       var endTime = Date.now();
+                       var delay = endTime - startTime - this.frameDuration;
+                       var frameDelay = delay / this.frameDuration;
+
+                       if (frameDelay > 1) {
+                               this.dropFrames += frameDelay;
+                       }
+
+                       // Do we have more stuff to animate?
+                       if (this.animations.length > 0) {
+                               helpers.requestAnimFrame.call(window, this.digestWrapper);
+                       }
+               }
+       };
 
 }).call(this);
index b2dcc004598b45d92c7a8b0f1dd9bf59f950902c..c01dfbaa955d7170149c711b8cdc5001f1cbab94 100755 (executable)
 
 (function() {
 
-    "use strict";
-
-    //Declare root variable - window in the browser, global on the server
-    var root = this,
-        previous = root.Chart;
-
-    //Occupy the global variable of Chart, and create a simple base class
-    var Chart = function(context) {
-        var chart = this;
-
-        // Support a jQuery'd canvas element
-        if (context.length && context[0].getContext) {
-            context = context[0];
-        }
-
-        // Support a canvas domnode
-        if (context.getContext) {
-            context = context.getContext("2d");
-        }
-
-        this.canvas = context.canvas;
-
-        this.ctx = context;
-
-        //Variables global to the chart
-        var computeDimension = function(element, dimension) {
-            if (element['offset' + dimension]) {
-                return element['offset' + dimension];
-            } else {
-                return document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
-            }
-        };
-
-        var width = this.width = computeDimension(context.canvas, 'Width') || context.canvas.width;
-        var height = this.height = computeDimension(context.canvas, 'Height') || context.canvas.height;
-
-        // Firefox requires this to work correctly
-        context.canvas.width = width;
-        context.canvas.height = height;
-
-        width = this.width = context.canvas.width;
-        height = this.height = context.canvas.height;
-        this.aspectRatio = this.width / this.height;
-        //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
-        helpers.retinaScale(this);
-
-        return this;
-    };
-
-    var defaultColor = 'rgba(0,0,0,0.1)';
-
-    //Globally expose the defaults to allow for user updating/changing
-    Chart.defaults = {
-        global: {
-            responsive: true,
-            maintainAspectRatio: true,
-            events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "touchend"],
-            hover: {
-                onHover: null,
-                mode: 'single',
-                animationDuration: 400,
-            },
-            onClick: null,
-            defaultColor: defaultColor,
-
-            // Element defaults defined in element extensions
-            elements: {}
-        },
-    };
-
-    //Create a dictionary of chart types, to allow for extension of existing types
-    Chart.types = {};
-
-    //Global Chart helpers object for utility methods and classes
-    var helpers = Chart.helpers = {};
-
-    //-- Basic js utility methods
-    var each = helpers.each = function(loopable, callback, self) {
-            var additionalArgs = Array.prototype.slice.call(arguments, 3);
-            // Check to see if null or undefined firstly.
-            if (loopable) {
-                if (loopable.length === +loopable.length) {
-                    var i;
-                    for (i = 0; i < loopable.length; i++) {
-                        callback.apply(self, [loopable[i], i].concat(additionalArgs));
-                    }
-                } else {
-                    for (var item in loopable) {
-                        callback.apply(self, [loopable[item], item].concat(additionalArgs));
-                    }
-                }
-            }
-        },
-        clone = helpers.clone = function(obj) {
-            var objClone = {};
-            each(obj, function(value, key) {
-                if (obj.hasOwnProperty(key)) {
-                    if (typeof value === 'object' && value !== null) {
-                        objClone[key] = clone(value);
-                    } else {
-                        objClone[key] = value;
-                    }
-                }
-            });
-            return objClone;
-        },
-        extend = helpers.extend = function(base) {
-            each(Array.prototype.slice.call(arguments, 1), function(extensionObject) {
-                each(extensionObject, function(value, key) {
-                    if (extensionObject.hasOwnProperty(key)) {
-                        base[key] = value;
-                    }
-                });
-            });
-            return base;
-        },
-        merge = helpers.merge = function(base, master) {
-            //Merge properties in left object over to a shallow clone of object right.
-            var args = Array.prototype.slice.call(arguments, 0);
-            args.unshift({});
-            return extend.apply(null, args);
-        },
-        // Need a special merge function to chart configs since they are now grouped
-        configMerge = helpers.configMerge = function(_base) {
-            var base = clone(_base);
-            helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) {
-                helpers.each(extension, function(value, key) {
-                    if (extension.hasOwnProperty(key)) {
-                        if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) {
-                            // In this case we have an array of objects replacing another array. Rather than doing a strict replace,
-                            // merge. This allows easy scale option merging
-                            var baseArray = base[key];
-
-                            helpers.each(value, function(valueObj, index) {
-                                if (index < baseArray.length) {
-                                    baseArray[index] = helpers.configMerge(baseArray[index], valueObj);
-                                } else {
-                                    baseArray.push(valueObj); // nothing to merge
-                                }
-                            });
-                        } else if (base.hasOwnProperty(key) && typeof base[key] == "object" && base[key] !== null && typeof value == "object") {
-                            // If we are overwriting an object with an object, do a merge of the properties.
-                            base[key] = helpers.configMerge(base[key], value);
-                        } else {
-                            // can just overwrite the value in this case
-                            base[key] = value;
-                        }
-                    }
-                });
-            });
-
-            return base;
-        },
-        getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) {
-            if (!value) {
-                return defaultValue;
-            }
-
-            if (helpers.isArray(value) && index < value.length) {
-                return value[index];
-            }
-
-            return value;
-        },
-        indexOf = helpers.indexOf = function(arrayToSearch, item) {
-            if (Array.prototype.indexOf) {
-                return arrayToSearch.indexOf(item);
-            } else {
-                for (var i = 0; i < arrayToSearch.length; i++) {
-                    if (arrayToSearch[i] === item) return i;
-                }
-                return -1;
-            }
-        },
-        where = helpers.where = function(collection, filterCallback) {
-            var filtered = [];
-
-            helpers.each(collection, function(item) {
-                if (filterCallback(item)) {
-                    filtered.push(item);
-                }
-            });
-
-            return filtered;
-        },
-        findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
-            // Default to start of the array
-            if (!startIndex) {
-                startIndex = -1;
-            }
-            for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
-                var currentItem = arrayToSearch[i];
-                if (filterCallback(currentItem)) {
-                    return currentItem;
-                }
-            }
-        },
-        findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
-            // Default to end of the array
-            if (!startIndex) {
-                startIndex = arrayToSearch.length;
-            }
-            for (var i = startIndex - 1; i >= 0; i--) {
-                var currentItem = arrayToSearch[i];
-                if (filterCallback(currentItem)) {
-                    return currentItem;
-                }
-            }
-        },
-        inherits = helpers.inherits = function(extensions) {
-            //Basic javascript inheritance based on the model created in Backbone.js
-            var parent = this;
-            var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() {
-                return parent.apply(this, arguments);
-            };
-
-            var Surrogate = function() {
-                this.constructor = ChartElement;
-            };
-            Surrogate.prototype = parent.prototype;
-            ChartElement.prototype = new Surrogate();
-
-            ChartElement.extend = inherits;
-
-            if (extensions) extend(ChartElement.prototype, extensions);
-
-            ChartElement.__super__ = parent.prototype;
-
-            return ChartElement;
-        },
-        noop = helpers.noop = function() {},
-        uid = helpers.uid = (function() {
-            var id = 0;
-            return function() {
-                return "chart-" + id++;
-            };
-        })(),
-        warn = helpers.warn = function(str) {
-            //Method for warning of errors
-            if (window.console && typeof window.console.warn === "function") console.warn(str);
-        },
-        amd = helpers.amd = (typeof define === 'function' && define.amd),
-        //-- Math methods
-        isNumber = helpers.isNumber = function(n) {
-            return !isNaN(parseFloat(n)) && isFinite(n);
-        },
-        max = helpers.max = function(array) {
-            return Math.max.apply(Math, array);
-        },
-        min = helpers.min = function(array) {
-            return Math.min.apply(Math, array);
-        },
-        sign = helpers.sign = function(x) {
-            if (Math.sign) {
-                return Math.sign(x);
-            } else {
-                x = +x; // convert to a number
-                if (x === 0 || isNaN(x)) {
-                    return x;
-                }
-                return x > 0 ? 1 : -1;
-            }
-        },
-        log10 = helpers.log10 = function(x) {
-            if (Math.log10) {
-                return Math.log10(x)
-            } else {
-                return Math.log(x) / Math.LN10;
-            }
-        },
-        cap = helpers.cap = function(valueToCap, maxValue, minValue) {
-            if (isNumber(maxValue)) {
-                if (valueToCap > maxValue) {
-                    return maxValue;
-                }
-            } else if (isNumber(minValue)) {
-                if (valueToCap < minValue) {
-                    return minValue;
-                }
-            }
-            return valueToCap;
-        },
-        getDecimalPlaces = helpers.getDecimalPlaces = function(num) {
-            if (num % 1 !== 0 && isNumber(num)) {
-                var s = num.toString();
-                if (s.indexOf("e-") < 0) {
-                    // no exponent, e.g. 0.01
-                    return s.split(".")[1].length;
-                } else if (s.indexOf(".") < 0) {
-                    // no decimal point, e.g. 1e-9
-                    return parseInt(s.split("e-")[1]);
-                } else {
-                    // exponent and decimal point, e.g. 1.23e-9
-                    var parts = s.split(".")[1].split("e-");
-                    return parts[0].length + parseInt(parts[1]);
-                }
-            } else {
-                return 0;
-            }
-        },
-        toRadians = helpers.toRadians = function(degrees) {
-            return degrees * (Math.PI / 180);
-        },
-        toDegrees = helpers.toDegrees = function(radians) {
-            return radians * (180 / Math.PI);
-        },
-        // Gets the angle from vertical upright to the point about a centre.
-        getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
-            var distanceFromXCenter = anglePoint.x - centrePoint.x,
-                distanceFromYCenter = anglePoint.y - centrePoint.y,
-                radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
-
-            var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
-
-            if (angle < (-0.5 * Math.PI)) {
-                angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
-            }
-
-            return {
-                angle: angle,
-                distance: radialDistanceFromCenter
-            };
-        },
-        aliasPixel = helpers.aliasPixel = function(pixelWidth) {
-            return (pixelWidth % 2 === 0) ? 0 : 0.5;
-        },
-        splineCurve = helpers.splineCurve = function(FirstPoint, MiddlePoint, AfterPoint, t) {
-            //Props to Rob Spencer at scaled innovation for his post on splining between points
-            //http://scaledinnovation.com/analytics/splines/aboutSplines.html
-            var d01 = Math.sqrt(Math.pow(MiddlePoint.x - FirstPoint.x, 2) + Math.pow(MiddlePoint.y - FirstPoint.y, 2)),
-                d12 = Math.sqrt(Math.pow(AfterPoint.x - MiddlePoint.x, 2) + Math.pow(AfterPoint.y - MiddlePoint.y, 2)),
-                fa = t * d01 / (d01 + d12), // scaling factor for triangle Ta
-                fb = t * d12 / (d01 + d12);
-            return {
-                previous: {
-                    x: MiddlePoint.x - fa * (AfterPoint.x - FirstPoint.x),
-                    y: MiddlePoint.y - fa * (AfterPoint.y - FirstPoint.y)
-                },
-                next: {
-                    x: MiddlePoint.x + fb * (AfterPoint.x - FirstPoint.x),
-                    y: MiddlePoint.y + fb * (AfterPoint.y - FirstPoint.y)
-                }
-            };
-        },
-        calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val) {
-            return Math.floor(Math.log(val) / Math.LN10);
-        },
-        calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly) {
-
-            //Set a minimum step of two - a point at the top of the graph, and a point at the base
-            var minSteps = 2,
-                maxSteps = Math.floor(drawingSize / (textSize * 1.5)),
-                skipFitting = (minSteps >= maxSteps);
-
-            var maxValue = max(valuesArray),
-                minValue = min(valuesArray);
-
-            // We need some degree of seperation here to calculate the scales if all the values are the same
-            // Adding/minusing 0.5 will give us a range of 1.
-            if (maxValue === minValue) {
-                maxValue += 0.5;
-                // So we don't end up with a graph with a negative start value if we've said always start from zero
-                if (minValue >= 0.5 && !startFromZero) {
-                    minValue -= 0.5;
-                } else {
-                    // Make up a whole number above the values
-                    maxValue += 0.5;
-                }
-            }
-
-            var valueRange = Math.abs(maxValue - minValue),
-                rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
-                graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
-                graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
-                graphRange = graphMax - graphMin,
-                stepValue = Math.pow(10, rangeOrderOfMagnitude),
-                numberOfSteps = Math.round(graphRange / stepValue);
-
-            //If we have more space on the graph we'll use it to give more definition to the data
-            while ((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
-                if (numberOfSteps > maxSteps) {
-                    stepValue *= 2;
-                    numberOfSteps = Math.round(graphRange / stepValue);
-                    // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
-                    if (numberOfSteps % 1 !== 0) {
-                        skipFitting = true;
-                    }
-                }
-                //We can fit in double the amount of scale points on the scale
-                else {
-                    //If user has declared ints only, and the step value isn't a decimal
-                    if (integersOnly && rangeOrderOfMagnitude >= 0) {
-                        //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
-                        if (stepValue / 2 % 1 === 0) {
-                            stepValue /= 2;
-                            numberOfSteps = Math.round(graphRange / stepValue);
-                        }
-                        //If it would make it a float break out of the loop
-                        else {
-                            break;
-                        }
-                    }
-                    //If the scale doesn't have to be an int, make the scale more granular anyway.
-                    else {
-                        stepValue /= 2;
-                        numberOfSteps = Math.round(graphRange / stepValue);
-                    }
-
-                }
-            }
-
-            if (skipFitting) {
-                numberOfSteps = minSteps;
-                stepValue = graphRange / numberOfSteps;
-            }
-            return {
-                steps: numberOfSteps,
-                stepValue: stepValue,
-                min: graphMin,
-                max: graphMin + (numberOfSteps * stepValue)
-            };
-
-        },
-        // Implementation of the nice number algorithm used in determining where axis labels will go
-        niceNum = helpers.niceNum = function(range, round) {
-            var exponent = Math.floor(helpers.log10(range));
-            var fraction = range / Math.pow(10, exponent);
-            var niceFraction;
-
-            if (round) {
-                if (fraction < 1.5) {
-                    niceFraction = 1;
-                } else if (fraction < 3) {
-                    niceFraction = 2;
-                } else if (fraction < 7) {
-                    niceFraction = 5;
-                } else {
-                    niceFraction = 10;
-                }
-            } else {
-                if (fraction <= 1.0) {
-                    niceFraction = 1;
-                } else if (fraction <= 2) {
-                    niceFraction = 2;
-                } else if (fraction <= 5) {
-                    niceFraction = 5;
-                } else {
-                    niceFraction = 10;
-                }
-            }
-
-            return niceFraction * Math.pow(10, exponent);
-        },
-        /* jshint ignore:start */
-        // Blows up jshint errors based on the new Function constructor
-        //Templating methods
-        //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
-        template = helpers.template = function(templateString, valuesObject) {
-
-            // If templateString is function rather than string-template - call the function for valuesObject
-
-            if (templateString instanceof Function) {
-                return templateString(valuesObject);
-            }
-
-            var cache = {};
-
-            function tmpl(str, data) {
-                // Figure out if we're getting a template, or if we need to
-                // load the template - and be sure to cache the result.
-                var fn = !/\W/.test(str) ?
-                    cache[str] = cache[str] :
-
-                    // Generate a reusable function that will serve as a template
-                    // generator (and which will be cached).
-                    new Function("obj",
-                        "var p=[],print=function(){p.push.apply(p,arguments);};" +
-
-                        // Introduce the data as local variables using with(){}
-                        "with(obj){p.push('" +
-
-                        // Convert the template into pure JavaScript
-                        str
-                        .replace(/[\r\t\n]/g, " ")
-                        .split("<%").join("\t")
-                        .replace(/((^|%>)[^\t]*)'/g, "$1\r")
-                        .replace(/\t=(.*?)%>/g, "',$1,'")
-                        .split("\t").join("');")
-                        .split("%>").join("p.push('")
-                        .split("\r").join("\\'") +
-                        "');}return p.join('');"
-                    );
-
-                // Provide some basic currying to the user
-                return data ? fn(data) : fn;
-            }
-            return tmpl(templateString, valuesObject);
-        },
-        /* jshint ignore:end */
-        generateLabels = helpers.generateLabels = function(templateString, numberOfSteps, graphMin, stepValue) {
-            var labelsArray = new Array(numberOfSteps);
-            if (templateString) {
-                each(labelsArray, function(val, index) {
-                    labelsArray[index] = template(templateString, {
-                        value: (graphMin + (stepValue * (index + 1)))
-                    });
-                });
-            }
-            return labelsArray;
-        },
-        //--Animation methods
-        //Easing functions adapted from Robert Penner's easing equations
-        //http://www.robertpenner.com/easing/
-        easingEffects = helpers.easingEffects = {
-            linear: function(t) {
-                return t;
-            },
-            easeInQuad: function(t) {
-                return t * t;
-            },
-            easeOutQuad: function(t) {
-                return -1 * t * (t - 2);
-            },
-            easeInOutQuad: function(t) {
-                if ((t /= 1 / 2) < 1) {
-                    return 1 / 2 * t * t;
-                }
-                return -1 / 2 * ((--t) * (t - 2) - 1);
-            },
-            easeInCubic: function(t) {
-                return t * t * t;
-            },
-            easeOutCubic: function(t) {
-                return 1 * ((t = t / 1 - 1) * t * t + 1);
-            },
-            easeInOutCubic: function(t) {
-                if ((t /= 1 / 2) < 1) {
-                    return 1 / 2 * t * t * t;
-                }
-                return 1 / 2 * ((t -= 2) * t * t + 2);
-            },
-            easeInQuart: function(t) {
-                return t * t * t * t;
-            },
-            easeOutQuart: function(t) {
-                return -1 * ((t = t / 1 - 1) * t * t * t - 1);
-            },
-            easeInOutQuart: function(t) {
-                if ((t /= 1 / 2) < 1) {
-                    return 1 / 2 * t * t * t * t;
-                }
-                return -1 / 2 * ((t -= 2) * t * t * t - 2);
-            },
-            easeInQuint: function(t) {
-                return 1 * (t /= 1) * t * t * t * t;
-            },
-            easeOutQuint: function(t) {
-                return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
-            },
-            easeInOutQuint: function(t) {
-                if ((t /= 1 / 2) < 1) {
-                    return 1 / 2 * t * t * t * t * t;
-                }
-                return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
-            },
-            easeInSine: function(t) {
-                return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
-            },
-            easeOutSine: function(t) {
-                return 1 * Math.sin(t / 1 * (Math.PI / 2));
-            },
-            easeInOutSine: function(t) {
-                return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
-            },
-            easeInExpo: function(t) {
-                return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
-            },
-            easeOutExpo: function(t) {
-                return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
-            },
-            easeInOutExpo: function(t) {
-                if (t === 0) {
-                    return 0;
-                }
-                if (t === 1) {
-                    return 1;
-                }
-                if ((t /= 1 / 2) < 1) {
-                    return 1 / 2 * Math.pow(2, 10 * (t - 1));
-                }
-                return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
-            },
-            easeInCirc: function(t) {
-                if (t >= 1) {
-                    return t;
-                }
-                return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
-            },
-            easeOutCirc: function(t) {
-                return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
-            },
-            easeInOutCirc: function(t) {
-                if ((t /= 1 / 2) < 1) {
-                    return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
-                }
-                return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
-            },
-            easeInElastic: function(t) {
-                var s = 1.70158;
-                var p = 0;
-                var a = 1;
-                if (t === 0) {
-                    return 0;
-                }
-                if ((t /= 1) == 1) {
-                    return 1;
-                }
-                if (!p) {
-                    p = 1 * 0.3;
-                }
-                if (a < Math.abs(1)) {
-                    a = 1;
-                    s = p / 4;
-                } else {
-                    s = p / (2 * Math.PI) * Math.asin(1 / a);
-                }
-                return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
-            },
-            easeOutElastic: function(t) {
-                var s = 1.70158;
-                var p = 0;
-                var a = 1;
-                if (t === 0) {
-                    return 0;
-                }
-                if ((t /= 1) == 1) {
-                    return 1;
-                }
-                if (!p) {
-                    p = 1 * 0.3;
-                }
-                if (a < Math.abs(1)) {
-                    a = 1;
-                    s = p / 4;
-                } else {
-                    s = p / (2 * Math.PI) * Math.asin(1 / a);
-                }
-                return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
-            },
-            easeInOutElastic: function(t) {
-                var s = 1.70158;
-                var p = 0;
-                var a = 1;
-                if (t === 0) {
-                    return 0;
-                }
-                if ((t /= 1 / 2) == 2) {
-                    return 1;
-                }
-                if (!p) {
-                    p = 1 * (0.3 * 1.5);
-                }
-                if (a < Math.abs(1)) {
-                    a = 1;
-                    s = p / 4;
-                } else {
-                    s = p / (2 * Math.PI) * Math.asin(1 / a);
-                }
-                if (t < 1) {
-                    return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
-                }
-                return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
-            },
-            easeInBack: function(t) {
-                var s = 1.70158;
-                return 1 * (t /= 1) * t * ((s + 1) * t - s);
-            },
-            easeOutBack: function(t) {
-                var s = 1.70158;
-                return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
-            },
-            easeInOutBack: function(t) {
-                var s = 1.70158;
-                if ((t /= 1 / 2) < 1) {
-                    return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
-                }
-                return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
-            },
-            easeInBounce: function(t) {
-                return 1 - easingEffects.easeOutBounce(1 - t);
-            },
-            easeOutBounce: function(t) {
-                if ((t /= 1) < (1 / 2.75)) {
-                    return 1 * (7.5625 * t * t);
-                } else if (t < (2 / 2.75)) {
-                    return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
-                } else if (t < (2.5 / 2.75)) {
-                    return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
-                } else {
-                    return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
-                }
-            },
-            easeInOutBounce: function(t) {
-                if (t < 1 / 2) {
-                    return easingEffects.easeInBounce(t * 2) * 0.5;
-                }
-                return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
-            }
-        },
-        //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
-        requestAnimFrame = helpers.requestAnimFrame = (function() {
-            return window.requestAnimationFrame ||
-                window.webkitRequestAnimationFrame ||
-                window.mozRequestAnimationFrame ||
-                window.oRequestAnimationFrame ||
-                window.msRequestAnimationFrame ||
-                function(callback) {
-                    return window.setTimeout(callback, 1000 / 60);
-                };
-        })(),
-        cancelAnimFrame = helpers.cancelAnimFrame = (function() {
-            return window.cancelAnimationFrame ||
-                window.webkitCancelAnimationFrame ||
-                window.mozCancelAnimationFrame ||
-                window.oCancelAnimationFrame ||
-                window.msCancelAnimationFrame ||
-                function(callback) {
-                    return window.clearTimeout(callback, 1000 / 60);
-                };
-        })(),
-        animationLoop = helpers.animationLoop = function(callback, totalSteps, easingString, onProgress, onComplete, chartInstance) {
-
-            var currentStep = 0,
-                easingFunction = easingEffects[easingString] || easingEffects.linear;
-
-            var animationFrame = function() {
-                currentStep++;
-                var stepDecimal = currentStep / totalSteps;
-                var easeDecimal = easingFunction(stepDecimal);
-
-                callback.call(chartInstance, easeDecimal, stepDecimal, currentStep);
-                onProgress.call(chartInstance, easeDecimal, stepDecimal);
-                if (currentStep < totalSteps) {
-                    chartInstance.animationFrame = requestAnimFrame(animationFrame);
-                } else {
-                    onComplete.apply(chartInstance);
-                }
-            };
-            requestAnimFrame(animationFrame);
-        },
-        //-- DOM methods
-        getRelativePosition = helpers.getRelativePosition = function(evt) {
-            var mouseX, mouseY;
-            var e = evt.originalEvent || evt,
-                canvas = evt.currentTarget || evt.srcElement,
-                boundingRect = canvas.getBoundingClientRect();
-
-            if (e.touches) {
-                mouseX = e.touches[0].clientX - boundingRect.left;
-                mouseY = e.touches[0].clientY - boundingRect.top;
-
-            } else {
-                mouseX = e.clientX - boundingRect.left;
-                mouseY = e.clientY - boundingRect.top;
-            }
-
-            return {
-                x: mouseX,
-                y: mouseY
-            };
-
-        },
-        addEvent = helpers.addEvent = function(node, eventType, method) {
-            if (node.addEventListener) {
-                node.addEventListener(eventType, method);
-            } else if (node.attachEvent) {
-                node.attachEvent("on" + eventType, method);
-            } else {
-                node["on" + eventType] = method;
-            }
-        },
-        removeEvent = helpers.removeEvent = function(node, eventType, handler) {
-            if (node.removeEventListener) {
-                node.removeEventListener(eventType, handler, false);
-            } else if (node.detachEvent) {
-                node.detachEvent("on" + eventType, handler);
-            } else {
-                node["on" + eventType] = noop;
-            }
-        },
-        bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) {
-            // Create the events object if it's not already present
-            if (!chartInstance.events) chartInstance.events = {};
-
-            each(arrayOfEvents, function(eventName) {
-                chartInstance.events[eventName] = function() {
-                    handler.apply(chartInstance, arguments);
-                };
-                addEvent(chartInstance.chart.canvas, eventName, chartInstance.events[eventName]);
-            });
-        },
-        unbindEvents = helpers.unbindEvents = function(chartInstance, arrayOfEvents) {
-            each(arrayOfEvents, function(handler, eventName) {
-                removeEvent(chartInstance.chart.canvas, eventName, handler);
-            });
-        },
-        getMaximumWidth = helpers.getMaximumWidth = function(domNode) {
-            var container = domNode.parentNode,
-                padding = parseInt(getStyle(container, 'padding-left')) + parseInt(getStyle(container, 'padding-right'));
-            // TODO = check cross browser stuff with this.
-            return container.clientWidth - padding;
-        },
-        getMaximumHeight = helpers.getMaximumHeight = function(domNode) {
-            var container = domNode.parentNode,
-                padding = parseInt(getStyle(container, 'padding-bottom')) + parseInt(getStyle(container, 'padding-top'));
-            // TODO = check cross browser stuff with this.
-            return container.clientHeight - padding;
-        },
-        getStyle = helpers.getStyle = function(el, property) {
-            return el.currentStyle ?
-                el.currentStyle[property] :
-                document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
-        },
-        getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
-        retinaScale = helpers.retinaScale = function(chart) {
-            var ctx = chart.ctx,
-                width = chart.canvas.width,
-                height = chart.canvas.height;
-
-            if (window.devicePixelRatio) {
-                ctx.canvas.style.width = width + "px";
-                ctx.canvas.style.height = height + "px";
-                ctx.canvas.height = height * window.devicePixelRatio;
-                ctx.canvas.width = width * window.devicePixelRatio;
-                ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
-            }
-        },
-        //-- Canvas methods
-        clear = helpers.clear = function(chart) {
-            chart.ctx.clearRect(0, 0, chart.width, chart.height);
-        },
-        fontString = helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
-            return fontStyle + " " + pixelSize + "px " + fontFamily;
-        },
-        longestText = helpers.longestText = function(ctx, font, arrayOfStrings) {
-            ctx.font = font;
-            var longest = 0;
-            each(arrayOfStrings, function(string) {
-                var textWidth = ctx.measureText(string).width;
-                longest = (textWidth > longest) ? textWidth : longest;
-            });
-            return longest;
-        },
-        drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) {
-            ctx.beginPath();
-            ctx.moveTo(x + radius, y);
-            ctx.lineTo(x + width - radius, y);
-            ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
-            ctx.lineTo(x + width, y + height - radius);
-            ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
-            ctx.lineTo(x + radius, y + height);
-            ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
-            ctx.lineTo(x, y + radius);
-            ctx.quadraticCurveTo(x, y, x + radius, y);
-            ctx.closePath();
-        },
-        color = helpers.color = function(color) {
-            if (!window.Color) {
-                console.log('Color.js not found!');
-                return color;
-            }
-            return window.Color(color);
-        },
-        isArray = helpers.isArray = function(obj) {
-            if (!Array.isArray) {
-                return Object.prototype.toString.call(arg) === '[object Array]';
-            }
-            return Array.isArray(obj);
-        };
-
-    //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
-    //Destroy method on the chart will remove the instance of the chart from this reference.
-    Chart.instances = {};
-
-    Chart.Type = function(config, instance) {
-        this.data = config.data;
-        this.options = config.options;
-        this.chart = instance;
-        this.id = uid();
-        //Add the chart instance to the global namespace
-        Chart.instances[this.id] = this;
-
-        // Initialize is always called when a chart type is created
-        // By default it is a no op, but it should be extended
-        if (this.options.responsive) {
-            this.resize();
-        }
-        this.initialize.call(this);
-    };
-
-    //Core methods that'll be a part of every chart type
-    extend(Chart.Type.prototype, {
-        initialize: function() {
-            return this;
-        },
-        clear: function() {
-            clear(this.chart);
-            return this;
-        },
-        stop: function() {
-            // Stops any current animation loop occuring
-            Chart.animationService.cancelAnimation(this);
-            return this;
-        },
-        resize: function() {
-            this.stop();
-            var canvas = this.chart.canvas,
-                newWidth = getMaximumWidth(this.chart.canvas),
-                newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
-
-            canvas.width = this.chart.width = newWidth;
-            canvas.height = this.chart.height = newHeight;
-
-            retinaScale(this.chart);
-
-            return this;
-        },
-        redraw: noop,
-        render: function(duration) {
-
-            if (this.options.animation.duration !== 0 || duration) {
-                var animation = new Chart.Animation();
-                animation.numSteps = (duration || this.options.animation.duration) / 16.66; //60 fps
-                animation.easing = this.options.animation.easing;
-
-                // render function
-                animation.render = function(chartInstance, animationObject) {
-                    var easingFunction = helpers.easingEffects[animationObject.easing];
-                    var stepDecimal = animationObject.currentStep / animationObject.numSteps;
-                    var easeDecimal = easingFunction(stepDecimal);
-
-                    chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);
-                };
-
-                // user events
-                animation.onAnimationProgress = this.options.onAnimationProgress;
-                animation.onAnimationComplete = this.options.onAnimationComplete;
-
-                Chart.animationService.addAnimation(this, animation, duration);
-            } else {
-                this.draw();
-                this.options.onAnimationComplete.call(this);
-            }
-            return this;
-        },
-        eachElement: function(callback) {
-            helpers.each(this.data.datasets, function(dataset, datasetIndex) {
-                helpers.each(dataset.metaData, callback, this, dataset.metaData, datasetIndex);
-            }, this);
-        },
-        eachValue: function(callback) {
-            helpers.each(this.data.datasets, function(dataset, datasetIndex) {
-                helpers.each(dataset.data, callback, this, datasetIndex);
-            }, this);
-        },
-        eachDataset: function(callback) {
-            helpers.each(this.data.datasets, callback, this);
-        },
-        getElementsAtEvent: function(e) {
-            var elementsArray = [],
-                eventPosition = helpers.getRelativePosition(e),
-                datasetIterator = function(dataset) {
-                    elementsArray.push(dataset.metaData[elementIndex]);
-                },
-                elementIndex;
-
-            for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; datasetIndex++) {
-                for (elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; elementIndex++) {
-                    if (this.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) {
-                        helpers.each(this.data.datasets, datasetIterator);
-                    }
-                }
-            }
-
-            return elementsArray.length ? elementsArray : [];
-        },
-        // Get the single element that was clicked on
-        // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was drawn
-        getElementAtEvent: function(e) {
-            var element = [];
-            var eventPosition = helpers.getRelativePosition(e);
-
-            for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; ++datasetIndex) {
-                for (var elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; ++elementIndex) {
-                    if (this.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) {
-                        element.push(this.data.datasets[datasetIndex].metaData[elementIndex]);
-                        return element;
-                    }
-                }
-            }
-
-            return [];
-        },
-        generateLegend: function() {
-            return template(this.options.legendTemplate, this);
-        },
-        destroy: function() {
-            this.clear();
-            unbindEvents(this, this.events);
-            var canvas = this.chart.canvas;
-
-            // Reset canvas height/width attributes starts a fresh with the canvas context
-            canvas.width = this.chart.width;
-            canvas.height = this.chart.height;
-
-            // < IE9 doesn't support removeProperty
-            if (canvas.style.removeProperty) {
-                canvas.style.removeProperty('width');
-                canvas.style.removeProperty('height');
-            } else {
-                canvas.style.removeAttribute('width');
-                canvas.style.removeAttribute('height');
-            }
-
-            delete Chart.instances[this.id];
-        },
-        toBase64Image: function() {
-            return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
-        }
-    });
-
-    Chart.Type.extend = function(extensions) {
-
-        var parent = this;
-
-        var ChartType = function() {
-            return parent.apply(this, arguments);
-        };
-
-        //Copy the prototype object of the this class
-        ChartType.prototype = clone(parent.prototype);
-        //Now overwrite some of the properties in the base class with the new extensions
-        extend(ChartType.prototype, extensions);
-
-        ChartType.extend = Chart.Type.extend;
-
-        if (extensions.name || parent.prototype.name) {
-
-            var chartName = extensions.name || parent.prototype.name;
-            //Assign any potential default values of the new chart type
-
-            //If none are defined, we'll use a clone of the chart type this is being extended from.
-            //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
-            //doesn't define some defaults of their own.
-
-            var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
-
-            Chart.defaults[chartName] = helpers.configMerge(baseDefaults, extensions.defaults);
-
-            Chart.types[chartName] = ChartType;
-
-            //Register this new chart type in the Chart prototype
-            Chart.prototype[chartName] = function(config) {
-                config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[chartName], config.options || {});
-                return new ChartType(config, this);
-            };
-        } else {
-            warn("Name not provided for this chart, so it hasn't been registered");
-        }
-        return parent;
-    };
-
-    Chart.Element = function(configuration) {
-        extend(this, configuration);
-        this.initialize.apply(this, arguments);
-    };
-    extend(Chart.Element.prototype, {
-        initialize: function() {},
-        pivot: function() {
-            if (!this._view) {
-                this._view = clone(this._model);
-            }
-            this._start = clone(this._view);
-            return this;
-        },
-        transition: function(ease) {
-            if (!this._view) {
-                this._view = clone(this._model);
-            }
-            if (!this._start) {
-                this.pivot();
-            }
-
-            each(this._model, function(value, key) {
-
-                if (key[0] === '_' || !this._model.hasOwnProperty(key)) {
-                    // Only non-underscored properties
-                }
-
-                // Init if doesn't exist
-                else if (!this._view[key]) {
-                    if (typeof value === 'number') {
-                        this._view[key] = value * ease;
-                    } else {
-                        this._view[key] = value || null;
-                    }
-                }
-
-                // No unnecessary computations
-                else if (this._model[key] === this._view[key]) {
-                    // It's the same! Woohoo!
-                }
-
-                // Color transitions if possible
-                else if (typeof value === 'string') {
-                    try {
-                        var color = helpers.color(this._start[key]).mix(helpers.color(this._model[key]), ease);
-                        this._view[key] = color.rgbString();
-                    } catch (err) {
-                        this._view[key] = value;
-                    }
-                }
-                // Number transitions
-                else if (typeof value === 'number') {
-                    var startVal = this._start[key] !== undefined ? this._start[key] : 0;
-                    this._view[key] = ((this._model[key] - startVal) * ease) + startVal;
-                }
-                // Everything else
-                else {
-                    this._view[key] = value;
-                }
-
-            }, this);
-
-            if (ease === 1) {
-                delete this._start;
-            }
-            return this;
-        },
-        tooltipPosition: function() {
-            return {
-                x: this._model.x,
-                y: this._model.y
-            };
-        },
-        hasValue: function() {
-            return isNumber(this._model.x) && isNumber(this._model.y);
-        }
-    });
-
-    Chart.Element.extend = inherits;
-
-
-    // Attach global event to resize each chart instance when the browser resizes
-    helpers.addEvent(window, "resize", (function() {
-        // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
-        var timeout;
-        return function() {
-            clearTimeout(timeout);
-            timeout = setTimeout(function() {
-                each(Chart.instances, function(instance) {
-                    // If the responsive flag is set in the chart instance config
-                    // Cascade the resize event down to the chart.
-                    if (instance.options.responsive) {
-                        instance.resize();
-                        instance.update();
-                    }
-                });
-            }, 50);
-        };
-    })());
-
-
-    if (amd) {
-        define(function() {
-            return Chart;
-        });
-    } else if (typeof module === 'object' && module.exports) {
-        module.exports = Chart;
-    }
-
-    root.Chart = Chart;
-
-    Chart.noConflict = function() {
-        root.Chart = previous;
-        return Chart;
-    };
+       "use strict";
+
+       //Declare root variable - window in the browser, global on the server
+       var root = this,
+               previous = root.Chart;
+
+       //Occupy the global variable of Chart, and create a simple base class
+       var Chart = function(context) {
+               var chart = this;
+
+               // Support a jQuery'd canvas element
+               if (context.length && context[0].getContext) {
+                       context = context[0];
+               }
+
+               // Support a canvas domnode
+               if (context.getContext) {
+                       context = context.getContext("2d");
+               }
+
+               this.canvas = context.canvas;
+
+               this.ctx = context;
+
+               //Variables global to the chart
+               var computeDimension = function(element, dimension) {
+                       if (element['offset' + dimension]) {
+                               return element['offset' + dimension];
+                       } else {
+                               return document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
+                       }
+               };
+
+               var width = this.width = computeDimension(context.canvas, 'Width') || context.canvas.width;
+               var height = this.height = computeDimension(context.canvas, 'Height') || context.canvas.height;
+
+               // Firefox requires this to work correctly
+               context.canvas.width = width;
+               context.canvas.height = height;
+
+               width = this.width = context.canvas.width;
+               height = this.height = context.canvas.height;
+               this.aspectRatio = this.width / this.height;
+               //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
+               helpers.retinaScale(this);
+
+               return this;
+       };
+
+       var defaultColor = 'rgba(0,0,0,0.1)';
+
+       //Globally expose the defaults to allow for user updating/changing
+       Chart.defaults = {
+               global: {
+                       responsive: true,
+                       maintainAspectRatio: true,
+                       events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "touchend"],
+                       hover: {
+                               onHover: null,
+                               mode: 'single',
+                               animationDuration: 400,
+                       },
+                       onClick: null,
+                       defaultColor: defaultColor,
+
+                       // Element defaults defined in element extensions
+                       elements: {}
+               },
+       };
+
+       //Create a dictionary of chart types, to allow for extension of existing types
+       Chart.types = {};
+
+       //Global Chart helpers object for utility methods and classes
+       var helpers = Chart.helpers = {};
+
+       //-- Basic js utility methods
+       var each = helpers.each = function(loopable, callback, self) {
+                       var additionalArgs = Array.prototype.slice.call(arguments, 3);
+                       // Check to see if null or undefined firstly.
+                       if (loopable) {
+                               if (loopable.length === +loopable.length) {
+                                       var i;
+                                       for (i = 0; i < loopable.length; i++) {
+                                               callback.apply(self, [loopable[i], i].concat(additionalArgs));
+                                       }
+                               } else {
+                                       for (var item in loopable) {
+                                               callback.apply(self, [loopable[item], item].concat(additionalArgs));
+                                       }
+                               }
+                       }
+               },
+               clone = helpers.clone = function(obj) {
+                       var objClone = {};
+                       each(obj, function(value, key) {
+                               if (obj.hasOwnProperty(key)) {
+                                       if (typeof value === 'object' && value !== null) {
+                                               objClone[key] = clone(value);
+                                       } else {
+                                               objClone[key] = value;
+                                       }
+                               }
+                       });
+                       return objClone;
+               },
+               extend = helpers.extend = function(base) {
+                       each(Array.prototype.slice.call(arguments, 1), function(extensionObject) {
+                               each(extensionObject, function(value, key) {
+                                       if (extensionObject.hasOwnProperty(key)) {
+                                               base[key] = value;
+                                       }
+                               });
+                       });
+                       return base;
+               },
+               merge = helpers.merge = function(base, master) {
+                       //Merge properties in left object over to a shallow clone of object right.
+                       var args = Array.prototype.slice.call(arguments, 0);
+                       args.unshift({});
+                       return extend.apply(null, args);
+               },
+               // Need a special merge function to chart configs since they are now grouped
+               configMerge = helpers.configMerge = function(_base) {
+                       var base = clone(_base);
+                       helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) {
+                               helpers.each(extension, function(value, key) {
+                                       if (extension.hasOwnProperty(key)) {
+                                               if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) {
+                                                       // In this case we have an array of objects replacing another array. Rather than doing a strict replace,
+                                                       // merge. This allows easy scale option merging
+                                                       var baseArray = base[key];
+
+                                                       helpers.each(value, function(valueObj, index) {
+                                                               if (index < baseArray.length) {
+                                                                       baseArray[index] = helpers.configMerge(baseArray[index], valueObj);
+                                                               } else {
+                                                                       baseArray.push(valueObj); // nothing to merge
+                                                               }
+                                                       });
+                                               } else if (base.hasOwnProperty(key) && typeof base[key] == "object" && base[key] !== null && typeof value == "object") {
+                                                       // If we are overwriting an object with an object, do a merge of the properties.
+                                                       base[key] = helpers.configMerge(base[key], value);
+                                               } else {
+                                                       // can just overwrite the value in this case
+                                                       base[key] = value;
+                                               }
+                                       }
+                               });
+                       });
+
+                       return base;
+               },
+               getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) {
+                       if (!value) {
+                               return defaultValue;
+                       }
+
+                       if (helpers.isArray(value) && index < value.length) {
+                               return value[index];
+                       }
+
+                       return value;
+               },
+               indexOf = helpers.indexOf = function(arrayToSearch, item) {
+                       if (Array.prototype.indexOf) {
+                               return arrayToSearch.indexOf(item);
+                       } else {
+                               for (var i = 0; i < arrayToSearch.length; i++) {
+                                       if (arrayToSearch[i] === item) return i;
+                               }
+                               return -1;
+                       }
+               },
+               where = helpers.where = function(collection, filterCallback) {
+                       var filtered = [];
+
+                       helpers.each(collection, function(item) {
+                               if (filterCallback(item)) {
+                                       filtered.push(item);
+                               }
+                       });
+
+                       return filtered;
+               },
+               findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
+                       // Default to start of the array
+                       if (!startIndex) {
+                               startIndex = -1;
+                       }
+                       for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
+                               var currentItem = arrayToSearch[i];
+                               if (filterCallback(currentItem)) {
+                                       return currentItem;
+                               }
+                       }
+               },
+               findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
+                       // Default to end of the array
+                       if (!startIndex) {
+                               startIndex = arrayToSearch.length;
+                       }
+                       for (var i = startIndex - 1; i >= 0; i--) {
+                               var currentItem = arrayToSearch[i];
+                               if (filterCallback(currentItem)) {
+                                       return currentItem;
+                               }
+                       }
+               },
+               inherits = helpers.inherits = function(extensions) {
+                       //Basic javascript inheritance based on the model created in Backbone.js
+                       var parent = this;
+                       var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() {
+                               return parent.apply(this, arguments);
+                       };
+
+                       var Surrogate = function() {
+                               this.constructor = ChartElement;
+                       };
+                       Surrogate.prototype = parent.prototype;
+                       ChartElement.prototype = new Surrogate();
+
+                       ChartElement.extend = inherits;
+
+                       if (extensions) extend(ChartElement.prototype, extensions);
+
+                       ChartElement.__super__ = parent.prototype;
+
+                       return ChartElement;
+               },
+               noop = helpers.noop = function() {},
+               uid = helpers.uid = (function() {
+                       var id = 0;
+                       return function() {
+                               return "chart-" + id++;
+                       };
+               })(),
+               warn = helpers.warn = function(str) {
+                       //Method for warning of errors
+                       if (window.console && typeof window.console.warn === "function") console.warn(str);
+               },
+               amd = helpers.amd = (typeof define === 'function' && define.amd),
+               //-- Math methods
+               isNumber = helpers.isNumber = function(n) {
+                       return !isNaN(parseFloat(n)) && isFinite(n);
+               },
+               max = helpers.max = function(array) {
+                       return Math.max.apply(Math, array);
+               },
+               min = helpers.min = function(array) {
+                       return Math.min.apply(Math, array);
+               },
+               sign = helpers.sign = function(x) {
+                       if (Math.sign) {
+                               return Math.sign(x);
+                       } else {
+                               x = +x; // convert to a number
+                               if (x === 0 || isNaN(x)) {
+                                       return x;
+                               }
+                               return x > 0 ? 1 : -1;
+                       }
+               },
+               log10 = helpers.log10 = function(x) {
+                       if (Math.log10) {
+                               return Math.log10(x)
+                       } else {
+                               return Math.log(x) / Math.LN10;
+                       }
+               },
+               cap = helpers.cap = function(valueToCap, maxValue, minValue) {
+                       if (isNumber(maxValue)) {
+                               if (valueToCap > maxValue) {
+                                       return maxValue;
+                               }
+                       } else if (isNumber(minValue)) {
+                               if (valueToCap < minValue) {
+                                       return minValue;
+                               }
+                       }
+                       return valueToCap;
+               },
+               getDecimalPlaces = helpers.getDecimalPlaces = function(num) {
+                       if (num % 1 !== 0 && isNumber(num)) {
+                               var s = num.toString();
+                               if (s.indexOf("e-") < 0) {
+                                       // no exponent, e.g. 0.01
+                                       return s.split(".")[1].length;
+                               } else if (s.indexOf(".") < 0) {
+                                       // no decimal point, e.g. 1e-9
+                                       return parseInt(s.split("e-")[1]);
+                               } else {
+                                       // exponent and decimal point, e.g. 1.23e-9
+                                       var parts = s.split(".")[1].split("e-");
+                                       return parts[0].length + parseInt(parts[1]);
+                               }
+                       } else {
+                               return 0;
+                       }
+               },
+               toRadians = helpers.toRadians = function(degrees) {
+                       return degrees * (Math.PI / 180);
+               },
+               toDegrees = helpers.toDegrees = function(radians) {
+                       return radians * (180 / Math.PI);
+               },
+               // Gets the angle from vertical upright to the point about a centre.
+               getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
+                       var distanceFromXCenter = anglePoint.x - centrePoint.x,
+                               distanceFromYCenter = anglePoint.y - centrePoint.y,
+                               radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
+
+                       var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
+
+                       if (angle < (-0.5 * Math.PI)) {
+                               angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
+                       }
+
+                       return {
+                               angle: angle,
+                               distance: radialDistanceFromCenter
+                       };
+               },
+               aliasPixel = helpers.aliasPixel = function(pixelWidth) {
+                       return (pixelWidth % 2 === 0) ? 0 : 0.5;
+               },
+               splineCurve = helpers.splineCurve = function(FirstPoint, MiddlePoint, AfterPoint, t) {
+                       //Props to Rob Spencer at scaled innovation for his post on splining between points
+                       //http://scaledinnovation.com/analytics/splines/aboutSplines.html
+                       var d01 = Math.sqrt(Math.pow(MiddlePoint.x - FirstPoint.x, 2) + Math.pow(MiddlePoint.y - FirstPoint.y, 2)),
+                               d12 = Math.sqrt(Math.pow(AfterPoint.x - MiddlePoint.x, 2) + Math.pow(AfterPoint.y - MiddlePoint.y, 2)),
+                               fa = t * d01 / (d01 + d12), // scaling factor for triangle Ta
+                               fb = t * d12 / (d01 + d12);
+                       return {
+                               previous: {
+                                       x: MiddlePoint.x - fa * (AfterPoint.x - FirstPoint.x),
+                                       y: MiddlePoint.y - fa * (AfterPoint.y - FirstPoint.y)
+                               },
+                               next: {
+                                       x: MiddlePoint.x + fb * (AfterPoint.x - FirstPoint.x),
+                                       y: MiddlePoint.y + fb * (AfterPoint.y - FirstPoint.y)
+                               }
+                       };
+               },
+               calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val) {
+                       return Math.floor(Math.log(val) / Math.LN10);
+               },
+               calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly) {
+
+                       //Set a minimum step of two - a point at the top of the graph, and a point at the base
+                       var minSteps = 2,
+                               maxSteps = Math.floor(drawingSize / (textSize * 1.5)),
+                               skipFitting = (minSteps >= maxSteps);
+
+                       var maxValue = max(valuesArray),
+                               minValue = min(valuesArray);
+
+                       // We need some degree of seperation here to calculate the scales if all the values are the same
+                       // Adding/minusing 0.5 will give us a range of 1.
+                       if (maxValue === minValue) {
+                               maxValue += 0.5;
+                               // So we don't end up with a graph with a negative start value if we've said always start from zero
+                               if (minValue >= 0.5 && !startFromZero) {
+                                       minValue -= 0.5;
+                               } else {
+                                       // Make up a whole number above the values
+                                       maxValue += 0.5;
+                               }
+                       }
+
+                       var valueRange = Math.abs(maxValue - minValue),
+                               rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
+                               graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+                               graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+                               graphRange = graphMax - graphMin,
+                               stepValue = Math.pow(10, rangeOrderOfMagnitude),
+                               numberOfSteps = Math.round(graphRange / stepValue);
+
+                       //If we have more space on the graph we'll use it to give more definition to the data
+                       while ((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
+                               if (numberOfSteps > maxSteps) {
+                                       stepValue *= 2;
+                                       numberOfSteps = Math.round(graphRange / stepValue);
+                                       // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
+                                       if (numberOfSteps % 1 !== 0) {
+                                               skipFitting = true;
+                                       }
+                               }
+                               //We can fit in double the amount of scale points on the scale
+                               else {
+                                       //If user has declared ints only, and the step value isn't a decimal
+                                       if (integersOnly && rangeOrderOfMagnitude >= 0) {
+                                               //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
+                                               if (stepValue / 2 % 1 === 0) {
+                                                       stepValue /= 2;
+                                                       numberOfSteps = Math.round(graphRange / stepValue);
+                                               }
+                                               //If it would make it a float break out of the loop
+                                               else {
+                                                       break;
+                                               }
+                                       }
+                                       //If the scale doesn't have to be an int, make the scale more granular anyway.
+                                       else {
+                                               stepValue /= 2;
+                                               numberOfSteps = Math.round(graphRange / stepValue);
+                                       }
+
+                               }
+                       }
+
+                       if (skipFitting) {
+                               numberOfSteps = minSteps;
+                               stepValue = graphRange / numberOfSteps;
+                       }
+                       return {
+                               steps: numberOfSteps,
+                               stepValue: stepValue,
+                               min: graphMin,
+                               max: graphMin + (numberOfSteps * stepValue)
+                       };
+
+               },
+               // Implementation of the nice number algorithm used in determining where axis labels will go
+               niceNum = helpers.niceNum = function(range, round) {
+                       var exponent = Math.floor(helpers.log10(range));
+                       var fraction = range / Math.pow(10, exponent);
+                       var niceFraction;
+
+                       if (round) {
+                               if (fraction < 1.5) {
+                                       niceFraction = 1;
+                               } else if (fraction < 3) {
+                                       niceFraction = 2;
+                               } else if (fraction < 7) {
+                                       niceFraction = 5;
+                               } else {
+                                       niceFraction = 10;
+                               }
+                       } else {
+                               if (fraction <= 1.0) {
+                                       niceFraction = 1;
+                               } else if (fraction <= 2) {
+                                       niceFraction = 2;
+                               } else if (fraction <= 5) {
+                                       niceFraction = 5;
+                               } else {
+                                       niceFraction = 10;
+                               }
+                       }
+
+                       return niceFraction * Math.pow(10, exponent);
+               },
+               /* jshint ignore:start */
+               // Blows up jshint errors based on the new Function constructor
+               //Templating methods
+               //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
+               template = helpers.template = function(templateString, valuesObject) {
+
+                       // If templateString is function rather than string-template - call the function for valuesObject
+
+                       if (templateString instanceof Function) {
+                               return templateString(valuesObject);
+                       }
+
+                       var cache = {};
+
+                       function tmpl(str, data) {
+                               // Figure out if we're getting a template, or if we need to
+                               // load the template - and be sure to cache the result.
+                               var fn = !/\W/.test(str) ?
+                                       cache[str] = cache[str] :
+
+                                       // Generate a reusable function that will serve as a template
+                                       // generator (and which will be cached).
+                                       new Function("obj",
+                                               "var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+                                               // Introduce the data as local variables using with(){}
+                                               "with(obj){p.push('" +
+
+                                               // Convert the template into pure JavaScript
+                                               str
+                                               .replace(/[\r\t\n]/g, " ")
+                                               .split("<%").join("\t")
+                                               .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+                                               .replace(/\t=(.*?)%>/g, "',$1,'")
+                                               .split("\t").join("');")
+                                               .split("%>").join("p.push('")
+                                               .split("\r").join("\\'") +
+                                               "');}return p.join('');"
+                                       );
+
+                               // Provide some basic currying to the user
+                               return data ? fn(data) : fn;
+                       }
+                       return tmpl(templateString, valuesObject);
+               },
+               /* jshint ignore:end */
+               generateLabels = helpers.generateLabels = function(templateString, numberOfSteps, graphMin, stepValue) {
+                       var labelsArray = new Array(numberOfSteps);
+                       if (templateString) {
+                               each(labelsArray, function(val, index) {
+                                       labelsArray[index] = template(templateString, {
+                                               value: (graphMin + (stepValue * (index + 1)))
+                                       });
+                               });
+                       }
+                       return labelsArray;
+               },
+               //--Animation methods
+               //Easing functions adapted from Robert Penner's easing equations
+               //http://www.robertpenner.com/easing/
+               easingEffects = helpers.easingEffects = {
+                       linear: function(t) {
+                               return t;
+                       },
+                       easeInQuad: function(t) {
+                               return t * t;
+                       },
+                       easeOutQuad: function(t) {
+                               return -1 * t * (t - 2);
+                       },
+                       easeInOutQuad: function(t) {
+                               if ((t /= 1 / 2) < 1) {
+                                       return 1 / 2 * t * t;
+                               }
+                               return -1 / 2 * ((--t) * (t - 2) - 1);
+                       },
+                       easeInCubic: function(t) {
+                               return t * t * t;
+                       },
+                       easeOutCubic: function(t) {
+                               return 1 * ((t = t / 1 - 1) * t * t + 1);
+                       },
+                       easeInOutCubic: function(t) {
+                               if ((t /= 1 / 2) < 1) {
+                                       return 1 / 2 * t * t * t;
+                               }
+                               return 1 / 2 * ((t -= 2) * t * t + 2);
+                       },
+                       easeInQuart: function(t) {
+                               return t * t * t * t;
+                       },
+                       easeOutQuart: function(t) {
+                               return -1 * ((t = t / 1 - 1) * t * t * t - 1);
+                       },
+                       easeInOutQuart: function(t) {
+                               if ((t /= 1 / 2) < 1) {
+                                       return 1 / 2 * t * t * t * t;
+                               }
+                               return -1 / 2 * ((t -= 2) * t * t * t - 2);
+                       },
+                       easeInQuint: function(t) {
+                               return 1 * (t /= 1) * t * t * t * t;
+                       },
+                       easeOutQuint: function(t) {
+                               return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
+                       },
+                       easeInOutQuint: function(t) {
+                               if ((t /= 1 / 2) < 1) {
+                                       return 1 / 2 * t * t * t * t * t;
+                               }
+                               return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
+                       },
+                       easeInSine: function(t) {
+                               return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
+                       },
+                       easeOutSine: function(t) {
+                               return 1 * Math.sin(t / 1 * (Math.PI / 2));
+                       },
+                       easeInOutSine: function(t) {
+                               return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
+                       },
+                       easeInExpo: function(t) {
+                               return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
+                       },
+                       easeOutExpo: function(t) {
+                               return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
+                       },
+                       easeInOutExpo: function(t) {
+                               if (t === 0) {
+                                       return 0;
+                               }
+                               if (t === 1) {
+                                       return 1;
+                               }
+                               if ((t /= 1 / 2) < 1) {
+                                       return 1 / 2 * Math.pow(2, 10 * (t - 1));
+                               }
+                               return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
+                       },
+                       easeInCirc: function(t) {
+                               if (t >= 1) {
+                                       return t;
+                               }
+                               return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
+                       },
+                       easeOutCirc: function(t) {
+                               return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
+                       },
+                       easeInOutCirc: function(t) {
+                               if ((t /= 1 / 2) < 1) {
+                                       return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
+                               }
+                               return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
+                       },
+                       easeInElastic: function(t) {
+                               var s = 1.70158;
+                               var p = 0;
+                               var a = 1;
+                               if (t === 0) {
+                                       return 0;
+                               }
+                               if ((t /= 1) == 1) {
+                                       return 1;
+                               }
+                               if (!p) {
+                                       p = 1 * 0.3;
+                               }
+                               if (a < Math.abs(1)) {
+                                       a = 1;
+                                       s = p / 4;
+                               } else {
+                                       s = p / (2 * Math.PI) * Math.asin(1 / a);
+                               }
+                               return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+                       },
+                       easeOutElastic: function(t) {
+                               var s = 1.70158;
+                               var p = 0;
+                               var a = 1;
+                               if (t === 0) {
+                                       return 0;
+                               }
+                               if ((t /= 1) == 1) {
+                                       return 1;
+                               }
+                               if (!p) {
+                                       p = 1 * 0.3;
+                               }
+                               if (a < Math.abs(1)) {
+                                       a = 1;
+                                       s = p / 4;
+                               } else {
+                                       s = p / (2 * Math.PI) * Math.asin(1 / a);
+                               }
+                               return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
+                       },
+                       easeInOutElastic: function(t) {
+                               var s = 1.70158;
+                               var p = 0;
+                               var a = 1;
+                               if (t === 0) {
+                                       return 0;
+                               }
+                               if ((t /= 1 / 2) == 2) {
+                                       return 1;
+                               }
+                               if (!p) {
+                                       p = 1 * (0.3 * 1.5);
+                               }
+                               if (a < Math.abs(1)) {
+                                       a = 1;
+                                       s = p / 4;
+                               } else {
+                                       s = p / (2 * Math.PI) * Math.asin(1 / a);
+                               }
+                               if (t < 1) {
+                                       return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+                               }
+                               return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
+                       },
+                       easeInBack: function(t) {
+                               var s = 1.70158;
+                               return 1 * (t /= 1) * t * ((s + 1) * t - s);
+                       },
+                       easeOutBack: function(t) {
+                               var s = 1.70158;
+                               return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
+                       },
+                       easeInOutBack: function(t) {
+                               var s = 1.70158;
+                               if ((t /= 1 / 2) < 1) {
+                                       return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
+                               }
+                               return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
+                       },
+                       easeInBounce: function(t) {
+                               return 1 - easingEffects.easeOutBounce(1 - t);
+                       },
+                       easeOutBounce: function(t) {
+                               if ((t /= 1) < (1 / 2.75)) {
+                                       return 1 * (7.5625 * t * t);
+                               } else if (t < (2 / 2.75)) {
+                                       return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
+                               } else if (t < (2.5 / 2.75)) {
+                                       return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
+                               } else {
+                                       return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
+                               }
+                       },
+                       easeInOutBounce: function(t) {
+                               if (t < 1 / 2) {
+                                       return easingEffects.easeInBounce(t * 2) * 0.5;
+                               }
+                               return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
+                       }
+               },
+               //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
+               requestAnimFrame = helpers.requestAnimFrame = (function() {
+                       return window.requestAnimationFrame ||
+                               window.webkitRequestAnimationFrame ||
+                               window.mozRequestAnimationFrame ||
+                               window.oRequestAnimationFrame ||
+                               window.msRequestAnimationFrame ||
+                               function(callback) {
+                                       return window.setTimeout(callback, 1000 / 60);
+                               };
+               })(),
+               cancelAnimFrame = helpers.cancelAnimFrame = (function() {
+                       return window.cancelAnimationFrame ||
+                               window.webkitCancelAnimationFrame ||
+                               window.mozCancelAnimationFrame ||
+                               window.oCancelAnimationFrame ||
+                               window.msCancelAnimationFrame ||
+                               function(callback) {
+                                       return window.clearTimeout(callback, 1000 / 60);
+                               };
+               })(),
+               animationLoop = helpers.animationLoop = function(callback, totalSteps, easingString, onProgress, onComplete, chartInstance) {
+
+                       var currentStep = 0,
+                               easingFunction = easingEffects[easingString] || easingEffects.linear;
+
+                       var animationFrame = function() {
+                               currentStep++;
+                               var stepDecimal = currentStep / totalSteps;
+                               var easeDecimal = easingFunction(stepDecimal);
+
+                               callback.call(chartInstance, easeDecimal, stepDecimal, currentStep);
+                               onProgress.call(chartInstance, easeDecimal, stepDecimal);
+                               if (currentStep < totalSteps) {
+                                       chartInstance.animationFrame = requestAnimFrame(animationFrame);
+                               } else {
+                                       onComplete.apply(chartInstance);
+                               }
+                       };
+                       requestAnimFrame(animationFrame);
+               },
+               //-- DOM methods
+               getRelativePosition = helpers.getRelativePosition = function(evt) {
+                       var mouseX, mouseY;
+                       var e = evt.originalEvent || evt,
+                               canvas = evt.currentTarget || evt.srcElement,
+                               boundingRect = canvas.getBoundingClientRect();
+
+                       if (e.touches) {
+                               mouseX = e.touches[0].clientX - boundingRect.left;
+                               mouseY = e.touches[0].clientY - boundingRect.top;
+
+                       } else {
+                               mouseX = e.clientX - boundingRect.left;
+                               mouseY = e.clientY - boundingRect.top;
+                       }
+
+                       return {
+                               x: mouseX,
+                               y: mouseY
+                       };
+
+               },
+               addEvent = helpers.addEvent = function(node, eventType, method) {
+                       if (node.addEventListener) {
+                               node.addEventListener(eventType, method);
+                       } else if (node.attachEvent) {
+                               node.attachEvent("on" + eventType, method);
+                       } else {
+                               node["on" + eventType] = method;
+                       }
+               },
+               removeEvent = helpers.removeEvent = function(node, eventType, handler) {
+                       if (node.removeEventListener) {
+                               node.removeEventListener(eventType, handler, false);
+                       } else if (node.detachEvent) {
+                               node.detachEvent("on" + eventType, handler);
+                       } else {
+                               node["on" + eventType] = noop;
+                       }
+               },
+               bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) {
+                       // Create the events object if it's not already present
+                       if (!chartInstance.events) chartInstance.events = {};
+
+                       each(arrayOfEvents, function(eventName) {
+                               chartInstance.events[eventName] = function() {
+                                       handler.apply(chartInstance, arguments);
+                               };
+                               addEvent(chartInstance.chart.canvas, eventName, chartInstance.events[eventName]);
+                       });
+               },
+               unbindEvents = helpers.unbindEvents = function(chartInstance, arrayOfEvents) {
+                       each(arrayOfEvents, function(handler, eventName) {
+                               removeEvent(chartInstance.chart.canvas, eventName, handler);
+                       });
+               },
+               getMaximumWidth = helpers.getMaximumWidth = function(domNode) {
+                       var container = domNode.parentNode,
+                               padding = parseInt(getStyle(container, 'padding-left')) + parseInt(getStyle(container, 'padding-right'));
+                       // TODO = check cross browser stuff with this.
+                       return container.clientWidth - padding;
+               },
+               getMaximumHeight = helpers.getMaximumHeight = function(domNode) {
+                       var container = domNode.parentNode,
+                               padding = parseInt(getStyle(container, 'padding-bottom')) + parseInt(getStyle(container, 'padding-top'));
+                       // TODO = check cross browser stuff with this.
+                       return container.clientHeight - padding;
+               },
+               getStyle = helpers.getStyle = function(el, property) {
+                       return el.currentStyle ?
+                               el.currentStyle[property] :
+                               document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
+               },
+               getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
+               retinaScale = helpers.retinaScale = function(chart) {
+                       var ctx = chart.ctx,
+                               width = chart.canvas.width,
+                               height = chart.canvas.height;
+
+                       if (window.devicePixelRatio) {
+                               ctx.canvas.style.width = width + "px";
+                               ctx.canvas.style.height = height + "px";
+                               ctx.canvas.height = height * window.devicePixelRatio;
+                               ctx.canvas.width = width * window.devicePixelRatio;
+                               ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
+                       }
+               },
+               //-- Canvas methods
+               clear = helpers.clear = function(chart) {
+                       chart.ctx.clearRect(0, 0, chart.width, chart.height);
+               },
+               fontString = helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
+                       return fontStyle + " " + pixelSize + "px " + fontFamily;
+               },
+               longestText = helpers.longestText = function(ctx, font, arrayOfStrings) {
+                       ctx.font = font;
+                       var longest = 0;
+                       each(arrayOfStrings, function(string) {
+                               var textWidth = ctx.measureText(string).width;
+                               longest = (textWidth > longest) ? textWidth : longest;
+                       });
+                       return longest;
+               },
+               drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) {
+                       ctx.beginPath();
+                       ctx.moveTo(x + radius, y);
+                       ctx.lineTo(x + width - radius, y);
+                       ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+                       ctx.lineTo(x + width, y + height - radius);
+                       ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+                       ctx.lineTo(x + radius, y + height);
+                       ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+                       ctx.lineTo(x, y + radius);
+                       ctx.quadraticCurveTo(x, y, x + radius, y);
+                       ctx.closePath();
+               },
+               color = helpers.color = function(color) {
+                       if (!window.Color) {
+                               console.log('Color.js not found!');
+                               return color;
+                       }
+                       return window.Color(color);
+               },
+               isArray = helpers.isArray = function(obj) {
+                       if (!Array.isArray) {
+                               return Object.prototype.toString.call(arg) === '[object Array]';
+                       }
+                       return Array.isArray(obj);
+               };
+
+       //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
+       //Destroy method on the chart will remove the instance of the chart from this reference.
+       Chart.instances = {};
+
+       Chart.Type = function(config, instance) {
+               this.data = config.data;
+               this.options = config.options;
+               this.chart = instance;
+               this.id = uid();
+               //Add the chart instance to the global namespace
+               Chart.instances[this.id] = this;
+
+               // Initialize is always called when a chart type is created
+               // By default it is a no op, but it should be extended
+               if (this.options.responsive) {
+                       this.resize();
+               }
+               this.initialize.call(this);
+       };
+
+       //Core methods that'll be a part of every chart type
+       extend(Chart.Type.prototype, {
+               initialize: function() {
+                       return this;
+               },
+               clear: function() {
+                       clear(this.chart);
+                       return this;
+               },
+               stop: function() {
+                       // Stops any current animation loop occuring
+                       Chart.animationService.cancelAnimation(this);
+                       return this;
+               },
+               resize: function() {
+                       this.stop();
+                       var canvas = this.chart.canvas,
+                               newWidth = getMaximumWidth(this.chart.canvas),
+                               newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
+
+                       canvas.width = this.chart.width = newWidth;
+                       canvas.height = this.chart.height = newHeight;
+
+                       retinaScale(this.chart);
+
+                       return this;
+               },
+               redraw: noop,
+               render: function(duration) {
+
+                       if (this.options.animation.duration !== 0 || duration) {
+                               var animation = new Chart.Animation();
+                               animation.numSteps = (duration || this.options.animation.duration) / 16.66; //60 fps
+                               animation.easing = this.options.animation.easing;
+
+                               // render function
+                               animation.render = function(chartInstance, animationObject) {
+                                       var easingFunction = helpers.easingEffects[animationObject.easing];
+                                       var stepDecimal = animationObject.currentStep / animationObject.numSteps;
+                                       var easeDecimal = easingFunction(stepDecimal);
+
+                                       chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);
+                               };
+
+                               // user events
+                               animation.onAnimationProgress = this.options.onAnimationProgress;
+                               animation.onAnimationComplete = this.options.onAnimationComplete;
+
+                               Chart.animationService.addAnimation(this, animation, duration);
+                       } else {
+                               this.draw();
+                               this.options.onAnimationComplete.call(this);
+                       }
+                       return this;
+               },
+               eachElement: function(callback) {
+                       helpers.each(this.data.datasets, function(dataset, datasetIndex) {
+                               helpers.each(dataset.metaData, callback, this, dataset.metaData, datasetIndex);
+                       }, this);
+               },
+               eachValue: function(callback) {
+                       helpers.each(this.data.datasets, function(dataset, datasetIndex) {
+                               helpers.each(dataset.data, callback, this, datasetIndex);
+                       }, this);
+               },
+               eachDataset: function(callback) {
+                       helpers.each(this.data.datasets, callback, this);
+               },
+               getElementsAtEvent: function(e) {
+                       var elementsArray = [],
+                               eventPosition = helpers.getRelativePosition(e),
+                               datasetIterator = function(dataset) {
+                                       elementsArray.push(dataset.metaData[elementIndex]);
+                               },
+                               elementIndex;
+
+                       for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; datasetIndex++) {
+                               for (elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; elementIndex++) {
+                                       if (this.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) {
+                                               helpers.each(this.data.datasets, datasetIterator);
+                                       }
+                               }
+                       }
+
+                       return elementsArray.length ? elementsArray : [];
+               },
+               // Get the single element that was clicked on
+               // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was drawn
+               getElementAtEvent: function(e) {
+                       var element = [];
+                       var eventPosition = helpers.getRelativePosition(e);
+
+                       for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; ++datasetIndex) {
+                               for (var elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; ++elementIndex) {
+                                       if (this.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) {
+                                               element.push(this.data.datasets[datasetIndex].metaData[elementIndex]);
+                                               return element;
+                                       }
+                               }
+                       }
+
+                       return [];
+               },
+               generateLegend: function() {
+                       return template(this.options.legendTemplate, this);
+               },
+               destroy: function() {
+                       this.clear();
+                       unbindEvents(this, this.events);
+                       var canvas = this.chart.canvas;
+
+                       // Reset canvas height/width attributes starts a fresh with the canvas context
+                       canvas.width = this.chart.width;
+                       canvas.height = this.chart.height;
+
+                       // < IE9 doesn't support removeProperty
+                       if (canvas.style.removeProperty) {
+                               canvas.style.removeProperty('width');
+                               canvas.style.removeProperty('height');
+                       } else {
+                               canvas.style.removeAttribute('width');
+                               canvas.style.removeAttribute('height');
+                       }
+
+                       delete Chart.instances[this.id];
+               },
+               toBase64Image: function() {
+                       return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
+               }
+       });
+
+       Chart.Type.extend = function(extensions) {
+
+               var parent = this;
+
+               var ChartType = function() {
+                       return parent.apply(this, arguments);
+               };
+
+               //Copy the prototype object of the this class
+               ChartType.prototype = clone(parent.prototype);
+               //Now overwrite some of the properties in the base class with the new extensions
+               extend(ChartType.prototype, extensions);
+
+               ChartType.extend = Chart.Type.extend;
+
+               if (extensions.name || parent.prototype.name) {
+
+                       var chartName = extensions.name || parent.prototype.name;
+                       //Assign any potential default values of the new chart type
+
+                       //If none are defined, we'll use a clone of the chart type this is being extended from.
+                       //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
+                       //doesn't define some defaults of their own.
+
+                       var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
+
+                       Chart.defaults[chartName] = helpers.configMerge(baseDefaults, extensions.defaults);
+
+                       Chart.types[chartName] = ChartType;
+
+                       //Register this new chart type in the Chart prototype
+                       Chart.prototype[chartName] = function(config) {
+                               config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[chartName], config.options || {});
+                               return new ChartType(config, this);
+                       };
+               } else {
+                       warn("Name not provided for this chart, so it hasn't been registered");
+               }
+               return parent;
+       };
+
+       Chart.Element = function(configuration) {
+               extend(this, configuration);
+               this.initialize.apply(this, arguments);
+       };
+       extend(Chart.Element.prototype, {
+               initialize: function() {},
+               pivot: function() {
+                       if (!this._view) {
+                               this._view = clone(this._model);
+                       }
+                       this._start = clone(this._view);
+                       return this;
+               },
+               transition: function(ease) {
+                       if (!this._view) {
+                               this._view = clone(this._model);
+                       }
+                       if (!this._start) {
+                               this.pivot();
+                       }
+
+                       each(this._model, function(value, key) {
+
+                               if (key[0] === '_' || !this._model.hasOwnProperty(key)) {
+                                       // Only non-underscored properties
+                               }
+
+                               // Init if doesn't exist
+                               else if (!this._view[key]) {
+                                       if (typeof value === 'number') {
+                                               this._view[key] = value * ease;
+                                       } else {
+                                               this._view[key] = value || null;
+                                       }
+                               }
+
+                               // No unnecessary computations
+                               else if (this._model[key] === this._view[key]) {
+                                       // It's the same! Woohoo!
+                               }
+
+                               // Color transitions if possible
+                               else if (typeof value === 'string') {
+                                       try {
+                                               var color = helpers.color(this._start[key]).mix(helpers.color(this._model[key]), ease);
+                                               this._view[key] = color.rgbString();
+                                       } catch (err) {
+                                               this._view[key] = value;
+                                       }
+                               }
+                               // Number transitions
+                               else if (typeof value === 'number') {
+                                       var startVal = this._start[key] !== undefined ? this._start[key] : 0;
+                                       this._view[key] = ((this._model[key] - startVal) * ease) + startVal;
+                               }
+                               // Everything else
+                               else {
+                                       this._view[key] = value;
+                               }
+
+                       }, this);
+
+                       if (ease === 1) {
+                               delete this._start;
+                       }
+                       return this;
+               },
+               tooltipPosition: function() {
+                       return {
+                               x: this._model.x,
+                               y: this._model.y
+                       };
+               },
+               hasValue: function() {
+                       return isNumber(this._model.x) && isNumber(this._model.y);
+               }
+       });
+
+       Chart.Element.extend = inherits;
+
+
+       // Attach global event to resize each chart instance when the browser resizes
+       helpers.addEvent(window, "resize", (function() {
+               // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
+               var timeout;
+               return function() {
+                       clearTimeout(timeout);
+                       timeout = setTimeout(function() {
+                               each(Chart.instances, function(instance) {
+                                       // If the responsive flag is set in the chart instance config
+                                       // Cascade the resize event down to the chart.
+                                       if (instance.options.responsive) {
+                                               instance.resize();
+                                               instance.update();
+                                       }
+                               });
+                       }, 50);
+               };
+       })());
+
+
+       if (amd) {
+               define(function() {
+                       return Chart;
+               });
+       } else if (typeof module === 'object' && module.exports) {
+               module.exports = Chart;
+       }
+
+       root.Chart = Chart;
+
+       Chart.noConflict = function() {
+               root.Chart = previous;
+               return Chart;
+       };
 
 }).call(this);
index dc9a9a7025224d837d6b0272cf84c4377de57218..ce5c82460036e0983c5b64a9aeace72ef5ad644b 100644 (file)
 (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 = {
-        // 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
-        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(type, scaleConstructor) {
-            this.constructors[type] = scaleConstructor;
-        },
-        getScaleConstructor: function(type) {
-            return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
-        },
-
-        // The interesting function
-        fitScalesForChart: function(chartInstance, width, height) {
-            var xPadding = width > 30 ? 5 : 2;
-            var yPadding = height > 30 ? 5 : 2;
-
-            if (chartInstance) {
-                var leftScales = helpers.where(chartInstance.scales, function(scaleInstance) {
-                    return scaleInstance.options.position == "left";
-                });
-                var rightScales = helpers.where(chartInstance.scales, function(scaleInstance) {
-                    return scaleInstance.options.position == "right";
-                });
-                var topScales = helpers.where(chartInstance.scales, function(scaleInstance) {
-                    return scaleInstance.options.position == "top";
-                });
-                var bottomScales = helpers.where(chartInstance.scales, function(scaleInstance) {
-                    return scaleInstance.options.position == "bottom";
-                });
-
-                var visibleLeftScales = helpers.where(chartInstance.scales, function(scaleInstance) {
-                    return scaleInstance.options.position == "left";
-                });
-                var visibleRightScales = helpers.where(chartInstance.scales, function(scaleInstance) {
-                    return scaleInstance.options.position == "right";
-                });
-                var visibleTopScales = helpers.where(chartInstance.scales, function(scaleInstance) {
-                    return scaleInstance.options.position == "top";
-                });
-                var visibleBottomScales = helpers.where(chartInstance.scales, function(scaleInstance) {
-                    return scaleInstance.options.position == "bottom";
-                });
-
-                // // Adjust the padding to take into account displaying labels
-                // if (topScales.length === 0 || bottomScales.length === 0) {
-                //     var maxFontHeight = 0;
-
-                //     var maxFontHeightFunction = function(scaleInstance) {
-                //         if (scaleInstance.options.labels.show) {
-                //             // Only consider font sizes for axes that actually show labels
-                //             maxFontHeight = Math.max(maxFontHeight, scaleInstance.options.labels.fontSize);
-                //         }
-                //     };
-
-                //     helpers.each(leftScales, maxFontHeightFunction);
-                //     helpers.each(rightScales, maxFontHeightFunction);
-
-                //     if (topScales.length === 0) {
-                //         // Add padding so that we can handle drawing the top nicely
-                //         yPadding += 0.75 * maxFontHeight; // 0.75 since padding added on both sides
-                //     }
-
-                //     if (bottomScales.length === 0) {
-                //         // Add padding so that we can handle drawing the bottom nicely
-                //         yPadding += 1.5 * maxFontHeight;
-                //     }
-                // }
-
-                // 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%
-
-                chartWidth -= (2 * xPadding);
-                chartHeight -= (2 * yPadding);
-
-
-                // Step 2
-                var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length);
-
-                // Step 3
-                var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length);
-
-                // Step 4;
-                var minimumScaleSizes = [];
-
-                var verticalScaleMinSizeFunction = function(scaleInstance) {
-                    var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight);
-                    minimumScaleSizes.push({
-                        horizontal: false,
-                        minSize: minSize,
-                        scale: scaleInstance,
-                    });
-                };
-
-                var horizontalScaleMinSizeFunction = function(scaleInstance) {
-                    var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight);
-                    minimumScaleSizes.push({
-                        horizontal: true,
-                        minSize: minSize,
-                        scale: scaleInstance,
-                    });
-                };
-
-                // 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 - (2 * yPadding);
-                var maxChartWidth = width - (2 * xPadding);
-
-                helpers.each(minimumScaleSizes, function(wrapper) {
-                    if (wrapper.horizontal) {
-                        maxChartHeight -= wrapper.minSize.height;
-                    } else {
-                        maxChartWidth -= wrapper.minSize.width;
-                    }
-                });
-
-                // At this point, maxChartHeight and maxChartWidth are the size the chart area could
-                // be if the axes are drawn at their minimum sizes.
-
-                // Step 6
-                var verticalScaleFitFunction = function(scaleInstance) {
-                    var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
-                        return wrapper.scale === scaleInstance;
-                    });
-
-                    if (wrapper) {
-                        scaleInstance.fit(wrapper.minSize.width, maxChartHeight);
-                    }
-                };
-
-                var horizontalScaleFitFunction = function(scaleInstance) {
-                    var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
-                        return wrapper.scale === scaleInstance;
-                    });
-
-                    var scaleMargin = {
-                        left: totalLeftWidth,
-                        right: totalRightWidth,
-                        top: 0,
-                        bottom: 0,
-                    };
-
-                    if (wrapper) {
-                        scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin);
-                    }
-                };
-
-                var totalLeftWidth = xPadding;
-                var totalRightWidth = xPadding;
-                var totalTopHeight = yPadding;
-                var totalBottomHeight = yPadding;
-
-                helpers.each(leftScales, verticalScaleFitFunction);
-                helpers.each(rightScales, verticalScaleFitFunction);
-
-                // Figure out how much margin is on the left and right of the horizontal axes
-                helpers.each(leftScales, function(scaleInstance) {
-                    totalLeftWidth += scaleInstance.width;
-                });
-
-                helpers.each(rightScales, function(scaleInstance) {
-                    totalRightWidth += scaleInstance.width;
-                });
-
-                helpers.each(topScales, horizontalScaleFitFunction);
-                helpers.each(bottomScales, horizontalScaleFitFunction);
-
-                helpers.each(topScales, function(scaleInstance) {
-                    totalTopHeight += scaleInstance.height;
-                });
-                helpers.each(bottomScales, function(scaleInstance) {
-                    totalBottomHeight += scaleInstance.height;
-                });
-
-                // Let the left scale know the final margin
-                helpers.each(leftScales, function(scaleInstance) {
-                    var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
-                        return wrapper.scale === scaleInstance;
-                    });
-
-                    var scaleMargin = {
-                        left: 0,
-                        right: 0,
-                        top: totalTopHeight,
-                        bottom: totalBottomHeight
-                    };
-
-                    if (wrapper) {
-                        scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
-                    }
-                });
-
-                helpers.each(rightScales, function(scaleInstance) {
-                    var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
-                        return wrapper.scale === scaleInstance;
-                    });
-
-                    var scaleMargin = {
-                        left: 0,
-                        right: 0,
-                        top: totalTopHeight,
-                        bottom: totalBottomHeight
-                    };
-
-                    if (wrapper) {
-                        scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
-                    }
-                });
-
-                // Step 7 
-                // Position the scales
-                var left = xPadding;
-                var top = yPadding;
-                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
-                chartInstance.chartArea = {
-                    left: totalLeftWidth,
-                    top: totalTopHeight,
-                    right: totalLeftWidth + maxChartWidth,
-                    bottom: totalTopHeight + maxChartHeight,
-                };
-            }
-        }
-    };
+       "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 = {
+               // 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
+               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(type, scaleConstructor) {
+                       this.constructors[type] = scaleConstructor;
+               },
+               getScaleConstructor: function(type) {
+                       return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
+               },
+
+               // The interesting function
+               fitScalesForChart: function(chartInstance, width, height) {
+                       var xPadding = width > 30 ? 5 : 2;
+                       var yPadding = height > 30 ? 5 : 2;
+
+                       if (chartInstance) {
+                               var leftScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "left";
+                               });
+                               var rightScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "right";
+                               });
+                               var topScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "top";
+                               });
+                               var bottomScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "bottom";
+                               });
+
+                               var visibleLeftScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "left";
+                               });
+                               var visibleRightScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "right";
+                               });
+                               var visibleTopScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "top";
+                               });
+                               var visibleBottomScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+                                       return scaleInstance.options.position == "bottom";
+                               });
+
+                               // // Adjust the padding to take into account displaying labels
+                               // if (topScales.length === 0 || bottomScales.length === 0) {
+                               //     var maxFontHeight = 0;
+
+                               //     var maxFontHeightFunction = function(scaleInstance) {
+                               //         if (scaleInstance.options.labels.show) {
+                               //             // Only consider font sizes for axes that actually show labels
+                               //             maxFontHeight = Math.max(maxFontHeight, scaleInstance.options.labels.fontSize);
+                               //         }
+                               //     };
+
+                               //     helpers.each(leftScales, maxFontHeightFunction);
+                               //     helpers.each(rightScales, maxFontHeightFunction);
+
+                               //     if (topScales.length === 0) {
+                               //         // Add padding so that we can handle drawing the top nicely
+                               //         yPadding += 0.75 * maxFontHeight; // 0.75 since padding added on both sides
+                               //     }
+
+                               //     if (bottomScales.length === 0) {
+                               //         // Add padding so that we can handle drawing the bottom nicely
+                               //         yPadding += 1.5 * maxFontHeight;
+                               //     }
+                               // }
+
+                               // 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%
+
+                               chartWidth -= (2 * xPadding);
+                               chartHeight -= (2 * yPadding);
+
+
+                               // Step 2
+                               var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length);
+
+                               // Step 3
+                               var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length);
+
+                               // Step 4;
+                               var minimumScaleSizes = [];
+
+                               var verticalScaleMinSizeFunction = function(scaleInstance) {
+                                       var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight);
+                                       minimumScaleSizes.push({
+                                               horizontal: false,
+                                               minSize: minSize,
+                                               scale: scaleInstance,
+                                       });
+                               };
+
+                               var horizontalScaleMinSizeFunction = function(scaleInstance) {
+                                       var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight);
+                                       minimumScaleSizes.push({
+                                               horizontal: true,
+                                               minSize: minSize,
+                                               scale: scaleInstance,
+                                       });
+                               };
+
+                               // 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 - (2 * yPadding);
+                               var maxChartWidth = width - (2 * xPadding);
+
+                               helpers.each(minimumScaleSizes, function(wrapper) {
+                                       if (wrapper.horizontal) {
+                                               maxChartHeight -= wrapper.minSize.height;
+                                       } else {
+                                               maxChartWidth -= wrapper.minSize.width;
+                                       }
+                               });
+
+                               // At this point, maxChartHeight and maxChartWidth are the size the chart area could
+                               // be if the axes are drawn at their minimum sizes.
+
+                               // Step 6
+                               var verticalScaleFitFunction = function(scaleInstance) {
+                                       var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
+                                               return wrapper.scale === scaleInstance;
+                                       });
+
+                                       if (wrapper) {
+                                               scaleInstance.fit(wrapper.minSize.width, maxChartHeight);
+                                       }
+                               };
+
+                               var horizontalScaleFitFunction = function(scaleInstance) {
+                                       var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
+                                               return wrapper.scale === scaleInstance;
+                                       });
+
+                                       var scaleMargin = {
+                                               left: totalLeftWidth,
+                                               right: totalRightWidth,
+                                               top: 0,
+                                               bottom: 0,
+                                       };
+
+                                       if (wrapper) {
+                                               scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin);
+                                       }
+                               };
+
+                               var totalLeftWidth = xPadding;
+                               var totalRightWidth = xPadding;
+                               var totalTopHeight = yPadding;
+                               var totalBottomHeight = yPadding;
+
+                               helpers.each(leftScales, verticalScaleFitFunction);
+                               helpers.each(rightScales, verticalScaleFitFunction);
+
+                               // Figure out how much margin is on the left and right of the horizontal axes
+                               helpers.each(leftScales, function(scaleInstance) {
+                                       totalLeftWidth += scaleInstance.width;
+                               });
+
+                               helpers.each(rightScales, function(scaleInstance) {
+                                       totalRightWidth += scaleInstance.width;
+                               });
+
+                               helpers.each(topScales, horizontalScaleFitFunction);
+                               helpers.each(bottomScales, horizontalScaleFitFunction);
+
+                               helpers.each(topScales, function(scaleInstance) {
+                                       totalTopHeight += scaleInstance.height;
+                               });
+                               helpers.each(bottomScales, function(scaleInstance) {
+                                       totalBottomHeight += scaleInstance.height;
+                               });
+
+                               // Let the left scale know the final margin
+                               helpers.each(leftScales, function(scaleInstance) {
+                                       var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
+                                               return wrapper.scale === scaleInstance;
+                                       });
+
+                                       var scaleMargin = {
+                                               left: 0,
+                                               right: 0,
+                                               top: totalTopHeight,
+                                               bottom: totalBottomHeight
+                                       };
+
+                                       if (wrapper) {
+                                               scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
+                                       }
+                               });
+
+                               helpers.each(rightScales, function(scaleInstance) {
+                                       var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
+                                               return wrapper.scale === scaleInstance;
+                                       });
+
+                                       var scaleMargin = {
+                                               left: 0,
+                                               right: 0,
+                                               top: totalTopHeight,
+                                               bottom: totalBottomHeight
+                                       };
+
+                                       if (wrapper) {
+                                               scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
+                                       }
+                               });
+
+                               // Step 7 
+                               // Position the scales
+                               var left = xPadding;
+                               var top = yPadding;
+                               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
+                               chartInstance.chartArea = {
+                                       left: totalLeftWidth,
+                                       top: totalTopHeight,
+                                       right: totalLeftWidth + maxChartWidth,
+                                       bottom: totalTopHeight + maxChartHeight,
+                               };
+                       }
+               }
+       };
 }).call(this);
index 72abb14f051f20ee96722cc40f454560af789507..308a0c069ec1f21fe00e9117edba41a41f7cb45a 100644 (file)
 
 (function() {
 
-    "use strict";
-
-    var root = this,
-        Chart = root.Chart,
-        helpers = Chart.helpers;
-
-    Chart.defaults.global.tooltips = {
-        enabled: true,
-        custom: null,
-        backgroundColor: "rgba(0,0,0,0.8)",
-        fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
-        fontSize: 10,
-        fontStyle: "normal",
-        fontColor: "#fff",
-        titleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
-        titleFontSize: 12,
-        titleFontStyle: "bold",
-        titleFontColor: "#fff",
-        yPadding: 6,
-        xPadding: 6,
-        caretSize: 8,
-        cornerRadius: 6,
-        xOffset: 10,
-        template: [
-            '<% if(label){ %>',
-            '<%=label %>: ',
-            '<% } %>',
-            '<%=value %>',
-        ].join(''),
-        multiTemplate: [
-            '<%if (datasetLabel){ %>',
-            '<%=datasetLabel %>: ',
-            '<% } %>',
-            '<%=value %>'
-        ].join(''),
-        multiKeyBackground: '#fff',
-    };
-
-    Chart.Tooltip = Chart.Element.extend({
-        initialize: function() {
-            var options = this._options;
-            helpers.extend(this, {
-                _model: {
-                    // Positioning
-                    xPadding: options.tooltips.xPadding,
-                    yPadding: options.tooltips.yPadding,
-                    xOffset: options.tooltips.xOffset,
-
-                    // Labels
-                    textColor: options.tooltips.fontColor,
-                    _fontFamily: options.tooltips.fontFamily,
-                    _fontStyle: options.tooltips.fontStyle,
-                    fontSize: options.tooltips.fontSize,
-
-                    // Title
-                    titleTextColor: options.tooltips.titleFontColor,
-                    _titleFontFamily: options.tooltips.titleFontFamily,
-                    _titleFontStyle: options.tooltips.titleFontStyle,
-                    titleFontSize: options.tooltips.titleFontSize,
-
-                    // Appearance
-                    caretHeight: options.tooltips.caretSize,
-                    cornerRadius: options.tooltips.cornerRadius,
-                    backgroundColor: options.tooltips.backgroundColor,
-                    opacity: 0,
-                    legendColorBackground: options.tooltips.multiKeyBackground,
-                },
-            });
-        },
-        update: function() {
-
-            var ctx = this._chart.ctx;
-
-            switch (this._options.hover.mode) {
-                case 'single':
-                    helpers.extend(this._model, {
-                        text: helpers.template(this._options.tooltips.template, {
-                            // These variables are available in the template function. Add others here
-                            element: this._active[0],
-                            value: this._data.datasets[this._active[0]._datasetIndex].data[this._active[0]._index],
-                            label: this._data.labels ? this._data.labels[this._active[0]._index] : '',
-                        }),
-                    });
-
-                    var tooltipPosition = this._active[0].tooltipPosition();
-                    helpers.extend(this._model, {
-                        x: Math.round(tooltipPosition.x),
-                        y: Math.round(tooltipPosition.y),
-                        caretPadding: tooltipPosition.padding
-                    });
-
-                    break;
-
-                case 'label':
-
-                    // Tooltip Content
-
-                    var dataArray,
-                        dataIndex;
-
-                    var labels = [],
-                        colors = [];
-
-                    for (var i = this._data.datasets.length - 1; i >= 0; i--) {
-                        dataArray = this._data.datasets[i].metaData;
-                        dataIndex = helpers.indexOf(dataArray, this._active[0]);
-                        if (dataIndex !== -1) {
-                            break;
-                        }
-                    }
-
-                    var medianPosition = (function(index) {
-                        // Get all the points at that particular index
-                        var elements = [],
-                            dataCollection,
-                            xPositions = [],
-                            yPositions = [],
-                            xMax,
-                            yMax,
-                            xMin,
-                            yMin;
-                        helpers.each(this._data.datasets, function(dataset) {
-                            dataCollection = dataset.metaData;
-                            if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()) {
-                                elements.push(dataCollection[dataIndex]);
-                            }
-                        }, this);
-
-                        // Reverse labels if stacked
-                        helpers.each(this._options.stacked ? elements.reverse() : elements, function(element) {
-                            xPositions.push(element._view.x);
-                            yPositions.push(element._view.y);
-
-                            //Include any colour information about the element
-                            labels.push(helpers.template(this._options.tooltips.multiTemplate, {
-                                // These variables are available in the template function. Add others here
-                                element: element,
-                                datasetLabel: this._data.datasets[element._datasetIndex].label,
-                                value: this._data.datasets[element._datasetIndex].data[element._index],
-                            }));
-                            colors.push({
-                                fill: element._view.backgroundColor,
-                                stroke: element._view.borderColor
-                            });
-
-                        }, this);
-
-                        yMin = helpers.min(yPositions);
-                        yMax = helpers.max(yPositions);
-
-                        xMin = helpers.min(xPositions);
-                        xMax = helpers.max(xPositions);
-
-                        return {
-                            x: (xMin > this._chart.width / 2) ? xMin : xMax,
-                            y: (yMin + yMax) / 2,
-                        };
-                    }).call(this, dataIndex);
-
-                    // Apply for now
-                    helpers.extend(this._model, {
-                        x: medianPosition.x,
-                        y: medianPosition.y,
-                        labels: labels,
-                        title: this._data.labels && this._data.labels.length ? this._data.labels[this._active[0]._index] : '',
-                        legendColors: colors,
-                        legendBackgroundColor: this._options.tooltips.multiKeyBackground,
-                    });
-
-
-                    // Calculate Appearance Tweaks
-
-                    this._model.height = (labels.length * this._model.fontSize) + ((labels.length - 1) * (this._model.fontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5;
-
-                    var titleWidth = ctx.measureText(this.title).width,
-                        //Label has a legend square as well so account for this.
-                        labelWidth = helpers.longestText(ctx, this.font, labels) + this._model.fontSize + 3,
-                        longestTextWidth = helpers.max([labelWidth, titleWidth]);
-
-                    this._model.width = longestTextWidth + (this._model.xPadding * 2);
-
-
-                    var halfHeight = this._model.height / 2;
-
-                    //Check to ensure the height will fit on the canvas
-                    if (this._model.y - halfHeight < 0) {
-                        this._model.y = halfHeight;
-                    } else if (this._model.y + halfHeight > this._chart.height) {
-                        this._model.y = this._chart.height - halfHeight;
-                    }
-
-                    //Decide whether to align left or right based on position on canvas
-                    if (this._model.x > this._chart.width / 2) {
-                        this._model.x -= this._model.xOffset + this._model.width;
-                    } else {
-                        this._model.x += this._model.xOffset;
-                    }
-                    break;
-            }
-
-            return this;
-        },
-        draw: function() {
-
-            var ctx = this._chart.ctx;
-            var vm = this._view;
-
-            switch (this._options.hover.mode) {
-                case 'single':
-
-                    ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily);
-
-                    vm.xAlign = "center";
-                    vm.yAlign = "above";
-
-                    //Distance between the actual element.y position and the start of the tooltip caret
-                    var caretPadding = vm.caretPadding || 2;
-
-                    var tooltipWidth = ctx.measureText(vm.text).width + 2 * vm.xPadding,
-                        tooltipRectHeight = vm.fontSize + 2 * vm.yPadding,
-                        tooltipHeight = tooltipRectHeight + vm.caretHeight + caretPadding;
-
-                    if (vm.x + tooltipWidth / 2 > this._chart.width) {
-                        vm.xAlign = "left";
-                    } else if (vm.x - tooltipWidth / 2 < 0) {
-                        vm.xAlign = "right";
-                    }
-
-                    if (vm.y - tooltipHeight < 0) {
-                        vm.yAlign = "below";
-                    }
-
-                    var tooltipX = vm.x - tooltipWidth / 2,
-                        tooltipY = vm.y - tooltipHeight;
-
-                    ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
-
-                    // Custom Tooltips
-                    if (this._custom) {
-                        this._custom(this._view);
-                    } else {
-                        switch (vm.yAlign) {
-                            case "above":
-                                //Draw a caret above the x/y
-                                ctx.beginPath();
-                                ctx.moveTo(vm.x, vm.y - caretPadding);
-                                ctx.lineTo(vm.x + vm.caretHeight, vm.y - (caretPadding + vm.caretHeight));
-                                ctx.lineTo(vm.x - vm.caretHeight, vm.y - (caretPadding + vm.caretHeight));
-                                ctx.closePath();
-                                ctx.fill();
-                                break;
-                            case "below":
-                                tooltipY = vm.y + caretPadding + vm.caretHeight;
-                                //Draw a caret below the x/y
-                                ctx.beginPath();
-                                ctx.moveTo(vm.x, vm.y + caretPadding);
-                                ctx.lineTo(vm.x + vm.caretHeight, vm.y + caretPadding + vm.caretHeight);
-                                ctx.lineTo(vm.x - vm.caretHeight, vm.y + caretPadding + vm.caretHeight);
-                                ctx.closePath();
-                                ctx.fill();
-                                break;
-                        }
-
-                        switch (vm.xAlign) {
-                            case "left":
-                                tooltipX = vm.x - tooltipWidth + (vm.cornerRadius + vm.caretHeight);
-                                break;
-                            case "right":
-                                tooltipX = vm.x - (vm.cornerRadius + vm.caretHeight);
-                                break;
-                        }
-
-                        helpers.drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, vm.cornerRadius);
-
-                        ctx.fill();
-
-                        ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString();
-                        ctx.textAlign = "center";
-                        ctx.textBaseline = "middle";
-                        ctx.fillText(vm.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipRectHeight / 2);
-
-                    }
-                    break;
-                case 'label':
-
-                    helpers.drawRoundedRectangle(ctx, vm.x, vm.y - vm.height / 2, vm.width, vm.height, vm.cornerRadius);
-                    ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
-                    ctx.fill();
-                    ctx.closePath();
-
-                    ctx.textAlign = "left";
-                    ctx.textBaseline = "middle";
-                    ctx.fillStyle = helpers.color(vm.titleTextColor).alpha(vm.opacity).rgbString();
-                    ctx.font = helpers.fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily);
-                    ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0));
-
-                    ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily);
-                    helpers.each(vm.labels, function(label, index) {
-                        ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString();
-                        ctx.fillText(label, vm.x + vm.xPadding + vm.fontSize + 3, this.getLineHeight(index + 1));
-
-                        //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
-                        //ctx.clearRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize/2, vm.fontSize, vm.fontSize);
-                        //Instead we'll make a white filled block to put the legendColour palette over.
-
-                        ctx.fillStyle = helpers.color(vm.legendColors[index].stroke).alpha(vm.opacity).rgbString();
-                        ctx.fillRect(vm.x + vm.xPadding - 1, this.getLineHeight(index + 1) - vm.fontSize / 2 - 1, vm.fontSize + 2, vm.fontSize + 2);
-
-                        ctx.fillStyle = helpers.color(vm.legendColors[index].fill).alpha(vm.opacity).rgbString();
-                        ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize);
-
-
-                    }, this);
-                    break;
-            }
-        },
-        getLineHeight: function(index) {
-            var baseLineHeight = this._view.y - (this._view.height / 2) + this._view.yPadding,
-                afterTitleIndex = index - 1;
-
-            //If the index is zero, we're getting the title
-            if (index === 0) {
-                return baseLineHeight + this._view.titleFontSize / 2;
-            } else {
-                return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5;
-            }
-
-        },
-    });
+       "use strict";
+
+       var root = this,
+               Chart = root.Chart,
+               helpers = Chart.helpers;
+
+       Chart.defaults.global.tooltips = {
+               enabled: true,
+               custom: null,
+               backgroundColor: "rgba(0,0,0,0.8)",
+               fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+               fontSize: 10,
+               fontStyle: "normal",
+               fontColor: "#fff",
+               titleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+               titleFontSize: 12,
+               titleFontStyle: "bold",
+               titleFontColor: "#fff",
+               yPadding: 6,
+               xPadding: 6,
+               caretSize: 8,
+               cornerRadius: 6,
+               xOffset: 10,
+               template: [
+                       '<% if(label){ %>',
+                       '<%=label %>: ',
+                       '<% } %>',
+                       '<%=value %>',
+               ].join(''),
+               multiTemplate: [
+                       '<%if (datasetLabel){ %>',
+                       '<%=datasetLabel %>: ',
+                       '<% } %>',
+                       '<%=value %>'
+               ].join(''),
+               multiKeyBackground: '#fff',
+       };
+
+       Chart.Tooltip = Chart.Element.extend({
+               initialize: function() {
+                       var options = this._options;
+                       helpers.extend(this, {
+                               _model: {
+                                       // Positioning
+                                       xPadding: options.tooltips.xPadding,
+                                       yPadding: options.tooltips.yPadding,
+                                       xOffset: options.tooltips.xOffset,
+
+                                       // Labels
+                                       textColor: options.tooltips.fontColor,
+                                       _fontFamily: options.tooltips.fontFamily,
+                                       _fontStyle: options.tooltips.fontStyle,
+                                       fontSize: options.tooltips.fontSize,
+
+                                       // Title
+                                       titleTextColor: options.tooltips.titleFontColor,
+                                       _titleFontFamily: options.tooltips.titleFontFamily,
+                                       _titleFontStyle: options.tooltips.titleFontStyle,
+                                       titleFontSize: options.tooltips.titleFontSize,
+
+                                       // Appearance
+                                       caretHeight: options.tooltips.caretSize,
+                                       cornerRadius: options.tooltips.cornerRadius,
+                                       backgroundColor: options.tooltips.backgroundColor,
+                                       opacity: 0,
+                                       legendColorBackground: options.tooltips.multiKeyBackground,
+                               },
+                       });
+               },
+               update: function() {
+
+                       var ctx = this._chart.ctx;
+
+                       switch (this._options.hover.mode) {
+                               case 'single':
+                                       helpers.extend(this._model, {
+                                               text: helpers.template(this._options.tooltips.template, {
+                                                       // These variables are available in the template function. Add others here
+                                                       element: this._active[0],
+                                                       value: this._data.datasets[this._active[0]._datasetIndex].data[this._active[0]._index],
+                                                       label: this._data.labels ? this._data.labels[this._active[0]._index] : '',
+                                               }),
+                                       });
+
+                                       var tooltipPosition = this._active[0].tooltipPosition();
+                                       helpers.extend(this._model, {
+                                               x: Math.round(tooltipPosition.x),
+                                               y: Math.round(tooltipPosition.y),
+                                               caretPadding: tooltipPosition.padding
+                                       });
+
+                                       break;
+
+                               case 'label':
+
+                                       // Tooltip Content
+
+                                       var dataArray,
+                                               dataIndex;
+
+                                       var labels = [],
+                                               colors = [];
+
+                                       for (var i = this._data.datasets.length - 1; i >= 0; i--) {
+                                               dataArray = this._data.datasets[i].metaData;
+                                               dataIndex = helpers.indexOf(dataArray, this._active[0]);
+                                               if (dataIndex !== -1) {
+                                                       break;
+                                               }
+                                       }
+
+                                       var medianPosition = (function(index) {
+                                               // Get all the points at that particular index
+                                               var elements = [],
+                                                       dataCollection,
+                                                       xPositions = [],
+                                                       yPositions = [],
+                                                       xMax,
+                                                       yMax,
+                                                       xMin,
+                                                       yMin;
+                                               helpers.each(this._data.datasets, function(dataset) {
+                                                       dataCollection = dataset.metaData;
+                                                       if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()) {
+                                                               elements.push(dataCollection[dataIndex]);
+                                                       }
+                                               }, this);
+
+                                               // Reverse labels if stacked
+                                               helpers.each(this._options.stacked ? elements.reverse() : elements, function(element) {
+                                                       xPositions.push(element._view.x);
+                                                       yPositions.push(element._view.y);
+
+                                                       //Include any colour information about the element
+                                                       labels.push(helpers.template(this._options.tooltips.multiTemplate, {
+                                                               // These variables are available in the template function. Add others here
+                                                               element: element,
+                                                               datasetLabel: this._data.datasets[element._datasetIndex].label,
+                                                               value: this._data.datasets[element._datasetIndex].data[element._index],
+                                                       }));
+                                                       colors.push({
+                                                               fill: element._view.backgroundColor,
+                                                               stroke: element._view.borderColor
+                                                       });
+
+                                               }, this);
+
+                                               yMin = helpers.min(yPositions);
+                                               yMax = helpers.max(yPositions);
+
+                                               xMin = helpers.min(xPositions);
+                                               xMax = helpers.max(xPositions);
+
+                                               return {
+                                                       x: (xMin > this._chart.width / 2) ? xMin : xMax,
+                                                       y: (yMin + yMax) / 2,
+                                               };
+                                       }).call(this, dataIndex);
+
+                                       // Apply for now
+                                       helpers.extend(this._model, {
+                                               x: medianPosition.x,
+                                               y: medianPosition.y,
+                                               labels: labels,
+                                               title: this._data.labels && this._data.labels.length ? this._data.labels[this._active[0]._index] : '',
+                                               legendColors: colors,
+                                               legendBackgroundColor: this._options.tooltips.multiKeyBackground,
+                                       });
+
+
+                                       // Calculate Appearance Tweaks
+
+                                       this._model.height = (labels.length * this._model.fontSize) + ((labels.length - 1) * (this._model.fontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5;
+
+                                       var titleWidth = ctx.measureText(this.title).width,
+                                               //Label has a legend square as well so account for this.
+                                               labelWidth = helpers.longestText(ctx, this.font, labels) + this._model.fontSize + 3,
+                                               longestTextWidth = helpers.max([labelWidth, titleWidth]);
+
+                                       this._model.width = longestTextWidth + (this._model.xPadding * 2);
+
+
+                                       var halfHeight = this._model.height / 2;
+
+                                       //Check to ensure the height will fit on the canvas
+                                       if (this._model.y - halfHeight < 0) {
+                                               this._model.y = halfHeight;
+                                       } else if (this._model.y + halfHeight > this._chart.height) {
+                                               this._model.y = this._chart.height - halfHeight;
+                                       }
+
+                                       //Decide whether to align left or right based on position on canvas
+                                       if (this._model.x > this._chart.width / 2) {
+                                               this._model.x -= this._model.xOffset + this._model.width;
+                                       } else {
+                                               this._model.x += this._model.xOffset;
+                                       }
+                                       break;
+                       }
+
+                       return this;
+               },
+               draw: function() {
+
+                       var ctx = this._chart.ctx;
+                       var vm = this._view;
+
+                       switch (this._options.hover.mode) {
+                               case 'single':
+
+                                       ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily);
+
+                                       vm.xAlign = "center";
+                                       vm.yAlign = "above";
+
+                                       //Distance between the actual element.y position and the start of the tooltip caret
+                                       var caretPadding = vm.caretPadding || 2;
+
+                                       var tooltipWidth = ctx.measureText(vm.text).width + 2 * vm.xPadding,
+                                               tooltipRectHeight = vm.fontSize + 2 * vm.yPadding,
+                                               tooltipHeight = tooltipRectHeight + vm.caretHeight + caretPadding;
+
+                                       if (vm.x + tooltipWidth / 2 > this._chart.width) {
+                                               vm.xAlign = "left";
+                                       } else if (vm.x - tooltipWidth / 2 < 0) {
+                                               vm.xAlign = "right";
+                                       }
+
+                                       if (vm.y - tooltipHeight < 0) {
+                                               vm.yAlign = "below";
+                                       }
+
+                                       var tooltipX = vm.x - tooltipWidth / 2,
+                                               tooltipY = vm.y - tooltipHeight;
+
+                                       ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
+
+                                       // Custom Tooltips
+                                       if (this._custom) {
+                                               this._custom(this._view);
+                                       } else {
+                                               switch (vm.yAlign) {
+                                                       case "above":
+                                                               //Draw a caret above the x/y
+                                                               ctx.beginPath();
+                                                               ctx.moveTo(vm.x, vm.y - caretPadding);
+                                                               ctx.lineTo(vm.x + vm.caretHeight, vm.y - (caretPadding + vm.caretHeight));
+                                                               ctx.lineTo(vm.x - vm.caretHeight, vm.y - (caretPadding + vm.caretHeight));
+                                                               ctx.closePath();
+                                                               ctx.fill();
+                                                               break;
+                                                       case "below":
+                                                               tooltipY = vm.y + caretPadding + vm.caretHeight;
+                                                               //Draw a caret below the x/y
+                                                               ctx.beginPath();
+                                                               ctx.moveTo(vm.x, vm.y + caretPadding);
+                                                               ctx.lineTo(vm.x + vm.caretHeight, vm.y + caretPadding + vm.caretHeight);
+                                                               ctx.lineTo(vm.x - vm.caretHeight, vm.y + caretPadding + vm.caretHeight);
+                                                               ctx.closePath();
+                                                               ctx.fill();
+                                                               break;
+                                               }
+
+                                               switch (vm.xAlign) {
+                                                       case "left":
+                                                               tooltipX = vm.x - tooltipWidth + (vm.cornerRadius + vm.caretHeight);
+                                                               break;
+                                                       case "right":
+                                                               tooltipX = vm.x - (vm.cornerRadius + vm.caretHeight);
+                                                               break;
+                                               }
+
+                                               helpers.drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, vm.cornerRadius);
+
+                                               ctx.fill();
+
+                                               ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString();
+                                               ctx.textAlign = "center";
+                                               ctx.textBaseline = "middle";
+                                               ctx.fillText(vm.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipRectHeight / 2);
+
+                                       }
+                                       break;
+                               case 'label':
+
+                                       helpers.drawRoundedRectangle(ctx, vm.x, vm.y - vm.height / 2, vm.width, vm.height, vm.cornerRadius);
+                                       ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
+                                       ctx.fill();
+                                       ctx.closePath();
+
+                                       ctx.textAlign = "left";
+                                       ctx.textBaseline = "middle";
+                                       ctx.fillStyle = helpers.color(vm.titleTextColor).alpha(vm.opacity).rgbString();
+                                       ctx.font = helpers.fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily);
+                                       ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0));
+
+                                       ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily);
+                                       helpers.each(vm.labels, function(label, index) {
+                                               ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString();
+                                               ctx.fillText(label, vm.x + vm.xPadding + vm.fontSize + 3, this.getLineHeight(index + 1));
+
+                                               //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
+                                               //ctx.clearRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize/2, vm.fontSize, vm.fontSize);
+                                               //Instead we'll make a white filled block to put the legendColour palette over.
+
+                                               ctx.fillStyle = helpers.color(vm.legendColors[index].stroke).alpha(vm.opacity).rgbString();
+                                               ctx.fillRect(vm.x + vm.xPadding - 1, this.getLineHeight(index + 1) - vm.fontSize / 2 - 1, vm.fontSize + 2, vm.fontSize + 2);
+
+                                               ctx.fillStyle = helpers.color(vm.legendColors[index].fill).alpha(vm.opacity).rgbString();
+                                               ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize);
+
+
+                                       }, this);
+                                       break;
+                       }
+               },
+               getLineHeight: function(index) {
+                       var baseLineHeight = this._view.y - (this._view.height / 2) + this._view.yPadding,
+                               afterTitleIndex = index - 1;
+
+                       //If the index is zero, we're getting the title
+                       if (index === 0) {
+                               return baseLineHeight + this._view.titleFontSize / 2;
+                       } else {
+                               return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5;
+                       }
+
+               },
+       });
 
 }).call(this);
index 0d3bddb68ffcbe54ad85089738609fe24c14ba0e..a726abfd29a5b8aeeca54345c620a6ca62b481d5 100644 (file)
 
 (function() {
 
-    "use strict";
-
-    var root = this,
-        Chart = root.Chart,
-        helpers = Chart.helpers;
-
-    Chart.defaults.global.elements.arc = {
-        backgroundColor: Chart.defaults.global.defaultColor,
-        borderColor: "#fff",
-        borderWidth: 2
-    };
-
-    Chart.Arc = Chart.Element.extend({
-        inGroupRange: function(mouseX) {
-            var vm = this._view;
-
-            if (vm) {
-                return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
-            } else {
-                return false;
-            }
-        },
-        inRange: function(chartX, chartY) {
-
-            var vm = this._view;
-
-            var pointRelativePosition = helpers.getAngleFromPoint(vm, {
-                x: chartX,
-                y: chartY
-            });
-
-            // Put into the range of (-PI/2, 3PI/2]
-            var startAngle = vm.startAngle < (-0.5 * Math.PI) ? vm.startAngle + (2.0 * Math.PI) : vm.startAngle > (1.5 * Math.PI) ? vm.startAngle - (2.0 * Math.PI) : vm.startAngle;
-            var endAngle = vm.endAngle < (-0.5 * Math.PI) ? vm.endAngle + (2.0 * Math.PI) : vm.endAngle > (1.5 * Math.PI) ? vm.endAngle - (2.0 * Math.PI) : vm.endAngle
-
-            //Check if within the range of the open/close angle
-            var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle),
-                withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius);
-
-            return (betweenAngles && withinRadius);
-            //Ensure within the outside of the arc centre, but inside arc outer
-        },
-        tooltipPosition: function() {
-            var vm = this._view;
-
-            var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),
-                rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
-            return {
-                x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
-                y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
-            };
-        },
-        draw: function() {
-
-            var ctx = this._chart.ctx;
-            var vm = this._view;
-
-            ctx.beginPath();
-
-            ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle);
-
-            ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true);
-
-            ctx.closePath();
-            ctx.strokeStyle = vm.borderColor;
-            ctx.lineWidth = vm.borderWidth;
-
-            ctx.fillStyle = vm.backgroundColor;
-
-            ctx.fill();
-            ctx.lineJoin = 'bevel';
-
-            if (vm.borderWidth) {
-                ctx.stroke();
-            }
-        }
-    });
+       "use strict";
+
+       var root = this,
+               Chart = root.Chart,
+               helpers = Chart.helpers;
+
+       Chart.defaults.global.elements.arc = {
+               backgroundColor: Chart.defaults.global.defaultColor,
+               borderColor: "#fff",
+               borderWidth: 2
+       };
+
+       Chart.Arc = Chart.Element.extend({
+               inGroupRange: function(mouseX) {
+                       var vm = this._view;
+
+                       if (vm) {
+                               return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
+                       } else {
+                               return false;
+                       }
+               },
+               inRange: function(chartX, chartY) {
+
+                       var vm = this._view;
+
+                       var pointRelativePosition = helpers.getAngleFromPoint(vm, {
+                               x: chartX,
+                               y: chartY
+                       });
+
+                       // Put into the range of (-PI/2, 3PI/2]
+                       var startAngle = vm.startAngle < (-0.5 * Math.PI) ? vm.startAngle + (2.0 * Math.PI) : vm.startAngle > (1.5 * Math.PI) ? vm.startAngle - (2.0 * Math.PI) : vm.startAngle;
+                       var endAngle = vm.endAngle < (-0.5 * Math.PI) ? vm.endAngle + (2.0 * Math.PI) : vm.endAngle > (1.5 * Math.PI) ? vm.endAngle - (2.0 * Math.PI) : vm.endAngle
+
+                       //Check if within the range of the open/close angle
+                       var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle),
+                               withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius);
+
+                       return (betweenAngles && withinRadius);
+                       //Ensure within the outside of the arc centre, but inside arc outer
+               },
+               tooltipPosition: function() {
+                       var vm = this._view;
+
+                       var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),
+                               rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
+                       return {
+                               x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
+                               y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
+                       };
+               },
+               draw: function() {
+
+                       var ctx = this._chart.ctx;
+                       var vm = this._view;
+
+                       ctx.beginPath();
+
+                       ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle);
+
+                       ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true);
+
+                       ctx.closePath();
+                       ctx.strokeStyle = vm.borderColor;
+                       ctx.lineWidth = vm.borderWidth;
+
+                       ctx.fillStyle = vm.backgroundColor;
+
+                       ctx.fill();
+                       ctx.lineJoin = 'bevel';
+
+                       if (vm.borderWidth) {
+                               ctx.stroke();
+                       }
+               }
+       });
 
 
 }).call(this);
index eaa245c76b8fe54060dbdff2486be003cdc6619f..edefce6d0920c136df9dd26cffcb7391800277ec 100644 (file)
 
 (function() {
 
-    "use strict";
-
-    var root = this,
-        Chart = root.Chart,
-        helpers = Chart.helpers;
-
-    Chart.defaults.global.elements.line = {
-        tension: 0.4,
-        backgroundColor: Chart.defaults.global.defaultColor,
-        borderWidth: 3,
-        borderColor: Chart.defaults.global.defaultColor,
-        fill: true, // do we fill in the area between the line and its base axis
-        skipNull: true,
-        drawNull: false,
-    };
-
-
-    Chart.Line = Chart.Element.extend({
-        draw: function() {
-
-            var vm = this._view;
-            var ctx = this._chart.ctx;
-            var first = this._children[0];
-            var last = this._children[this._children.length - 1];
-
-            // Draw the background first (so the border is always on top)
-            helpers.each(this._children, function(point, index) {
-                var previous = this.previousPoint(point, this._children, index);
-                var next = this.nextPoint(point, this._children, index);
-
-                // First point only
-                if (index === 0) {
-                    ctx.moveTo(point._view.x, point._view.y);
-                    return;
-                }
-
-                // Start Skip and drag along scale baseline
-                if (point._view.skip && vm.skipNull && !this._loop) {
-                    ctx.lineTo(previous._view.x, point._view.y);
-                    ctx.moveTo(next._view.x, point._view.y);
-                }
-                // End Skip Stright line from the base line
-                else if (previous._view.skip && vm.skipNull && !this._loop) {
-                    ctx.moveTo(point._view.x, previous._view.y);
-                    ctx.lineTo(point._view.x, point._view.y);
-                }
-
-                if (previous._view.skip && vm.skipNull) {
-                    ctx.moveTo(point._view.x, point._view.y);
-                }
-                // Normal Bezier Curve
-                else {
-                    if (vm.tension > 0) {
-                        ctx.bezierCurveTo(
-                            previous._view.controlPointNextX,
-                            previous._view.controlPointNextY,
-                            point._view.controlPointPreviousX,
-                            point._view.controlPointPreviousY,
-                            point._view.x,
-                            point._view.y
-                        );
-                    } else {
-                        ctx.lineTo(point._view.x, point._view.y);
-                    }
-                }
-            }, this);
-
-            // For radial scales, loop back around to the first point
-            if (this._loop) {
-                if (vm.tension > 0 && !first._view.skip) {
-
-                    ctx.bezierCurveTo(
-                        last._view.controlPointNextX,
-                        last._view.controlPointNextY,
-                        first._view.controlPointPreviousX,
-                        first._view.controlPointPreviousY,
-                        first._view.x,
-                        first._view.y
-                    );
-                } else {
-                    ctx.lineTo(first._view.x, first._view.y);
-                }
-            }
-
-            // If we had points and want to fill this line, do so.
-            if (this._children.length > 0 && vm.fill) {
-                //Round off the line by going to the base of the chart, back to the start, then fill.
-                ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero);
-                ctx.lineTo(this._children[0]._view.x, vm.scaleZero);
-                ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
-                ctx.closePath();
-                ctx.fill();
-            }
-
-
-            // Now draw the line between all the points with any borders
-            ctx.lineWidth = vm.borderWidth || Chart.defaults.global.defaultColor;
-            ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
-            ctx.beginPath();
-
-            helpers.each(this._children, function(point, index) {
-                var previous = this.previousPoint(point, this._children, index);
-                var next = this.nextPoint(point, this._children, index);
-
-                // First point only
-                if (index === 0) {
-                    ctx.moveTo(point._view.x, point._view.y);
-                    return;
-                }
-
-                // Start Skip and drag along scale baseline
-                if (point._view.skip && vm.skipNull && !this._loop) {
-                    ctx.moveTo(previous._view.x, point._view.y);
-                    ctx.moveTo(next._view.x, point._view.y);
-                    return;
-                }
-                // End Skip Stright line from the base line
-                if (previous._view.skip && vm.skipNull && !this._loop) {
-                    ctx.moveTo(point._view.x, previous._view.y);
-                    ctx.moveTo(point._view.x, point._view.y);
-                    return;
-                }
-
-                if (previous._view.skip && vm.skipNull) {
-                    ctx.moveTo(point._view.x, point._view.y);
-                    return;
-                }
-                // Normal Bezier Curve
-                if (vm.tension > 0) {
-                    ctx.bezierCurveTo(
-                        previous._view.controlPointNextX,
-                        previous._view.controlPointNextY,
-                        point._view.controlPointPreviousX,
-                        point._view.controlPointPreviousY,
-                        point._view.x,
-                        point._view.y
-                    );
-                } else {
-                    ctx.lineTo(point._view.x, point._view.y);
-                }
-            }, this);
-
-            if (this._loop && !first._view.skip) {
-                if (vm.tension > 0) {
-
-                    ctx.bezierCurveTo(
-                        last._view.controlPointNextX,
-                        last._view.controlPointNextY,
-                        first._view.controlPointPreviousX,
-                        first._view.controlPointPreviousY,
-                        first._view.x,
-                        first._view.y
-                    );
-                } else {
-                    ctx.lineTo(first._view.x, first._view.y);
-                }
-            }
-
-
-            ctx.stroke();
-
-        },
-        nextPoint: function(point, collection, index) {
-            if (this.loop) {
-                return collection[index + 1] || collection[0];
-            }
-            return collection[index + 1] || collection[collection.length - 1];
-        },
-        previousPoint: function(point, collection, index) {
-            if (this.loop) {
-                return collection[index - 1] || collection[collection.length - 1];
-            }
-            return collection[index - 1] || collection[0];
-        },
-    });
+       "use strict";
+
+       var root = this,
+               Chart = root.Chart,
+               helpers = Chart.helpers;
+
+       Chart.defaults.global.elements.line = {
+               tension: 0.4,
+               backgroundColor: Chart.defaults.global.defaultColor,
+               borderWidth: 3,
+               borderColor: Chart.defaults.global.defaultColor,
+               fill: true, // do we fill in the area between the line and its base axis
+               skipNull: true,
+               drawNull: false,
+       };
+
+
+       Chart.Line = Chart.Element.extend({
+               draw: function() {
+
+                       var vm = this._view;
+                       var ctx = this._chart.ctx;
+                       var first = this._children[0];
+                       var last = this._children[this._children.length - 1];
+
+                       // Draw the background first (so the border is always on top)
+                       helpers.each(this._children, function(point, index) {
+                               var previous = this.previousPoint(point, this._children, index);
+                               var next = this.nextPoint(point, this._children, index);
+
+                               // First point only
+                               if (index === 0) {
+                                       ctx.moveTo(point._view.x, point._view.y);
+                                       return;
+                               }
+
+                               // Start Skip and drag along scale baseline
+                               if (point._view.skip && vm.skipNull && !this._loop) {
+                                       ctx.lineTo(previous._view.x, point._view.y);
+                                       ctx.moveTo(next._view.x, point._view.y);
+                               }
+                               // End Skip Stright line from the base line
+                               else if (previous._view.skip && vm.skipNull && !this._loop) {
+                                       ctx.moveTo(point._view.x, previous._view.y);
+                                       ctx.lineTo(point._view.x, point._view.y);
+                               }
+
+                               if (previous._view.skip && vm.skipNull) {
+                                       ctx.moveTo(point._view.x, point._view.y);
+                               }
+                               // Normal Bezier Curve
+                               else {
+                                       if (vm.tension > 0) {
+                                               ctx.bezierCurveTo(
+                                                       previous._view.controlPointNextX,
+                                                       previous._view.controlPointNextY,
+                                                       point._view.controlPointPreviousX,
+                                                       point._view.controlPointPreviousY,
+                                                       point._view.x,
+                                                       point._view.y
+                                               );
+                                       } else {
+                                               ctx.lineTo(point._view.x, point._view.y);
+                                       }
+                               }
+                       }, this);
+
+                       // For radial scales, loop back around to the first point
+                       if (this._loop) {
+                               if (vm.tension > 0 && !first._view.skip) {
+
+                                       ctx.bezierCurveTo(
+                                               last._view.controlPointNextX,
+                                               last._view.controlPointNextY,
+                                               first._view.controlPointPreviousX,
+                                               first._view.controlPointPreviousY,
+                                               first._view.x,
+                                               first._view.y
+                                       );
+                               } else {
+                                       ctx.lineTo(first._view.x, first._view.y);
+                               }
+                       }
+
+                       // If we had points and want to fill this line, do so.
+                       if (this._children.length > 0 && vm.fill) {
+                               //Round off the line by going to the base of the chart, back to the start, then fill.
+                               ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero);
+                               ctx.lineTo(this._children[0]._view.x, vm.scaleZero);
+                               ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
+                               ctx.closePath();
+                               ctx.fill();
+                       }
+
+
+                       // Now draw the line between all the points with any borders
+                       ctx.lineWidth = vm.borderWidth || Chart.defaults.global.defaultColor;
+                       ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
+                       ctx.beginPath();
+
+                       helpers.each(this._children, function(point, index) {
+                               var previous = this.previousPoint(point, this._children, index);
+                               var next = this.nextPoint(point, this._children, index);
+
+                               // First point only
+                               if (index === 0) {
+                                       ctx.moveTo(point._view.x, point._view.y);
+                                       return;
+                               }
+
+                               // Start Skip and drag along scale baseline
+                               if (point._view.skip && vm.skipNull && !this._loop) {
+                                       ctx.moveTo(previous._view.x, point._view.y);
+                                       ctx.moveTo(next._view.x, point._view.y);
+                                       return;
+                               }
+                               // End Skip Stright line from the base line
+                               if (previous._view.skip && vm.skipNull && !this._loop) {
+                                       ctx.moveTo(point._view.x, previous._view.y);
+                                       ctx.moveTo(point._view.x, point._view.y);
+                                       return;
+                               }
+
+                               if (previous._view.skip && vm.skipNull) {
+                                       ctx.moveTo(point._view.x, point._view.y);
+                                       return;
+                               }
+                               // Normal Bezier Curve
+                               if (vm.tension > 0) {
+                                       ctx.bezierCurveTo(
+                                               previous._view.controlPointNextX,
+                                               previous._view.controlPointNextY,
+                                               point._view.controlPointPreviousX,
+                                               point._view.controlPointPreviousY,
+                                               point._view.x,
+                                               point._view.y
+                                       );
+                               } else {
+                                       ctx.lineTo(point._view.x, point._view.y);
+                               }
+                       }, this);
+
+                       if (this._loop && !first._view.skip) {
+                               if (vm.tension > 0) {
+
+                                       ctx.bezierCurveTo(
+                                               last._view.controlPointNextX,
+                                               last._view.controlPointNextY,
+                                               first._view.controlPointPreviousX,
+                                               first._view.controlPointPreviousY,
+                                               first._view.x,
+                                               first._view.y
+                                       );
+                               } else {
+                                       ctx.lineTo(first._view.x, first._view.y);
+                               }
+                       }
+
+
+                       ctx.stroke();
+
+               },
+               nextPoint: function(point, collection, index) {
+                       if (this.loop) {
+                               return collection[index + 1] || collection[0];
+                       }
+                       return collection[index + 1] || collection[collection.length - 1];
+               },
+               previousPoint: function(point, collection, index) {
+                       if (this.loop) {
+                               return collection[index - 1] || collection[collection.length - 1];
+                       }
+                       return collection[index - 1] || collection[0];
+               },
+       });
 
 }).call(this);
index f402d7292022ef7fb10f67e924f7ece75193d827..13e272de6f75be851f17255622f59988a983daf0 100644 (file)
 
 (function() {
 
-    "use strict";
-
-    var root = this,
-        Chart = root.Chart,
-        helpers = Chart.helpers;
-
-    Chart.defaults.global.elements.point = {
-        radius: 3,
-        backgroundColor: Chart.defaults.global.defaultColor,
-        borderWidth: 1,
-        borderColor: Chart.defaults.global.defaultColor,
-        // Hover
-        hitRadius: 1,
-        hoverRadius: 4,
-        hoverBorderWidth: 1,
-    };
-
-
-    Chart.Point = Chart.Element.extend({
-        inRange: function(mouseX, mouseY) {
-            var vm = this._view;
-            var hoverRange = vm.hitRadius + vm.radius;
-            return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2));
-        },
-        inGroupRange: function(mouseX) {
-            var vm = this._view;
-
-            if (vm) {
-                return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2));
-            } else {
-                return false;
-            }
-        },
-        tooltipPosition: function() {
-            var vm = this._view;
-            return {
-                x: vm.x,
-                y: vm.y,
-                padding: vm.radius + vm.borderWidth
-            };
-        },
-        draw: function() {
-
-            var vm = this._view;
-            var ctx = this._chart.ctx;
-
-
-            if (vm.skip) {
-                return;
-            }
-
-            if (vm.radius > 0 || vm.borderWidth > 0) {
-
-                ctx.beginPath();
-
-                ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2);
-                ctx.closePath();
-
-                ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
-                ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth;
-
-                ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
-
-                ctx.fill();
-                ctx.stroke();
-            }
-        }
-    });
+       "use strict";
+
+       var root = this,
+               Chart = root.Chart,
+               helpers = Chart.helpers;
+
+       Chart.defaults.global.elements.point = {
+               radius: 3,
+               backgroundColor: Chart.defaults.global.defaultColor,
+               borderWidth: 1,
+               borderColor: Chart.defaults.global.defaultColor,
+               // Hover
+               hitRadius: 1,
+               hoverRadius: 4,
+               hoverBorderWidth: 1,
+       };
+
+
+       Chart.Point = Chart.Element.extend({
+               inRange: function(mouseX, mouseY) {
+                       var vm = this._view;
+                       var hoverRange = vm.hitRadius + vm.radius;
+                       return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2));
+               },
+               inGroupRange: function(mouseX) {
+                       var vm = this._view;
+
+                       if (vm) {
+                               return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2));
+                       } else {
+                               return false;
+                       }
+               },
+               tooltipPosition: function() {
+                       var vm = this._view;
+                       return {
+                               x: vm.x,
+                               y: vm.y,
+                               padding: vm.radius + vm.borderWidth
+                       };
+               },
+               draw: function() {
+
+                       var vm = this._view;
+                       var ctx = this._chart.ctx;
+
+
+                       if (vm.skip) {
+                               return;
+                       }
+
+                       if (vm.radius > 0 || vm.borderWidth > 0) {
+
+                               ctx.beginPath();
+
+                               ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2);
+                               ctx.closePath();
+
+                               ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
+                               ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth;
+
+                               ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
+
+                               ctx.fill();
+                               ctx.stroke();
+                       }
+               }
+       });
 
 
 }).call(this);
index fc4a58c0f17477c5953a79803e47fe9247f6cc73..7c35df01d3e9f7c6ac11390be408e586af41e7f0 100644 (file)
 
 (function() {
 
-    "use strict";
+       "use strict";
 
-    var root = this,
-        Chart = root.Chart,
-        helpers = Chart.helpers;
+       var root = this,
+               Chart = root.Chart,
+               helpers = Chart.helpers;
 
-    Chart.defaults.global.elements.rectangle = {
-        backgroundColor: Chart.defaults.global.defaultColor,
-        borderWidth: 0,
-        borderColor: Chart.defaults.global.defaultColor,
-    };
+       Chart.defaults.global.elements.rectangle = {
+               backgroundColor: Chart.defaults.global.defaultColor,
+               borderWidth: 0,
+               borderColor: Chart.defaults.global.defaultColor,
+       };
 
-    Chart.Rectangle = Chart.Element.extend({
-        draw: function() {
+       Chart.Rectangle = Chart.Element.extend({
+               draw: function() {
 
-            var ctx = this._chart.ctx;
-            var vm = this._view;
+                       var ctx = this._chart.ctx;
+                       var vm = this._view;
 
-            var halfWidth = vm.width / 2,
-                leftX = vm.x - halfWidth,
-                rightX = vm.x + halfWidth,
-                top = vm.base - (vm.base - vm.y),
-                halfStroke = vm.borderWidth / 2;
+                       var halfWidth = vm.width / 2,
+                               leftX = vm.x - halfWidth,
+                               rightX = vm.x + halfWidth,
+                               top = vm.base - (vm.base - vm.y),
+                               halfStroke = vm.borderWidth / 2;
 
-            // Canvas doesn't allow us to stroke inside the width so we can
-            // adjust the sizes to fit if we're setting a stroke on the line
-            if (vm.borderWidth) {
-                leftX += halfStroke;
-                rightX -= halfStroke;
-                top += halfStroke;
-            }
+                       // Canvas doesn't allow us to stroke inside the width so we can
+                       // adjust the sizes to fit if we're setting a stroke on the line
+                       if (vm.borderWidth) {
+                               leftX += halfStroke;
+                               rightX -= halfStroke;
+                               top += halfStroke;
+                       }
 
-            ctx.beginPath();
+                       ctx.beginPath();
 
-            ctx.fillStyle = vm.backgroundColor;
-            ctx.strokeStyle = vm.borderColor;
-            ctx.lineWidth = vm.borderWidth;
+                       ctx.fillStyle = vm.backgroundColor;
+                       ctx.strokeStyle = vm.borderColor;
+                       ctx.lineWidth = vm.borderWidth;
 
-            // It'd be nice to keep this class totally generic to any rectangle
-            // and simply specify which border to miss out.
-            ctx.moveTo(leftX, vm.base);
-            ctx.lineTo(leftX, top);
-            ctx.lineTo(rightX, top);
-            ctx.lineTo(rightX, vm.base);
-            ctx.fill();
-            if (vm.borderWidth) {
-                ctx.stroke();
-            }
-        },
-        height: function() {
-            var vm = this._view;
-            return vm.base - vm.y;
-        },
-        inRange: function(mouseX, mouseY) {
-            var vm = this._view;
-            if (vm.y < vm.base) {
-                return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base);
-            } else {
-                return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y);
-            }
-        },
-        inGroupRange: function(mouseX) {
-            var vm = this._view;
-            return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2);
-        },
-        tooltipPosition: function() {
-            var vm = this._view;
-            if (vm.y < vm.base) {
-                return {
-                    x: vm.x,
-                    y: vm.y
-                };
-            } else {
-                return {
-                    x: vm.x,
-                    y: vm.base
-                };
-            }
-        },
-    });
+                       // It'd be nice to keep this class totally generic to any rectangle
+                       // and simply specify which border to miss out.
+                       ctx.moveTo(leftX, vm.base);
+                       ctx.lineTo(leftX, top);
+                       ctx.lineTo(rightX, top);
+                       ctx.lineTo(rightX, vm.base);
+                       ctx.fill();
+                       if (vm.borderWidth) {
+                               ctx.stroke();
+                       }
+               },
+               height: function() {
+                       var vm = this._view;
+                       return vm.base - vm.y;
+               },
+               inRange: function(mouseX, mouseY) {
+                       var vm = this._view;
+                       if (vm.y < vm.base) {
+                               return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base);
+                       } else {
+                               return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y);
+                       }
+               },
+               inGroupRange: function(mouseX) {
+                       var vm = this._view;
+                       return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2);
+               },
+               tooltipPosition: function() {
+                       var vm = this._view;
+                       if (vm.y < vm.base) {
+                               return {
+                                       x: vm.x,
+                                       y: vm.y
+                               };
+                       } else {
+                               return {
+                                       x: vm.x,
+                                       y: vm.base
+                               };
+                       }
+               },
+       });
 
 }).call(this);