From 8e9de00529b00a3811b09d969a4b7bc87ca8be16 Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Sun, 30 Aug 2015 14:56:48 -0400 Subject: [PATCH 1/2] Doughnut controller tests --- src/controllers/controller.doughnut.js | 28 +- test/controller.doughnut.tests.js | 429 +++++++++++++++++++++++++ 2 files changed, 448 insertions(+), 9 deletions(-) create mode 100644 test/controller.doughnut.tests.js diff --git a/src/controllers/controller.doughnut.js b/src/controllers/controller.doughnut.js index 8b161c29a..2513a0689 100644 --- a/src/controllers/controller.doughnut.js +++ b/src/controllers/controller.doughnut.js @@ -49,10 +49,6 @@ return this.chart.data.datasets[this.index]; }, - getScaleForId: function(scaleID) { - return this.chart.scales[scaleID]; - }, - addElements: function() { this.getDataset().metaData = this.getDataset().metaData || []; helpers.each(this.getDataset().data, function(value, index) { @@ -71,7 +67,9 @@ _index: index, }); - this.getDataset().backgroundColor.splice(index, 0, colorForNewElement); + if (colorForNewElement && helpers.isArray(this.getDataset().backgroundColor)) { + this.getDataset().backgroundColor.splice(index, 0, colorForNewElement); + } // Reset the point this.updateElement(arc, index, true); @@ -92,7 +90,6 @@ this.chart.innerRadius = this.chart.options.cutoutPercentage ? (this.chart.outerRadius / 100) * (this.chart.options.cutoutPercentage) : 1; this.chart.radiusLength = (this.chart.outerRadius - this.chart.innerRadius) / this.chart.data.datasets.length; - this.getDataset().total = 0; helpers.each(this.getDataset().data, function(value) { this.getDataset().total += Math.abs(value); @@ -101,6 +98,21 @@ this.outerRadius = this.chart.outerRadius - (this.chart.radiusLength * this.index); this.innerRadius = this.outerRadius - this.chart.radiusLength; + // Make sure we have metaData for each data point + var numData = this.getDataset().data.length; + var numArcs = this.getDataset().metaData.length; + + // Make sure that we handle number of datapoints changing + if (numData < numArcs) { + // Remove excess bars for data points that have been removed + this.getDataset().metaData.splice(numData, numArcs - numData) + } else if (numData > numArcs) { + // Add new elements + for (var index = numArcs; index < numData; ++index) { + this.addElementAndReset(index); + } + } + helpers.each(this.getDataset().metaData, function(arc, index) { this.updateElement(arc, index, reset); }, this); @@ -165,15 +177,13 @@ }, this); }, - - setHoverStyle: function(arc) { var dataset = this.chart.data.datasets[arc._datasetIndex]; var index = arc._index; arc._model.backgroundColor = arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(arc._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); arc._model.borderColor = arc.custom && arc.custom.hoverBorderColor ? arc.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(arc._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - arc._model.borderWidth = arc.custom && arc.custom.hoverBorderWidth ? arc.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, arc._model.borderWidth); + arc._model.borderWidth = arc.custom && arc.custom.hoverBorderWidth ? arc.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, arc._model.borderWidth); }, removeHoverStyle: function(arc) { diff --git a/test/controller.doughnut.tests.js b/test/controller.doughnut.tests.js new file mode 100644 index 000000000..cef606263 --- /dev/null +++ b/test/controller.doughnut.tests.js @@ -0,0 +1,429 @@ +// Test the bar controller +describe('Doughnut controller tests', function() { + it('Should be constructed', function() { + var chart = { + data: { + datasets: [{ + data: [] + }] + } + }; + + var controller = new Chart.controllers.doughnut(chart, 0); + expect(controller).not.toBe(undefined); + expect(controller.index).toBe(0); + expect(chart.data.datasets[0].metaData).toEqual([]); + + controller.updateIndex(1); + expect(controller.index).toBe(1); + }); + + it('Should create arc elements for each data item during initialization', function() { + var chart = { + data: { + datasets: [{ + data: [10, 15, 0, 4] + }] + }, + config: { + type: 'doughnut' + }, + options: { + } + }; + + var controller = new Chart.controllers.doughnut(chart, 0); + + expect(chart.data.datasets[0].metaData.length).toBe(4); // 4 rectangles created + expect(chart.data.datasets[0].metaData[0] instanceof Chart.elements.Arc).toBe(true); + expect(chart.data.datasets[0].metaData[1] instanceof Chart.elements.Arc).toBe(true); + expect(chart.data.datasets[0].metaData[2] instanceof Chart.elements.Arc).toBe(true); + expect(chart.data.datasets[0].metaData[3] instanceof Chart.elements.Arc).toBe(true); + }); + + it ('Should remove elements', function() { + var chart = { + data: { + datasets: [{ + data: [10, 15, 0, 4] + }] + }, + config: { + type: 'doughnut' + }, + options: { + } + }; + + var controller = new Chart.controllers.doughnut(chart, 0); + controller.removeElement(1); + expect(chart.data.datasets[0].metaData.length).toBe(3); + }); + + it ('Should reset and update elements', function() { + var chart = { + chart: { + width: 100, + height: 200, + }, + data: { + datasets: [{ + data: [10, 15, 0, 4] + }, { + data: [1] + }], + labels: ['label0', 'label1', 'label2', 'label3'] + }, + config: { + type: 'doughnut' + }, + options: { + animation: { + animateRotate: false, + animateScale: false + }, + cutoutPercentage: 50, + elements: { + arc: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2, + hoverBackgroundColor: 'rgb(255, 255, 255)' + } + } + } + }; + + var controller = new Chart.controllers.doughnut(chart, 0); + controller.reset(); // reset first + + expect(chart.data.datasets[0].metaData[0]._model).toEqual({ + x: 50, + y: 100, + startAngle: Math.PI * -0.5, + circumference: 2.166614539857563, + outerRadius: 49, + innerRadius: 36.75, + }); + + expect(chart.data.datasets[0].metaData[1]._model).toEqual({ + x: 50, + y: 100, + startAngle: Math.PI * -0.5, + circumference: 3.2499218097863447, + outerRadius: 49, + innerRadius: 36.75, + }); + + expect(chart.data.datasets[0].metaData[2]._model).toEqual({ + x: 50, + y: 100, + startAngle: Math.PI * -0.5, + circumference: 0, + outerRadius: 49, + innerRadius: 36.75, + }); + + expect(chart.data.datasets[0].metaData[3]._model).toEqual({ + x: 50, + y: 100, + startAngle: Math.PI * -0.5, + circumference: 0.8666458159430251, + outerRadius: 49, + innerRadius: 36.75, + }); + + controller.update(); + + expect(chart.data.datasets[0].metaData[0]._model).toEqual({ + x: 50, + y: 100, + startAngle: Math.PI * -0.5, + endAngle: 0.5958182130626666, + circumference: 2.166614539857563, + outerRadius: 49, + innerRadius: 36.75, + + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2, + hoverBackgroundColor: 'rgb(255, 255, 255)', + + label: 'label0', + }); + + expect(chart.data.datasets[0].metaData[1]._model).toEqual({ + x: 50, + y: 100, + startAngle: 0.5958182130626666, + endAngle: 3.8457400228490113, + circumference: 3.2499218097863447, + outerRadius: 49, + innerRadius: 36.75, + + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2, + hoverBackgroundColor: 'rgb(255, 255, 255)', + + label: 'label1' + }); + + expect(chart.data.datasets[0].metaData[2]._model).toEqual({ + x: 50, + y: 100, + startAngle: 3.8457400228490113, + endAngle: 3.8457400228490113, + circumference: 0, + outerRadius: 49, + innerRadius: 36.75, + + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2, + hoverBackgroundColor: 'rgb(255, 255, 255)', + + label: 'label2' + }); + + expect(chart.data.datasets[0].metaData[3]._model).toEqual({ + x: 50, + y: 100, + startAngle: 3.8457400228490113, + endAngle: 4.712385838792036, + circumference: 0.8666458159430251, + outerRadius: 49, + innerRadius: 36.75, + + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2, + hoverBackgroundColor: 'rgb(255, 255, 255)', + + label: 'label3' + }); + + // Change the amount of data and ensure that arcs are updated accordingly + chart.data.datasets[0].data = [1, 2]; // remove 2 elements from dataset 0 + controller.update(); + + expect(chart.data.datasets[0].metaData.length).toBe(2); + expect(chart.data.datasets[0].metaData[0] instanceof Chart.elements.Arc).toBe(true); + expect(chart.data.datasets[0].metaData[1] instanceof Chart.elements.Arc).toBe(true); + + // Add data + chart.data.datasets[0].data = [1, 2, 3, 4]; + controller.update(); + + expect(chart.data.datasets[0].metaData.length).toBe(4); + expect(chart.data.datasets[0].metaData[0] instanceof Chart.elements.Arc).toBe(true); + expect(chart.data.datasets[0].metaData[1] instanceof Chart.elements.Arc).toBe(true); + expect(chart.data.datasets[0].metaData[2] instanceof Chart.elements.Arc).toBe(true); + expect(chart.data.datasets[0].metaData[3] instanceof Chart.elements.Arc).toBe(true); + }); + + it ('should draw all arcs', function() { + var chart = { + chart: { + width: 100, + height: 200, + }, + data: { + datasets: [{ + data: [10, 15, 0, 4] + }], + labels: ['label0', 'label1', 'label2', 'label3'] + }, + config: { + type: 'doughnut' + }, + options: { + animation: { + animateRotate: false, + animateScale: false + }, + cutoutPercentage: 50, + elements: { + arc: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2, + hoverBackgroundColor: 'rgb(255, 255, 255)' + } + } + } + }; + + var controller = new Chart.controllers.doughnut(chart, 0); + + spyOn(chart.data.datasets[0].metaData[0], 'draw'); + spyOn(chart.data.datasets[0].metaData[1], 'draw'); + spyOn(chart.data.datasets[0].metaData[2], 'draw'); + spyOn(chart.data.datasets[0].metaData[3], 'draw'); + + controller.draw(); + + expect(chart.data.datasets[0].metaData[0].draw.calls.count()).toBe(1); + expect(chart.data.datasets[0].metaData[1].draw.calls.count()).toBe(1); + expect(chart.data.datasets[0].metaData[2].draw.calls.count()).toBe(1); + expect(chart.data.datasets[0].metaData[3].draw.calls.count()).toBe(1); + }); + + it ('should set the hover style of an arc', function() { + var chart = { + chart: { + width: 100, + height: 200, + }, + data: { + datasets: [{ + data: [10, 15, 0, 4] + }], + labels: ['label0', 'label1', 'label2', 'label3'] + }, + config: { + type: 'doughnut' + }, + options: { + animation: { + animateRotate: false, + animateScale: false + }, + cutoutPercentage: 50, + elements: { + arc: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2, + } + } + } + }; + + var controller = new Chart.controllers.doughnut(chart, 0); + controller.reset(); // reset first + controller.update(); + + var arc = chart.data.datasets[0].metaData[0]; + + controller.setHoverStyle(arc); + + expect(arc._model.backgroundColor).toBe('rgb(230, 0, 0)'); + expect(arc._model.borderColor).toBe('rgb(0, 0, 230)'); + expect(arc._model.borderWidth).toBe(2); + + // Set a dataset style to take precedence + chart.data.datasets[0].hoverBackgroundColor = 'rgb(9, 9, 9)'; + chart.data.datasets[0].hoverBorderColor = 'rgb(18, 18, 18)'; + chart.data.datasets[0].hoverBorderWidth = 1.56; + + controller.setHoverStyle(arc); + + expect(arc._model.backgroundColor).toBe('rgb(9, 9, 9)'); + expect(arc._model.borderColor).toBe('rgb(18, 18, 18)'); + expect(arc._model.borderWidth).toBe(1.56); + + // Dataset styles can be an array + chart.data.datasets[0].hoverBackgroundColor = ['rgb(255, 255, 255)', 'rgb(9, 9, 9)']; + chart.data.datasets[0].hoverBorderColor = ['rgb(18, 18, 18)']; + chart.data.datasets[0].hoverBorderWidth = [0.1, 1.56]; + + controller.setHoverStyle(arc); + + expect(arc._model.backgroundColor).toBe('rgb(255, 255, 255)'); + expect(arc._model.borderColor).toBe('rgb(18, 18, 18)'); + expect(arc._model.borderWidth).toBe(0.1); + + // Element custom styles also work + arc.custom = { + hoverBackgroundColor: 'rgb(7, 7, 7)', + hoverBorderColor: 'rgb(17, 17, 17)', + hoverBorderWidth: 3.14159, + }; + + controller.setHoverStyle(arc); + + expect(arc._model.backgroundColor).toBe('rgb(7, 7, 7)'); + expect(arc._model.borderColor).toBe('rgb(17, 17, 17)'); + expect(arc._model.borderWidth).toBe(3.14159); + }); + + it ('should unset the hover style of an arc', function() { + var chart = { + chart: { + width: 100, + height: 200, + }, + data: { + datasets: [{ + data: [10, 15, 0, 4] + }], + labels: ['label0', 'label1', 'label2', 'label3'] + }, + config: { + type: 'doughnut' + }, + options: { + animation: { + animateRotate: false, + animateScale: false + }, + cutoutPercentage: 50, + elements: { + arc: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2, + } + } + } + }; + + var controller = new Chart.controllers.doughnut(chart, 0); + controller.reset(); // reset first + controller.update(); + + var arc = chart.data.datasets[0].metaData[0]; + + controller.removeHoverStyle(arc); + + expect(arc._model.backgroundColor).toBe('rgb(255, 0, 0)'); + expect(arc._model.borderColor).toBe('rgb(0, 0, 255)'); + expect(arc._model.borderWidth).toBe(2); + + // Set a dataset style to take precedence + chart.data.datasets[0].backgroundColor = 'rgb(9, 9, 9)'; + chart.data.datasets[0].borderColor = 'rgb(18, 18, 18)'; + chart.data.datasets[0].borderWidth = 1.56; + + controller.removeHoverStyle(arc); + + expect(arc._model.backgroundColor).toBe('rgb(9, 9, 9)'); + expect(arc._model.borderColor).toBe('rgb(18, 18, 18)'); + expect(arc._model.borderWidth).toBe(1.56); + + // Dataset styles can be an array + chart.data.datasets[0].backgroundColor = ['rgb(255, 255, 255)', 'rgb(9, 9, 9)']; + chart.data.datasets[0].borderColor = ['rgb(18, 18, 18)']; + chart.data.datasets[0].borderWidth = [0.1, 1.56]; + + controller.removeHoverStyle(arc); + + expect(arc._model.backgroundColor).toBe('rgb(255, 255, 255)'); + expect(arc._model.borderColor).toBe('rgb(18, 18, 18)'); + expect(arc._model.borderWidth).toBe(0.1); + + // Element custom styles also work + arc.custom = { + backgroundColor: 'rgb(7, 7, 7)', + borderColor: 'rgb(17, 17, 17)', + borderWidth: 3.14159, + }; + + controller.removeHoverStyle(arc); + + expect(arc._model.backgroundColor).toBe('rgb(7, 7, 7)'); + expect(arc._model.borderColor).toBe('rgb(17, 17, 17)'); + expect(arc._model.borderWidth).toBe(3.14159); + }); +}); \ No newline at end of file From ce623075e336cef842253f0ddc7c7767b40c167a Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Sun, 30 Aug 2015 17:42:42 -0400 Subject: [PATCH 2/2] Add line controller tests. Fixed an issue when the number of data points changes. When adding the tension to a point, get the same tension as the line if it has been overridden at the dataset level. When setting the hover style of a point, allow setting the radius independently of the regular radius. Use the hoverRadius property in point.custom or the dataset.pointHoverRadius. Allow setting the border width independently at the dataset level. Use dataset.pointHoverBorderWidth. This can be an array or a number. --- src/controllers/controller.line.js | 25 +- test/controller.line.tests.js | 814 +++++++++++++++++++++++++++++ 2 files changed, 832 insertions(+), 7 deletions(-) create mode 100644 test/controller.line.tests.js diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index d007498a8..608b5d704 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -109,6 +109,21 @@ var xScale = this.getScaleForId(this.getDataset().xAxisID); var scaleBase; + // Handle the number of data points changing + var numData = this.getDataset().data.length; + var numPoints = this.getDataset().metaData.length; + + // Make sure that we handle number of datapoints changing + if (numData < numPoints) { + // Remove excess bars for data points that have been removed + this.getDataset().metaData.splice(numData, numPoints - numData) + } else if (numData > numPoints) { + // Add new elements + for (var index = numPoints; index < numData; ++index) { + this.addElementAndReset(index); + } + } + if (yScale.min < 0 && yScale.max < 0) { scaleBase = yScale.getPixelForValue(yScale.max); } else if (yScale.min > 0 && yScale.max > 0) { @@ -180,7 +195,7 @@ x: xScale.getPointPixelForValue(this.getDataset().data[index], index, this.index), y: reset ? scaleBase : yScale.getPointPixelForValue(this.getDataset().data[index], index, this.index), // Appearance - tension: point.custom && point.custom.tension ? point.custom.tension : this.chart.options.elements.line.tension, + tension: point.custom && point.custom.tension ? point.custom.tension : (this.getDataset().tension || this.chart.options.elements.line.tension), radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius), backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor), borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor), @@ -253,10 +268,10 @@ var dataset = this.chart.data.datasets[point._datasetIndex]; var index = point._index; - point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, point._model.borderWidth); + point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, point._model.borderWidth); }, removeHoverStyle: function(point) { @@ -268,9 +283,5 @@ point._model.borderColor = point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor); point._model.borderWidth = point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth); } - }); - - - }).call(this); diff --git a/test/controller.line.tests.js b/test/controller.line.tests.js new file mode 100644 index 000000000..154435c70 --- /dev/null +++ b/test/controller.line.tests.js @@ -0,0 +1,814 @@ +// Test the bar controller +describe('Line controller tests', function() { + it('Should be constructed', function() { + var chart = { + data: { + datasets: [{ + xAxisID: 'myXAxis', + yAxisID: 'myYAxis', + data: [] + }] + } + }; + + var controller = new Chart.controllers.line(chart, 0); + expect(controller).not.toBe(undefined); + expect(controller.index).toBe(0); + expect(chart.data.datasets[0].metaData).toEqual([]); + + controller.updateIndex(1); + expect(controller.index).toBe(1); + }); + + it('Should use the first scale IDs if the dataset does not specify them', function() { + var chart = { + data: { + datasets: [{ + data: [] + }] + }, + options: { + scales: { + xAxes: [{ + id: 'firstXScaleID' + }], + yAxes: [{ + id: 'firstYScaleID' + }] + } + } + }; + + var controller = new Chart.controllers.line(chart, 0); + expect(chart.data.datasets[0].xAxisID).toBe('firstXScaleID'); + expect(chart.data.datasets[0].yAxisID).toBe('firstYScaleID'); + }); + + it('Should create line elements and point elements for each data item during initialization', function() { + var chart = { + data: { + datasets: [{ + data: [10, 15, 0, -4] + }] + }, + config: { + type: 'line' + }, + options: { + scales: { + xAxes: [{ + id: 'firstXScaleID' + }], + yAxes: [{ + id: 'firstYScaleID' + }] + } + } + }; + + var controller = new Chart.controllers.line(chart, 0); + + expect(chart.data.datasets[0].metaData.length).toBe(4); // 4 points created + expect(chart.data.datasets[0].metaData[0] instanceof Chart.elements.Point).toBe(true); + expect(chart.data.datasets[0].metaData[1] instanceof Chart.elements.Point).toBe(true); + expect(chart.data.datasets[0].metaData[2] instanceof Chart.elements.Point).toBe(true); + expect(chart.data.datasets[0].metaData[3] instanceof Chart.elements.Point).toBe(true); + expect(chart.data.datasets[0].metaDataset instanceof Chart.elements.Line).toBe(true); // 1 line element + }); + + it('should remove elements', function() { + var chart = { + data: { + datasets: [{ + data: [10, 15, 0, -4] + }] + }, + config: { + type: 'line' + }, + options: { + scales: { + xAxes: [{ + id: 'firstXScaleID' + }], + yAxes: [{ + id: 'firstYScaleID' + }] + } + } + }; + + var controller = new Chart.controllers.line(chart, 0); + controller.removeElement(0); + expect(chart.data.datasets[0].metaData.length).toBe(3); + }); + + it ('should draw all elements', function() { + var chart = { + data: { + datasets: [{ + data: [10, 15, 0, -4] + }] + }, + config: { + type: 'line' + }, + options: { + scales: { + xAxes: [{ + id: 'firstXScaleID' + }], + yAxes: [{ + id: 'firstYScaleID' + }] + } + } + }; + + var controller = new Chart.controllers.line(chart, 0); + + spyOn(chart.data.datasets[0].metaDataset, 'draw'); + spyOn(chart.data.datasets[0].metaData[0], 'draw'); + spyOn(chart.data.datasets[0].metaData[1], 'draw'); + spyOn(chart.data.datasets[0].metaData[2], 'draw'); + spyOn(chart.data.datasets[0].metaData[3], 'draw'); + + controller.draw(); + + expect(chart.data.datasets[0].metaDataset.draw.calls.count()).toBe(1); + expect(chart.data.datasets[0].metaData[0].draw.calls.count()).toBe(1); + expect(chart.data.datasets[0].metaData[2].draw.calls.count()).toBe(1); + expect(chart.data.datasets[0].metaData[3].draw.calls.count()).toBe(1); + }); + + it ('should update elements', function() { + var chart = { + chartArea: { + bottom: 100, + left: 0, + right: 200, + top: 0 + }, + data: { + datasets: [{ + data: [10, 15, 0, -4] + }] + }, + config: { + type: 'line' + }, + options: { + elements: { + line: { + backgroundColor: 'rgb(255, 0, 0)', + borderCapStyle: 'round', + borderColor: 'rgb(0, 255, 0)', + borderDash: [], + borderDashOffset: 0.1, + borderJoinStyle: 'bevel', + borderWidth: 1.2, + fill: true, + skipNull: true, + tension: 0.1, + }, + point: { + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 1, + borderColor: Chart.defaults.global.defaultColor, + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1, + radius: 3, + } + }, + scales: { + xAxes: [{ + id: 'firstXScaleID' + }], + yAxes: [{ + id: 'firstYScaleID' + }] + } + }, + scales: { + firstXScaleID: { + getPointPixelForValue: function(value, index) { + return index * 3; + } + }, + firstYScaleID: { + calculateBarBase: function(datasetIndex, index) { + return this.getPixelForValue(0); + }, + getPointPixelForValue: function(value, datasetIndex, index) { + return this.getPixelForValue(value); + }, + getPixelForValue: function(value) { + return value * 2; + }, + max: 10, + min: -10, + } + } + }; + + var controller = new Chart.controllers.line(chart, 0); + controller.update(); + + // Line element + expect(chart.data.datasets[0].metaDataset._model).toEqual({ + backgroundColor: 'rgb(255, 0, 0)', + borderCapStyle: 'round', + borderColor: 'rgb(0, 255, 0)', + borderDash: [], + borderDashOffset: 0.1, + borderJoinStyle: 'bevel', + borderWidth: 1.2, + fill: true, + drawNull: undefined, + skipNull: true, + tension: 0.1, + + scaleTop: undefined, + scaleBottom: undefined, + scaleZero: 0, + }); + + expect(chart.data.datasets[0].metaData[0]._model).toEqual({ + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 1, + borderColor: Chart.defaults.global.defaultColor, + hitRadius: 1, + radius: 3, + skip: false, + tension: 0.1, + + // Point + x: 0, + y: 20, + + // Control points + controlPointPreviousX: 0, + controlPointPreviousY: 20, + controlPointNextX: 0.30000000000000004, + controlPointNextY: 21, + }); + + expect(chart.data.datasets[0].metaData[1]._model).toEqual({ + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 1, + borderColor: Chart.defaults.global.defaultColor, + hitRadius: 1, + radius: 3, + skip: false, + tension: 0.1, + + // Point + x: 3, + y: 30, + + // Control points + controlPointPreviousX: 2.845671490812908, + controlPointPreviousY: 30.514428363956974, + controlPointNextX: 3.4456714908129076, + controlPointNextY: 28.514428363956974, + }); + + expect(chart.data.datasets[0].metaData[2]._model).toEqual({ + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 1, + borderColor: Chart.defaults.global.defaultColor, + hitRadius: 1, + radius: 3, + skip: false, + tension: 0.1, + + // Point + x: 6, + y: 0, + + // Control points + controlPointPreviousX: 5.532486979550596, + controlPointPreviousY: 2.9609157961795605, + controlPointNextX: 6.132486979550595, + controlPointNextY: 0, + }); + + expect(chart.data.datasets[0].metaData[3]._model).toEqual({ + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 1, + borderColor: Chart.defaults.global.defaultColor, + hitRadius: 1, + radius: 3, + skip: false, + tension: 0.1, + + // Point + x: 9, + y: -8, + + // Control points + controlPointPreviousX: 8.7, + controlPointPreviousY: 0, + controlPointNextX: 9, + controlPointNextY: 0, + }); + + // Use dataset level styles for lines & points + chart.data.datasets[0].tension = 0.2; + chart.data.datasets[0].backgroundColor = 'rgb(98, 98, 98)'; + chart.data.datasets[0].borderColor = 'rgb(8, 8, 8)'; + chart.data.datasets[0].borderWidth = 0.55; + chart.data.datasets[0].borderCapStyle = 'butt'; + chart.data.datasets[0].borderDash = [2, 3]; + chart.data.datasets[0].borderDashOffset = 7; + chart.data.datasets[0].borderJoinStyle = 'miter'; + chart.data.datasets[0].fill = false; + chart.data.datasets[0].skipNull = false; + chart.data.datasets[0].drawNull = true; + + // point styles + chart.data.datasets[0].radius = 22; + chart.data.datasets[0].hitRadius = 3.3; + chart.data.datasets[0].pointBackgroundColor = 'rgb(128, 129, 130)'; + chart.data.datasets[0].pointBorderColor = 'rgb(56, 57, 58)'; + chart.data.datasets[0].pointBorderWidth = 1.123; + + controller.update(); + + expect(chart.data.datasets[0].metaDataset._model).toEqual({ + backgroundColor: 'rgb(98, 98, 98)', + borderCapStyle: 'butt', + borderColor: 'rgb(8, 8, 8)', + borderDash: [2, 3], + borderDashOffset: 7, + borderJoinStyle: 'miter', + borderWidth: 0.55, + fill: false, + drawNull: true, + skipNull: false, + tension: 0.2, + + scaleTop: undefined, + scaleBottom: undefined, + scaleZero: 0, + }); + + expect(chart.data.datasets[0].metaData[0]._model).toEqual({ + backgroundColor: 'rgb(128, 129, 130)', + borderWidth: 1.123, + borderColor: 'rgb(56, 57, 58)', + hitRadius: 3.3, + radius: 22, + skip: false, + tension: 0.2, + + // Point + x: 0, + y: 20, + + // Control points + controlPointPreviousX: 0, + controlPointPreviousY: 20, + controlPointNextX: 0.6000000000000001, + controlPointNextY: 22, + }); + + expect(chart.data.datasets[0].metaData[1]._model).toEqual({ + backgroundColor: 'rgb(128, 129, 130)', + borderWidth: 1.123, + borderColor: 'rgb(56, 57, 58)', + hitRadius: 3.3, + radius: 22, + skip: false, + tension: 0.2, + + // Point + x: 3, + y: 30, + + // Control points + controlPointPreviousX: 2.6913429816258154, + controlPointPreviousY: 31.028856727913947, + controlPointNextX: 3.891342981625815, + controlPointNextY: 27.028856727913947, + }); + + expect(chart.data.datasets[0].metaData[2]._model).toEqual({ + backgroundColor: 'rgb(128, 129, 130)', + borderWidth: 1.123, + borderColor: 'rgb(56, 57, 58)', + hitRadius: 3.3, + radius: 22, + skip: false, + tension: 0.2, + + // Point + x: 6, + y: 0, + + // Control points + controlPointPreviousX: 5.0649739591011915, + controlPointPreviousY: 5.921831592359121, + controlPointNextX: 6.264973959101192, + controlPointNextY: 0, + }); + + expect(chart.data.datasets[0].metaData[3]._model).toEqual({ + backgroundColor: 'rgb(128, 129, 130)', + borderWidth: 1.123, + borderColor: 'rgb(56, 57, 58)', + hitRadius: 3.3, + radius: 22, + skip: false, + tension: 0.2, + + // Point + x: 9, + y: -8, + + // Control points + controlPointPreviousX: 8.4, + controlPointPreviousY: 0, + controlPointNextX: 9, + controlPointNextY: 0, + }); + + // Use custom styles for lines & first point + chart.data.datasets[0].metaDataset.custom = { + tension: 0.25, + backgroundColor: 'rgb(55, 55, 54)', + borderColor: 'rgb(8, 7, 6)', + borderWidth: 0.3, + borderCapStyle: 'square', + borderDash: [4, 3], + borderDashOffset: 4.4, + borderJoinStyle: 'round', + fill: true, + skipNull: true, + drawNull: false, + }; + + // point styles + chart.data.datasets[0].metaData[0].custom = { + radius: 2.2, + backgroundColor: 'rgb(0, 1, 3)', + borderColor: 'rgb(4, 6, 8)', + borderWidth: 0.787, + tension: 0.15, + skip: true, + hitRadius: 5, + }; + + controller.update(); + + expect(chart.data.datasets[0].metaDataset._model).toEqual({ + backgroundColor: 'rgb(55, 55, 54)', + borderCapStyle: 'square', + borderColor: 'rgb(8, 7, 6)', + borderDash: [4, 3], + borderDashOffset: 4.4, + borderJoinStyle: 'round', + borderWidth: 0.3, + fill: true, + drawNull: true, + skipNull: false, + tension: 0.25, + + scaleTop: undefined, + scaleBottom: undefined, + scaleZero: 0, + }); + + expect(chart.data.datasets[0].metaData[0]._model).toEqual({ + backgroundColor: 'rgb(0, 1, 3)', + borderWidth: 0.787, + borderColor: 'rgb(4, 6, 8)', + hitRadius: 5, + radius: 2.2, + skip: true, + tension: 0.15, + + // Point + x: 0, + y: 20, + + // Control points + controlPointPreviousX: 0, + controlPointPreviousY: 20, + controlPointNextX: 0.44999999999999996, + controlPointNextY: 21.5, + }); + }); + + it ('should handle number of data point changes in update', function() { + var chart = { + chartArea: { + bottom: 100, + left: 0, + right: 200, + top: 0 + }, + data: { + datasets: [{ + data: [10, 15, 0, -4] + }] + }, + config: { + type: 'line' + }, + options: { + elements: { + line: { + backgroundColor: 'rgb(255, 0, 0)', + borderCapStyle: 'round', + borderColor: 'rgb(0, 255, 0)', + borderDash: [], + borderDashOffset: 0.1, + borderJoinStyle: 'bevel', + borderWidth: 1.2, + fill: true, + skipNull: true, + tension: 0.1, + }, + point: { + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 1, + borderColor: Chart.defaults.global.defaultColor, + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1, + radius: 3, + } + }, + scales: { + xAxes: [{ + id: 'firstXScaleID' + }], + yAxes: [{ + id: 'firstYScaleID' + }] + } + }, + scales: { + firstXScaleID: { + getPointPixelForValue: function(value, index) { + return index * 3; + } + }, + firstYScaleID: { + calculateBarBase: function(datasetIndex, index) { + return this.getPixelForValue(0); + }, + getPointPixelForValue: function(value, datasetIndex, index) { + return this.getPixelForValue(value); + }, + getPixelForValue: function(value) { + return value * 2; + }, + max: 10, + min: -10, + } + } + }; + + var controller = new Chart.controllers.line(chart, 0); + chart.data.datasets[0].data = [1, 2]; // remove 2 items + + controller.update(); + expect(chart.data.datasets[0].metaData.length).toBe(2); + expect(chart.data.datasets[0].metaData[0] instanceof Chart.elements.Point).toBe(true); + expect(chart.data.datasets[0].metaData[1] instanceof Chart.elements.Point).toBe(true); + + chart.data.datasets[0].data = [1, 2, 3, 4, 5]; // add 3 items + + controller.update(); + expect(chart.data.datasets[0].metaData.length).toBe(5); + expect(chart.data.datasets[0].metaData[0] instanceof Chart.elements.Point).toBe(true); + expect(chart.data.datasets[0].metaData[1] instanceof Chart.elements.Point).toBe(true); + expect(chart.data.datasets[0].metaData[2] instanceof Chart.elements.Point).toBe(true); + expect(chart.data.datasets[0].metaData[3] instanceof Chart.elements.Point).toBe(true); + expect(chart.data.datasets[0].metaData[4] instanceof Chart.elements.Point).toBe(true); + }); + + it ('should set point hover styles', function() { + var chart = { + chartArea: { + bottom: 100, + left: 0, + right: 200, + top: 0 + }, + data: { + datasets: [{ + data: [10, 15, 0, -4] + }] + }, + config: { + type: 'line' + }, + options: { + elements: { + line: { + backgroundColor: 'rgb(255, 0, 0)', + borderCapStyle: 'round', + borderColor: 'rgb(0, 255, 0)', + borderDash: [], + borderDashOffset: 0.1, + borderJoinStyle: 'bevel', + borderWidth: 1.2, + fill: true, + skipNull: true, + tension: 0.1, + }, + point: { + backgroundColor: 'rgb(255, 255, 0)', + borderWidth: 1, + borderColor: 'rgb(255, 255, 255)', + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1, + radius: 3, + } + }, + scales: { + xAxes: [{ + id: 'firstXScaleID' + }], + yAxes: [{ + id: 'firstYScaleID' + }] + } + }, + scales: { + firstXScaleID: { + getPointPixelForValue: function(value, index) { + return index * 3; + } + }, + firstYScaleID: { + calculateBarBase: function(datasetIndex, index) { + return this.getPixelForValue(0); + }, + getPointPixelForValue: function(value, datasetIndex, index) { + return this.getPixelForValue(value); + }, + getPixelForValue: function(value) { + return value * 2; + }, + max: 10, + min: -10, + } + } + }; + + var controller = new Chart.controllers.line(chart, 0); + controller.update(); + var point = chart.data.datasets[0].metaData[0]; + + controller.setHoverStyle(point); + expect(point._model.backgroundColor).toBe('rgb(229, 230, 0)'); + expect(point._model.borderColor).toBe('rgb(230, 230, 230)'); + expect(point._model.borderWidth).toBe(1); + expect(point._model.radius).toBe(4); + + // Can set hover style per dataset + chart.data.datasets[0].pointHoverRadius = 3.3; + chart.data.datasets[0].pointHoverBackgroundColor = 'rgb(77, 79, 81)'; + chart.data.datasets[0].pointHoverBorderColor = 'rgb(123, 125, 127)'; + chart.data.datasets[0].pointHoverBorderWidth = 2.1; + + controller.setHoverStyle(point); + expect(point._model.backgroundColor).toBe('rgb(77, 79, 81)'); + expect(point._model.borderColor).toBe('rgb(123, 125, 127)'); + expect(point._model.borderWidth).toBe(2.1); + expect(point._model.radius).toBe(3.3); + + // Custom style + point.custom = { + hoverRadius: 4.4, + hoverBorderWidth: 5.5, + hoverBackgroundColor: 'rgb(0, 0, 0)', + hoverBorderColor: 'rgb(10, 10, 10)' + }; + + controller.setHoverStyle(point); + expect(point._model.backgroundColor).toBe('rgb(0, 0, 0)'); + expect(point._model.borderColor).toBe('rgb(10, 10, 10)'); + expect(point._model.borderWidth).toBe(5.5); + expect(point._model.radius).toBe(4.4); + }); + + it ('should remove hover styles', function() { + var chart = { + chartArea: { + bottom: 100, + left: 0, + right: 200, + top: 0 + }, + data: { + datasets: [{ + data: [10, 15, 0, -4] + }] + }, + config: { + type: 'line' + }, + options: { + elements: { + line: { + backgroundColor: 'rgb(255, 0, 0)', + borderCapStyle: 'round', + borderColor: 'rgb(0, 255, 0)', + borderDash: [], + borderDashOffset: 0.1, + borderJoinStyle: 'bevel', + borderWidth: 1.2, + fill: true, + skipNull: true, + tension: 0.1, + }, + point: { + backgroundColor: 'rgb(255, 255, 0)', + borderWidth: 1, + borderColor: 'rgb(255, 255, 255)', + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1, + radius: 3, + } + }, + scales: { + xAxes: [{ + id: 'firstXScaleID' + }], + yAxes: [{ + id: 'firstYScaleID' + }] + } + }, + scales: { + firstXScaleID: { + getPointPixelForValue: function(value, index) { + return index * 3; + } + }, + firstYScaleID: { + calculateBarBase: function(datasetIndex, index) { + return this.getPixelForValue(0); + }, + getPointPixelForValue: function(value, datasetIndex, index) { + return this.getPixelForValue(value); + }, + getPixelForValue: function(value) { + return value * 2; + }, + max: 10, + min: -10, + } + } + }; + + var controller = new Chart.controllers.line(chart, 0); + controller.update(); + var point = chart.data.datasets[0].metaData[0]; + + chart.options.elements.point.backgroundColor = 'rgb(45, 46, 47)'; + chart.options.elements.point.borderColor = 'rgb(50, 51, 52)'; + chart.options.elements.point.borderWidth = 10.1; + chart.options.elements.point.radius = 1.01; + + controller.removeHoverStyle(point); + expect(point._model.backgroundColor).toBe('rgb(45, 46, 47)'); + expect(point._model.borderColor).toBe('rgb(50, 51, 52)'); + expect(point._model.borderWidth).toBe(10.1); + expect(point._model.radius).toBe(1.01); + + // Can set hover style per dataset + chart.data.datasets[0].radius = 3.3; + chart.data.datasets[0].pointBackgroundColor = 'rgb(77, 79, 81)'; + chart.data.datasets[0].pointBorderColor = 'rgb(123, 125, 127)'; + chart.data.datasets[0].pointBorderWidth = 2.1; + + controller.removeHoverStyle(point); + expect(point._model.backgroundColor).toBe('rgb(77, 79, 81)'); + expect(point._model.borderColor).toBe('rgb(123, 125, 127)'); + expect(point._model.borderWidth).toBe(2.1); + expect(point._model.radius).toBe(3.3); + + // Custom style + point.custom = { + radius: 4.4, + borderWidth: 5.5, + backgroundColor: 'rgb(0, 0, 0)', + borderColor: 'rgb(10, 10, 10)' + }; + + controller.removeHoverStyle(point); + expect(point._model.backgroundColor).toBe('rgb(0, 0, 0)'); + expect(point._model.borderColor).toBe('rgb(10, 10, 10)'); + expect(point._model.borderWidth).toBe(5.5); + expect(point._model.radius).toBe(4.4); + }); +}); \ No newline at end of file