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/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.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 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