]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Refactoring of the line drawing function to make `spanGaps` work correctly. Added...
authorEvert Timberg <evert.timberg+github@gmail.com>
Sun, 24 Jul 2016 22:12:36 +0000 (18:12 -0400)
committerEvert Timberg <evert.timberg+github@gmail.com>
Sun, 24 Jul 2016 22:12:36 +0000 (18:12 -0400)
src/controllers/controller.line.js
src/elements/element.line.js
test/element.line.tests.js

index 8fc18f490be6887b8d32d467e51b3f04506b1478..5fa91107a935fd6af65b92540c33feaa691915d3 100644 (file)
@@ -247,7 +247,9 @@ module.exports = function(Chart) {
                        var me = this;
                        var meta = me.getMeta();
                        var area = me.chart.chartArea;
-                       var points = meta.data || [];
+
+                       // only consider points that are drawn in case the spanGaps option is ued
+                       var points = (meta.data || []).filter(function(pt) { return !pt._model.skip; });
                        var i, ilen, point, model, controlPoints;
 
                        var needToCap = me.chart.options.elements.line.capBezierPoints;
index dc8228febaec7f7022bfe67c59ba5637b8f7ce20..4a6cd6e4fd8a7c29bce3b1e3a4fd1659fbf59b74 100644 (file)
@@ -19,107 +19,104 @@ module.exports = function(Chart) {
        };
 
        Chart.elements.Line = Chart.Element.extend({
-               lineToNextPoint: function(previousPoint, point, nextPoint, skipHandler, previousSkipHandler) {
-                       var me = this;
-                       var ctx = me._chart.ctx;
-                       var spanGaps = me._view ? me._view.spanGaps : false;
-
-                       if (point._view.skip && !spanGaps) {
-                               skipHandler.call(me, previousPoint, point, nextPoint);
-                       } else if (previousPoint._view.skip && !spanGaps) {
-                               previousSkipHandler.call(me, previousPoint, point, nextPoint);
-                       } else if (point._view.steppedLine === true) {
-                               ctx.lineTo(point._view.x, previousPoint._view.y);
-                               ctx.lineTo(point._view.x, point._view.y);                               
-                       } else if (point._view.tension === 0) {
-                               ctx.lineTo(point._view.x, point._view.y);
-                       } else {
-                               // Line between points
-                               ctx.bezierCurveTo(
-                                       previousPoint._view.controlPointNextX,
-                                       previousPoint._view.controlPointNextY,
-                                       point._view.controlPointPreviousX,
-                                       point._view.controlPointPreviousY,
-                                       point._view.x,
-                                       point._view.y
-                               );
-                       }
-               },
-
                draw: function() {
                        var me = this;
-
                        var vm = me._view;
+                       var spanGaps = vm.spanGaps;
+                       var scaleZero = vm.scaleZero;
+                       var loop = me._loop;
+
                        var ctx = me._chart.ctx;
-                       var first = me._children[0];
-                       var last = me._children[me._children.length - 1];
+                       ctx.save();
 
-                       function loopBackToStart(drawLineToCenter) {
-                               if (!first._view.skip && !last._view.skip) {
-                                       // Draw a bezier line from last to first
+                       // Helper function to draw a line to a point
+                       function lineToPoint(previousPoint, point) {
+                               var vm = point._view;
+                               if (point._view.steppedLine === true) {
+                                       ctx.lineTo(point._view.x, previousPoint._view.y);
+                                       ctx.lineTo(point._view.x, point._view.y);                               
+                               } else if (point._view.tension === 0) {
+                                       ctx.lineTo(vm.x, vm.y);
+                               } else {
                                        ctx.bezierCurveTo(
-                                               last._view.controlPointNextX,
-                                               last._view.controlPointNextY,
-                                               first._view.controlPointPreviousX,
-                                               first._view.controlPointPreviousY,
-                                               first._view.x,
-                                               first._view.y
+                                               previousPoint._view.controlPointNextX,
+                                               previousPoint._view.controlPointNextY,
+                                               vm.controlPointPreviousX,
+                                               vm.controlPointPreviousY,
+                                               vm.x,
+                                               vm.y
                                        );
-                               } else if (drawLineToCenter) {
-                                       // Go to center
-                                       ctx.lineTo(me._view.scaleZero.x, me._view.scaleZero.y);
                                }
                        }
 
-                       ctx.save();
+                       var points = me._children.slice(); // clone array
+                       var lastDrawnIndex = -1;
+
+                       // If we are looping, adding the first point again
+                       if (loop && points.length) {
+                               points.push(points[0]);
+                       }
 
-                       // If we had points and want to fill this line, do so.
-                       if (me._children.length > 0 && vm.fill) {
-                               // Draw the background first (so the border is always on top)
+                       // Fill Line
+                       if (points.length && vm.fill) {
                                ctx.beginPath();
 
-                               helpers.each(me._children, function(point, index) {
-                                       var previous = helpers.previousItem(me._children, index);
-                                       var next = helpers.nextItem(me._children, index);
+                               for (var index = 0; index < points.length; ++index) {
+                                       var current = points[index];
+                                       var previous = helpers.previousItem(points, index);
+
+                                       var currentVM = current._view;
 
                                        // First point moves to it's starting position no matter what
                                        if (index === 0) {
-                                               if (me._loop) {
-                                                       ctx.moveTo(vm.scaleZero.x, vm.scaleZero.y);
+                                               if (loop) {
+                                                       ctx.moveTo(scaleZero.x, scaleZero.y);
                                                } else {
-                                                       ctx.moveTo(point._view.x, vm.scaleZero);
+                                                       ctx.moveTo(currentVM.x, scaleZero);
                                                }
 
-                                               if (point._view.skip) {
-                                                       if (!me._loop) {
-                                                               ctx.moveTo(next._view.x, me._view.scaleZero);
-                                                       }
-                                               } else {
-                                                       ctx.lineTo(point._view.x, point._view.y);
+                                               if (!currentVM.skip) {
+                                                       lastDrawnIndex = index;
+                                                       ctx.lineTo(currentVM.x, currentVM.y);
                                                }
                                        } else {
-                                               me.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) {
-                                                       if (me._loop) {
-                                                               // Go to center
-                                                               ctx.lineTo(me._view.scaleZero.x, me._view.scaleZero.y);
+                                               previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
+
+                                               if (currentVM.skip) {
+                                                       // Only do this if this is the first point that is skipped
+                                                       if (!spanGaps && lastDrawnIndex === (index - 1)) {
+                                                               if (loop) {
+                                                                       ctx.lineTo(scaleZero.x, scaleZero.y);
+                                                               } else {
+                                                                       ctx.lineTo(previous._view.x, scaleZero);
+                                                               }
+                                                       }
+                                               } else {
+                                                       if (lastDrawnIndex !== (index - 1)) {
+                                                               // There was a gap and this is the first point after the gap. If we've never drawn a point, this is a special case. 
+                                                               // If the first data point is NaN, then there is no real gap to skip
+                                                               if (spanGaps && lastDrawnIndex !== -1) {
+                                                                       // We are spanning the gap, so simple draw a line to this point
+                                                                       lineToPoint(previous, current)
+                                                               } else {
+                                                                       if (loop) {
+                                                                               ctx.lineTo(currentVM.x, currentVM.y);
+                                                                       } else {
+                                                                               ctx.lineTo(currentVM.x, scaleZero);
+                                                                               ctx.lineTo(currentVM.x, currentVM.y);
+                                                                       }
+                                                               }
                                                        } else {
-                                                               ctx.lineTo(previousPoint._view.x, me._view.scaleZero);
-                                                               ctx.moveTo(nextPoint._view.x, me._view.scaleZero);
+                                                               // Line to next point
+                                                               lineToPoint(previous, current);
                                                        }
-                                               }, function(previousPoint, point) {
-                                                       // If we skipped the last point, draw a line to ourselves so that the fill is nice
-                                                       ctx.lineTo(point._view.x, point._view.y);
-                                               });
+                                                       lastDrawnIndex = index;
+                                               }
                                        }
-                               }, me);
+                               }
 
-                               // For radial scales, loop back around to the first point
-                               if (me._loop) {
-                                       loopBackToStart(true);
-                               } else {
-                                       //Round off the line by going to the base of the chart, back to the start, then fill.
-                                       ctx.lineTo(me._children[me._children.length - 1]._view.x, vm.scaleZero);
-                                       ctx.lineTo(me._children[0]._view.x, vm.scaleZero);
+                               if (!loop) {
+                                       ctx.lineTo(points[points.length - 1]._view.x, scaleZero);
                                }
 
                                ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor;
@@ -127,8 +124,8 @@ module.exports = function(Chart) {
                                ctx.fill();
                        }
 
+                       // Stroke Line Options
                        var globalOptionLineElements = globalDefaults.elements.line;
-                       // Now draw the line between all the points with any borders
                        ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
 
                        // IE 9 and 10 do not support line dash
@@ -140,26 +137,38 @@ module.exports = function(Chart) {
                        ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
                        ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
                        ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
+
+                       // Stroke Line
                        ctx.beginPath();
+                       lastDrawnIndex = -1;
 
-                       helpers.each(me._children, function(point, index) {
-                               var previous = helpers.previousItem(me._children, index);
-                               var next = helpers.nextItem(me._children, index);
+                       for (var index = 0; index < points.length; ++index) {
+                               var current = points[index];
+                               var previous = helpers.previousItem(points, index);
+                               var currentVM = current._view;
 
+                               // First point moves to it's starting position no matter what
                                if (index === 0) {
-                                       ctx.moveTo(point._view.x, point._view.y);
+                                       if (currentVM.skip) {
+                                               
+                                       } else {
+                                               ctx.moveTo(currentVM.x, currentVM.y);
+                                               lastDrawnIndex = index;
+                                       }
                                } else {
-                                       me.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) {
-                                               ctx.moveTo(nextPoint._view.x, nextPoint._view.y);
-                                       }, function(previousPoint, point) {
-                                               // If we skipped the last point, move up to our point preventing a line from being drawn
-                                               ctx.moveTo(point._view.x, point._view.y);
-                                       });
-                               }
-                       }, me);
+                                       previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
 
-                       if (me._loop && me._children.length > 0) {
-                               loopBackToStart();
+                                       if (!currentVM.skip) {
+                                               if (lastDrawnIndex !== (index - 1) && !spanGaps) {
+                                                       // There was a gap and this is the first point after the gap
+                                                       ctx.moveTo(currentVM.x, currentVM.y);
+                                               } else {
+                                                       // Line to next point
+                                                       lineToPoint(previous, current);
+                                               }
+                                               lastDrawnIndex = index;
+                                       }
+                               }
                        }
 
                        ctx.stroke();
index 926345feb9279a3ef9ccb9259839f2c20c28af53..ef5c4748f88472f8648c1d5e4cb5d1359687d2db 100644 (file)
@@ -126,6 +126,253 @@ describe('Line element tests', function() {
                }]);
        });
 
+       it('should draw with straight lines for a tension of 0', function() {
+               var mockContext = window.createMockContext();
+
+               // Create our points
+               var points = [];
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 0,
+                       _view: {
+                               x: 0,
+                               y: 10,
+                               controlPointNextX: 0,
+                               controlPointNextY: 10,
+                               tension: 0
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 1,
+                       _view: {
+                               x: 5,
+                               y: 0,
+                               controlPointPreviousX: 5,
+                               controlPointPreviousY: 0,
+                               controlPointNextX: 5,
+                               controlPointNextY: 0,
+                               tension: 0
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 2,
+                       _view: {
+                               x: 15,
+                               y: -10,
+                               controlPointPreviousX: 15,
+                               controlPointPreviousY: -10,
+                               controlPointNextX: 15,
+                               controlPointNextY: -10,
+                               tension: 0
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 3,
+                       _view: {
+                               x: 19,
+                               y: -5,
+                               controlPointPreviousX: 19,
+                               controlPointPreviousY: -5,
+                               controlPointNextX: 19,
+                               controlPointNextY: -5,
+                               tension: 0
+                       }
+               }));
+
+               var line = new Chart.elements.Line({
+                       _datasetindex: 2,
+                       _chart: {
+                               ctx: mockContext,
+                       },
+                       _children: points,
+                       // Need to provide some settings
+                       _view: {
+                               fill: false, // don't want to fill
+                               tension: 0.0, // no bezier curve for now
+                               scaleZero: 0
+                       }
+               });
+
+               line.draw();
+
+               expect(mockContext.getCalls()).toEqual([{
+                       name: 'save',
+                       args: [],
+               }, {
+                       name: 'setLineCap',
+                       args: ['butt']
+               }, {
+                       name: 'setLineDash',
+                       args: [
+                               []
+                       ]
+               }, {
+                       name: 'setLineDashOffset',
+                       args: [0.0]
+               }, {
+                       name: 'setLineJoin',
+                       args: ['miter']
+               }, {
+                       name: 'setLineWidth',
+                       args: [3]
+               }, {
+                       name: 'setStrokeStyle',
+                       args: ['rgba(0,0,0,0.1)']
+               }, {
+                       name: 'beginPath',
+                       args: []
+               }, {
+                       name: 'moveTo',
+                       args: [0, 10]
+               }, {
+                       name: 'lineTo',
+                       args: [5, 0]
+               }, {
+                       name: 'lineTo',
+                       args: [15, -10]
+               }, {
+                       name: 'lineTo',
+                       args: [19, -5]
+               }, {
+                       name: 'stroke',
+                       args: [],
+               }, {
+                       name: 'restore',
+                       args: []
+               }]);
+       });
+
+       it('should draw stepped lines', function() {
+               var mockContext = window.createMockContext();
+
+               // Create our points
+               var points = [];
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 0,
+                       _view: {
+                               x: 0,
+                               y: 10,
+                               controlPointNextX: 0,
+                               controlPointNextY: 10,
+                               steppedLine: true
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 1,
+                       _view: {
+                               x: 5,
+                               y: 0,
+                               controlPointPreviousX: 5,
+                               controlPointPreviousY: 0,
+                               controlPointNextX: 5,
+                               controlPointNextY: 0,
+                               steppedLine: true
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 2,
+                       _view: {
+                               x: 15,
+                               y: -10,
+                               controlPointPreviousX: 15,
+                               controlPointPreviousY: -10,
+                               controlPointNextX: 15,
+                               controlPointNextY: -10,
+                               steppedLine: true
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 3,
+                       _view: {
+                               x: 19,
+                               y: -5,
+                               controlPointPreviousX: 19,
+                               controlPointPreviousY: -5,
+                               controlPointNextX: 19,
+                               controlPointNextY: -5,
+                               steppedLine: true
+                       }
+               }));
+
+               var line = new Chart.elements.Line({
+                       _datasetindex: 2,
+                       _chart: {
+                               ctx: mockContext,
+                       },
+                       _children: points,
+                       // Need to provide some settings
+                       _view: {
+                               fill: false, // don't want to fill
+                               tension: 0.0, // no bezier curve for now
+                               scaleZero: 0
+                       }
+               });
+
+               line.draw();
+
+               expect(mockContext.getCalls()).toEqual([{
+                       name: 'save',
+                       args: [],
+               }, {
+                       name: 'setLineCap',
+                       args: ['butt']
+               }, {
+                       name: 'setLineDash',
+                       args: [
+                               []
+                       ]
+               }, {
+                       name: 'setLineDashOffset',
+                       args: [0.0]
+               }, {
+                       name: 'setLineJoin',
+                       args: ['miter']
+               }, {
+                       name: 'setLineWidth',
+                       args: [3]
+               }, {
+                       name: 'setStrokeStyle',
+                       args: ['rgba(0,0,0,0.1)']
+               }, {
+                       name: 'beginPath',
+                       args: []
+               }, {
+                       name: 'moveTo',
+                       args: [0, 10]
+               }, {
+                       name: 'lineTo',
+                       args: [5, 10]
+               }, {
+                       name: 'lineTo',
+                       args: [5, 0]
+               }, {
+                       name: 'lineTo',
+                       args: [15, 0]
+               }, {
+                       name: 'lineTo',
+                       args: [15, -10]
+               }, {
+                       name: 'lineTo',
+                       args: [19, -10]
+               }, {
+                       name: 'lineTo',
+                       args: [19, -5]
+               }, {
+                       name: 'stroke',
+                       args: [],
+               }, {
+                       name: 'restore',
+                       args: []
+               }]);
+       });
+
        it('should draw with custom settings', function() {
                var mockContext = window.createMockContext();
 
@@ -138,7 +385,451 @@ describe('Line element tests', function() {
                                x: 0,
                                y: 10,
                                controlPointNextX: 0,
-                               controlPointNextY: 10
+                               controlPointNextY: 10
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 1,
+                       _view: {
+                               x: 5,
+                               y: 0,
+                               controlPointPreviousX: 5,
+                               controlPointPreviousY: 0,
+                               controlPointNextX: 5,
+                               controlPointNextY: 0
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 2,
+                       _view: {
+                               x: 15,
+                               y: -10,
+                               controlPointPreviousX: 15,
+                               controlPointPreviousY: -10,
+                               controlPointNextX: 15,
+                               controlPointNextY: -10
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 3,
+                       _view: {
+                               x: 19,
+                               y: -5,
+                               controlPointPreviousX: 19,
+                               controlPointPreviousY: -5,
+                               controlPointNextX: 19,
+                               controlPointNextY: -5
+                       }
+               }));
+
+               var line = new Chart.elements.Line({
+                       _datasetindex: 2,
+                       _chart: {
+                               ctx: mockContext,
+                       },
+                       _children: points,
+                       // Need to provide some settings
+                       _view: {
+                               fill: true,
+                               scaleZero: 2, // for filling lines
+                               tension: 0.0, // no bezier curve for now
+
+                               borderCapStyle: 'round',
+                               borderColor: 'rgb(255, 255, 0)',
+                               borderDash: [2, 2],
+                               borderDashOffset: 1.5,
+                               borderJoinStyle: 'bevel',
+                               borderWidth: 4,
+                               backgroundColor: 'rgb(0, 0, 0)'
+                       }
+               });
+
+               line.draw();
+
+               var expected = [{
+                       name: 'save',
+                       args: []
+               }, {
+                       name: 'beginPath',
+                       args: []
+               }, {
+                       name: 'moveTo',
+                       args: [0, 2]
+               }, {
+                       name: 'lineTo',
+                       args: [0, 10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [0, 10, 5, 0, 5, 0]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [5, 0, 15, -10, 15, -10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [15, -10, 19, -5, 19, -5]
+               }, {
+                       name: 'lineTo',
+                       args: [19, 2]
+               }, {
+                       name: 'setFillStyle',
+                       args: ['rgb(0, 0, 0)']
+               }, {
+                       name: 'closePath',
+                       args: []
+               }, {
+                       name: 'fill',
+                       args: []
+               }, {
+                       name: 'setLineCap',
+                       args: ['round']
+               }, {
+                       name: 'setLineDash',
+                       args: [
+                               [2, 2]
+                       ]
+               }, {
+                       name: 'setLineDashOffset',
+                       args: [1.5]
+               }, {
+                       name: 'setLineJoin',
+                       args: ['bevel']
+               }, {
+                       name: 'setLineWidth',
+                       args: [4]
+               }, {
+                       name: 'setStrokeStyle',
+                       args: ['rgb(255, 255, 0)']
+               }, {
+                       name: 'beginPath',
+                       args: []
+               }, {
+                       name: 'moveTo',
+                       args: [0, 10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [0, 10, 5, 0, 5, 0]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [5, 0, 15, -10, 15, -10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [15, -10, 19, -5, 19, -5]
+               }, {
+                       name: 'stroke',
+                       args: []
+               }, {
+                       name: 'restore',
+                       args: []
+               }];
+               expect(mockContext.getCalls()).toEqual(expected);
+       });
+
+       it('should skip points correctly', function() {
+               var mockContext = window.createMockContext();
+
+               // Create our points
+               var points = [];
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 0,
+                       _view: {
+                               x: 0,
+                               y: 10,
+                               controlPointNextX: 0,
+                               controlPointNextY: 10
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 1,
+                       _view: {
+                               x: 5,
+                               y: 0,
+                               controlPointPreviousX: 5,
+                               controlPointPreviousY: 0,
+                               controlPointNextX: 5,
+                               controlPointNextY: 0
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 2,
+                       _view: {
+                               x: 15,
+                               y: -10,
+                               controlPointPreviousX: 15,
+                               controlPointPreviousY: -10,
+                               controlPointNextX: 15,
+                               controlPointNextY: -10,
+                               skip: true
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 3,
+                       _view: {
+                               x: 19,
+                               y: -5,
+                               controlPointPreviousX: 19,
+                               controlPointPreviousY: -5,
+                               controlPointNextX: 19,
+                               controlPointNextY: -5
+                       }
+               }));
+
+               var line = new Chart.elements.Line({
+                       _datasetindex: 2,
+                       _chart: {
+                               ctx: mockContext,
+                       },
+                       _children: points,
+                       // Need to provide some settings
+                       _view: {
+                               fill: true,
+                               scaleZero: 2, // for filling lines
+                               tension: 0.0, // no bezier curve for now
+                       }
+               });
+
+               line.draw();
+
+               var expected = [{
+                       name: 'save',
+                       args: []
+               }, {
+                       name: 'beginPath',
+                       args: []
+               }, {
+                       name: 'moveTo',
+                       args: [0, 2]
+               }, {
+                       name: 'lineTo',
+                       args: [0, 10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [0, 10, 5, 0, 5, 0]
+               }, {
+                       name: 'lineTo',
+                       args: [5, 2]
+               }, {
+                       name: 'lineTo',
+                       args: [19, 2]
+               }, {
+                       name: 'lineTo',
+                       args: [19, -5]
+               }, {
+                       name: 'lineTo',
+                       args: [19, 2]
+               }, {
+                       name: 'setFillStyle',
+                       args: ['rgba(0,0,0,0.1)']
+               }, {
+                       name: 'closePath',
+                       args: []
+               }, {
+                       name: 'fill',
+                       args: []
+               }, {
+                       name: 'setLineCap',
+                       args: ['butt']
+               }, {
+                       name: 'setLineDash',
+                       args: [
+                               []
+                       ]
+               }, {
+                       name: 'setLineDashOffset',
+                       args: [0]
+               }, {
+                       name: 'setLineJoin',
+                       args: ['miter']
+               }, {
+                       name: 'setLineWidth',
+                       args: [3]
+               }, {
+                       name: 'setStrokeStyle',
+                       args: ['rgba(0,0,0,0.1)']
+               }, {
+                       name: 'beginPath',
+                       args: []
+               }, {
+                       name: 'moveTo',
+                       args: [0, 10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [0, 10, 5, 0, 5, 0]
+               }, {
+                       name: 'moveTo',
+                       args: [19, -5]
+               }, {
+                       name: 'stroke',
+                       args: []
+               }, {
+                       name: 'restore',
+                       args: []
+               }];
+               expect(mockContext.getCalls()).toEqual(expected);
+       });
+
+       it('should skip points correctly when spanGaps is true', function() {
+               var mockContext = window.createMockContext();
+
+               // Create our points
+               var points = [];
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 0,
+                       _view: {
+                               x: 0,
+                               y: 10,
+                               controlPointNextX: 0,
+                               controlPointNextY: 10
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 1,
+                       _view: {
+                               x: 5,
+                               y: 0,
+                               controlPointPreviousX: 5,
+                               controlPointPreviousY: 0,
+                               controlPointNextX: 5,
+                               controlPointNextY: 0
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 2,
+                       _view: {
+                               x: 15,
+                               y: -10,
+                               controlPointPreviousX: 15,
+                               controlPointPreviousY: -10,
+                               controlPointNextX: 15,
+                               controlPointNextY: -10,
+                               skip: true
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 3,
+                       _view: {
+                               x: 19,
+                               y: -5,
+                               controlPointPreviousX: 19,
+                               controlPointPreviousY: -5,
+                               controlPointNextX: 19,
+                               controlPointNextY: -5
+                       }
+               }));
+
+               var line = new Chart.elements.Line({
+                       _datasetindex: 2,
+                       _chart: {
+                               ctx: mockContext,
+                       },
+                       _children: points,
+                       // Need to provide some settings
+                       _view: {
+                               fill: true,
+                               scaleZero: 2, // for filling lines
+                               tension: 0.0, // no bezier curve for now
+                               spanGaps: true
+                       }
+               });
+
+               line.draw();
+
+               var expected = [{
+                       name: 'save',
+                       args: []
+               }, {
+                       name: 'beginPath',
+                       args: []
+               }, {
+                       name: 'moveTo',
+                       args: [0, 2]
+               }, {
+                       name: 'lineTo',
+                       args: [0, 10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [0, 10, 5, 0, 5, 0]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [5, 0, 19, -5, 19, -5]
+               }, {
+                       name: 'lineTo',
+                       args: [19, 2]
+               }, {
+                       name: 'setFillStyle',
+                       args: ['rgba(0,0,0,0.1)']
+               }, {
+                       name: 'closePath',
+                       args: []
+               }, {
+                       name: 'fill',
+                       args: []
+               }, {
+                       name: 'setLineCap',
+                       args: ['butt']
+               }, {
+                       name: 'setLineDash',
+                       args: [
+                               []
+                       ]
+               }, {
+                       name: 'setLineDashOffset',
+                       args: [0]
+               }, {
+                       name: 'setLineJoin',
+                       args: ['miter']
+               }, {
+                       name: 'setLineWidth',
+                       args: [3]
+               }, {
+                       name: 'setStrokeStyle',
+                       args: ['rgba(0,0,0,0.1)']
+               }, {
+                       name: 'beginPath',
+                       args: []
+               }, {
+                       name: 'moveTo',
+                       args: [0, 10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [0, 10, 5, 0, 5, 0]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [5, 0, 19, -5, 19, -5]
+               }, {
+                       name: 'stroke',
+                       args: []
+               }, {
+                       name: 'restore',
+                       args: []
+               }];
+               expect(mockContext.getCalls()).toEqual(expected);
+       });
+
+       it('should skip the first point correctly', function() {
+               var mockContext = window.createMockContext();
+
+               // Create our points
+               var points = [];
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 0,
+                       _view: {
+                               x: 0,
+                               y: 10,
+                               controlPointNextX: 0,
+                               controlPointNextY: 10,
+                               skip: true
                        }
                }));
                points.push(new Chart.elements.Point({
@@ -189,14 +880,6 @@ describe('Line element tests', function() {
                                fill: true,
                                scaleZero: 2, // for filling lines
                                tension: 0.0, // no bezier curve for now
-
-                               borderCapStyle: 'round',
-                               borderColor: 'rgb(255, 255, 0)',
-                               borderDash: [2, 2],
-                               borderDashOffset: 1.5,
-                               borderJoinStyle: 'bevel',
-                               borderWidth: 4,
-                               backgroundColor: 'rgb(0, 0, 0)'
                        }
                });
 
@@ -204,7 +887,7 @@ describe('Line element tests', function() {
 
                var expected = [{
                        name: 'save',
-                       args: [],
+                       args: []
                }, {
                        name: 'beginPath',
                        args: []
@@ -213,10 +896,10 @@ describe('Line element tests', function() {
                        args: [0, 2]
                }, {
                        name: 'lineTo',
-                       args: [0, 10]
+                       args: [5, 2]
                }, {
-                       name: 'bezierCurveTo',
-                       args: [0, 10, 5, 0, 5, 0]
+                       name: 'lineTo',
+                       args: [5, 0]
                }, {
                        name: 'bezierCurveTo',
                        args: [5, 0, 15, -10, 15, -10]
@@ -226,12 +909,9 @@ describe('Line element tests', function() {
                }, {
                        name: 'lineTo',
                        args: [19, 2]
-               }, {
-                       name: 'lineTo',
-                       args: [0, 2]
                }, {
                        name: 'setFillStyle',
-                       args: ['rgb(0, 0, 0)']
+                       args: ['rgba(0,0,0,0.1)']
                }, {
                        name: 'closePath',
                        args: []
@@ -240,33 +920,30 @@ describe('Line element tests', function() {
                        args: []
                }, {
                        name: 'setLineCap',
-                       args: ['round']
+                       args: ['butt']
                }, {
                        name: 'setLineDash',
                        args: [
-                               [2, 2]
+                               []
                        ]
                }, {
                        name: 'setLineDashOffset',
-                       args: [1.5]
+                       args: [0]
                }, {
                        name: 'setLineJoin',
-                       args: ['bevel']
+                       args: ['miter']
                }, {
                        name: 'setLineWidth',
-                       args: [4]
+                       args: [3]
                }, {
                        name: 'setStrokeStyle',
-                       args: ['rgb(255, 255, 0)']
+                       args: ['rgba(0,0,0,0.1)']
                }, {
                        name: 'beginPath',
                        args: []
                }, {
                        name: 'moveTo',
-                       args: [0, 10]
-               }, {
-                       name: 'bezierCurveTo',
-                       args: [0, 10, 5, 0, 5, 0]
+                       args: [5, 0]
                }, {
                        name: 'bezierCurveTo',
                        args: [5, 0, 15, -10, 15, -10]
@@ -275,7 +952,7 @@ describe('Line element tests', function() {
                        args: [15, -10, 19, -5, 19, -5]
                }, {
                        name: 'stroke',
-                       args: [],
+                       args: []
                }, {
                        name: 'restore',
                        args: []
@@ -283,7 +960,7 @@ describe('Line element tests', function() {
                expect(mockContext.getCalls()).toEqual(expected);
        });
 
-       it ('should skip points correctly', function() {
+       it('should skip the last point correctly', function() {
                var mockContext = window.createMockContext();
 
                // Create our points
@@ -319,8 +996,7 @@ describe('Line element tests', function() {
                                controlPointPreviousX: 15,
                                controlPointPreviousY: -10,
                                controlPointNextX: 15,
-                               controlPointNextY: -10,
-                               skip: true
+                               controlPointNextY: -10
                        }
                }));
                points.push(new Chart.elements.Point({
@@ -332,7 +1008,8 @@ describe('Line element tests', function() {
                                controlPointPreviousX: 19,
                                controlPointPreviousY: -5,
                                controlPointNextX: 19,
-                               controlPointNextY: -5
+                               controlPointNextY: -5,
+                               skip: true
                        }
                }));
 
@@ -368,20 +1045,14 @@ describe('Line element tests', function() {
                        name: 'bezierCurveTo',
                        args: [0, 10, 5, 0, 5, 0]
                }, {
-                       name: 'lineTo',
-                       args: [5, 2]
-               }, {
-                       name: 'moveTo',
-                       args: [19, 2]
-               }, {
-                       name: 'lineTo',
-                       args: [19, -5]
+                       name: 'bezierCurveTo',
+                       args: [5, 0, 15, -10, 15, -10]
                }, {
-                       name: 'lineTo',
-                       args: [19, 2]
+                       name: 'lineTo',
+                       args: [15, 2]
                }, {
                        name: 'lineTo',
-                       args: [0, 2]
+                       args: [19, 2]
                }, {
                        name: 'setFillStyle',
                        args: ['rgba(0,0,0,0.1)']
@@ -401,7 +1072,7 @@ describe('Line element tests', function() {
                        ]
                }, {
                        name: 'setLineDashOffset',
-                       args: [0.0]
+                       args: [0]
                }, {
                        name: 'setLineJoin',
                        args: ['miter']
@@ -421,14 +1092,11 @@ describe('Line element tests', function() {
                        name: 'bezierCurveTo',
                        args: [0, 10, 5, 0, 5, 0]
                }, {
-                       name: 'moveTo',
-                       args: [19, -5]
-               }, {
-                       name: 'moveTo',
-                       args: [19, -5]
+                       name: 'bezierCurveTo',
+                       args: [5, 0, 15, -10, 15, -10]
                }, {
                        name: 'stroke',
-                       args: [],
+                       args: []
                }, {
                        name: 'restore',
                        args: []
@@ -727,9 +1395,156 @@ describe('Line element tests', function() {
                }, {
                        name: 'moveTo',
                        args: [15, -10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [15, -10, 19, -5, 19, -5]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [19, -5, 0, 10, 0, 10]
+               }, {
+                       name: 'stroke',
+                       args: [],
+               }, {
+                       name: 'restore',
+                       args: []
+               }]);
+       });
+
+       it('should be able to draw with a loop back to the beginning point when span gaps is true and there is a skip in the middle of the dataset', function() {
+               var mockContext = window.createMockContext();
+
+               // Create our points
+               var points = [];
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 0,
+                       _view: {
+                               x: 0,
+                               y: 10,
+                               controlPointPreviousX: 0,
+                               controlPointPreviousY: 10,
+                               controlPointNextX: 0,
+                               controlPointNextY: 10
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 1,
+                       _view: {
+                               x: 5,
+                               y: 0,
+                               controlPointPreviousX: 5,
+                               controlPointPreviousY: 0,
+                               controlPointNextX: 5,
+                               controlPointNextY: 0,
+                               skip: true
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 2,
+                       _view: {
+                               x: 15,
+                               y: -10,
+                               controlPointPreviousX: 15,
+                               controlPointPreviousY: -10,
+                               controlPointNextX: 15,
+                               controlPointNextY: -10
+                       }
+               }));
+               points.push(new Chart.elements.Point({
+                       _datasetindex: 2,
+                       _index: 3,
+                       _view: {
+                               x: 19,
+                               y: -5,
+                               controlPointPreviousX: 19,
+                               controlPointPreviousY: -5,
+                               controlPointNextX: 19,
+                               controlPointNextY: -5
+                       }
+               }));
+
+               var line = new Chart.elements.Line({
+                       _datasetindex: 2,
+                       _chart: {
+                               ctx: mockContext,
+                       },
+                       _children: points,
+                       _loop: true, // want the line to loop back to the first point
+                       // Need to provide some settings
+                       _view: {
+                               fill: true, // don't want to fill
+                               tension: 0.0, // no bezier curve for now
+                               scaleZero: {
+                                       x: 3,
+                                       y: 2
+                               },
+                               spanGaps: true
+                       }
+               });
+
+               line.draw();
+
+               expect(mockContext.getCalls()).toEqual([{
+                       name: 'save',
+                       args: [],
+               }, {
+                       name: 'beginPath',
+                       args: []
                }, {
                        name: 'moveTo',
-                       args: [15, -10]
+                       args: [3, 2]
+               }, {
+                       name: 'lineTo',
+                       args: [0, 10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [0, 10, 15, -10, 15, -10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [15, -10, 19, -5, 19, -5]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [19, -5, 0, 10, 0, 10]
+               }, {
+                       name: 'setFillStyle',
+                       args: ['rgba(0,0,0,0.1)']
+               }, {
+                       name: 'closePath',
+                       args: []
+               }, {
+                       name: 'fill',
+                       args: []
+               }, {
+                       name: 'setLineCap',
+                       args: ['butt']
+               }, {
+                       name: 'setLineDash',
+                       args: [
+                               []
+                       ]
+               }, {
+                       name: 'setLineDashOffset',
+                       args: [0.0]
+               }, {
+                       name: 'setLineJoin',
+                       args: ['miter']
+               }, {
+                       name: 'setLineWidth',
+                       args: [3]
+               }, {
+                       name: 'setStrokeStyle',
+                       args: ['rgba(0,0,0,0.1)']
+               }, {
+                       name: 'beginPath',
+                       args: []
+               }, {
+                       name: 'moveTo',
+                       args: [0, 10]
+               }, {
+                       name: 'bezierCurveTo',
+                       args: [0, 10, 15, -10, 15, -10]
                }, {
                        name: 'bezierCurveTo',
                        args: [15, -10, 19, -5, 19, -5]
@@ -873,9 +1688,6 @@ describe('Line element tests', function() {
                }, {
                        name: 'beginPath',
                        args: []
-               }, {
-                       name: 'moveTo',
-                       args: [0, 10]
                }, {
                        name: 'moveTo',
                        args: [5, 0]
@@ -992,7 +1804,7 @@ describe('Line element tests', function() {
                        args: [3, 2]
                }, {
                        name: 'lineTo',
-                       args: [3, 2]
+                       args: [0, 10]
                }, {
                        name: 'setFillStyle',
                        args: ['rgba(0,0,0,0.1)']
@@ -1036,7 +1848,7 @@ describe('Line element tests', function() {
                        args: [5, 0, 15, -10, 15, -10]
                }, {
                        name: 'moveTo',
-                       args: [19, -5]
+                       args: [0, 10]
                }, {
                        name: 'stroke',
                        args: [],
@@ -1045,4 +1857,4 @@ describe('Line element tests', function() {
                        args: []
                }]);
        });
-});
+});
\ No newline at end of file