├── .gitignore ├── bower.json ├── README.md ├── package.json ├── LICENSE ├── samples └── stacked-bar.html ├── docs └── Stacked-Bar-Chart.md └── src └── Chart.StackedBar.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | 4 | node_modules/* 5 | custom/* 6 | 7 | docs/index.md 8 | bower_components 9 | 10 | .idea 11 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chart.StackedBar.js", 3 | "version": "1.2.1", 4 | "description": "StackedBar implementation for Chart.js", 5 | "homepage": "https://github.com/nnnick/Chart.js", 6 | "author": "Regaddi", 7 | "main": [ 8 | "src/Chart.StackedBar.js" 9 | ], 10 | "dependencies": { 11 | "Chart.js": ">= 1.0.0-beta" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chart.StackedBar.js 2 | =================== 3 | 4 | *StackedBar plugin for Chart.js* [chartjs.org](http://www.chartjs.org) 5 | 6 | ## Documentation 7 | 8 | You can find the documentation under `/docs` 9 | 10 | ## License 11 | 12 | Chart.StackedBar.js is available under the [MIT license](http://opensource.org/licenses/MIT). 13 | 14 | ## Bugs & issues 15 | 16 | When reporting bugs or issues, if you could include a link to a simple [jsbin](http://jsbin.com) or similar demonstrating the issue, that'd be really helpful. 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chart.StackedBar.js", 3 | "homepage": "http://www.chartjs.org", 4 | "description": "StackedBar implementation for Chart.js", 5 | "private": true, 6 | "version": "1.2.1", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/Regaddi/Chart.StackedBar.js.git" 10 | }, 11 | "dependences": { 12 | "Chart.js": ">= 1.0.2" 13 | }, 14 | "main": "src/Chart.StackedBar.js", 15 | "devDependencies": { 16 | "gulp": "3.5.x", 17 | "gulp-concat": "~2.1.x", 18 | "gulp-uglify": "~0.2.x", 19 | "gulp-util": "~2.2.x", 20 | "gulp-jshint": "~1.5.1", 21 | "gulp-size": "~0.4.0", 22 | "gulp-connect": "~2.0.5" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Christian Stuff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /samples/stacked-bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stacked Bar Chart 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | 16 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/Stacked-Bar-Chart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: StackedBar Chart 3 | anchor: stacked-bar-chart 4 | --- 5 | 6 | ### Introduction 7 | A stacked bar chart is a way of showing data as bars. 8 | 9 |
10 | 11 |
12 | 13 | ### Example usage 14 | ```javascript 15 | var myStackedBarChart = new Chart(ctx).StackedBar(data, options); 16 | ``` 17 | 18 | ### Data structure 19 | 20 | ```javascript 21 | var data = { 22 | labels: ["January", "February", "March", "April", "May", "June", "July"], 23 | datasets: [ 24 | { 25 | label: "My First dataset", 26 | fillColor: "rgba(220,220,220,0.5)", 27 | strokeColor: "rgba(220,220,220,0.8)", 28 | highlightFill: "rgba(220,220,220,0.75)", 29 | highlightStroke: "rgba(220,220,220,1)", 30 | data: [65, 59, 80, 81, 56, 55, 40] 31 | }, 32 | { 33 | label: "My Second dataset", 34 | fillColor: "rgba(151,187,205,0.5)", 35 | strokeColor: "rgba(151,187,205,0.8)", 36 | highlightFill: "rgba(151,187,205,0.75)", 37 | highlightStroke: "rgba(151,187,205,1)", 38 | data: [28, 48, 40, 19, 86, 27, 90] 39 | } 40 | ] 41 | }; 42 | ``` 43 | 44 | ### Chart Options 45 | 46 | These are the customisation options specific to StackedBar charts. These options are merged with the [global chart configuration options](#getting-started-global-chart-configuration), and form the options of the chart. 47 | 48 | ```javascript 49 | { 50 | //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value 51 | scaleBeginAtZero : true, 52 | 53 | //Boolean - Whether grid lines are shown across the chart 54 | scaleShowGridLines : true, 55 | 56 | //String - Colour of the grid lines 57 | scaleGridLineColor : "rgba(0,0,0,.05)", 58 | 59 | //Number - Width of the grid lines 60 | scaleGridLineWidth : 1, 61 | 62 | //Boolean - If there is a stroke on each bar 63 | barShowStroke : true, 64 | 65 | //Number - Pixel width of the bar stroke 66 | barStrokeWidth : 2, 67 | 68 | //Number - Spacing between each of the X value sets 69 | barValueSpacing : 5, 70 | 71 | //Boolean - Whether bars should be rendered on a percentage base 72 | relativeBars : false, 73 | 74 | {% raw %} 75 | //String - A legend template 76 | legendTemplate : "", 77 | {% endraw %} 78 | 79 | //Boolean - Hide labels with value set to 0 80 | tooltipHideZero: false 81 | } 82 | ``` 83 | 84 | You can override these for your `Chart` instance by passing a second argument into the `StackedBar` method as an object with the keys you want to override. 85 | 86 | For example, we could have a stacked bar chart without a stroke on each bar by doing the following: 87 | 88 | ```javascript 89 | new Chart(ctx).StackedBar(data, { 90 | barShowStroke: false 91 | }); 92 | // This will create a chart with all of the default options, merged from the global config, 93 | // and the StackedBar chart defaults but this particular instance will have `barShowStroke` set to false. 94 | ``` 95 | 96 | We can also change these defaults values for each StackedBar type that is created, this object is available at `Chart.defaults.StackedBar`. 97 | 98 | ### Prototype methods 99 | 100 | #### .getBarsAtEvent( event ) 101 | 102 | Calling `getBarsAtEvent(event)` on your Chart instance passing an argument of an event, or jQuery event, will return the bar elements that are at that the same position of that event. 103 | 104 | ```javascript 105 | canvas.onclick = function(evt){ 106 | var activeBars = myStackedBarChart.getBarsAtEvent(evt); 107 | // => activeBars is an array of bars on the canvas that are at the same position as the click event. 108 | }; 109 | ``` 110 | 111 | This functionality may be useful for implementing DOM based tooltips, or triggering custom behaviour in your application. 112 | 113 | #### .update( ) 114 | 115 | Calling `update()` on your Chart instance will re-render the chart with any updated values, allowing you to edit the value of multiple existing points, then render those in one animated render loop. 116 | 117 | ```javascript 118 | myStackedBarChart.datasets[0].bars[2].value = 50; 119 | // Would update the first dataset's value of 'March' to be 50 120 | myStackedBarChart.update(); 121 | // Calling update now animates the position of March from 90 to 50. 122 | ``` 123 | 124 | #### .addData( valuesArray, label ) 125 | 126 | Calling `addData(valuesArray, label)` on your Chart instance passing an array of values for each dataset, along with a label for those bars. 127 | 128 | ```javascript 129 | // The values array passed into addData should be one for each dataset in the chart 130 | myStackedBarChart.addData([40, 60], "August"); 131 | // The new data will now animate at the end of the chart. 132 | ``` 133 | 134 | #### .removeData( ) 135 | 136 | Calling `removeData()` on your Chart instance will remove the first value for all datasets on the chart. 137 | 138 | ```javascript 139 | myStackedBarChart.removeData(); 140 | // The chart will now animate and remove the first bar 141 | ``` 142 | -------------------------------------------------------------------------------- /src/Chart.StackedBar.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | "use strict"; 3 | if (typeof define === 'function' && define.amd) { 4 | // AMD. Register as an anonymous module. 5 | define(['chart.js'], factory); 6 | } else if (typeof exports === 'object') { 7 | // Node/CommonJS 8 | module.exports = factory(require('chart.js')); 9 | } else { 10 | // Global browser 11 | factory(Chart); 12 | } 13 | }(function (Chart) { 14 | "use strict"; 15 | 16 | var helpers = Chart.helpers; 17 | 18 | function drawLine(ctx, scale, annotation) { 19 | ctx.beginPath(); 20 | ctx.moveTo(scale.calculateBarX(annotation.value), scale.startPoint); 21 | ctx.strokeStyle = annotation.lineColor; 22 | ctx.lineTo(scale.calculateBarX(annotation.value), scale.endPoint); 23 | ctx.stroke(); 24 | } 25 | 26 | function drawLabel(ctx, scale, annotation) { 27 | ctx.textAlign = 'left'; 28 | ctx.fillStyle = annotation.labelColor; 29 | ctx.fillText(annotation.labelText, scale.calculateBarX(annotation.value), scale.startPoint - 5); 30 | } 31 | 32 | function drawAnnotations(chartType) { 33 | var annotations = chartType.options.annotation ? chartType.options.annotation.annotations : []; 34 | var datasets = chartType.datasets; 35 | var scale = chartType.scale; 36 | var ctx = chartType.chart.ctx; 37 | 38 | helpers.each(annotations, function (annotation) { 39 | if (annotation.type !== 'line') return; 40 | 41 | helpers.each(datasets, function (dataset) { 42 | drawLine(ctx, scale, annotation); 43 | drawLabel(ctx, scale, annotation); 44 | }); 45 | }); 46 | } 47 | 48 | var defaultConfig = { 49 | scaleBeginAtZero : true, 50 | 51 | //Boolean - Whether grid lines are shown across the chart 52 | scaleShowGridLines : true, 53 | 54 | //String - Colour of the grid lines 55 | scaleGridLineColor : "rgba(0,0,0,.05)", 56 | 57 | //Number - Width of the grid lines 58 | scaleGridLineWidth : 1, 59 | 60 | //Boolean - Whether to show horizontal lines (except X axis) 61 | scaleShowHorizontalLines: true, 62 | 63 | //Boolean - Whether to show vertical lines (except Y axis) 64 | scaleShowVerticalLines: true, 65 | 66 | //Boolean - If there is a stroke on each bar 67 | barShowStroke : true, 68 | 69 | //Number - Pixel width of the bar stroke 70 | barStrokeWidth : 2, 71 | 72 | //Number - Spacing between each of the X value sets 73 | barValueSpacing : 5, 74 | 75 | //Boolean - Whether bars should be rendered on a percentage base 76 | relativeBars : false, 77 | 78 | //String - A legend template 79 | legendTemplate : "", 80 | 81 | //Boolean - Show total legend 82 | showTotal: false, 83 | 84 | //String - Color of total legend 85 | totalColor: '#fff', 86 | 87 | //String - Total Label 88 | totalLabel: 'Total', 89 | 90 | //Boolean - Hide labels with value set to 0 91 | tooltipHideZero: false 92 | }; 93 | 94 | Chart.Type.extend({ 95 | name: "StackedBar", 96 | defaults : defaultConfig, 97 | initialize: function(data){ 98 | //Expose options as a scope variable here so we can access it in the ScaleClass 99 | var options = this.options; 100 | 101 | // Save data as a source for updating of values & methods 102 | this.data = data; 103 | 104 | this.ScaleClass = Chart.Scale.extend({ 105 | offsetGridLines : true, 106 | calculateBarX : function(barIndex){ 107 | return this.calculateX(barIndex); 108 | }, 109 | calculateBarY : function(datasets, dsIndex, barIndex, value){ 110 | var offset = 0, 111 | sum = 0; 112 | 113 | for(var i = 0; i < datasets.length; i++) { 114 | sum += datasets[i].bars[barIndex].value; 115 | } 116 | for(i = dsIndex; i < datasets.length; i++) { 117 | if(i === dsIndex && value) { 118 | offset += value; 119 | } else { 120 | offset = +offset + +datasets[i].bars[barIndex].value; 121 | } 122 | } 123 | 124 | if(options.relativeBars) { 125 | offset = offset / sum * 100; 126 | } 127 | 128 | return this.calculateY(offset); 129 | }, 130 | calculateBaseWidth : function(){ 131 | return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing); 132 | }, 133 | calculateBaseHeight : function(){ 134 | return (this.calculateY(1) - this.calculateY(0)); 135 | }, 136 | calculateBarWidth : function(datasetCount){ 137 | //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset 138 | return this.calculateBaseWidth(); 139 | }, 140 | calculateBarHeight : function(datasets, dsIndex, barIndex, value) { 141 | var sum = 0; 142 | 143 | for(var i = 0; i < datasets.length; i++) { 144 | sum += datasets[i].bars[barIndex].value; 145 | } 146 | 147 | if(!value) { 148 | value = datasets[dsIndex].bars[barIndex].value; 149 | } 150 | 151 | if(options.relativeBars) { 152 | value = value / sum * 100; 153 | } 154 | 155 | return this.calculateY(value); 156 | } 157 | }); 158 | 159 | this.datasets = []; 160 | 161 | //Set up tooltip events on the chart 162 | if (this.options.showTooltips){ 163 | helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ 164 | var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : []; 165 | 166 | this.eachBars(function(bar){ 167 | bar.restore(['fillColor', 'strokeColor']); 168 | }); 169 | helpers.each(activeBars, function(activeBar){ 170 | activeBar.fillColor = activeBar.highlightFill; 171 | activeBar.strokeColor = activeBar.highlightStroke; 172 | }); 173 | this.showTooltip(activeBars); 174 | }); 175 | } 176 | 177 | //Declare the extension of the default point, to cater for the options passed in to the constructor 178 | this.BarClass = Chart.Rectangle.extend({ 179 | strokeWidth : this.options.barStrokeWidth, 180 | showStroke : this.options.barShowStroke, 181 | ctx : this.chart.ctx 182 | }); 183 | 184 | //Iterate through each of the datasets, and build this into a property of the chart 185 | helpers.each(data.datasets,function(dataset,datasetIndex){ 186 | 187 | var datasetObject = { 188 | label : dataset.label || null, 189 | fillColor : dataset.fillColor, 190 | strokeColor : dataset.strokeColor, 191 | bars : [] 192 | }; 193 | 194 | this.datasets.push(datasetObject); 195 | 196 | helpers.each(dataset.data,function(dataPoint,index){ 197 | if(!helpers.isNumber(dataPoint)){ 198 | dataPoint = 0; 199 | } 200 | //Add a new point for each piece of data, passing any required data to draw. 201 | //Add 0 as value if !isNumber (e.g. empty values are useful when 0 values should be hidden in tooltip) 202 | datasetObject.bars.push(new this.BarClass({ 203 | value : dataPoint, 204 | label : data.labels[index], 205 | datasetLabel: dataset.label, 206 | strokeColor : dataset.strokeColor, 207 | fillColor : dataset.fillColor, 208 | highlightFill : dataset.highlightFill || dataset.fillColor, 209 | highlightStroke : dataset.highlightStroke || dataset.strokeColor 210 | })); 211 | },this); 212 | 213 | },this); 214 | 215 | this.buildScale(data.labels); 216 | 217 | this.eachBars(function(bar, index, datasetIndex){ 218 | helpers.extend(bar, { 219 | base: this.scale.endPoint, 220 | height: 0, 221 | width : this.scale.calculateBarWidth(this.datasets.length), 222 | x: this.scale.calculateBarX(index), 223 | y: this.scale.endPoint 224 | }); 225 | bar.save(); 226 | }, this); 227 | 228 | this.render(); 229 | }, 230 | showTooltip : function(ChartElements, forceRedraw){ 231 | // Only redraw the chart if we've actually changed what we're hovering on. 232 | if (typeof this.activeElements === 'undefined') this.activeElements = []; 233 | 234 | helpers = Chart.helpers; 235 | 236 | var isChanged = (function(Elements){ 237 | var changed = false; 238 | 239 | if (Elements.length !== this.activeElements.length){ 240 | changed = true; 241 | return changed; 242 | } 243 | 244 | helpers.each(Elements, function(element, index){ 245 | if (element !== this.activeElements[index]){ 246 | changed = true; 247 | } 248 | }, this); 249 | return changed; 250 | }).call(this, ChartElements); 251 | 252 | if (!isChanged && !forceRedraw){ 253 | return; 254 | } 255 | else{ 256 | this.activeElements = ChartElements; 257 | } 258 | this.draw(); 259 | if(this.options.customTooltips){ 260 | this.options.customTooltips(false); 261 | } 262 | if (ChartElements.length > 0){ 263 | // If we have multiple datasets, show a MultiTooltip for all of the data points at that index 264 | if (this.datasets && this.datasets.length > 1) { 265 | var dataArray, 266 | dataIndex; 267 | 268 | for (var i = this.datasets.length - 1; i >= 0; i--) { 269 | dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments; 270 | dataIndex = helpers.indexOf(dataArray, ChartElements[0]); 271 | if (dataIndex !== -1){ 272 | break; 273 | } 274 | } 275 | var tooltipLabels = [], 276 | tooltipColors = [], 277 | medianPosition = (function(index) { 278 | 279 | // Get all the points at that particular index 280 | var Elements = [], 281 | dataCollection, 282 | xPositions = [], 283 | yPositions = [], 284 | xMax, 285 | yMax, 286 | xMin, 287 | yMin; 288 | helpers.each(this.datasets, function(dataset){ 289 | dataCollection = dataset.points || dataset.bars || dataset.segments; 290 | if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){ 291 | Elements.push(dataCollection[dataIndex]); 292 | } 293 | }); 294 | 295 | var total = { 296 | datasetLabel: this.options.totalLabel, 297 | value: 0, 298 | fillColor: this.options.totalColor, 299 | strokeColor: this.options.totalColor 300 | }; 301 | 302 | helpers.each(Elements, function(element) { 303 | if (this.options.tooltipHideZero && element.value === 0) { 304 | return; 305 | } 306 | 307 | xPositions.push(element.x); 308 | yPositions.push(element.y); 309 | 310 | total.value += element.value; 311 | 312 | //Include any colour information about the element(s) 313 | if (element.value !== 0) { 314 | tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element)); 315 | tooltipColors.push({ 316 | fill: element._saved.fillColor || element.fillColor, 317 | stroke: element._saved.strokeColor || element.strokeColor 318 | }); 319 | } 320 | 321 | }, this); 322 | 323 | if (this.options.showTotal) { 324 | tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, total)); 325 | tooltipColors.push({ 326 | fill: total.fillColor, 327 | stroke: total.strokeColor 328 | }); 329 | } 330 | 331 | yMin = helpers.min(yPositions); 332 | yMax = helpers.max(yPositions); 333 | 334 | xMin = helpers.min(xPositions); 335 | xMax = helpers.max(xPositions); 336 | 337 | return { 338 | x: (xMin > this.chart.width/2) ? xMin : xMax, 339 | y: (yMin + yMax)/2 340 | }; 341 | }).call(this, dataIndex); 342 | 343 | new Chart.MultiTooltip({ 344 | x: medianPosition.x, 345 | y: medianPosition.y, 346 | xPadding: this.options.tooltipXPadding, 347 | yPadding: this.options.tooltipYPadding, 348 | xOffset: this.options.tooltipXOffset, 349 | fillColor: this.options.tooltipFillColor, 350 | textColor: this.options.tooltipFontColor, 351 | fontFamily: this.options.tooltipFontFamily, 352 | fontStyle: this.options.tooltipFontStyle, 353 | fontSize: this.options.tooltipFontSize, 354 | titleTextColor: this.options.tooltipTitleFontColor, 355 | titleFontFamily: this.options.tooltipTitleFontFamily, 356 | titleFontStyle: this.options.tooltipTitleFontStyle, 357 | titleFontSize: this.options.tooltipTitleFontSize, 358 | cornerRadius: this.options.tooltipCornerRadius, 359 | labels: tooltipLabels, 360 | legendColors: tooltipColors, 361 | legendColorBackground : this.options.multiTooltipKeyBackground, 362 | title: ChartElements[0].label, 363 | chart: this.chart, 364 | ctx: this.chart.ctx, 365 | custom: this.options.customTooltips 366 | }).draw(); 367 | 368 | } else { 369 | helpers.each(ChartElements, function(Element) { 370 | var tooltipPosition = Element.tooltipPosition(); 371 | new Chart.Tooltip({ 372 | x: Math.round(tooltipPosition.x), 373 | y: Math.round(tooltipPosition.y), 374 | xPadding: this.options.tooltipXPadding, 375 | yPadding: this.options.tooltipYPadding, 376 | fillColor: this.options.tooltipFillColor, 377 | textColor: this.options.tooltipFontColor, 378 | fontFamily: this.options.tooltipFontFamily, 379 | fontStyle: this.options.tooltipFontStyle, 380 | fontSize: this.options.tooltipFontSize, 381 | caretHeight: this.options.tooltipCaretSize, 382 | cornerRadius: this.options.tooltipCornerRadius, 383 | text: helpers.template(this.options.tooltipTemplate, Element), 384 | chart: this.chart, 385 | custom: this.options.customTooltips 386 | }).draw(); 387 | }, this); 388 | } 389 | } 390 | return this; 391 | }, 392 | update : function(){ 393 | 394 | //Iterate through each of the datasets, and build this into a property of the chart 395 | helpers.each(this.datasets,function(dataset,datasetIndex){ 396 | 397 | helpers.extend(this.datasets[datasetIndex], { 398 | label : dataset.label || null, 399 | fillColor : dataset.fillColor, 400 | strokeColor : dataset.strokeColor, 401 | }); 402 | 403 | helpers.each(dataset.data,function(dataPoint,index){ 404 | helpers.extend(this.datasets[datasetIndex].bars[index], { 405 | value : dataPoint, 406 | label : this.data.labels[index], 407 | datasetLabel: dataset.label, 408 | strokeColor : dataset.strokeColor, 409 | fillColor : dataset.fillColor, 410 | highlightFill : dataset.highlightFill || dataset.fillColor, 411 | highlightStroke : dataset.highlightStroke || dataset.strokeColor 412 | }); 413 | },this); 414 | 415 | },this); 416 | 417 | 418 | this.scale.update(); 419 | // Reset any highlight colours before updating. 420 | helpers.each(this.activeElements, function(activeElement){ 421 | activeElement.restore(['fillColor', 'strokeColor']); 422 | }); 423 | 424 | this.eachBars(function(bar){ 425 | bar.save(); 426 | }); 427 | this.render(); 428 | }, 429 | eachBars : function(callback){ 430 | helpers.each(this.datasets,function(dataset, datasetIndex){ 431 | helpers.each(dataset.bars, callback, this, datasetIndex); 432 | },this); 433 | }, 434 | getBarsAtEvent : function(e){ 435 | var barsArray = [], 436 | eventPosition = helpers.getRelativePosition(e), 437 | datasetIterator = function(dataset){ 438 | barsArray.push(dataset.bars[barIndex]); 439 | }, 440 | barIndex; 441 | 442 | for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) { 443 | for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) { 444 | if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){ 445 | helpers.each(this.datasets, datasetIterator); 446 | return barsArray; 447 | } 448 | } 449 | } 450 | 451 | return barsArray; 452 | }, 453 | buildScale : function(labels){ 454 | var self = this; 455 | 456 | var dataTotal = function(){ 457 | var values = []; 458 | helpers.each(self.datasets, function(dataset) { 459 | helpers.each(dataset.bars, function(bar, barIndex) { 460 | if(!values[barIndex]) values[barIndex] = 0; 461 | if(self.options.relativeBars) { 462 | values[barIndex] = 100; 463 | } else { 464 | values[barIndex] = +values[barIndex] + +bar.value; 465 | } 466 | }); 467 | }); 468 | return values; 469 | }; 470 | 471 | var scaleOptions = { 472 | templateString : this.options.scaleLabel, 473 | height : this.chart.height, 474 | width : this.chart.width, 475 | ctx : this.chart.ctx, 476 | textColor : this.options.scaleFontColor, 477 | fontSize : this.options.scaleFontSize, 478 | fontStyle : this.options.scaleFontStyle, 479 | fontFamily : this.options.scaleFontFamily, 480 | valuesCount : labels.length, 481 | beginAtZero : this.options.scaleBeginAtZero, 482 | integersOnly : this.options.scaleIntegersOnly, 483 | calculateYRange: function(currentHeight){ 484 | var updatedRanges = helpers.calculateScaleRange( 485 | dataTotal(), 486 | currentHeight, 487 | this.fontSize, 488 | this.beginAtZero, 489 | this.integersOnly 490 | ); 491 | helpers.extend(this, updatedRanges); 492 | }, 493 | xLabels : this.options.xLabels || labels, 494 | font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), 495 | lineWidth : this.options.scaleLineWidth, 496 | lineColor : this.options.scaleLineColor, 497 | gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, 498 | gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", 499 | showHorizontalLines : this.options.scaleShowHorizontalLines, 500 | showVerticalLines : this.options.scaleShowVerticalLines, 501 | padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0, 502 | showLabels : this.options.scaleShowLabels, 503 | display : this.options.showScale 504 | }; 505 | 506 | if (this.options.scaleOverride){ 507 | helpers.extend(scaleOptions, { 508 | calculateYRange: helpers.noop, 509 | steps: this.options.scaleSteps, 510 | stepValue: this.options.scaleStepWidth, 511 | min: this.options.scaleStartValue, 512 | max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) 513 | }); 514 | } 515 | 516 | this.scale = new this.ScaleClass(scaleOptions); 517 | }, 518 | addData : function(valuesArray,label){ 519 | //Map the values array for each of the datasets 520 | helpers.each(valuesArray,function(value,datasetIndex){ 521 | if (helpers.isNumber(value)){ 522 | //Add a new point for each piece of data, passing any required data to draw. 523 | //Add 0 as value if !isNumber (e.g. empty values are useful when 0 values should be hidden in tooltip) 524 | this.datasets[datasetIndex].bars.push(new this.BarClass({ 525 | value : helpers.isNumber(value)?value:0, 526 | label : label, 527 | x: this.scale.calculateBarX(this.scale.valuesCount+1), 528 | y: this.scale.endPoint, 529 | width : this.scale.calculateBarWidth(this.datasets.length), 530 | base : this.scale.endPoint, 531 | strokeColor : this.datasets[datasetIndex].strokeColor, 532 | fillColor : this.datasets[datasetIndex].fillColor 533 | })); 534 | } 535 | },this); 536 | 537 | this.scale.addXLabel(label); 538 | //Then re-render the chart. 539 | this.update(); 540 | }, 541 | removeData : function(){ 542 | this.scale.removeXLabel(); 543 | //Then re-render the chart. 544 | helpers.each(this.datasets,function(dataset){ 545 | dataset.bars.shift(); 546 | },this); 547 | this.update(); 548 | }, 549 | reflow : function(){ 550 | helpers.extend(this.BarClass.prototype,{ 551 | y: this.scale.endPoint, 552 | base : this.scale.endPoint 553 | }); 554 | var newScaleProps = helpers.extend({ 555 | height : this.chart.height, 556 | width : this.chart.width 557 | }); 558 | this.scale.update(newScaleProps); 559 | }, 560 | draw : function(ease){ 561 | var easingDecimal = ease || 1; 562 | this.clear(); 563 | 564 | var ctx = this.chart.ctx; 565 | 566 | this.scale.draw(easingDecimal); 567 | 568 | drawAnnotations(this); 569 | 570 | //Draw all the bars for each dataset 571 | helpers.each(this.datasets,function(dataset,datasetIndex){ 572 | helpers.each(dataset.bars,function(bar,index){ 573 | var y = this.scale.calculateBarY(this.datasets, datasetIndex, index, bar.value), 574 | height = this.scale.calculateBarHeight(this.datasets, datasetIndex, index, bar.value); 575 | 576 | //Transition then draw 577 | if(bar.value > 0) { 578 | bar.transition({ 579 | base : this.scale.endPoint - (Math.abs(height) - Math.abs(y)), 580 | x : this.scale.calculateBarX(index), 581 | y : Math.abs(y), 582 | height : Math.abs(height), 583 | width : this.scale.calculateBarWidth(this.datasets.length) 584 | }, easingDecimal).draw(); 585 | } 586 | },this); 587 | },this); 588 | } 589 | }); 590 | })); 591 | --------------------------------------------------------------------------------