From b22e1e3e9c67fbe3522cde4cae9555e998c8b010 Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Tue, 2 Jun 2015 20:44:24 -0400 Subject: [PATCH] Update scatter chart. Still need to get tooltips working --- src/Chart.Scatter.js | 539 +++++++++++++++++++++++++------------------ 1 file changed, 311 insertions(+), 228 deletions(-) diff --git a/src/Chart.Scatter.js b/src/Chart.Scatter.js index 348e3defb..071ff4dbd 100644 --- a/src/Chart.Scatter.js +++ b/src/Chart.Scatter.js @@ -77,27 +77,12 @@ }], }, - //Number - Tension of the bezier curve between points - tension: 0.4, - - //Number - Radius of each point dot in pixels - pointRadius: 4, - - //Number - Pixel width of point dot border - pointBorderWidth: 1, - - //Number - amount extra to add to the radius to cater for hit detection outside the drawn point - pointHoverRadius: 20, - - //Number - Pixel width of dataset border - borderWidth: 2, - //String - A legend template legendTemplate: "", tooltips: { - template: "(<%= dataX %>, <%= dataY %>)", - multiTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%>(<%= dataX %>, <%= dataY %>)", + template: "(<%= value.x %>, <%= value.y %>)", + multiTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%>(<%= value.x %>, <%= value.y %>)", }, }; @@ -108,30 +93,29 @@ defaults: defaultConfig, initialize: function() { //Custom Point Defaults - this.PointClass = Chart.Point.extend({ - _chart: this.chart, - offsetGridLines: this.options.offsetGridLines, - borderWidth: this.options.pointBorderWidth, - radius: this.options.pointRadius, - hoverRadius: this.options.pointHoverRadius, - }); - - // Events - helpers.bindEvents(this, this.options.events, this.events); - - // Build Scale - this.buildScale(); - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - //Create a new line and its points for each dataset and piece of data helpers.each(this.data.datasets, function(dataset, datasetIndex) { - dataset.metaDataset = new Chart.Line(); + dataset.metaDataset = new Chart.Line({ + _chart: this.chart, + _datasetIndex: datasetIndex, + _points: dataset.metaData, + }); + dataset.metaData = []; + helpers.each(dataset.data, function(dataPoint, index) { - dataset.metaData.push(new this.PointClass()); + dataset.metaData.push(new Chart.Point({ + _datasetIndex: datasetIndex, + _index: index, + _chart: this.chart, + _model: { + x: 0, //xScale.getPixelForValue(null, index, true), + y: 0, //this.chartArea.bottom, + }, + })); + }, this); - // Make sure each dataset is bound to an x and a y axis + // The line chart onlty supports a single x axis because the x axis is always a dataset axis if (!dataset.xAxisID) { dataset.xAxisID = this.options.scales.xAxes[0].id; } @@ -139,42 +123,11 @@ if (!dataset.yAxisID) { dataset.yAxisID = this.options.scales.yAxes[0].id; } + }, this); - // Set defaults for lines - this.eachDataset(function(dataset, datasetIndex) { - dataset = helpers.merge(this.options, dataset); - helpers.extend(dataset.metaDataset, { - _points: dataset.metaData, - _datasetIndex: datasetIndex, - _chart: this.chart, - }); - // Copy to view model - dataset.metaDataset.save(); - }, this); - - // Set defaults for points - this.eachElement(function(point, index, dataset, datasetIndex) { - var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; - - helpers.extend(point, { - x: xScale.getPixelForValue(index), - y: this.chartArea.bottom, - _datasetIndex: datasetIndex, - _index: index, - _chart: this.chart - }); - - // Default bezier control points - helpers.extend(point, { - controlPointPreviousX: this.previousPoint(dataset, index).x, - controlPointPreviousY: this.nextPoint(dataset, index).y, - controlPointNextX: this.previousPoint(dataset, index).x, - controlPointNextY: this.nextPoint(dataset, index).y, - }); - // Copy to view model - point.save(); - }, this); + // Build and fit the scale. Needs to happen after the axis IDs have been set + this.buildScale(); // Create tooltip instance exclusively for this chart with some defaults. this.tooltip = new Chart.Tooltip({ @@ -183,149 +136,131 @@ _options: this.options, }, this); + // Need to fit scales before we reset elements. + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + + // Reset so that we animation from the baseline + this.resetElements(); + + // Update that shiz this.update(); }, nextPoint: function(collection, index) { - return collection[index - 1] || collection[index]; - }, - previousPoint: function(collection, index) { return collection[index + 1] || collection[index]; }, - events: function(e) { - // If exiting chart - if (e.type == 'mouseout') { - return this; - } + previousPoint: function(collection, index) { + return collection[index - 1] || collection[index]; + }, + resetElements: function() { + // Update the points + this.eachElement(function(point, index, dataset, datasetIndex) { + var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; + var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; - this.lastActive = this.lastActive || []; + var yScalePoint; - // Find Active Elements - this.active = function() { - switch (this.options.hover.mode) { - case 'single': - return this.getElementAtEvent(e); - case 'label': - return this.getElementsAtEvent(e); - case 'dataset': - return this.getDatasetAtEvent(e); - default: - return e; - } - }.call(this); - - // On Hover hook - if (this.options.onHover) { - this.options.onHover.call(this, this.active); - } - - // Remove styling for last active (even if it may still be active) - if (this.lastActive.length) { - switch (this.options.hover.mode) { - case 'single': - this.lastActive[0].backgroundColor = this.data.datasets[this.lastActive[0]._datasetIndex].pointBackgroundColor; - this.lastActive[0].borderColor = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderColor; - this.lastActive[0].borderWidth = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderWidth; - break; - case 'label': - for (var i = 0; i < this.lastActive.length; i++) { - this.lastActive[i].backgroundColor = this.data.datasets[this.lastActive[i]._datasetIndex].pointBackgroundColor; - this.lastActive[i].borderColor = this.data.datasets[this.lastActive[i]._datasetIndex].pointBorderColor; - this.lastActive[i].borderWidth = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderWidth; - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in hover styling - if (this.active.length && this.options.hover.mode) { - switch (this.options.hover.mode) { - case 'single': - this.active[0].backgroundColor = this.data.datasets[this.active[0]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[0].backgroundColor).saturate(0.5).darken(0.35).rgbString(); - this.active[0].borderColor = this.data.datasets[this.active[0]._datasetIndex].hoverBorderColor || helpers.color(this.active[0].borderColor).saturate(0.5).darken(0.35).rgbString(); - this.active[0].borderWidth = this.data.datasets[this.active[0]._datasetIndex].borderWidth + 10; - break; - case 'label': - for (var i = 0; i < this.active.length; i++) { - this.active[i].backgroundColor = this.data.datasets[this.active[i]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[i].backgroundColor).saturate(0.5).darken(0.35).rgbString(); - this.active[i].borderColor = this.data.datasets[this.active[i]._datasetIndex].hoverBorderColor || helpers.color(this.active[i].borderColor).saturate(0.5).darken(0.35).rgbString(); - this.active[i].borderWidth = this.data.datasets[this.active[i]._datasetIndex].borderWidth + 2; - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in Tooltips - if (this.options.tooltips.enabled) { - - // The usual updates - this.tooltip.initialize(); - - // Active - if (this.active.length) { - helpers.extend(this.tooltip, { - opacity: 1, - _active: this.active, - }); - - this.tooltip.update(); + if (yScale.min < 0 && yScale.max < 0) { + // all less than 0. use the top + yScalePoint = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + yScalePoint = yScale.getPixelForValue(yScale.min); } else { - // Inactive - helpers.extend(this.tooltip, { - opacity: 0, - }); + yScalePoint = yScale.getPixelForValue(0); } - } - // Hover animations - this.tooltip.pivot(); + helpers.extend(point, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, + _index: index, - if (!this.animating) { - var changed; + // Desired view properties + _model: { + x: xScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].x), // value not used in dataset scale, but we want a consistent API between scales + y: yScalePoint, - helpers.each(this.active, function(element, index) { - if (element !== this.lastActive[index]) { - changed = true; - } - }, this); + // Appearance + tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, + radius: point.custom && point.custom.radius ? point.custom.pointRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointRadius, index, this.options.elements.point.radius), + backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBackgroundColor, index, this.options.elements.point.backgroundColor), + borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderColor, index, this.options.elements.point.borderColor), + borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderWidth, index, this.options.elements.point.borderWidth), + skip: (typeof this.data.datasets[datasetIndex].data[index].x != 'number') || (typeof this.data.datasets[datasetIndex].data[index].y != 'number'), - // If entering, leaving, or changing elements, animate the change via pivot - if ((!this.lastActive.length && this.active.length) || - (this.lastActive.length && !this.active.length) || - (this.lastActive.length && this.active.length && changed)) { + // Tooltip + hoverRadius: point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointHitRadius, index, this.options.elements.point.hitRadius), + }, + }); + }, this); - this.stop(); - this.render(this.options.hoverAnimationDuration); + // Update control points for the bezier curve + this.eachElement(function(point, index, dataset, datasetIndex) { + var controlPoints = helpers.splineCurve( + this.previousPoint(dataset, index)._model, + point._model, + this.nextPoint(dataset, index)._model, + point._model.tension + ); + + point._model.controlPointPreviousX = controlPoints.previous.x; + point._model.controlPointNextX = controlPoints.next.x; + + // Prevent the bezier going outside of the bounds of the graph + + // Cap puter bezier handles to the upper/lower scale bounds + if (controlPoints.next.y > this.chartArea.bottom) { + point._model.controlPointNextY = this.chartArea.bottom; + } else if (controlPoints.next.y < this.chartArea.top) { + point._model.controlPointNextY = this.chartArea.top; + } else { + point._model.controlPointNextY = controlPoints.next.y; } - } - // Remember Last Active - this.lastActive = this.active; - return this; + // Cap inner bezier handles to the upper/lower scale bounds + if (controlPoints.previous.y > this.chartArea.bottom) { + point._model.controlPointPreviousY = this.chartArea.bottom; + } else if (controlPoints.previous.y < this.chartArea.top) { + point._model.controlPointPreviousY = this.chartArea.top; + } else { + point._model.controlPointPreviousY = controlPoints.previous.y; + } + // Now pivot the point for animation + point.pivot(); + }, this); }, update: function() { - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); // Update the lines this.eachDataset(function(dataset, datasetIndex) { + var yScale = this.scales[dataset.yAxisID]; + helpers.extend(dataset.metaDataset, { - backgroundColor: dataset.backgroundColor || this.options.backgroundColor, - borderWidth: dataset.borderWidth || this.options.borderWidth, - borderColor: dataset.borderColor || this.options.borderColor, - tension: dataset.tension || this.options.tension, - scaleTop: this.chartArea.top, - scaleBottom: this.chartArea.bottom, - _points: dataset.metaData, + // Utility + _scale: yScale, _datasetIndex: datasetIndex, + // Data + _children: dataset.metaData, + // Model + _model: { + // Appearance + tension: dataset.tension || this.options.elements.line.tension, + backgroundColor: dataset.backgroundColor || this.options.elements.line.backgroundColor, + borderWidth: dataset.borderWidth || this.options.elements.line.borderWidth, + borderColor: dataset.borderColor || this.options.elements.line.borderColor, + fill: dataset.fill !== undefined ? dataset.fill : this.options.elements.line.fill, // use the value from the dataset if it was provided. else fall back to the default + skipNull: dataset.skipNull !== undefined ? dataset.skipNull : this.options.elements.line.skipNull, + drawNull: dataset.drawNull !== undefined ? dataset.drawNull : this.options.elements.line.drawNull, + // Scale + scaleTop: yScale.top, + scaleBottom: yScale.bottom, + scaleZero: yScale.getPixelForValue(0), + }, }); + dataset.metaDataset.pivot(); }); @@ -335,58 +270,65 @@ var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; helpers.extend(point, { - x: xScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].x), - y: yScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].y), - dataX: this.data.datasets[datasetIndex].data[index].x, - dataY: this.data.datasets[datasetIndex].data[index].y, - label: '', // so that the multitooltip looks ok - value: this.data.datasets[datasetIndex].data[index].y, // for legacy reasons - datasetLabel: this.data.datasets[datasetIndex].label, - // Appearance - hoverBackgroundColor: this.data.datasets[datasetIndex].pointHoverBackgroundColor || this.options.pointHoverBackgroundColor, - hoverBorderColor: this.data.datasets[datasetIndex].pointHoverBorderColor || this.options.pointHoverBorderColor, - hoverRadius: this.data.datasets[datasetIndex].pointHoverRadius || this.options.pointHoverRadius, - radius: this.data.datasets[datasetIndex].pointRadius || this.options.pointRadius, - borderWidth: this.data.datasets[datasetIndex].pointBorderWidth || this.options.pointBorderWidth, - borderColor: this.data.datasets[datasetIndex].pointBorderColor || this.options.pointBorderColor, - backgroundColor: this.data.datasets[datasetIndex].pointBackgroundColor || this.options.pointBackgroundColor, - tension: this.data.datasets[datasetIndex].metaDataset.tension, + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, _datasetIndex: datasetIndex, _index: index, + + // Desired view properties + _model: { + x: xScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].x), + y: yScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].y), + + // Appearance + tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, + radius: point.custom && point.custom.radius ? point.custom.pointRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointRadius, index, this.options.elements.point.radius), + backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBackgroundColor, index, this.options.elements.point.backgroundColor), + borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderColor, index, this.options.elements.point.borderColor), + borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderWidth, index, this.options.elements.point.borderWidth), + skip: (typeof this.data.datasets[datasetIndex].data[index].x != 'number') || (typeof this.data.datasets[datasetIndex].data[index].y != 'number'), + + // Tooltip + hoverRadius: point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointHitRadius, index, this.options.elements.point.hitRadius), + }, }); }, this); + // Update control points for the bezier curve this.eachElement(function(point, index, dataset, datasetIndex) { var controlPoints = helpers.splineCurve( - this.previousPoint(dataset, index), - point, - this.nextPoint(dataset, index), - point.tension + this.previousPoint(dataset, index)._model, + point._model, + this.nextPoint(dataset, index)._model, + point._model.tension ); - point.controlPointPreviousX = controlPoints.previous.x; - point.controlPointNextX = controlPoints.next.x; + point._model.controlPointPreviousX = controlPoints.previous.x; + point._model.controlPointNextX = controlPoints.next.x; // Prevent the bezier going outside of the bounds of the graph // Cap puter bezier handles to the upper/lower scale bounds if (controlPoints.next.y > this.chartArea.bottom) { - point.controlPointNextY = this.chartArea.bottom; + point._model.controlPointNextY = this.chartArea.bottom; } else if (controlPoints.next.y < this.chartArea.top) { - point.controlPointNextY = this.chartArea.top; + point._model.controlPointNextY = this.chartArea.top; } else { - point.controlPointNextY = controlPoints.next.y; + point._model.controlPointNextY = controlPoints.next.y; } // Cap inner bezier handles to the upper/lower scale bounds if (controlPoints.previous.y > this.chartArea.bottom) { - point.controlPointPreviousY = this.chartArea.bottom; + point._model.controlPointPreviousY = this.chartArea.bottom; } else if (controlPoints.previous.y < this.chartArea.top) { - point.controlPointPreviousY = this.chartArea.top; + point._model.controlPointPreviousY = this.chartArea.top; } else { - point.controlPointPreviousY = controlPoints.previous.y; + point._model.controlPointPreviousY = controlPoints.previous.y; } + // Now pivot the point for animation point.pivot(); }, this); @@ -465,25 +407,28 @@ options: yAxisOptions, calculateRange: calculateYRange, id: yAxisOptions.id, + getPointPixelForValue: function(value, index, datasetIndex) { + return this.getPixelForValue(value); + } }); this.scales[scale.id] = scale; }, this); - }, - redraw: function() { - }, draw: function(ease) { - var easingDecimal = ease || 1; this.clear(); - - // Draw all the scales - helpers.each(this.scales, function(scale) { - scale.draw(this.chartArea); - }, this); - this.eachDataset(function(dataset, datasetIndex) { + // Draw all the scales + helpers.each(this.scales, function(scale) { + scale.draw(this.chartArea); + }, this); + + // reverse for-loop for proper stacking + for (var i = this.data.datasets.length - 1; i >= 0; i--) { + + var dataset = this.data.datasets[i]; + // Transition Point Locations helpers.each(dataset.metaData, function(point, index) { point.transition(easingDecimal); @@ -496,11 +441,149 @@ helpers.each(dataset.metaData, function(point) { point.draw(); }); - }, this); + } // Finally draw the tooltip this.tooltip.transition(easingDecimal).draw(); - } + }, + events: function(e) { + // If exiting chart + if (e.type == 'mouseout') { + return this; + } + + this.lastActive = this.lastActive || []; + + // Find Active Elements + this.active = function() { + switch (this.options.hover.mode) { + case 'single': + return this.getElementAtEvent(e); + case 'label': + return this.getElementsAtEvent(e); + case 'dataset': + return this.getDatasetAtEvent(e); + default: + return e; + } + }.call(this); + + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); + } + + var dataset; + var index; + // Remove styling for last active (even if it may still be active) + if (this.lastActive.length) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; + index = this.lastActive[0]._index; + + this.lastActive[0]._model.radius = this.lastActive[0].custom && this.lastActive[0].custom.radius ? this.lastActive[0].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius); + this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); + this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); + this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); + break; + case 'label': + for (var i = 0; i < this.lastActive.length; i++) { + dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; + index = this.lastActive[i]._index; + + this.lastActive[i]._model.radius = this.lastActive[i].custom && this.lastActive[i].custom.radius ? this.lastActive[i].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius); + this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); + this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); + this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + // Built in hover styling + if (this.active.length && this.options.hover.mode) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.active[0]._datasetIndex]; + index = this.active[0]._index; + + this.active[0]._model.radius = this.active[0].custom && this.active[0].custom.hoverRadius ? this.active[0].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[0]._model.radius + 2); + this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.35).rgbString()); + this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[0]._model.borderColor).saturate(0.5).darken(0.35).rgbString()); + this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[0]._model.borderWidth + 2); + break; + case 'label': + for (var i = 0; i < this.active.length; i++) { + dataset = this.data.datasets[this.active[i]._datasetIndex]; + index = this.active[i]._index; + + this.active[i]._model.radius = this.active[i].custom && this.active[i].custom.hoverRadius ? this.active[i].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[i]._model.radius + 2); + this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.35).rgbString()); + this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[i]._model.borderColor).saturate(0.5).darken(0.35).rgbString()); + this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[i]._model.borderWidth + 2); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + // Built in Tooltips + if (this.options.tooltips.enabled) { + + // The usual updates + this.tooltip.initialize(); + + // Active + if (this.active.length) { + helpers.extend(this.tooltip, { + opacity: 1, + _active: this.active, + }); + + this.tooltip.update(); + } else { + // Inactive + helpers.extend(this.tooltip, { + opacity: 0, + }); + } + } + + // Hover animations + this.tooltip.pivot(); + + if (!this.animating) { + var changed; + + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; + } + }, this); + + // If entering, leaving, or changing elements, animate the change via pivot + if ((!this.lastActive.length && this.active.length) || + (this.lastActive.length && !this.active.length) || + (this.lastActive.length && this.active.length && changed)) { + + this.stop(); + this.render(this.options.hoverAnimationDuration); + } + } + + // Remember Last Active + this.lastActive = this.active; + return this; + + }, });