diff --git a/samples/line-skip-points.html b/samples/line-skip-points.html new file mode 100644 index 000000000..a0a9e6e11 --- /dev/null +++ b/samples/line-skip-points.html @@ -0,0 +1,175 @@ + + + + + Line Chart + + + + + + +
+ +
+
+
+ + + + + +
+

Legend

+
+
+
+ + + + diff --git a/samples/radar-skip-points.html b/samples/radar-skip-points.html new file mode 100644 index 000000000..14c5072a5 --- /dev/null +++ b/samples/radar-skip-points.html @@ -0,0 +1,152 @@ + + + + + Radar Chart + + + + + +
+ +
+ + + + + +
+

Legend

+
+ +
+
+ + + + diff --git a/samples/radar.html b/samples/radar.html index da45b3e8f..eab9a8655 100644 --- a/samples/radar.html +++ b/samples/radar.html @@ -41,7 +41,7 @@ label: "My First dataset", backgroundColor: "rgba(220,220,220,0.2)", pointBackgroundColor: "rgba(220,220,220,1)", - data: [null, randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()] + data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()] }, { label: 'Hidden dataset', hidden: true, @@ -52,7 +52,7 @@ pointBackgroundColor: "rgba(151,187,205,1)", hoverPointBackgroundColor: "#fff", pointHighlightStroke: "rgba(151,187,205,1)", - data: [null, randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()] + data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()] },] }, options: { diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 954686e06..2f2ef279d 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -251,28 +251,12 @@ point._model.tension ); - point._model.controlPointPreviousX = controlPoints.previous.x; - point._model.controlPointNextX = controlPoints.next.x; - // Prevent the bezier going outside of the bounds of the graph + point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left); + point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top); - // Cap outer bezier handles to the upper/lower scale bounds - if (controlPoints.next.y > this.chart.chartArea.bottom) { - point._model.controlPointNextY = this.chart.chartArea.bottom; - } else if (controlPoints.next.y < this.chart.chartArea.top) { - point._model.controlPointNextY = this.chart.chartArea.top; - } else { - point._model.controlPointNextY = controlPoints.next.y; - } - - // Cap inner bezier handles to the upper/lower scale bounds - if (controlPoints.previous.y > this.chart.chartArea.bottom) { - point._model.controlPointPreviousY = this.chart.chartArea.bottom; - } else if (controlPoints.previous.y < this.chart.chartArea.top) { - point._model.controlPointPreviousY = this.chart.chartArea.top; - } else { - point._model.controlPointPreviousY = controlPoints.previous.y; - } + point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left); + point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top); // Now pivot the point for animation point.pivot(); diff --git a/src/controllers/controller.radar.js b/src/controllers/controller.radar.js index b8bcf1441..639c87722 100644 --- a/src/controllers/controller.radar.js +++ b/src/controllers/controller.radar.js @@ -197,28 +197,12 @@ point._model.tension ); - point._model.controlPointPreviousX = controlPoints.previous.x; - point._model.controlPointNextX = controlPoints.next.x; - // Prevent the bezier going outside of the bounds of the graph + point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left); + point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top); - // Cap outer bezier handles to the upper/lower scale bounds - if (controlPoints.next.y > this.chart.chartArea.bottom) { - point._model.controlPointNextY = this.chart.chartArea.bottom; - } else if (controlPoints.next.y < this.chart.chartArea.top) { - point._model.controlPointNextY = this.chart.chartArea.top; - } else { - point._model.controlPointNextY = controlPoints.next.y; - } - - // Cap inner bezier handles to the upper/lower scale bounds - if (controlPoints.previous.y > this.chart.chartArea.bottom) { - point._model.controlPointPreviousY = this.chart.chartArea.bottom; - } else if (controlPoints.previous.y < this.chart.chartArea.top) { - point._model.controlPointPreviousY = this.chart.chartArea.top; - } else { - point._model.controlPointPreviousY = controlPoints.previous.y; - } + point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left); + point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top); // Now pivot the point for animation point.pivot(); diff --git a/src/core/core.element.js b/src/core/core.element.js index 594269619..d9c0d35a6 100644 --- a/src/core/core.element.js +++ b/src/core/core.element.js @@ -38,7 +38,7 @@ // Init if doesn't exist else if (!this._view[key]) { - if (typeof value === 'number') { + if (typeof value === 'number' && isNaN(this._view[key]) === false) { this._view[key] = value * ease; } else { this._view[key] = value || null; @@ -61,14 +61,13 @@ } // Number transitions else if (typeof value === 'number') { - var startVal = this._start[key] !== undefined ? this._start[key] : 0; + var startVal = this._start[key] !== undefined && isNaN(this._start[key]) === false ? this._start[key] : 0; this._view[key] = ((this._model[key] - startVal) * ease) + startVal; } // Everything else else { this._view[key] = value; } - }, this); if (ease === 1) { diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 1f19a5c0a..50d5c51ec 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -321,27 +321,29 @@ aliasPixel = helpers.aliasPixel = function(pixelWidth) { return (pixelWidth % 2 === 0) ? 0 : 0.5; }, - splineCurve = helpers.splineCurve = function(FirstPoint, MiddlePoint, AfterPoint, t) { + splineCurve = helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { //Props to Rob Spencer at scaled innovation for his post on splining between points //http://scaledinnovation.com/analytics/splines/aboutSplines.html // This function must also respect "skipped" points - var previous = FirstPoint, - current = MiddlePoint, - next = AfterPoint; + var previous = firstPoint.skip ? middlePoint : firstPoint, + current = middlePoint, + next = afterPoint.skip ? middlePoint : afterPoint; - if (previous.skip) { - previous = current; - } - if (next.skip) { - next = current; - } + var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); + var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); + + var s01 = d01 / (d01 + d12); + var s12 = d12 / (d01 + d12); + + // If all points are the same, s01 & s02 will be inf + s01 = isNaN(s01) ? 0 : s01; + s12 = isNaN(s12) ? 0 : s12; + + var fa = t * s01; // scaling factor for triangle Ta + var fb = t * s12; - var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)), - d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)), - fa = t * d01 / (d01 + d12), // scaling factor for triangle Ta - fb = t * d12 / (d01 + d12); return { previous: { x: current.x - fa * (next.x - previous.x), diff --git a/src/elements/element.line.js b/src/elements/element.line.js index f094dea6e..00ae696cf 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -29,64 +29,38 @@ fill: true, // do we fill in the area between the line and its base axis }; - Chart.elements.Line = Chart.Element.extend({ + lineToNextPoint: function(previousPoint, point, nextPoint, skipHandler, previousSkipHandler) { + var ctx = this._chart.ctx; + + if (point._view.skip) { + skipHandler.call(this, previousPoint, point, nextPoint); + } else if (previousPoint._view.skip) { + previousSkipHandler.call(this, previousPoint, point, nextPoint); + } 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 _this = this; var vm = this._view; var ctx = this._chart.ctx; var first = this._children[0]; var last = this._children[this._children.length - 1]; - ctx.save(); - - // Draw the background first (so the border is always on top) - helpers.each(this._children, function(point, index) { - - var previous = helpers.previousItem(this._children, index); - var next = helpers.nextItem(this._children, index); - - // First point moves to it's starting position no matter what - if (!index) { - ctx.moveTo(point._view.x, vm.scaleZero); - } - - // Skip this point, draw to scaleZero, move to next point, and draw to next point - if (point._view.skip && !this.loop) { - ctx.lineTo(previous._view.x, vm.scaleZero); - ctx.moveTo(next._view.x, vm.scaleZero); - return; - } - - // The previous line was skipped, so just draw a normal straight line to the point - if (previous._view.skip) { - ctx.lineTo(point._view.x, point._view.y); - return; - } - - // Draw a bezier to point - if (vm.tension > 0 && index) { - //ctx.lineTo(point._view.x, point._view.y); - ctx.bezierCurveTo( - previous._view.controlPointNextX, - previous._view.controlPointNextY, - point._view.controlPointPreviousX, - point._view.controlPointPreviousY, - point._view.x, - point._view.y - ); - return; - } - - // Draw a straight line to the point - ctx.lineTo(point._view.x, point._view.y); - - }, this); - - // For radial scales, loop back around to the first point - if (this._loop) { - // Draw a bezier line - if (vm.tension > 0 && !first._view.skip) { + function loopBackToStart(drawLineToCenter) { + if (!first._view.skip && !last._view.skip) { + // Draw a bezier line from last to first ctx.bezierCurveTo( last._view.controlPointNextX, last._view.controlPointNextY, @@ -95,23 +69,68 @@ first._view.x, first._view.y ); - return; + } else if (drawLineToCenter) { + // Go to center + ctx.lineTo(_this._view.scaleZero.x, _this._view.scaleZero.y); } - // Draw a straight line - ctx.lineTo(first._view.x, first._view.y); } + ctx.save(); + // If we had points and want to fill this line, do so. if (this._children.length > 0 && vm.fill) { - //Round off the line by going to the base of the chart, back to the start, then fill. - ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero); - ctx.lineTo(this._children[0]._view.x, vm.scaleZero); + // Draw the background first (so the border is always on top) + ctx.beginPath(); + + helpers.each(this._children, function(point, index) { + var previous = helpers.previousItem(this._children, index); + var next = helpers.nextItem(this._children, index); + + // First point moves to it's starting position no matter what + if (index === 0) { + if (this._loop) { + ctx.moveTo(vm.scaleZero.x, vm.scaleZero.y); + } else { + ctx.moveTo(point._view.x, vm.scaleZero); + } + + if (point._view.skip) { + if (!this._loop) { + ctx.moveTo(next._view.x, this._view.scaleZero); + } + } else { + ctx.lineTo(point._view.x, point._view.y); + } + } else { + this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) { + if (this._loop) { + // Go to center + ctx.lineTo(this._view.scaleZero.x, this._view.scaleZero.y); + } else { + ctx.lineTo(previousPoint._view.x, this._view.scaleZero); + ctx.moveTo(nextPoint._view.x, this._view.scaleZero); + } + }, function(previousPoint, point, nextPoint) { + // 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); + }); + } + }, this); + + // For radial scales, loop back around to the first point + if (this._loop) { + loopBackToStart(true); + } else { + //Round off the line by going to the base of the chart, back to the start, then fill. + ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero); + ctx.lineTo(this._children[0]._view.x, vm.scaleZero); + } + ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; ctx.closePath(); ctx.fill(); } - // Now draw the line between all the points with any borders ctx.lineCap = vm.borderCapStyle || Chart.defaults.global.elements.line.borderCapStyle; @@ -130,62 +149,20 @@ var previous = helpers.previousItem(this._children, index); var next = helpers.nextItem(this._children, index); - if (!index) { - ctx.moveTo(point._view.x, vm.scaleZero); - } - - // Skip this point and move to the next points zeroPoint - if (point._view.skip && !this.loop) { - ctx.moveTo(next._view.x, vm.scaleZero); - return; - } - - // Previous point was skipped, just move to the point - if (previous._view.skip) { + if (index === 0) { ctx.moveTo(point._view.x, point._view.y); - return; + } else { + this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) { + ctx.moveTo(nextPoint._view.x, nextPoint._view.y); + }, function(previousPoint, point, nextPoint) { + // 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); + }); } - - // If First point, move to the point ahead of time (so a line doesn't get drawn up the left axis) - if (!index) { - ctx.moveTo(point._view.x, point._view.y); - } - - // Draw a bezier line to the point - if (vm.tension > 0 && index) { - ctx.bezierCurveTo( - previous._view.controlPointNextX, - previous._view.controlPointNextY, - point._view.controlPointPreviousX, - point._view.controlPointPreviousY, - point._view.x, - point._view.y - ); - return; - } - - // Draw a straight line to the point - ctx.lineTo(point._view.x, point._view.y); - }, this); - if (this._loop && !first._view.skip) { - - // Draw a bezier line to the first point - if (vm.tension > 0) { - ctx.bezierCurveTo( - last._view.controlPointNextX, - last._view.controlPointNextY, - first._view.controlPointPreviousX, - first._view.controlPointPreviousY, - first._view.x, - first._view.y - ); - return; - } - - // Draw a straight line to the first point - ctx.lineTo(first._view.x, first._view.y); + if (this._loop) { + loopBackToStart(); } ctx.stroke(); diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 15fdb3626..19f310ec2 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -272,7 +272,10 @@ return index * angleMultiplier - (Math.PI / 2); }, getDistanceFromCenterForValue: function(value) { - if (value === null) return 0; // null always in center + if (value === null) { + return 0; // null always in center + } + // Take into account half font size + the yPadding of the top value var scalingFactor = this.drawingArea / (this.max - this.min); if (this.options.reverse) { diff --git a/test/controller.line.tests.js b/test/controller.line.tests.js index cdfb9bdc9..b8e8695e2 100644 --- a/test/controller.line.tests.js +++ b/test/controller.line.tests.js @@ -192,7 +192,7 @@ describe('Line controller tests', function() { chartArea: { bottom: 200, left: xScale.left, - right: 200, + right: xScale.left + 200, top: 0 }, data: data, diff --git a/test/element.line.tests.js b/test/element.line.tests.js index 2b4028fbe..926345feb 100644 --- a/test/element.line.tests.js +++ b/test/element.line.tests.js @@ -22,6 +22,8 @@ describe('Line element tests', function() { _view: { x: 0, y: 10, + controlPointNextX: 0, + controlPointNextY: 10 } })); points.push(new Chart.elements.Point({ @@ -30,6 +32,10 @@ describe('Line element tests', function() { _view: { x: 5, y: 0, + controlPointPreviousX: 5, + controlPointPreviousY: 0, + controlPointNextX: 5, + controlPointNextY: 0 } })); points.push(new Chart.elements.Point({ @@ -38,6 +44,10 @@ describe('Line element tests', function() { _view: { x: 15, y: -10, + controlPointPreviousX: 15, + controlPointPreviousY: -10, + controlPointNextX: 15, + controlPointNextY: -10 } })); points.push(new Chart.elements.Point({ @@ -46,6 +56,10 @@ describe('Line element tests', function() { _view: { x: 19, y: -5, + controlPointPreviousX: 19, + controlPointPreviousY: -5, + controlPointNextX: 19, + controlPointNextY: -5 } })); @@ -68,21 +82,6 @@ describe('Line element tests', function() { expect(mockContext.getCalls()).toEqual([{ name: 'save', args: [], - }, { - name: 'moveTo', - args: [0, 0] - }, { - name: 'lineTo', - args: [0, 10] - }, { - name: 'lineTo', - args: [5, 0] - }, { - name: 'lineTo', - args: [15, -10] - }, { - name: 'lineTo', - args: [19, -5] }, { name: 'setLineCap', args: ['butt'] @@ -106,24 +105,18 @@ describe('Line element tests', function() { }, { name: 'beginPath', args: [] - }, { - name: 'moveTo', - args: [0, 0] }, { name: 'moveTo', args: [0, 10] }, { - name: 'lineTo', - args: [0, 10] + name: 'bezierCurveTo', + args: [0, 10, 5, 0, 5, 0] }, { - name: 'lineTo', - args: [5, 0] + name: 'bezierCurveTo', + args: [5, 0, 15, -10, 15, -10] }, { - name: 'lineTo', - args: [15, -10] - }, { - name: 'lineTo', - args: [19, -5] + name: 'bezierCurveTo', + args: [15, -10, 19, -5, 19, -5] }, { name: 'stroke', args: [], @@ -143,7 +136,9 @@ describe('Line element tests', function() { _index: 0, _view: { x: 0, - y: 10 + y: 10, + controlPointNextX: 0, + controlPointNextY: 10 } })); points.push(new Chart.elements.Point({ @@ -151,7 +146,11 @@ describe('Line element tests', function() { _index: 1, _view: { x: 5, - y: 0 + y: 0, + controlPointPreviousX: 5, + controlPointPreviousY: 0, + controlPointNextX: 5, + controlPointNextY: 0 } })); points.push(new Chart.elements.Point({ @@ -159,7 +158,11 @@ describe('Line element tests', function() { _index: 2, _view: { x: 15, - y: -10 + y: -10, + controlPointPreviousX: 15, + controlPointPreviousY: -10, + controlPointNextX: 15, + controlPointNextY: -10 } })); points.push(new Chart.elements.Point({ @@ -167,7 +170,11 @@ describe('Line element tests', function() { _index: 3, _view: { x: 19, - y: -5 + y: -5, + controlPointPreviousX: 19, + controlPointPreviousY: -5, + controlPointNextX: 19, + controlPointNextY: -5 } })); @@ -198,6 +205,9 @@ describe('Line element tests', function() { var expected = [{ name: 'save', args: [], + }, { + name: 'beginPath', + args: [] }, { name: 'moveTo', args: [0, 2] @@ -205,14 +215,14 @@ describe('Line element tests', function() { name: 'lineTo', args: [0, 10] }, { - name: 'lineTo', - args: [5, 0] + name: 'bezierCurveTo', + args: [0, 10, 5, 0, 5, 0] }, { - name: 'lineTo', - args: [15, -10] + name: 'bezierCurveTo', + args: [5, 0, 15, -10, 15, -10] }, { - name: 'lineTo', - args: [19, -5] + name: 'bezierCurveTo', + args: [15, -10, 19, -5, 19, -5] }, { name: 'lineTo', args: [19, 2] @@ -251,24 +261,171 @@ describe('Line element tests', function() { }, { 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: 'moveTo', + args: [19, 2] + }, { + name: 'lineTo', + args: [19, -5] + }, { + name: 'lineTo', + args: [19, 2] + }, { + name: 'lineTo', + args: [0, 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.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: [0, 10] + name: 'bezierCurveTo', + args: [0, 10, 5, 0, 5, 0] }, { - name: 'lineTo', - args: [5, 0] + name: 'moveTo', + args: [19, -5] }, { - name: 'lineTo', - args: [15, -10] - }, { - name: 'lineTo', - args: [19, -5] + name: 'moveTo', + args: [19, -5] }, { name: 'stroke', args: [], @@ -289,7 +446,11 @@ describe('Line element tests', function() { _index: 0, _view: { x: 0, - y: 10 + y: 10, + controlPointPreviousX: 0, + controlPointPreviousY: 10, + controlPointNextX: 0, + controlPointNextY: 10 } })); points.push(new Chart.elements.Point({ @@ -297,7 +458,11 @@ describe('Line element tests', function() { _index: 1, _view: { x: 5, - y: 0 + y: 0, + controlPointPreviousX: 5, + controlPointPreviousY: 0, + controlPointNextX: 5, + controlPointNextY: 0 } })); points.push(new Chart.elements.Point({ @@ -305,7 +470,11 @@ describe('Line element tests', function() { _index: 2, _view: { x: 15, - y: -10 + y: -10, + controlPointPreviousX: 15, + controlPointPreviousY: -10, + controlPointNextX: 15, + controlPointNextY: -10 } })); points.push(new Chart.elements.Point({ @@ -313,7 +482,11 @@ describe('Line element tests', function() { _index: 3, _view: { x: 19, - y: -5 + y: -5, + controlPointPreviousX: 19, + controlPointPreviousY: -5, + controlPointNextX: 19, + controlPointNextY: -5 } })); @@ -326,9 +499,12 @@ describe('Line element tests', function() { _loop: true, // want the line to loop back to the first point // Need to provide some settings _view: { - fill: false, // don't want to fill + fill: true, // don't want to fill tension: 0.0, // no bezier curve for now - scaleZero: 0, + scaleZero: { + x: 3, + y: 2 + }, } }); @@ -337,24 +513,36 @@ describe('Line element tests', function() { expect(mockContext.getCalls()).toEqual([{ name: 'save', args: [], + }, { + name: 'beginPath', + args: [] }, { name: 'moveTo', - args: [0, 0] + args: [3, 2] }, { name: 'lineTo', args: [0, 10] }, { - name: 'lineTo', - args: [5, 0] + name: 'bezierCurveTo', + args: [0, 10, 5, 0, 5, 0] }, { - name: 'lineTo', - args: [15, -10] + name: 'bezierCurveTo', + args: [5, 0, 15, -10, 15, -10] }, { - name: 'lineTo', - args: [19, -5] + name: 'bezierCurveTo', + args: [15, -10, 19, -5, 19, -5] }, { - name: 'lineTo', - args: [0, 10] + 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'] @@ -378,27 +566,21 @@ describe('Line element tests', function() { }, { name: 'beginPath', args: [] - }, { - name: 'moveTo', - args: [0, 0] }, { name: 'moveTo', args: [0, 10] }, { - name: 'lineTo', - args: [0, 10] + name: 'bezierCurveTo', + args: [0, 10, 5, 0, 5, 0] }, { - name: 'lineTo', - args: [5, 0] + name: 'bezierCurveTo', + args: [5, 0, 15, -10, 15, -10] }, { - name: 'lineTo', - args: [15, -10] + name: 'bezierCurveTo', + args: [15, -10, 19, -5, 19, -5] }, { - name: 'lineTo', - args: [19, -5] - }, { - name: 'lineTo', - args: [0, 10] + name: 'bezierCurveTo', + args: [19, -5, 0, 10, 0, 10] }, { name: 'stroke', args: [], @@ -408,7 +590,7 @@ describe('Line element tests', function() { }]); }); - it('should draw with bezier curves if tension > 0', function() { + it('should be able to draw with a loop back to the beginning point when there is a skip in the middle of the dataset', function() { var mockContext = window.createMockContext(); // Create our points @@ -419,10 +601,10 @@ describe('Line element tests', function() { _view: { x: 0, y: 10, - controlPointNextX: 1, - controlPointNextY: 1, controlPointPreviousX: 0, - controlPointPreviousY: 0, + controlPointPreviousY: 10, + controlPointNextX: 0, + controlPointNextY: 10 } })); points.push(new Chart.elements.Point({ @@ -431,10 +613,11 @@ describe('Line element tests', function() { _view: { x: 5, y: 0, - controlPointNextX: 6, - controlPointNextY: 7, - controlPointPreviousX: 4, - controlPointPreviousY: 3, + controlPointPreviousX: 5, + controlPointPreviousY: 0, + controlPointNextX: 5, + controlPointNextY: 0, + skip: true } })); points.push(new Chart.elements.Point({ @@ -443,10 +626,10 @@ describe('Line element tests', function() { _view: { x: 15, y: -10, - controlPointNextX: 16, - controlPointNextY: 17, - controlPointPreviousX: 14, - controlPointPreviousY: 13, + controlPointPreviousX: 15, + controlPointPreviousY: -10, + controlPointNextX: 15, + controlPointNextY: -10 } })); points.push(new Chart.elements.Point({ @@ -455,10 +638,10 @@ describe('Line element tests', function() { _view: { x: 19, y: -5, - controlPointNextX: 20, - controlPointNextY: 21, - controlPointPreviousX: 18, - controlPointPreviousY: 17, + controlPointPreviousX: 19, + controlPointPreviousY: -5, + controlPointNextX: 19, + controlPointNextY: -5 } })); @@ -468,51 +651,47 @@ describe('Line element tests', function() { ctx: mockContext, }, _children: points, + _loop: true, // want the line to loop back to the first point // Need to provide some settings _view: { - fill: true, - scaleZero: 2, // for filling lines - tension: 0.5, // have bezier curves - - borderCapStyle: 'round', - borderColor: 'rgb(255, 255, 0)', - borderDash: [2, 2], - borderDashOffset: 1.5, - borderJoinStyle: 'bevel', - borderWidth: 4, - backgroundColor: 'rgb(0, 0, 0)' + fill: true, // don't want to fill + tension: 0.0, // no bezier curve for now + scaleZero: { + x: 3, + y: 2 + }, } }); line.draw(); - var expected = [{ + expect(mockContext.getCalls()).toEqual([{ name: 'save', args: [], + }, { + name: 'beginPath', + args: [] }, { name: 'moveTo', - args: [0, 2] + args: [3, 2] }, { name: 'lineTo', args: [0, 10] }, { - name: 'bezierCurveTo', - args: [1, 1, 4, 3, 5, 0] - }, { - name: 'bezierCurveTo', - args: [6, 7, 14, 13, 15, -10] - }, { - name: 'bezierCurveTo', - args: [16, 17, 18, 17, 19, -5] + name: 'lineTo', + args: [3, 2] }, { name: 'lineTo', - args: [19, 2] + args: [15, -10] }, { - name: 'lineTo', - args: [0, 2] + name: 'bezierCurveTo', + args: [15, -10, 19, -5, 19, -5] + }, { + name: 'bezierCurveTo', + args: [19, -5, 0, 10, 0, 10] }, { name: 'setFillStyle', - args: ['rgb(0, 0, 0)'] + args: ['rgba(0,0,0,0.1)'] }, { name: 'closePath', args: [] @@ -521,52 +700,349 @@ describe('Line element tests', function() { args: [] }, { name: 'setLineCap', - args: ['round'] + args: ['butt'] }, { name: 'setLineDash', args: [ - [2, 2] + [] ] }, { name: 'setLineDashOffset', - args: [1.5] + args: [0.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, 2] + args: [0, 10] }, { name: 'moveTo', - args: [0, 10] + args: [15, -10] }, { - name: 'lineTo', - args: [0, 10] + name: 'moveTo', + args: [15, -10] }, { name: 'bezierCurveTo', - args: [1, 1, 4, 3, 5, 0] + args: [15, -10, 19, -5, 19, -5] }, { name: 'bezierCurveTo', - args: [6, 7, 14, 13, 15, -10] - }, { - name: 'bezierCurveTo', - args: [16, 17, 18, 17, 19, -5] + args: [19, -5, 0, 10, 0, 10] }, { name: 'stroke', args: [], }, { name: 'restore', args: [] - }]; - expect(mockContext.getCalls()).toEqual(expected); + }]); + }); + + it('should be able to draw with a loop back to the beginning point when the first point is skipped', 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, + skip: true + } + })); + 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, + _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 + }, + } + }); + + line.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [3, 2] + }, { + name: 'lineTo', + args: [5, 0] + }, { + name: 'bezierCurveTo', + args: [5, 0, 15, -10, 15, -10] + }, { + name: 'bezierCurveTo', + args: [15, -10, 19, -5, 19, -5] + }, { + name: 'lineTo', + args: [3, 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.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: 'moveTo', + args: [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: [] + }]); + }); + + it('should be able to draw with a loop back to the beginning point when the last point is skipped', 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, + } + })); + 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, + skip: true + } + })); + + 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 + }, + } + }); + + line.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [3, 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: 'lineTo', + args: [3, 2] + }, { + name: 'lineTo', + args: [3, 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.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, 15, -10, 15, -10] + }, { + name: 'moveTo', + args: [19, -5] + }, { + name: 'stroke', + args: [], + }, { + name: 'restore', + args: [] + }]); }); });