--- /dev/null
+<!doctype html>
+<html>
+
+<head>
+ <title>Bar Chart</title>
+ <script src="../node_modules/jquery/dist/jquery.min.js"></script>
+ <script src="../Chart.js"></script>
+ <style type="text/css">
+ canvas {
+ border: 1px solid red;
+ }
+ </style>
+</head>
+
+<body>
+ <div id="container" style="width: 100%; height: 25%;">
+ <canvas id="canvas"></canvas>
+ </div>
+ <button id="randomizeData">Randomize Data</button>
+ <button id="addDataset">Add Dataset</button>
+ <button id="removeDataset">Remove Dataset</button>
+ <button id="addData">Add Data</button>
+ <button id="removeData">Remove Data</button>
+ <button id="show">Show</button>
+ <div>
+ <h3>Legend</h3>
+ <div id="legendContainer">
+ </div>
+ </div>
+ <script>
+ var randomScalingFactor = function() {
+ return (Math.random() > 0.5 ? 1.0 : -1.0) * Math.round(Math.random() * 100);
+ };
+ var randomColorFactor = function() {
+ return Math.round(Math.random() * 255);
+ };
+ var randomColor = function() {
+ return 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',.7)';
+ };
+
+ var bubbleChartData = {
+ animation: {
+ duration: 10000
+ },
+ datasets: [{
+ label: "My First dataset",
+ backgroundColor: randomColor(),
+ data: [{
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }]
+ }, {
+ label: "My Second dataset",
+ backgroundColor: randomColor(),
+ data: [{
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }, {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ }]
+ }]
+
+ };
+
+ function updateLegend() {
+ $legendContainer = $('#legendContainer');
+ $legendContainer.empty();
+ $legendContainer.append(window.myChart.generateLegend());
+ }
+
+ window.onload = function() {
+ var ctx = document.getElementById("canvas").getContext("2d");
+ window.myChart = new Chart(ctx, {
+ type: 'bubble',
+ data: bubbleChartData,
+ options: {
+ responsive: true,
+ }
+ });
+
+ updateLegend();
+ };
+
+ $('#randomizeData').click(function() {
+ var zero = Math.random() < 0.2 ? true : false;
+ $.each(bubbleChartData.datasets, function(i, dataset) {
+ dataset.backgroundColor = randomColor();
+ dataset.data = dataset.data.map(function() {
+ return {
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ };
+ });
+ });
+ window.myChart.update();
+ updateLegend();
+ });
+
+ $('#addDataset').click(function() {
+ var newDataset = {
+ backgroundColor: randomColor(),
+ data: []
+ };
+
+ for (var index = 0; index < bubbleChartData.datasets[0].data.length; ++index) {
+ newDataset.data.push({
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ });
+ }
+
+ bubbleChartData.datasets.push(newDataset);
+ window.myChart.update();
+ updateLegend();
+ });
+
+ $('#addData').click(function() {
+ if (bubbleChartData.datasets.length > 0) {
+
+ for (var index = 0; index < bubbleChartData.datasets.length; ++index) {
+ //window.myChart.addData(randomScalingFactor(), index);
+ bubbleChartData.datasets[index].data.push({
+ x: randomScalingFactor(),
+ y: randomScalingFactor(),
+ r: Math.abs(randomScalingFactor()) / 5,
+ });
+ }
+
+ window.myChart.update();
+ updateLegend();
+ }
+ });
+
+ $('#removeDataset').click(function() {
+ bubbleChartData.datasets.splice(0, 1);
+ window.myChart.update();
+ updateLegend();
+ });
+
+ $('#removeData').click(function() {
+
+ bubbleChartData.datasets.forEach(function(dataset, datasetIndex) {
+ dataset.data.pop();
+ });
+
+ window.myChart.update();
+ updateLegend();
+ });
+
+ $('#show').click(function() {
+ document.getElementById('container').style.display = '';
+ });
+ </script>
+</body>
+
+</html>
--- /dev/null
+(function() {
+ "use strict";
+
+ var root = this;
+ var Chart = root.Chart;
+ var helpers = Chart.helpers;
+
+ var defaultConfig = {
+ 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 %>)",
+ multiTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%>(<%= value.x %>, <%= value.y %>)",
+ },
+
+ };
+
+ Chart.Bubble = function(context, config) {
+ config.options = helpers.configMerge(defaultConfig, config.options);
+ config.type = 'bubble';
+ return new Chart(context, config);
+ };
+
+}).call(this);
--- /dev/null
+(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),
+ skip: point.custom && point.custom.skip ? point.custom.skip : this.getDataset().data[index] === null,
+
+ // Tooltip
+ hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius),
+ },
+ });
+
+ 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);