]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Check in work from floobits with minor changes to fix some errors
authorEvert Timberg <evert.timberg@gmail.com>
Sun, 31 May 2015 13:47:10 +0000 (09:47 -0400)
committerEvert Timberg <evert.timberg@gmail.com>
Sun, 31 May 2015 13:47:10 +0000 (09:47 -0400)
src/Chart.Core.js
src/Chart.Line.js

index 48d6be231aab1520a7406090915306fa9208e6d9..a41e81f0e2a28d3abfb708c04eaec749c259c474 100755 (executable)
 
         return this;
     };
+
+    var defaultColor = 'rgba(0,0,0,0.1)';
+
     //Globally expose the defaults to allow for user updating/changing
     Chart.defaults = {
         global: {
-
-            // Animation defaults
             animation: {
-                // Number - Number of animation steps
                 duration: 1000,
-
-                // String - Animation easing effect
                 easing: "easeOutQuart",
-
-                // Function - Will fire on animation progression.
                 onProgress: function() {},
-
-                // Function - Will fire on animation completion.
                 onComplete: function() {},
             },
-
-            // Boolean - whether or not the chart should be responsive and resize when the browser does.
             responsive: false,
-
-            // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
             maintainAspectRatio: true,
-
-            // Array - Array of string names to attach interaction events
             events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "touchend"],
-
-            // Hover defaults
             hover: {
-
-                // String || boolean
-                mode: 'label', // 'label', 'dataset', 'false'
-
-                //Function(event, activeElements) - Custom hover handler
                 onHover: null,
-
-                //Function - Custom hover handler
                 animationDuration: 400,
             },
-
-            //Function(event, clickedElements) - Custom click handler 
             onClick: null,
-
-            // Tooltip Defaults
             tooltips: {
-
-                // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
                 enabled: true,
-
-                // Function - Determines whether to draw built-in tooltip or call custom tooltip function
                 custom: null,
-
-                // String - Tooltip background colour
                 backgroundColor: "rgba(0,0,0,0.8)",
-
-                // String - Tooltip label font declaration for the scale label
                 fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
-
-                // Number - Tooltip label font size in pixels
                 fontSize: 14,
-
-                // String - Tooltip font weight style
                 fontStyle: "normal",
-
-                // String - Tooltip label font colour
                 fontColor: "#fff",
-
-                // String - Tooltip title font declaration for the scale label
                 titleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
-
-                // Number - Tooltip title font size in pixels
                 titleFontSize: 14,
-
-                // String - Tooltip title font weight style
                 titleFontStyle: "bold",
-
-                // String - Tooltip title font colour
                 titleFontColor: "#fff",
-
-                // Number - pixel width of padding around text
                 yPadding: 6,
-
-                // Number - pixel width of padding around text
                 xPadding: 6,
-
-                // Number - Size of the caret on the
                 caretSize: 8,
-
-                // Number - Pixel radius of the border
                 cornerRadius: 6,
-
-                // Number - Pixel offset from point x to edge
                 xOffset: 10,
-
-                // String - Template string for singles
                 template: "<%if (label){%><%=label%>: <%}%><%= value %>",
-
-                // String - Template string for singles
                 multiTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= value %>",
-
-                // String - Colour behind the legend colour block
                 multiKeyBackground: '#fff',
 
             },
-
-            // Color String - Used for undefined Colros
-            defaultColor: 'rgba(0,0,0,0.1)',
-
-        }
+            defaultColor: defaultColor,
+
+            // Element defaults
+            elements: {
+                line: {
+                    tension: 0.4,
+                    backgroundColor: defaultColor,
+                    borderWidth: 3,
+                    borderColor: defaultColor,
+                    // Hover
+                    hitRadius: 6,
+                    hoverBorderWidth: 2,
+                },
+                point: {
+                    radius: 3,
+                    backgroundColor: defaultColor,
+                    borderWidth: 1,
+                    borderColor: defaultColor,
+                    // Hover
+                    hitRadius: 6,
+                    hoverRadius: 5,
+                    hoverBorderWidth: 2,
+                },
+            }
+        },
     };
 
     //Create a dictionary of chart types, to allow for extension of existing types
 
             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);
     };
 
     Chart.Element = function(configuration) {
-        extend(this, {
-            _vm: {},
-        });
         extend(this, configuration);
         this.initialize.apply(this, arguments);
     };
     extend(Chart.Element.prototype, {
         initialize: function() {},
-        save: function() {
-            this._vm = clone(this);
-            delete this._vm._vm;
-            delete this._vm._start;
-            return this;
-        },
         pivot: function() {
-            if (this._start) {
-                this._start = clone(this);
-                helpers.extend(this._start, this._vm);
-            }
+            this._start = clone(this._view);
             return this;
         },
         transition: function(ease) {
+            if (!this._view) {
+                this._view = clone(this._model);
+            }
             if (!this._start) {
-                if (!this._vm) {
-                    this.save();
-                }
-                this._start = clone(this._vm);
+                this.pivot();
             }
 
-            each(this, function(value, key) {
+            each(this._model, function(value, key) {
 
                 if (key[0] === '_' || !this.hasOwnProperty(key)) {
                     // Only non-underscored properties
                 }
 
                 // Init if doesn't exist
-                else if (!this._vm[key]) {
-                    this._vm[key] = value || null;
+                else if (!this._view[key]) {
+                    this._view[key] = value || null;
                 }
 
                 // No unnecessary computations
-                else if (this[key] === this._vm[key]) {
+                else if (this[key] === this._view[key]) {
                     // It's the same! Woohoo!
                 }
 
                 else if (typeof value === 'string') {
                     try {
                         var color = helpers.color(this._start[key]).mix(helpers.color(this[key]), ease);
-                        this._vm[key] = color.rgbString();
+                        this._view[key] = color.rgbString();
                     } catch (err) {
-                        this._vm[key] = value;
+                        this._view[key] = value;
                     }
                 }
                 // Number transitions
                 else if (typeof value === 'number') {
-
-                    this._vm[key] = ((this[key] - this._start[key]) * ease) + this._start[key];
-                } else {
-                    // Everything else
-                    this._vm[key] = value;
+                    this._view[key] = ((this[key] - this._start[key]) * ease) + this._start[key];
+                }
+                // Everything else
+                else {
+                    this._view[key] = value;
                 }
 
             }, this);
 
     Chart.Point = Chart.Element.extend({
         inRange: function(mouseX, mouseY) {
-            var vm = this._vm;
+            var vm = this._view;
             var hoverRange = vm.hoverRadius + 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._vm;
+            var vm = this._view;
             return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + this.hoverRadius, 2));
         },
         tooltipPosition: function() {
-            var vm = this._vm;
+            var vm = this._view;
             return {
                 x: vm.x,
                 y: vm.y,
         },
         draw: function() {
 
-            var vm = this._vm;
+            var vm = this._view;
             var ctx = this._chart.ctx;
 
             if (vm.radius > 0 || vm.borderWidth > 0) {
     Chart.Line = Chart.Element.extend({
         draw: function() {
 
-            var vm = this._vm;
+            var vm = this._view;
             var ctx = this._chart.ctx;
-            var first = vm._points[0];
-            var last = vm._points[vm._points.length - 1];
+            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(vm._points, function(point, index) {
+            helpers.each(this._children, function(point, index) {
                 if (index === 0) {
-                    ctx.moveTo(point._vm.x, point._vm.y);
+                    ctx.moveTo(point._view.x, point._view.y);
                 } else {
                     if (vm._tension > 0 || 1) {
-                        var previous = this.previousPoint(point, vm._points, index);
+                        var previous = this.previousPoint(point, this._children, index);
 
                         ctx.bezierCurveTo(
-                            previous._vm.controlPointNextX,
-                            previous._vm.controlPointNextY,
-                            point._vm.controlPointPreviousX,
-                            point._vm.controlPointPreviousY,
-                            point._vm.x,
-                            point._vm.y
+                            previous._view.controlPointNextX,
+                            previous._view.controlPointNextY,
+                            point._view.controlPointPreviousX,
+                            point._view.controlPointPreviousY,
+                            point._view.x,
+                            point._view.y
                         );
                     } else {
-                        ctx.lineTo(point._vm.x, point._vm.y);
+                        ctx.lineTo(point._view.x, point._view.y);
                     }
                 }
             }, this);
                 if (vm._tension > 0 || 1) {
 
                     ctx.bezierCurveTo(
-                        last._vm.controlPointNextX,
-                        last._vm.controlPointNextY,
-                        first._vm.controlPointPreviousX,
-                        first._vm.controlPointPreviousY,
-                        first._vm.x,
-                        first._vm.y
+                        last._view.controlPointNextX,
+                        last._view.controlPointNextY,
+                        first._view.controlPointPreviousX,
+                        first._view.controlPointPreviousY,
+                        first._view.x,
+                        first._view.y
                     );
                 } else {
-                    ctx.lineTo(first._vm.x, first._vm.y);
+                    ctx.lineTo(first._view.x, first._view.y);
                 }
             }
 
-            if (vm._points.length > 0) {
+            if (this._children.length > 0) {
                 //Round off the line by going to the base of the chart, back to the start, then fill.
-                ctx.lineTo(vm._points[vm._points.length - 1].x, vm.scaleZero);
-                ctx.lineTo(vm._points[0].x, vm.scaleZero);
+                ctx.lineTo(this._children[this._children.length - 1].x, vm.scaleZero);
+                ctx.lineTo(this._children[0].x, vm.scaleZero);
                 ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
                 ctx.closePath();
                 ctx.fill();
             ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
             ctx.beginPath();
 
-            helpers.each(vm._points, function(point, index) {
+            helpers.each(this._children, function(point, index) {
                 if (index === 0) {
-                    ctx.moveTo(point._vm.x, point._vm.y);
+                    ctx.moveTo(point._view.x, point._view.y);
                 } else {
                     if (vm._tension > 0 || 1) {
-                        var previous = this.previousPoint(point, vm._points, index);
+                        var previous = this.previousPoint(point, this._children, index);
 
                         ctx.bezierCurveTo(
-                            previous._vm.controlPointNextX,
-                            previous._vm.controlPointNextY,
-                            point._vm.controlPointPreviousX,
-                            point._vm.controlPointPreviousY,
-                            point._vm.x,
-                            point._vm.y
+                            previous._view.controlPointNextX,
+                            previous._view.controlPointNextY,
+                            point._view.controlPointPreviousX,
+                            point._view.controlPointPreviousY,
+                            point._view.x,
+                            point._view.y
                         );
                     } else {
-                        ctx.lineTo(point._vm.x, point._vm.y);
+                        ctx.lineTo(point._view.x, point._view.y);
                     }
                 }
             }, this);
                 if (vm._tension > 0 || 1) {
 
                     ctx.bezierCurveTo(
-                        last._vm.controlPointNextX,
-                        last._vm.controlPointNextY,
-                        first._vm.controlPointPreviousX,
-                        first._vm.controlPointPreviousY,
-                        first._vm.x,
-                        first._vm.y
+                        last._view.controlPointNextX,
+                        last._view.controlPointNextY,
+                        first._view.controlPointPreviousX,
+                        first._view.controlPointPreviousY,
+                        first._view.x,
+                        first._view.y
                     );
                 } else {
-                    ctx.lineTo(first._vm.x, first._vm.y);
+                    ctx.lineTo(first._view.x, first._view.y);
                 }
             }
 
     Chart.Arc = Chart.Element.extend({
         inRange: function(chartX, chartY) {
 
-            var vm = this._vm;
+            var vm = this._view;
 
             var pointRelativePosition = helpers.getAngleFromPoint(vm, {
                 x: chartX,
             //Ensure within the outside of the arc centre, but inside arc outer
         },
         tooltipPosition: function() {
-            var vm = this._vm;
+            var vm = this._view;
 
             var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),
                 rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
         draw: function() {
 
             var ctx = this._chart.ctx;
-            var vm = this._vm;
+            var vm = this._view;
 
             ctx.beginPath();
 
     Chart.Rectangle = Chart.Element.extend({
         draw: function() {
 
-            var vm = this._vm;
+            var vm = this._view;
 
             var ctx = this.ctx,
                 halfWidth = vm.width / 2,
             }
         },
         height: function() {
-            var vm = this._vm;
+            var vm = this._view;
             return vm.base - vm.y;
         },
         inRange: function(mouseX, mouseY) {
-            var vm = this._vm;
+            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 {
             }
         },
         inGroupRange: function(mouseX) {
-            var vm = this._vm;
+            var vm = this._view;
             return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2);
         },
         tooltipPosition: function() {
-            var vm = this._vm;
+            var vm = this._view;
             if (vm.y < vm.base) {
                 return {
                     x: vm.x,
                         });
 
                         helpers.each(elements, function(element) {
-                            xPositions.push(element._vm.x);
-                            yPositions.push(element._vm.y);
+                            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, element));
                             colors.push({
-                                fill: element._vm.backgroundColor,
-                                stroke: element._vm.borderColor
+                                fill: element._view.backgroundColor,
+                                stroke: element._view.borderColor
                             });
 
                         }, this);
         draw: function() {
 
             var ctx = this._chart.ctx;
-            var vm = this._vm;
+            var vm = this._view;
 
             switch (this._options.hover.mode) {
                 case 'single':
 
                     // Custom Tooltips
                     if (this._custom) {
-                        this._custom(this._vm);
+                        this._custom(this._view);
                     } else {
                         switch (vm.yAlign) {
                             case "above":
             }
         },
         getLineHeight: function(index) {
-            var baseLineHeight = this._vm.y - (this._vm.height / 2) + this._vm.yPadding,
+            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._vm.titleFontSize / 2;
+                return baseLineHeight + this._view.titleFontSize / 2;
             } else {
-                return baseLineHeight + ((this._vm.fontSize * 1.5 * afterTitleIndex) + this._vm.fontSize / 2) + this._vm.titleFontSize * 1.5;
+                return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5;
             }
 
         },
index 25de6c2bfde00a6e90bbb8a04f78a5ece8d1407d..ec2c766a63dcf8f58efc5bcc6bd79fb410609e2c 100644 (file)
@@ -7,6 +7,13 @@
 
     var defaultConfig = {
 
+        stacked: false,
+
+        hover: {
+            mode: "label"
+        },
+
+        legendTemplate: "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].borderColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>",
         scales: {
             xAxes: [{
                 scaleType: "dataset", // scatter should not use a dataset axis
                 }
             }],
         },
-
-        //Boolean - Whether to stack the lines essentially creating a stacked area chart.
-        stacked: false,
-
-        point: {
-            // Number - Radius of each point dot in pixels
-            radius: 3,
-
-            // Number - Pixel width of point dot border
-            borderWidth: 1,
-
-            // Number - Pixel width of point on hover
-            hoverRadius: 5,
-
-            // Number - Pixel width of point dot border on hover
-            hoverBorderWidth: 2,
-
-            // Color
-            backgroundColor: Chart.defaults.global.defaultColor,
-
-            // Color
-            borderColor: Chart.defaults.global.defaultColor,
-
-            //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
-            hitRadius: 6,
-        },
-
-        line: {
-            //Number - Tension of the bezier curve between points. Use 0 to turn off bezier tension
-            tension: 0.4,
-        },
-
-        //Number - Pixel width of dataset border
-        borderWidth: 2,
-        //Number - Pixel width of dataset border on hover
-        hoverBorderWidth: 2,
-
-        //String - A legend template
-        legendTemplate: "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].borderColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>",
     };
 
 
         defaults: defaultConfig,
         initialize: function() {
 
+            var _this = this;
+
             // Events
             helpers.bindEvents(this, this.options.events, this.events);
 
-            var _this = this;
-
-            //Create a new line and its points for each dataset and piece of data
+            // Create a new line and its points for each dataset and piece of data
             helpers.each(this.data.datasets, function(dataset, datasetIndex) {
-                dataset.metaDataset = new Chart.Line();
+
+                dataset.metaDataset = new Chart.Line({
+                    _chart: this.chart,
+                    _datasetIndex: datasetIndex,
+                    _points: dataset.metaData,
+                });
+
                 dataset.metaData = [];
+
                 helpers.each(dataset.data, function(dataPoint, index) {
-                    dataset.metaData.push(new Chart.Point());
+                    dataset.metaData.push(new Chart.Point({
+                        _datasetIndex: datasetIndex,
+                        _index: index,
+                        _chart: this.chart,
+                        _model: {
+                            x: 0,//xScale.getPixelForValue(null, index, true),
+                            y: 0, //this.chartArea.bottom,
+                            controlPointPreviousX: this.previousPoint(dataset.data, index).x,
+                            controlPointPreviousY: this.nextPoint(dataset.data, index).y,
+                            controlPointNextX: this.previousPoint(dataset.data, index).x,
+                            controlPointNextY: this.nextPoint(dataset.data, index).y,
+                        },
+                    }));
                 }, this);
 
-                // The line chart only supports a single x axis because the x axis is always a dataset axis
+                // The line chart onlty 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();
-            Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
-
-            // Set defaults for lines
-            this.eachDataset(function(dataset, datasetIndex) {
-                helpers.extend(dataset.metaDataset, {
-                    _points: dataset.metaData,
-                    _datasetIndex: datasetIndex,
-                    _chart: this.chart,
-                });
-                // Fill in dataset defaults from options
-                helpers.extend(dataset, helpers.merge(this.options, dataset));
-                // Copy to view modele
-                dataset.metaDataset.save();
-            }, this);
-
-            // Set defaults for points
-            this.eachElement(function(point, index, dataset, datasetIndex) {
-                var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID];
-
-                helpers.extend(point, {
-                    x: xScale.getPixelForValue(null, index, true),
-                    y: this.chartArea.bottom,
-                    _datasetIndex: datasetIndex,
-                    _index: index,
-                    _chart: this.chart
-                });
-
-                // Default bezier control points
-                helpers.extend(point, {
-                    controlPointPreviousX: this.previousPoint(dataset, index).x,
-                    controlPointPreviousY: this.nextPoint(dataset, index).y,
-                    controlPointNextX: this.previousPoint(dataset, index).x,
-                    controlPointNextY: this.nextPoint(dataset, index).y,
-                });
-                // Copy to view model
-                point.save();
-            }, this);
 
             // Create tooltip instance exclusively for this chart with some defaults.
             this.tooltip = new Chart.Tooltip({
                 _options: this.options,
             }, this);
 
+            // Update that shiz
             this.update();
         },
         nextPoint: function(collection, index) {
             return collection[index + 1] || collection[index];
         },
         update: function() {
+
             Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
 
             // Update the lines
 
                 helpers.extend(dataset.metaDataset, {
                     // Utility
+                    _scale: yScale,
                     _datasetIndex: datasetIndex,
-
                     // Data
-                    _points: dataset.metaData,
-
-                    // Geometry
-                    scaleTop: yScale.top,
-                    scaleBottom: yScale.bottom,
-                    scaleZero: yScale.getPixelForValue(0),
-
-                    // Appearance
-                    tension: dataset.tension || this.options.line.tension,
-                    backgroundColor: dataset.backgroundColor || this.options.backgroundColor,
-                    borderWidth: dataset.borderWidth || this.options.borderWidth,
-                    borderColor: dataset.borderColor || this.options.borderColor,
+                    _children: dataset.metaData,
+                    // Model
+                    _model: {
+                        // Appearance
+                        tension: dataset.tension || this.options.elements.line.tension,
+                        backgroundColor: dataset.backgroundColor || this.options.elements.line.backgroundColor,
+                        borderWidth: dataset.borderWidth || this.options.elements.line.borderWidth,
+                        borderColor: dataset.borderColor || this.options.elements.line.borderColor,
+                        // Scale
+                        scaleTop: yScale.top,
+                        scaleBottom: yScale.bottom,
+                        scaleZero: yScale.getPixelForValue(0),
+                    },
                 });
+
                 dataset.metaDataset.pivot();
             });
 
                 helpers.extend(point, {
                     // Utility
                     _chart: this.chart,
+                    _xScale: xScale,
+                    _yScale: yScale,
                     _datasetIndex: datasetIndex,
                     _index: index,
 
-                    // Data
-                    label: this.data.labels[index],
-                    value: this.data.datasets[datasetIndex].data[index],
-                    datasetLabel: this.data.datasets[datasetIndex].label,
-
-                    // Geometry
-                    offsetGridLines: this.options.offsetGridLines,
-                    x: xScale.getPixelForValue(null, index, true), // value not used in dataset scale, but we want a consistent API between scales
-                    y: yScale.getPointPixelForValue(this.data.datasets[datasetIndex].data[index], index, datasetIndex),
-                    tension: this.data.datasets[datasetIndex].metaDataset.tension,
-
-                    // Appearnce
-                    radius: this.data.datasets[datasetIndex].pointRadius || this.options.point.radius,
-                    backgroundColor: this.data.datasets[datasetIndex].pointBackgroundColor || this.options.point.backgroundColor,
-                    borderWidth: this.data.datasets[datasetIndex].pointBorderWidth || this.options.point.borderWidth,
-
-                    // Tooltip
-                    hoverRadius: this.data.datasets[datasetIndex].pointHitRadius || this.options.point.hitRadius,
+                    // Desired view properties
+                    _model: {
+                        x: xScale.getPixelForValue(null, index, true), // value not used in dataset scale, but we want a consistent API between scales
+                        y: yScale.getPointPixelForValue(this.data.datasets[datasetIndex].data[index], index, datasetIndex),
+
+                        // Appearance
+                        tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension,
+                        radius: point.custom && point.custom.radius ? point.custom.pointRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointRadius, index, this.options.elements.point.radius),
+                        backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBackgroundColor, index, this.options.elements.point.backgroundColor),
+                        borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderColor, index, this.options.elements.point.borderColor),
+                        borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderWidth, index, this.options.elements.point.borderWidth),
+
+                        // Tooltip
+                        hoverRadius: point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointHitRadius, index, this.options.elements.point.hitRadius),
+                    },
                 });
             }, this);
 
             // Update control points for the bezier curve
             this.eachElement(function(point, index, dataset, datasetIndex) {
                 var controlPoints = helpers.splineCurve(
-                    this.previousPoint(dataset, index),
-                    point,
-                    this.nextPoint(dataset, index),
-                    point.tension
+                    this.previousPoint(dataset, index)._model,
+                    point._model,
+                    this.nextPoint(dataset, index)._model,
+                    point._model.tension
                 );
 
-                point.controlPointPreviousX = controlPoints.previous.x;
-                point.controlPointNextX = controlPoints.next.x;
+                point._model.controlPointPreviousX = controlPoints.previous.x;
+                point._model.controlPointNextX = controlPoints.next.x;
 
                 // Prevent the bezier going outside of the bounds of the graph
 
                 // Cap puter bezier handles to the upper/lower scale bounds
                 if (controlPoints.next.y > this.chartArea.bottom) {
-                    point.controlPointNextY = this.chartArea.bottom;
+                    point._model.controlPointNextY = this.chartArea.bottom;
                 } else if (controlPoints.next.y < this.chartArea.top) {
-                    point.controlPointNextY = this.chartArea.top;
+                    point._model.controlPointNextY = this.chartArea.top;
                 } else {
-                    point.controlPointNextY = controlPoints.next.y;
+                    point._model.controlPointNextY = controlPoints.next.y;
                 }
 
                 // Cap inner bezier handles to the upper/lower scale bounds
                 if (controlPoints.previous.y > this.chartArea.bottom) {
-                    point.controlPointPreviousY = this.chartArea.bottom;
+                    point._model.controlPointPreviousY = this.chartArea.bottom;
                 } else if (controlPoints.previous.y < this.chartArea.top) {
-                    point.controlPointPreviousY = this.chartArea.top;
+                    point._model.controlPointPreviousY = this.chartArea.top;
                 } else {
-                    point.controlPointPreviousY = controlPoints.previous.y;
+                    point._model.controlPointPreviousY = controlPoints.previous.y;
                 }
+
                 // Now pivot the point for animation
                 point.pivot();
             }, this);
 
                 this.scales[scale.id] = scale;
             }, this);
-        },
-        redraw: function() {
-
         },
         draw: function(ease) {
 
             }
 
             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].radius = dataset.pointRadius;
-                        this.lastActive[0].backgroundColor = dataset.pointBackgroundColor;
-                        this.lastActive[0].borderColor = dataset.pointBorderColor;
-                        this.lastActive[0].borderWidth = dataset.pointBorderWidth;
+                        this.lastActive[0]._model.radius = this.lastActive[0].custom && this.lastActive[0].custom.radius ? this.lastActive[0].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius);
+                        this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor);
+                        this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor);
+                        this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.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].radius = dataset.pointRadius;
-                            this.lastActive[i].backgroundColor = dataset.pointBackgroundColor;
-                            this.lastActive[i].borderColor = dataset.pointBorderColor;
-                            this.lastActive[i].borderWidth = dataset.pointBorderWidth;
+                            this.lastActive[i]._model.radius = this.lastActive[i].custom && this.lastActive[i].custom.radius ? this.lastActive[i].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius);
+                            this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor);
+                            this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor);
+                            this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth);
                         }
                         break;
                     case 'dataset':
                 switch (this.options.hover.mode) {
                     case 'single':
                         dataset = this.data.datasets[this.active[0]._datasetIndex];
+                        index = this.active[0]._index;
 
-                        this.active[0].radius = dataset.pointHoverRadius || dataset.pointRadius + 2;
-                        this.active[0].backgroundColor = dataset.pointHoverBackgroundColor || helpers.color(dataset.pointBackgroundColor).saturate(0.5).darken(0.35).rgbString();
-                        this.active[0].borderColor = dataset.pointHoverBorderColor || helpers.color(dataset.pointBorderColor).saturate(0.5).darken(0.35).rgbString();
-                        this.active[0].borderWidth = dataset.pointHoverBorderWidth || dataset.pointBorderWidth + 2;
+                        this.active[0]._model.radius = this.active[0].custom && this.active[0].custom.hoverRadius ? this.active[0].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[0]._model.radius + 2);
+                        this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.35).rgbString());
+                        this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[0]._model.borderColor).saturate(0.5).darken(0.35).rgbString());
+                        this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[0]._model.borderWidth + 2);
                         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].radius = dataset.pointHoverRadius || dataset.pointRadius + 2;
-                            this.active[i].backgroundColor = dataset.pointHoverBackgroundColor || helpers.color(dataset.pointBackgroundColor).saturate(0.5).darken(0.35).rgbString();
-                            this.active[i].borderColor = dataset.pointHoverBorderColor || helpers.color(dataset.pointBorderColor).saturate(0.5).darken(0.35).rgbString();
-                            this.active[i].borderWidth = dataset.pointHoverBorderWidth || dataset.pointBorderWidth + 2;
+                            this.active[i]._model.radius = this.active[i].custom && this.active[i].custom.hoverRadius ? this.active[i].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[i]._model.radius + 2);
+                            this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.35).rgbString());
+                            this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[i]._model.borderColor).saturate(0.5).darken(0.35).rgbString());
+                            this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[i]._model.borderWidth + 2);
                         }
                         break;
                     case 'dataset':