diff --git a/docs/charts/radar.md b/docs/charts/radar.md index b8a41c8384c..476b1daf4a9 100644 --- a/docs/charts/radar.md +++ b/docs/charts/radar.md @@ -88,6 +88,7 @@ All point* properties can be specified as an array. If these are set to an array | `pointHoverBorderColor` | `Color/Color[]` | Point border color when hovered. | `pointHoverBorderWidth` | `Number/Number[]` | Border width of point when hovered. | `pointHoverRadius` | `Number/Number[]` | The radius of the point when hovered. +| `spanGaps` | `Boolean` | If true, lines will be drawn between points with no or null data. If false, points with `NaN` data will create a break in the line ### pointStyle The style of point. Options are: diff --git a/src/controllers/controller.radar.js b/src/controllers/controller.radar.js index 5de4e4ede0a..19cf39c3cd2 100644 --- a/src/controllers/controller.radar.js +++ b/src/controllers/controller.radar.js @@ -29,11 +29,12 @@ module.exports = function(Chart) { var me = this; var meta = me.getMeta(); var line = meta.dataset; - var points = meta.data; + var points = meta.data || []; var custom = line.custom || {}; var dataset = me.getDataset(); var lineElementOptions = me.chart.options.elements.line; var scale = me.chart.scale; + var i, ilen; // Compatibility: If the properties are defined with only the old name, use those values if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { @@ -50,6 +51,7 @@ module.exports = function(Chart) { // Model _model: { // Appearance + spanGaps: helpers.valueOrDefault(dataset.spanGaps, me.chart.options.spanGaps), tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), @@ -65,12 +67,17 @@ module.exports = function(Chart) { meta.dataset.pivot(); // Update Points - helpers.each(points, function(point, index) { - me.updateElement(point, index, reset); - }, me); + for (i = 0, ilen = points.length; i < ilen; i++) { + me.updateElement(points[i], i, reset); + } // Update bezier control points me.updateBezierControlPoints(); + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; i++) { + points[i].pivot(); + } }, updateElement: function(point, index, reset) { var me = this; @@ -98,6 +105,7 @@ module.exports = function(Chart) { _model: { x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales y: reset ? scale.yCenter : pointPosition.y, + skip: custom.skip || isNaN(dataset.data[index]) || dataset.data[index] === null, // Appearance tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension), @@ -111,32 +119,38 @@ module.exports = function(Chart) { hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius) } }); - - point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); }, updateBezierControlPoints: function() { - var chartArea = this.chart.chartArea; - var meta = this.getMeta(); + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (meta.dataset._model.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } - helpers.each(meta.data, function(point, index) { - var model = point._model; - var controlPoints = helpers.splineCurve( - helpers.previousItem(meta.data, index, true)._model, + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + controlPoints = helpers.splineCurve( + helpers.previousItem(points, i, true)._model, model, - helpers.nextItem(meta.data, index, true)._model, + helpers.nextItem(points, i, true)._model, model.tension ); - - // Prevent the bezier going outside of the bounds of the graph - model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); - model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); - - model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); - model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); - - // Now pivot the point for animation - point.pivot(); - }); + model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); + model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); + model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); + model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); + } }, setHoverStyle: function(point) { diff --git a/src/elements/element.line.js b/src/elements/element.line.js index 1500d353c96..abbe70c2b87 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -34,9 +34,15 @@ module.exports = Element.extend({ var lastDrawnIndex = -1; var index, current, previous, currentVM; - // If we are looping, adding the first point again + // If we are looping, adding the first non-skipped point again if (me._loop && points.length) { - points.push(points[0]); + for (index = 0; index < points.length; index++) { + current = points[index]; + if (!current._view.skip) { + points.push(current); + break; + } + } } ctx.save(); diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index eb8dad4c3b0..140e7252214 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -220,16 +220,22 @@ function doFill(ctx, points, mapper, view, color, loop) { var len0 = 0; var len1 = 0; var i, ilen, index, p0, p1, d0, d1; + var loopOffset; ctx.beginPath(); - for (i = 0, ilen = (count + !!loop); i < ilen; ++i) { + for (i = 0, ilen = count; i < ilen; ++i) { index = i % count; p0 = points[index]._view; p1 = mapper(p0, index, view); d0 = isDrawable(p0); d1 = isDrawable(p1); + if (loop && loopOffset === undefined && d0) { + loopOffset = i + 1; + ilen = count + loopOffset; + } + if (d0 && d1) { len0 = curve0.push(p0); len1 = curve1.push(p1); diff --git a/test/fixtures/controller.radar/span-gaps-end-true.json b/test/fixtures/controller.radar/span-gaps-end-true.json new file mode 100644 index 00000000000..dfe31b1b895 --- /dev/null +++ b/test/fixtures/controller.radar/span-gaps-end-true.json @@ -0,0 +1,28 @@ +{ + "type": "radar", + "data": { + "labels": ["A", "B", "C", "D", "E"], + "datasets": [{ + "label": "Dataset", + "backgroundColor": "#FF6384", + "borderColor": "#FF6384", + "pointBackgroundColor": "#FF6384", + "data": [10, 20, 30, 50, null] + }] + }, + "options": { + "spanGaps": true, + "legend": { + "display": false + }, + "title": { + "display": false + }, + "scale": { + "display": false, + "ticks": { + "beginAtZero": true + } + } + } +} \ No newline at end of file diff --git a/test/fixtures/controller.radar/span-gaps-end-true.png b/test/fixtures/controller.radar/span-gaps-end-true.png new file mode 100644 index 00000000000..e28c1c5a296 Binary files /dev/null and b/test/fixtures/controller.radar/span-gaps-end-true.png differ diff --git a/test/fixtures/controller.radar/span-gaps-middle-false.json b/test/fixtures/controller.radar/span-gaps-middle-false.json new file mode 100644 index 00000000000..8cdc2b94a59 --- /dev/null +++ b/test/fixtures/controller.radar/span-gaps-middle-false.json @@ -0,0 +1,28 @@ +{ + "type": "radar", + "data": { + "labels": ["A", "B", "C", "D", "E"], + "datasets": [{ + "label": "Dataset", + "backgroundColor": "#FF6384", + "borderColor": "#FF6384", + "pointBackgroundColor": "#FF6384", + "data": [10, 20, 30, null, 40] + }] + }, + "options": { + "spanGaps": true, + "legend": { + "display": false + }, + "title": { + "display": false + }, + "scale": { + "display": false, + "ticks": { + "beginAtZero": true + } + } + } +} \ No newline at end of file diff --git a/test/fixtures/controller.radar/span-gaps-middle-false.png b/test/fixtures/controller.radar/span-gaps-middle-false.png new file mode 100644 index 00000000000..169ba3b8bb8 Binary files /dev/null and b/test/fixtures/controller.radar/span-gaps-middle-false.png differ diff --git a/test/fixtures/controller.radar/span-gaps-middle-true.json b/test/fixtures/controller.radar/span-gaps-middle-true.json new file mode 100644 index 00000000000..8cdc2b94a59 --- /dev/null +++ b/test/fixtures/controller.radar/span-gaps-middle-true.json @@ -0,0 +1,28 @@ +{ + "type": "radar", + "data": { + "labels": ["A", "B", "C", "D", "E"], + "datasets": [{ + "label": "Dataset", + "backgroundColor": "#FF6384", + "borderColor": "#FF6384", + "pointBackgroundColor": "#FF6384", + "data": [10, 20, 30, null, 40] + }] + }, + "options": { + "spanGaps": true, + "legend": { + "display": false + }, + "title": { + "display": false + }, + "scale": { + "display": false, + "ticks": { + "beginAtZero": true + } + } + } +} \ No newline at end of file diff --git a/test/fixtures/controller.radar/span-gaps-middle-true.png b/test/fixtures/controller.radar/span-gaps-middle-true.png new file mode 100644 index 00000000000..3972f069d0e Binary files /dev/null and b/test/fixtures/controller.radar/span-gaps-middle-true.png differ diff --git a/test/fixtures/controller.radar/span-gaps-start-true.json b/test/fixtures/controller.radar/span-gaps-start-true.json new file mode 100644 index 00000000000..8dd592c5785 --- /dev/null +++ b/test/fixtures/controller.radar/span-gaps-start-true.json @@ -0,0 +1,28 @@ +{ + "type": "radar", + "data": { + "labels": ["A", "B", "C", "D", "E"], + "datasets": [{ + "label": "Dataset", + "backgroundColor": "#FF6384", + "borderColor": "#FF6384", + "pointBackgroundColor": "#FF6384", + "data": [null, 20, 30, 50, 40] + }] + }, + "options": { + "spanGaps": true, + "legend": { + "display": false + }, + "title": { + "display": false + }, + "scale": { + "display": false, + "ticks": { + "beginAtZero": true + } + } + } +} \ No newline at end of file diff --git a/test/fixtures/controller.radar/span-gaps-start-true.png b/test/fixtures/controller.radar/span-gaps-start-true.png new file mode 100644 index 00000000000..dad944fc189 Binary files /dev/null and b/test/fixtures/controller.radar/span-gaps-start-true.png differ diff --git a/test/specs/element.line.tests.js b/test/specs/element.line.tests.js index f9f351d7db6..89ee8471f3b 100644 --- a/test/specs/element.line.tests.js +++ b/test/specs/element.line.tests.js @@ -1879,6 +1879,9 @@ describe('Chart.elements.Line', function() { }, { name: 'lineTo', args: [19, -5] + }, { + name: 'lineTo', + args: [5, 0] }, { name: 'stroke', args: [],