Chart.js/src/Chart.Bar.js
2015-05-15 00:04:42 -06:00

379 lines
12 KiB
JavaScript

(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,
//Number - Pixel width of the bar border
barBorderWidth : 2,
//Number - Spacing between each of the X value sets
barValueSpacing : 5,
//Number - Spacing between data sets within X values
barDatasetSpacing : 1,
//String / Boolean - Hover mode for events.
hoverMode : 'single', // 'label', 'dataset', 'false'
//Function - Custom hover handler
onHover : null,
//Function - Custom hover handler
hoverAnimationDuration : 400,
//String - A legend template
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].backgroundColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
};
Chart.Type.extend({
name: "Bar",
defaults : defaultConfig,
initialize: function(data){
// Save data as a source for updating of values & methods
this.data = 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, elementIndex){
//Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
var xWidth = this.calculateBaseWidth(),
xAbsolute = this.calculateX(elementIndex) - (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);
}
});
// Events
helpers.bindEvents(this, this.options.tooltipEvents, this.onHover);
//Declare the extension of the default point, to cater for the options passed in to the constructor
this.BarClass = Chart.Rectangle.extend({
ctx : this.chart.ctx,
});
// Build Scale
this.buildScale(data.labels);
//Create a new bar for each piece of data
helpers.each(this.data.datasets,function(dataset,datasetIndex){
dataset.metaData = [];
helpers.each(dataset.data,function(dataPoint,index){
dataset.metaData.push(new this.BarClass());
},this);
},this);
// Set defaults for bars
this.eachElement(function(bar, index, datasetIndex){
helpers.extend(bar, {
width : this.scale.calculateBarWidth(this.data.datasets.length),
x: this.scale.calculateBarX(this.data.datasets.length, datasetIndex, index),
y: this.calculateBaseY(),
_datasetIndex: datasetIndex,
_index: index,
});
// Copy to view model
bar.save();
}, this);
// Create tooltip instance exclusively for this chart with some defaults.
this.tooltip = new Chart.Tooltip({
_chart: this.chart,
_data: this.data,
_options: this.options,
}, this);
// Update the chart with the latest data.
this.update();
},
onHover: function(e){
// If exiting chart
if(e.type == 'mouseout'){
return this;
}
this.lastActive = this.lastActive || [];
// Find Active Elements
this.active = function(){
switch(this.options.hoverMode){
case 'single':
return this.getElementAtEvent(e);
case 'label':
return this.getElementsAtEvent(e);
case 'dataset':
return this.getDatasetAtEvent(e);
default:
return e;
}
}.call(this);
// On Hover hook
if(this.options.onHover){
this.options.onHover.call(this, this.active);
}
// Remove styling for last active (even if it may still be active)
if(this.lastActive.length){
switch(this.options.hoverMode){
case 'single':
this.lastActive[0].backgroundColor = this.data.datasets[this.lastActive[0]._datasetIndex].backgroundColor;
this.lastActive[0].borderColor = this.data.datasets[this.lastActive[0]._datasetIndex].borderColor;
this.lastActive[0].borderWidth = 0;
break;
case 'label':
for (var i = 0; i < this.lastActive.length; i++) {
this.lastActive[i].backgroundColor = this.data.datasets[this.lastActive[i]._datasetIndex].backgroundColor;
this.lastActive[i].borderColor = this.data.datasets[this.lastActive[i]._datasetIndex].borderColor;
this.lastActive[i].borderWidth = 0;
}
break;
case 'dataset':
break;
default:
// Don't change anything
}
}
// Built in hover styling
if(this.active.length && this.options.hoverMode){
switch(this.options.hoverMode){
case 'single':
this.active[0].backgroundColor = this.data.datasets[this.active[0]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[0].backgroundColor).saturate(0.5).darken(0.35).rgbString();
this.active[0].borderColor = this.data.datasets[this.active[0]._datasetIndex].hoverBorderColor || helpers.color(this.active[0].borderColor).saturate(0.5).darken(0.35).rgbString();
break;
case 'label':
for (var i = 0; i < this.active.length; i++) {
this.active[i].backgroundColor = this.data.datasets[this.active[i]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[i].backgroundColor).saturate(0.5).darken(0.35).rgbString();
this.active[i].borderColor = this.data.datasets[this.active[i]._datasetIndex].hoverBorderColor || helpers.color(this.active[i].borderColor).saturate(0.5).darken(0.35).rgbString();
}
break;
case 'dataset':
break;
default:
// Don't change anything
}
}
// Built in Tooltips
if(this.options.showTooltips){
// The usual updates
this.tooltip.initialize();
// Active
if(this.active.length){
helpers.extend(this.tooltip, {
opacity: 1,
_active: this.active,
});
this.tooltip.update();
}
else{
// Inactive
helpers.extend(this.tooltip, {
opacity: 0,
});
}
}
// Hover animations
if(!this.animating){
var changed;
helpers.each(this.active, function(element, index){
if (element !== this.lastActive[index]){
changed = true;
}
}, this);
// If entering, leaving, or changing elements, animate the change via pivot
if ((!this.lastActive.length && this.active.length) ||
(this.lastActive.length && !this.active.length)||
(this.lastActive.length && this.active.length && changed)){
this.tooltip.pivot();
this.render(this.options.hoverAnimationDuration);
}
}
// Remember Last Active
this.lastActive = this.active;
return this;
},
// Calculate the base point for the bar.
calculateBaseY: function() {
var base = this.scale.endPoint;
if (this.scale.beginAtZero || ((this.scale.min <= 0 && this.scale.max >= 0) || (this.scale.min >= 0 && this.scale.max <= 0)))
{
base = this.scale.calculateY(0);
base += this.options.scaleGridLineWidth;
}
else if (this.scale.min < 0 && this.scale.max < 0)
{
// All values are negative. Use the top as the base
base = this.scale.startPoint;
}
return base;
},
update : function(){
this.scale.update();
this.eachElement(function(bar, index, datasetIndex){
helpers.extend(bar, {
width : this.scale.calculateBarWidth(this.data.datasets.length),
x: this.scale.calculateBarX(this.data.datasets.length, datasetIndex, index),
y: this.scale.calculateY(this.data.datasets[datasetIndex].data[index]),
value : this.data.datasets[datasetIndex].data[index],
label : this.data.labels[index],
datasetLabel: this.data.datasets[datasetIndex].label,
borderColor : this.data.datasets[datasetIndex].borderColor,
borderWidth : this.data.datasets[datasetIndex].borderWidth,
backgroundColor : this.data.datasets[datasetIndex].backgroundColor,
_datasetIndex: datasetIndex,
_index: index,
_start: undefined
});
}, this);
this.render();
},
buildScale : function(labels){
var self = this;
var dataTotal = function(){
var values = [];
self.eachValue(function(value){
values.push(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.borderWidth,
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);
},
// This should be incorportated into the init as something like a default value. "Reflow" seems like a weird word for a fredraw function
redraw : function(){
var base = this.calculateBaseY();
this.eachElement(function(element, index, datasetIndex){
helpers.extend(element,{
y: base,
base : base
});
});
render();
},
draw : function(ease){
var easingDecimal = ease || 1;
this.clear();
this.scale.draw(easingDecimal);
//Draw all the bars for each dataset
this.eachElement(function(bar, index, datasetIndex){
if (bar.hasValue()){
// Update the bar basepoint
bar.base = this.calculateBaseY();
//Transition
bar.transition(easingDecimal).draw();
}
}, this);
// Finally draw the tooltip
this.tooltip.transition(easingDecimal).draw();
}
});
}).call(this);