(function() { "use strict"; var root = this, Chart = root.Chart, helpers = Chart.helpers; Chart.defaults.bubble = { hover: { mode: "single" }, scales: { xAxes: [{ type: "linear", // bubble should probably use a linear scale by default position: "bottom", id: "x-axis-0", // need an ID so datasets can reference the scale }], yAxes: [{ type: "linear", position: "left", id: "y-axis-0", }], }, tooltips: { template: "(<%= value.x %>, <%= value.y %>, <%= value.r %>)", multiTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%>(<%= value.x %>, <%= value.y %>, <%= value.r %>)", }, }; Chart.controllers.bubble = function(chart, datasetIndex) { this.initialize.call(this, chart, datasetIndex); }; helpers.extend(Chart.controllers.bubble.prototype, { initialize: function(chart, datasetIndex) { this.chart = chart; this.index = datasetIndex; this.linkScales(); this.addElements(); }, updateIndex: function(datasetIndex) { this.index = datasetIndex; }, linkScales: function() { if (!this.getDataset().xAxisID) { this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id; } if (!this.getDataset().yAxisID) { this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id; } }, getDataset: function() { 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) { this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({ _chart: this.chart.chart, _datasetIndex: this.index, _index: index, }); }, this); }, addElementAndReset: function(index) { this.getDataset().metaData = this.getDataset().metaData || []; var point = new Chart.elements.Point({ _chart: this.chart.chart, _datasetIndex: this.index, _index: index, }); // Reset the point this.updateElement(point, index, true); // Add to the points array this.getDataset().metaData.splice(index, 0, point); }, removeElement: function(index) { this.getDataset().metaData.splice(index, 1); }, reset: function() { this.update(true); }, buildOrUpdateElements: function buildOrUpdateElements() { // 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); } } }, update: function update(reset) { var points = this.getDataset().metaData; var yScale = this.getScaleForId(this.getDataset().yAxisID); var xScale = this.getScaleForId(this.getDataset().xAxisID); var scaleBase; if (yScale.min < 0 && yScale.max < 0) { scaleBase = yScale.getPixelForValue(yScale.max); } else if (yScale.min > 0 && yScale.max > 0) { scaleBase = yScale.getPixelForValue(yScale.min); } else { scaleBase = yScale.getPixelForValue(0); } // Update Points helpers.each(points, function(point, index) { this.updateElement(point, index, reset); }, this); }, updateElement: function(point, index, reset) { var yScale = this.getScaleForId(this.getDataset().yAxisID); var xScale = this.getScaleForId(this.getDataset().xAxisID); var scaleBase; if (yScale.min < 0 && yScale.max < 0) { scaleBase = yScale.getPixelForValue(yScale.max); } else if (yScale.min > 0 && yScale.max > 0) { scaleBase = yScale.getPixelForValue(yScale.min); } else { scaleBase = yScale.getPixelForValue(0); } helpers.extend(point, { // Utility _chart: this.chart.chart, _xScale: xScale, _yScale: yScale, _datasetIndex: this.index, _index: index, // Desired view properties _model: { x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo), y: reset ? scaleBase : yScale.getPixelForValue(this.getDataset().data[index], index, this.index), // Appearance radius: reset ? 0 : point.custom && point.custom.radius ? point.custom.radius : this.getRadius(this.getDataset().data[index]), backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.point.backgroundColor), borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.point.borderColor), borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.point.borderWidth), // Tooltip hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius), }, }); point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); point.pivot(); }, getRadius: function(value) { return value.r || this.chart.options.elements.point.radius; }, draw: function(ease) { var easingDecimal = ease || 1; // Transition and Draw the Points helpers.each(this.getDataset().metaData, function(point, index) { point.transition(easingDecimal); point.draw(); }, this); }, setHoverStyle: function(point) { // Point var dataset = this.chart.data.datasets[point._datasetIndex]; var index = point._index; point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, this.chart.options.elements.point.hoverRadius)) + this.getRadius(this.getDataset().data[point._index]); point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, 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.hoverBorderColor, 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.hoverBorderWidth, index, point._model.borderWidth); }, removeHoverStyle: function(point) { var dataset = this.chart.data.datasets[point._datasetIndex]; var index = point._index; point._model.radius = point.custom && point.custom.radius ? point.custom.radius : this.getRadius(this.getDataset().data[point._index]); point._model.backgroundColor = point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.point.backgroundColor); point._model.borderColor = point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.point.borderColor); point._model.borderWidth = point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.point.borderWidth); } }); }).call(this);