diff --git a/docs/01-Chart-Configuration.md b/docs/01-Chart-Configuration.md index 1aeaab940fb..ef0a5b57f46 100644 --- a/docs/01-Chart-Configuration.md +++ b/docs/01-Chart-Configuration.md @@ -392,7 +392,7 @@ borderDash | Array | `[]` | Default line dash. See [MDN](https://developer.mozil borderDashOffset | Number | 0.0 | Default line dash offset. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset) borderJoinStyle | String | 'miter' | Default line join style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin) capBezierPoints | Boolean | true | If true, bezier control points are kept inside the chart. If false, no restriction is enforced. -fill | Boolean | true | If true, the line is filled. +fill | Boolean or String | true | If true, the fill is assumed to be to zero. String values are 'zero', 'top', and 'bottom' to fill to different locations. If `false`, no fill is added stepped | Boolean | false | If true, the line is shown as a stepped line and 'tension' will be ignored #### Point Configuration diff --git a/src/elements/element.line.js b/src/elements/element.line.js index d81e7861cd5..b63ece78c90 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -15,7 +15,7 @@ module.exports = function(Chart) { borderDashOffset: 0.0, borderJoinStyle: 'miter', capBezierPoints: true, - fill: true // do we fill in the area between the line and its base axis + fill: true, // do we fill in the area between the line and its base axis }; Chart.elements.Line = Chart.Element.extend({ @@ -23,9 +23,18 @@ module.exports = function(Chart) { var me = this; var vm = me._view; var spanGaps = vm.spanGaps; - var scaleZero = vm.scaleZero; + var fillPoint = vm.scaleZero; var loop = me._loop; + // Handle different fill modes for cartesian lines + if (!loop) { + if (vm.fill === 'top') { + fillPoint = vm.scaleTop; + } else if (vm.fill === 'bottom') { + fillPoint = vm.scaleBottom; + } + } + var ctx = me._chart.ctx; ctx.save(); @@ -71,9 +80,9 @@ module.exports = function(Chart) { // First point moves to it's starting position no matter what if (index === 0) { if (loop) { - ctx.moveTo(scaleZero.x, scaleZero.y); + ctx.moveTo(fillPoint.x, fillPoint.y); } else { - ctx.moveTo(currentVM.x, scaleZero); + ctx.moveTo(currentVM.x, fillPoint); } if (!currentVM.skip) { @@ -87,9 +96,9 @@ module.exports = function(Chart) { // 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); + ctx.lineTo(fillPoint.x, fillPoint.y); } else { - ctx.lineTo(previous._view.x, scaleZero); + ctx.lineTo(previous._view.x, fillPoint); } } } else { @@ -102,7 +111,7 @@ module.exports = function(Chart) { } else if (loop) { ctx.lineTo(currentVM.x, currentVM.y); } else { - ctx.lineTo(currentVM.x, scaleZero); + ctx.lineTo(currentVM.x, fillPoint); ctx.lineTo(currentVM.x, currentVM.y); } } else { @@ -115,7 +124,7 @@ module.exports = function(Chart) { } if (!loop && lastDrawnIndex !== -1) { - ctx.lineTo(points[lastDrawnIndex]._view.x, scaleZero); + ctx.lineTo(points[lastDrawnIndex]._view.x, fillPoint); } ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor; diff --git a/test/element.line.tests.js b/test/element.line.tests.js index 38a56fe3e97..0f495c9d1fa 100644 --- a/test/element.line.tests.js +++ b/test/element.line.tests.js @@ -527,6 +527,318 @@ describe('Line element tests', function() { expect(mockContext.getCalls()).toEqual(expected); }); + it('should draw with fillMode top', 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 + } + })); + 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: 'top', + scaleZero: 2, // for filling lines + scaleTop: -2, + scaleBottom: 10, + 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 draw with fillMode bottom', 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 + } + })); + 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: 'bottom', + scaleZero: 2, // for filling lines + scaleTop: -2, + scaleBottom: 10, + 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, 10] + }, { + 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, 10] + }, { + 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();