diff --git a/vendor/assets/javascripts/Chart.js b/vendor/assets/javascripts/Chart.js
index cd79b6d..4826159 100644
--- a/vendor/assets/javascripts/Chart.js
+++ b/vendor/assets/javascripts/Chart.js
@@ -3079,7 +3079,7 @@
helpers.each(this.segments,function(segment){
segment.save();
});
-
+
this.reflow();
this.render();
},
@@ -3474,4 +3474,502 @@
+}).call(this);
+
+
+// Horizontal Bar Chart Type
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+
+ var defaultConfig = {
+ //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
+ scaleBeginAtZero : true,
+
+ //Boolean - Whether grid lines are shown across the chart
+ scaleShowGridLines : true,
+
+ //String - Colour of the grid lines
+ scaleGridLineColor : "rgba(0,0,0,.05)",
+
+ //Number - Width of the grid lines
+ scaleGridLineWidth : 1,
+
+ //Boolean - Whether to show horizontal lines (except X axis)
+ scaleShowHorizontalLines: true,
+
+ //Boolean - Whether to show vertical lines (except Y axis)
+ scaleShowVerticalLines: true,
+
+ //Boolean - If there is a stroke on each bar
+ barShowStroke : true,
+
+ //Number - Pixel width of the bar stroke
+ barStrokeWidth : 2,
+
+ //Number - Spacing between each of the X value sets
+ barValueSpacing : 5,
+
+ //Number - Spacing between data sets within X values
+ barDatasetSpacing : 1,
+
+ //String - A legend template
+ legendTemplate : "
-legend\"><% for (var i=0; i- \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
<%}%>
"
+
+ };
+
+ Chart.HorizontalRectangle = Chart.Element.extend({
+ draw : function(){
+ var ctx = this.ctx,
+ halfHeight = this.height/2,
+ top = this.y - halfHeight,
+ bottom = this.y + halfHeight,
+ right = this.left - (this.left - this.x),
+ halfStroke = this.strokeWidth / 2;
+
+ // Canvas doesn't allow us to stroke inside the width so we can
+ // adjust the sizes to fit if we're setting a stroke on the line
+ if (this.showStroke){
+ top += halfStroke;
+ bottom -= halfStroke;
+ right += halfStroke;
+ }
+
+ ctx.beginPath();
+
+ ctx.fillStyle = this.fillColor;
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ // It'd be nice to keep this class totally generic to any rectangle
+ // and simply specify which border to miss out.
+ ctx.moveTo(this.left, top);
+ ctx.lineTo(right, top);
+ ctx.lineTo(right, bottom);
+ ctx.lineTo(this.left, bottom);
+ ctx.fill();
+ if (this.showStroke){
+ ctx.stroke();
+ }
+ },
+ inRange : function(chartX,chartY){
+ return (chartX >= this.left && chartX <= this.x && chartY >= (this.y - this.height/2) && chartY <= (this.y + this.height/2));
+ }
+ });
+
+ Chart.Type.extend({
+ name: "HorizontalBar",
+ defaults : defaultConfig,
+ initialize: function(data){
+
+ //Expose options as a scope variable here so we can access it in the ScaleClass
+ var options = this.options;
+
+ this.ScaleClass = Chart.Scale.extend({
+ offsetGridLines : true,
+ calculateBarX : function(datasetCount, datasetIndex, barIndex){
+ //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
+ var xWidth = this.calculateBaseWidth(),
+ xAbsolute = this.calculateX(barIndex) - (xWidth/2),
+ barWidth = this.calculateBarWidth(datasetCount);
+
+ return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
+ },
+ calculateBaseWidth : function(){
+ return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
+ },
+ calculateBarWidth : function(datasetCount){
+ //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
+ var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
+
+ return (baseWidth / datasetCount);
+ },
+
+ calculateBaseHeight : function(){
+ return ((this.endPoint - this.startPoint) / this.yLabels.length) - (2*options.barValueSpacing);
+ },
+ calculateBarHeight : function(datasetCount){
+ //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
+ var baseHeight = this.calculateBaseHeight() - ((datasetCount) * options.barDatasetSpacing);
+
+ return (baseHeight / datasetCount);
+ },
+
+ calculateXInvertXY : function(value) {
+ var scalingFactor = (this.width - Math.round(this.xScalePaddingLeft) - this.xScalePaddingRight) / (this.max - this.min);
+ return Math.round(this.xScalePaddingLeft) + (scalingFactor * (value - this.min));
+ },
+
+ calculateYInvertXY : function(index){
+ return index * ((this.startPoint - this.endPoint) / (this.yLabels.length));
+ },
+
+ calculateBarY : function(datasetCount, datasetIndex, barIndex){
+ //Reusable method for calculating the yPosition of a given bar based on datasetIndex & height of the bar
+ var yHeight = this.calculateBaseHeight(),
+ yAbsolute = (this.endPoint + this.calculateYInvertXY(barIndex) - (yHeight / 2)) - 5,
+ barHeight = this.calculateBarHeight(datasetCount);
+ if (datasetCount > 1) yAbsolute = yAbsolute + (barHeight * (datasetIndex - 1)) - (datasetIndex * options.barDatasetSpacing) + barHeight/2;
+ return yAbsolute;
+ },
+
+ buildCalculatedLabels : function() {
+ this.calculatedLabels = [];
+
+ var stepDecimalPlaces = helpers.getDecimalPlaces(this.stepValue);
+
+ for (var i=0; i<=this.steps; i++){
+ this.calculatedLabels.push(helpers.template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
+ }
+ },
+
+ buildYLabels : function(){
+ this.buildYLabelCounter = (typeof this.buildYLabelCounter === 'undefined') ? 0 : this.buildYLabelCounter + 1;
+ this.buildCalculatedLabels();
+ if(this.buildYLabelCounter === 0) this.yLabels = this.xLabels;
+ this.xLabels = this.calculatedLabels;
+ this.yLabelWidth = (this.display && this.showLabels) ? helpers.longestText(this.ctx,this.font,this.yLabels) : 0;
+ },
+
+ calculateX : function(index){
+ var isRotated = (this.xLabelRotation > 0),
+ innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
+ valueWidth = innerWidth/(this.steps - ((this.offsetGridLines) ? 0 : 1)),
+ valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
+
+ if (this.offsetGridLines){
+ valueOffset += (valueWidth/2);
+ }
+
+ return Math.round(valueOffset);
+ },
+
+ draw : function(){
+ var ctx = this.ctx,
+ yLabelGap = (this.endPoint - this.startPoint) / this.yLabels.length,
+ xStart = Math.round(this.xScalePaddingLeft);
+ if (this.display){
+ ctx.fillStyle = this.textColor;
+ ctx.font = this.font;
+ helpers.each(this.yLabels,function(labelString,index){
+ var yLabelCenter = this.endPoint - (yLabelGap * index),
+ linePositionY = Math.round(yLabelCenter);
+
+ yLabelCenter -= yLabelGap / 2;
+
+ ctx.textAlign = "right";
+ ctx.textBaseline = "middle";
+ if (this.showLabels){
+ ctx.fillText(labelString,xStart - 10,yLabelCenter);
+ }
+ ctx.beginPath();
+ if (index > 0){
+ // This is a grid line in the centre, so drop that
+ ctx.lineWidth = this.gridLineWidth;
+ ctx.strokeStyle = this.gridLineColor;
+ } else {
+ // This is the first line on the scale
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ }
+
+ linePositionY += helpers.aliasPixel(ctx.lineWidth);
+
+ ctx.moveTo(xStart, linePositionY);
+ ctx.lineTo(this.width, linePositionY);
+ ctx.stroke();
+ ctx.closePath();
+
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ ctx.beginPath();
+ ctx.moveTo(xStart - 5, linePositionY);
+ ctx.lineTo(xStart, linePositionY);
+ ctx.stroke();
+ ctx.closePath();
+
+ },this);
+
+ helpers.each(this.xLabels,function(label,index){
+ var width = this.calculateX(1) - this.calculateX(0);
+ var xPos = this.calculateX(index) + helpers.aliasPixel(this.lineWidth) - (width / 2),
+ // Check to see if line/bar here and decide where to place the line
+ linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + helpers.aliasPixel(this.lineWidth),
+ isRotated = (this.xLabelRotation > 0);
+
+ ctx.beginPath();
+
+ if (index > 0){
+ // This is a grid line in the centre, so drop that
+ ctx.lineWidth = this.gridLineWidth;
+ ctx.strokeStyle = this.gridLineColor;
+ } else {
+ // This is the first line on the scale
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ }
+ ctx.moveTo(linePos,this.endPoint);
+ ctx.lineTo(linePos,this.startPoint - 3);
+ ctx.stroke();
+ ctx.closePath();
+
+
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+
+
+ // Small lines at the bottom of the base grid line
+ ctx.beginPath();
+ ctx.moveTo(linePos,this.endPoint);
+ ctx.lineTo(linePos,this.endPoint + 5);
+ ctx.stroke();
+ ctx.closePath();
+
+ ctx.save();
+ ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
+ ctx.rotate(helpers.radians(this.xLabelRotation)*-1);
+ ctx.font = this.font;
+ ctx.textAlign = (isRotated) ? "right" : "center";
+ ctx.textBaseline = (isRotated) ? "middle" : "top";
+ ctx.fillText(label, 0, 0);
+ ctx.restore();
+ },this);
+
+ }
+ }
+
+ });
+
+ this.datasets = [];
+
+ //Set up tooltip events on the chart
+ if (this.options.showTooltips){
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+ var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
+
+ this.eachBars(function(bar){
+ bar.restore(['fillColor', 'strokeColor']);
+ });
+ helpers.each(activeBars, function(activeBar){
+ activeBar.fillColor = activeBar.highlightFill;
+ activeBar.strokeColor = activeBar.highlightStroke;
+ });
+ this.showTooltip(activeBars);
+ });
+ }
+
+ //Declare the extension of the default point, to cater for the options passed in to the constructor
+ this.BarClass = Chart.HorizontalRectangle.extend({
+ strokeWidth : this.options.barStrokeWidth,
+ showStroke : this.options.barShowStroke,
+ ctx : this.chart.ctx
+ });
+
+ //Iterate through each of the datasets, and build this into a property of the chart
+ helpers.each(data.datasets,function(dataset,datasetIndex){
+
+ var datasetObject = {
+ label : dataset.label || null,
+ fillColor : dataset.fillColor,
+ strokeColor : dataset.strokeColor,
+ bars : []
+ };
+
+ this.datasets.push(datasetObject);
+
+ helpers.each(dataset.data,function(dataPoint,index){
+ //Add a new point for each piece of data, passing any required data to draw.
+ datasetObject.bars.push(new this.BarClass({
+ value : dataPoint,
+ label : data.labels[index],
+ datasetLabel: dataset.label,
+ strokeColor : dataset.strokeColor,
+ fillColor : dataset.fillColor,
+ highlightFill : dataset.highlightFill || dataset.fillColor,
+ highlightStroke : dataset.highlightStroke || dataset.strokeColor
+ }));
+ },this);
+
+ },this);
+
+ this.buildScale(data.labels);
+
+ this.BarClass.prototype.left = Math.round(this.scale.xScalePaddingLeft);
+
+ this.eachBars(function(bar, index, datasetIndex){
+ helpers.extend(bar, {
+ x: Math.round(this.scale.xScalePaddingLeft),
+ y : this.scale.calculateBarY(this.datasets.length, datasetIndex, index),
+ height : this.scale.calculateBarHeight(this.datasets.length)
+ });
+ bar.save();
+ }, this);
+
+ this.render();
+ },
+ update : function(){
+ this.scale.update();
+ // Reset any highlight colours before updating.
+ helpers.each(this.activeElements, function(activeElement){
+ activeElement.restore(['fillColor', 'strokeColor']);
+ });
+
+ this.eachBars(function(bar){
+ bar.save();
+ });
+ this.render();
+ },
+ eachBars : function(callback){
+ helpers.each(this.datasets,function(dataset, datasetIndex){
+ helpers.each(dataset.bars, callback, this, datasetIndex);
+ },this);
+ },
+ getBarsAtEvent : function(e){
+ var barsArray = [],
+ eventPosition = helpers.getRelativePosition(e),
+ datasetIterator = function(dataset){
+ barsArray.push(dataset.bars[barIndex]);
+ },
+ barIndex;
+
+ for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
+ for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
+ if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
+ helpers.each(this.datasets, datasetIterator);
+ return barsArray;
+ }
+ }
+ }
+
+ return barsArray;
+ },
+ buildScale : function(labels){
+ var self = this;
+
+ var dataTotal = function(){
+ var values = [];
+ self.eachBars(function(bar){
+ values.push(bar.value);
+ });
+ return values;
+ };
+
+ var scaleOptions = {
+ templateString : this.options.scaleLabel,
+ height : this.chart.height,
+ width : this.chart.width,
+ ctx : this.chart.ctx,
+ textColor : this.options.scaleFontColor,
+ fontSize : this.options.scaleFontSize,
+ fontStyle : this.options.scaleFontStyle,
+ fontFamily : this.options.scaleFontFamily,
+ valuesCount : labels.length,
+ beginAtZero : this.options.scaleBeginAtZero,
+ integersOnly : this.options.scaleIntegersOnly,
+ calculateYRange: function(currentHeight){
+ var updatedRanges = helpers.calculateScaleRange(
+ dataTotal(),
+ currentHeight,
+ this.fontSize,
+ this.beginAtZero,
+ this.integersOnly
+ );
+ helpers.extend(this, updatedRanges);
+ },
+ xLabels : labels,
+ font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
+ lineWidth : this.options.scaleLineWidth,
+ lineColor : this.options.scaleLineColor,
+ showHorizontalLines : this.options.scaleShowHorizontalLines,
+ showVerticalLines : this.options.scaleShowVerticalLines,
+ gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
+ gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
+ padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
+ showLabels : this.options.scaleShowLabels,
+ display : this.options.showScale
+ };
+
+ if (this.options.scaleOverride){
+ helpers.extend(scaleOptions, {
+ calculateYRange: helpers.noop,
+ steps: this.options.scaleSteps,
+ stepValue: this.options.scaleStepWidth,
+ min: this.options.scaleStartValue,
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+ });
+ }
+
+ this.scale = new this.ScaleClass(scaleOptions);
+ },
+ addData : function(valuesArray,label){
+ //Map the values array for each of the datasets
+ helpers.each(valuesArray,function(value,datasetIndex){
+ //Add a new point for each piece of data, passing any required data to draw.
+ this.datasets[datasetIndex].bars.push(new this.BarClass({
+ value : value,
+ label : label,
+ x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
+ y: this.scale.endPoint,
+ width : this.scale.calculateBarWidth(this.datasets.length),
+ base : this.scale.endPoint,
+ strokeColor : this.datasets[datasetIndex].strokeColor,
+ fillColor : this.datasets[datasetIndex].fillColor
+ }));
+ },this);
+
+ this.scale.addXLabel(label);
+ //Then re-render the chart.
+ this.update();
+ },
+ removeData : function(){
+ this.scale.removeXLabel();
+ //Then re-render the chart.
+ helpers.each(this.datasets,function(dataset){
+ dataset.bars.shift();
+ },this);
+ this.update();
+ },
+ reflow : function(){
+ helpers.extend(this.BarClass.prototype,{
+ y: this.scale.endPoint,
+ base : this.scale.endPoint
+ });
+ var newScaleProps = helpers.extend({
+ height : this.chart.height,
+ width : this.chart.width
+ });
+
+ this.scale.update(newScaleProps);
+ },
+ draw : function(ease){
+ var easingDecimal = ease || 1;
+ this.clear();
+
+ var ctx = this.chart.ctx;
+
+ this.scale.draw(easingDecimal);
+
+ //Draw all the bars for each dataset
+ helpers.each(this.datasets,function(dataset,datasetIndex){
+ helpers.each(dataset.bars,function(bar,index){
+ if (bar.hasValue()){
+ bar.left = Math.round(this.scale.xScalePaddingLeft);
+ //Transition then draw
+ bar.transition({
+ x : this.scale.calculateXInvertXY(bar.value),
+ y : this.scale.calculateBarY(this.datasets.length, datasetIndex, index),
+ height : this.scale.calculateBarHeight(this.datasets.length)
+ }, easingDecimal).draw();
+ }
+ },this);
+
+ },this);
+ }
+ });
+
+
}).call(this);