]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Bubble Chart Type and Bubble Controller 1512/head
authorTanner Linsley <tannerlinsley@gmail.com>
Mon, 5 Oct 2015 18:40:28 +0000 (12:40 -0600)
committerTanner Linsley <tannerlinsley@gmail.com>
Mon, 5 Oct 2015 18:40:28 +0000 (12:40 -0600)
samples/bubble.html [new file with mode: 0644]
src/charts/Chart.Bubble.js [new file with mode: 0644]
src/controllers/controller.bubble.js [new file with mode: 0644]

diff --git a/samples/bubble.html b/samples/bubble.html
new file mode 100644 (file)
index 0000000..a52cd9c
--- /dev/null
@@ -0,0 +1,206 @@
+<!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>
diff --git a/src/charts/Chart.Bubble.js b/src/charts/Chart.Bubble.js
new file mode 100644 (file)
index 0000000..d4715c0
--- /dev/null
@@ -0,0 +1,39 @@
+(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);
diff --git a/src/controllers/controller.bubble.js b/src/controllers/controller.bubble.js
new file mode 100644 (file)
index 0000000..f6c5228
--- /dev/null
@@ -0,0 +1,218 @@
+(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);