From: Evert Timberg Date: Sun, 24 Jul 2016 22:12:36 +0000 (-0400) Subject: Refactoring of the line drawing function to make `spanGaps` work correctly. Added... X-Git-Tag: v2.2.0~2^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2016630daa7b296425441c376d1756084bca214c;p=thirdparty%2FChart.js.git Refactoring of the line drawing function to make `spanGaps` work correctly. Added a lot more test conditions to the line element tests. Ensured that the line controller correctly calculated bezier control points when there was a point to be skipped --- diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 8fc18f490..5fa91107a 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -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; diff --git a/src/elements/element.line.js b/src/elements/element.line.js index dc8228feb..4a6cd6e4f 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -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(); diff --git a/test/element.line.tests.js b/test/element.line.tests.js index 926345feb..ef5c4748f 100644 --- a/test/element.line.tests.js +++ b/test/element.line.tests.js @@ -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