1
0
mirror of https://github.com/e107inc/e107.git synced 2025-02-13 11:04:38 +01:00
php-e107/e107_plugins/log/js/awesomechart.js

1404 lines
47 KiB
JavaScript

/*
* Copyright 2011 Georgios Migdos
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Array.prototype.numericSortReverse = function(data){
this.sort(function(a, b){
return data[b] - data[a];
});
}
Array.prototype.max = function() {
var max = this[0];
var len = this.length;
for (var i = 1; i < len; i++){
if (this[i] > max){
max = this[i];
}
}
return max;
}
Array.prototype.min = function() {
var min = this[0];
var len = this.length;
for (var i = 1; i < len; i++){
if (this[i] < min){
min = this[i];
}
}
return min;
}
function AwesomeChart(canvasElementId){
var canvas = (typeof canvasElementId === 'string') ? document.getElementById(canvasElementId) : canvasElementId;
this.ctx = canvas.getContext('2d');
this.width = this.ctx.canvas.width;
this.height = this.ctx.canvas.height;
this.numberOfDecimals = 0;
this.proportionalSizes = true;
this.widthSizeFactor = this.width/400;
this.heightSizeFactor = this.height/400;
this.chartType = 'bar';
this.randomColors = false;
this.animate = false;
this.animationFrames = 60;
this.marginTop = 10;
this.marginBottom = 10;
this.marginLeft = 10;
this.marginRight = 10;
this.labelMargin = 10;
this.dataValueMargin = 20;
this.titleMargin = 10;
this.yAxisLabelMargin = 5;
this.data = new Array();
this.labels = new Array();
this.colors = new Array();
this.title = null;
this.backgroundFillStyle = 'rgba(255,255,255,0)';
this.borderStrokeStyle = 'rgba(255,255,255,0)';
this.borderWidth = 1.0;
this.labelFillStyle = 'rgb(220, 36, 0)';
this.labelFont = 'sans-serif';
this.labelFontHeight = 12;
this.labelFontStyle = '';
this.dataValueFillStyle = '#333';
this.dataValueFont = 'sans-serif';
this.dataValueFontHeight = 15;
this.dataValueFontStyle = '';
this.titleFillStyle = '#333';
this.titleFont = 'sans-serif';
this.titleFontHeight = 16;
this.titleFontStyle = 'bold';
this.yAxisLabelFillStyle = '#333';
this.yAxisLabelFont = 'sans-serif';
this.yAxisLabelFontHeight = 10;
this.yAxisLabelFontStyle = '';
var lingrad = this.ctx.createLinearGradient(0,0,0,this.height);
lingrad.addColorStop(0.2, '#fdfdfd');
lingrad.addColorStop(0.8, '#ededed');
this.chartBackgroundFillStyle = lingrad;
this.chartBorderStrokeStyle = '#999';
this.chartBorderLineWidth = 1;
this.chartHorizontalLineStrokeStyle = '#999';
this.chartHorizontalLineWidth = 1;
this.chartVerticalLineStrokeStyle = '#999';
this.chartVerticalLineWidth = 1;
this.chartMarkerSize = 5;
this.chartPointRadius = 4;
this.chartPointFillStyle = 'rgb(150, 36, 0)';
this.chartLineStrokeStyle = 'rgba(150, 36, 0, 0.5)';
this.chartLineWidth = 2;
this.barFillStyle = 'rgb(220, 36, 0)';
this.barStrokeStyle = '#fff';
this.barBorderWidth = 2.0;
this.barShadowColor = 'rgba(0, 0, 0, 0.5)';
this.barShadowBlur = 5;
this.barShadowOffsetX = 3.0;
this.barShadowOffsetY = 0.0;
this.barHGap = 20;
this.barVGap = 20;
this.explosionOffset = 20;
this.pieFillStyle = 'rgb(220, 36, 0)';
this.pieStrokeStyle = '#fff';
this.pieBorderWidth = 2.0;
this.pieShadowColor = 'rgba(0, 0, 0, 0.5)';
this.pieShadowBlur = 5;
this.pieShadowOffsetX = 3.0;
this.pieShadowOffsetY = 0.0;
this.pieStart = 0;
this.pieTotal = null;
this.generateRandomColor = function(){
var rgb = new Array();
for(var i=0; i<3; i++){
rgb.push(Math.ceil(Math.random()*150 + 50));
}
return 'rgb('+rgb.join(",")+')';
}
/*Set the chart's data in the format:
*
* {
* "label-1": data-value-1,
* "label-2": data-value-2,
* "label-3": data-value-3,
* ....
* "label-N": data-value-N,
* }
*
*/
this.setChartDataFromJSON = function(jsonObj){
for(var p in jsonObj){
this.labels.push(p);
this.data.push(jsonObj[p]);
}
}
this.draw = function(){
var context = this.ctx;
context.lineCap = 'round';
var minFactor = Math.min(this.widthSizeFactor, this.heightSizeFactor);
if(this.proportionalSizes){
this.labelMargin = this.labelMargin * this.heightSizeFactor;
this.dataValueMargin = this.dataValueMargin * this.heightSizeFactor;
this.titleMargin = this.titleMargin * this.heightSizeFactor;
this.yAxisLabelMargin = this.yAxisLabelMargin * this.heightSizeFactor;
this.labelFontHeight = this.labelFontHeight * minFactor;
this.dataValueFontHeight = this.dataValueFontHeight * minFactor;
this.titleFontHeight = this.titleFontHeight * minFactor;
this.yAxisLabelFontHeight = this.yAxisLabelFontHeight * minFactor;
this.barHGap = this.barHGap * this.widthSizeFactor;
this.barVGap = this.barHGap * this.heightSizeFactor;
this.explosionOffset = this.explosionOffset * minFactor;
}
if(this.randomColors){
for(var i=0; i<this.data.length; i++){
if(!this.colors[i]){
this.colors[i] = this.generateRandomColor();
}
}
}
if(this.chartType == "pie"){
if(this.animate){
this.animatePieChart("pie");
}else{
this.drawPieChart(false);
}
}else if( (this.chartType == "ring") || (this.chartType == "doughnut")){
if(this.animate){
this.animatePieChart("ring");
}else{
this.drawPieChart(true);
}
}else if(this.chartType == "exploded pie"){
if(this.animate){
this.animatePieChart("exploded");
}else{
this.drawExplodedPieChart();
}
}else if(this.chartType == "horizontal bars"){
if(this.animate) {
this.animateVerticalBarChart();
}else{
this.drawVerticalBarChart();
}
}else if(this.chartType == "pareto"){
this.drawParetoChart();
}else{
if(this.animate) {
this.animateBarChart();
}else{
this.drawBarChart();
}
}
this.drawTitleAndBorders();
}
this.drawTitleAndBorders = function() {
var context = this.ctx;
if(this.title!=null){
//Draw the title:
context.font = this.titleFontStyle + ' ' + this.titleFontHeight + 'px '+ this.titleFont;
context.fillStyle = this.titleFillStyle;
context.textAlign = 'center';
context.textBaseline = 'bottom';
context.fillText(this.title, this.width/2, this.marginTop+this.titleFontHeight, this.width-10);
}
//Draw the outer border:
context.lineWidth = this.borderWidth;
context.strokeStyle = this.borderStrokeStyle;
context.strokeRect(0, 0, this.width, this.height);
context.globalCompositeOperation = 'destination-over';
//Fill the background:
context.fillStyle = this.backgroundFillStyle;
context.fillRect(0, 0, this.width, this.height);
context.globalCompositeOperation = 'source-over';
}
this.drawBarChart = function(){
var context = this.ctx;
//Calculate bar size:
var n = this.data.length;
var maxData = this.data.max();
var minData = this.data.min();
var barWidth = (this.width - this.marginLeft
- this.marginRight - (n-1) * this.barHGap) / n;
var barMaxTopY = this.marginTop + this.labelMargin + this.labelFontHeight + this.dataValueMargin + this.dataValueFontHeight;
var barMinTopY = this.height - this.marginBottom;
if(this.title!=null){
barMaxTopY += this.titleFontHeight + this.titleMargin;
}
var barBottomY = this.height - this.marginBottom;
if(minData<0){
barMinTopY = this.height - this.marginBottom - this.labelMargin - this.labelFontHeight - this.dataValueMargin - this.dataValueFontHeight;
barBottomY = barMinTopY + ((this.height - this.marginBottom - barMaxTopY - this.labelMargin - this.labelFontHeight - this.dataValueMargin - this.dataValueFontHeight) * minData) / (Math.abs(minData)+maxData);
}
var maxBarHeight = Math.max(Math.abs(barBottomY - barMaxTopY), Math.abs(barBottomY - barMinTopY));
var maxBarAbsData = Math.max(Math.abs(minData), Math.abs(maxData));
var x = this.marginLeft;
var y = barBottomY;
var barHeight = 0;
var di = 0;
for(var i=0; i<this.data.length; i++){
di = this.data[i];
barHeight = di * maxBarHeight / maxBarAbsData;
//Draw the bar:
if(this.colors[i]){
context.fillStyle = this.colors[i];
}else{
context.fillStyle = this.barFillStyle;
}
context.strokeStyle = this.barStrokeStyle;
context.lineWidth = this.barBorderWidth;
context.beginPath();
context.moveTo(x, y);
context.lineTo(x, y - barHeight);
context.lineTo(x + barWidth, y - barHeight);
context.lineTo(x + barWidth, y);
context.save();
context.shadowOffsetX = this.barShadowOffsetX;
context.shadowOffsetY = this.barShadowOffsetY;
context.shadowBlur = this.barShadowBlur;
context.shadowColor = this.barShadowColor;
context.fill();
context.restore();
context.stroke();
//Draw the label:
context.font = this.labelFontStyle + ' ' + this.labelFontHeight + 'px '+ this.labelFont;
if(this.colors[i]){
context.fillStyle = this.colors[i];
}else{
context.fillStyle = this.labelFillStyle;
}
context.textAlign = 'center';
if(this.labels[i]){
if(di>=0){
context.textBaseline = 'bottom';
context.fillText(this.labels[i], x + barWidth/2, barBottomY - barHeight - this.labelMargin, barWidth);
}else{
context.textBaseline = 'top';
context.fillText(this.labels[i], x + barWidth/2, barBottomY - barHeight + this.labelMargin, barWidth);
}
}
//Draw the data value:
context.font = this.dataValueFontStyle + ' ' + this.dataValueFontHeight + 'px '+ this.dataValueFont;
context.fillStyle = this.dataValueFillStyle;
context.textAlign = 'center';
if(di>=0){
context.textBaseline = 'bottom';
context.fillText(di, x + barWidth/2, barBottomY - barHeight - this.labelMargin - this.dataValueMargin, barWidth);
}else{
context.textBaseline = 'top';
context.fillText(di, x + barWidth/2, barBottomY - barHeight + this.labelMargin + this.dataValueMargin, barWidth);
}
//Update x:
x = x + barWidth + this.barHGap;
}
}
this.animateBarChart = function() {
var aw = this,
numFrames = this.animationFrames,
currentFrame = 0,
maxData = this.data.max(),
minData = this.data.min(),
barMaxTopY = this.marginTop + this.labelMargin + this.labelFontHeight + this.dataValueMargin + this.dataValueFontHeight,
barMinTopY = barBottomY = this.height - this.marginBottom;
if(this.title!=null){
barMaxTopY += this.titleFontHeight + this.titleMargin;
}
if(minData<0){
barMinTopY = this.height - this.marginBottom - this.labelMargin - this.labelFontHeight - this.dataValueMargin - this.dataValueFontHeight;
barBottomY = barMinTopY + ((this.height - this.marginBottom - barMaxTopY - this.labelMargin - this.labelFontHeight - this.dataValueMargin - this.dataValueFontHeight) * minData) / (Math.abs(minData)+maxData);
}
var chartAreaHeight = barMinTopY - barMaxTopY,
changeOfMarginBottom = 0,
changeOfMarginTop = 0;
var belowZeroMaxBarHeight = 0;
if(minData<0){
var maxBarHeight = Math.max(Math.abs(barBottomY - barMaxTopY), Math.abs(barBottomY - barMinTopY)),
maxBarAbsData = Math.max(Math.abs(minData), Math.abs(maxData));
belowZeroMaxBarHeight = Math.abs(minData * maxBarHeight / maxBarAbsData + this.labelMargin + this.labelFontHeight);
}
this.marginBottom += belowZeroMaxBarHeight;
if(this.title!=null){
this.titleMargin += chartAreaHeight - belowZeroMaxBarHeight;
}else{
this.marginTop += chartAreaHeight - belowZeroMaxBarHeight;
}
changeOfMarginBottom = belowZeroMaxBarHeight / numFrames;
changeOfMarginTop = (chartAreaHeight - belowZeroMaxBarHeight) / numFrames;
var updateBarChart = function() {
if(currentFrame++ < numFrames) {
aw.marginBottom -= changeOfMarginBottom;
if(aw.title!=null){
aw.titleMargin -= changeOfMarginTop;
}else{
aw.marginTop -= changeOfMarginTop;
}
aw.ctx.clearRect(0, 0, aw.width, aw.height);
aw.drawBarChart();
aw.drawTitleAndBorders();
// Standard
if (typeof(window.requestAnimationFrame) == 'function') {
window.requestAnimationFrame(updateBarChart);
// IE 10+
} else if (typeof(window.msRequestAnimationFrame) == 'function') {
window.msRequestAnimationFrame(updateBarChart);
// Chrome
} else if (typeof(window.webkitRequestAnimationFrame) == 'function') {
window.webkitRequestAnimationFrame(updateBarChart);
// Firefox
} else if (window.mozRequestAnimationFrame) { // Seems rather slow in FF6 - so disabled
window.mozRequestAnimationFrame(updateBarChart);
// Default fallback to setTimeout
} else {
setTimeout(updateBarChart, 16.6666666);
}
}
}
updateBarChart();
}
this.drawVerticalBarChart = function(){
var context = this.ctx;
context.save();
context.translate(this.width/2, this.height/2);
context.rotate(Math.PI/2);
context.translate(-this.width/2, -this.height/2);
//Calculate bar size:
var n = this.data.length;
var maxData = this.data.max();
var minData = this.data.min();
var marginLeft = this.marginLeft;
if(this.title!=null){
marginLeft += this.titleFontHeight + this.titleMargin;
}
var barWidth = (this.width - marginLeft - this.marginRight - (n-1) * this.barHGap) / n;
context.font = this.labelFontStyle + ' ' + this.labelFontHeight + 'px '+ this.labelFont;
var maxLabelWidth = 0;
var labelWidth = 0;
for(var i=0; i<this.labels.length; i++){
labelWidth = context.measureText(this.labels[i]).width;
if(labelWidth>maxLabelWidth){
maxLabelWidth = labelWidth;
}
}
context.font = this.dataValueFontStyle + ' ' + this.dataValueFontHeight + 'px '+ this.dataValueFont;
var maxDataValueWidth = 0;
var dataValueWidth = 0;
for(var i=0; i<this.data.length; i++){
dataValueWidth = context.measureText(this.data[i]).width;
if(dataValueWidth>maxDataValueWidth){
maxDataValueWidth = dataValueWidth;
}
}
var barMaxTopY = this.marginTop + Math.max( (this.labelMargin + maxLabelWidth), (this.dataValueMargin + maxDataValueWidth) );
var barMinTopY = this.height - this.marginBottom;
var barBottomY = this.height - this.marginBottom;
if(minData<0){
barMinTopY = this.height - this.marginBottom - this.labelMargin - this.labelFontHeight - this.dataValueMargin - this.dataValueFontHeight;
barBottomY = barMinTopY + ((this.height - this.marginBottom - barMaxTopY - this.labelMargin - this.labelFontHeight - this.dataValueMargin - this.dataValueFontHeight) * minData) / (Math.abs(minData)+maxData);
}
var maxBarHeight = Math.max(Math.abs(barBottomY - barMaxTopY), Math.abs(barBottomY - barMinTopY));
var maxBarAbsData = Math.max(Math.abs(minData), Math.abs(maxData));
var x = marginLeft;
var y = barBottomY;
var barHeight = 0;
var di = 0;
for(var i=0; i<this.data.length; i++){
di = this.data[i];
barHeight = di * maxBarHeight / maxBarAbsData;
//Draw the bar:
if(this.colors[i]){
context.fillStyle = this.colors[i];
}else{
context.fillStyle = this.barFillStyle;
}
context.strokeStyle = this.barStrokeStyle;
context.lineWidth = this.barBorderWidth;
context.beginPath();
context.moveTo(x, y);
context.lineTo(x, y - barHeight);
context.lineTo(x + barWidth, y - barHeight);
context.lineTo(x + barWidth, y);
context.save();
context.shadowOffsetX = this.barShadowOffsetX;
context.shadowOffsetY = this.barShadowOffsetY;
context.shadowBlur = this.barShadowBlur;
context.shadowColor = this.barShadowColor;
context.fill();
context.restore();
context.stroke();
//Draw the label:
context.font = this.labelFontStyle + ' ' + this.labelFontHeight + 'px '+ this.labelFont;
if(this.colors[i]){
context.fillStyle = this.colors[i];
}else{
context.fillStyle = this.labelFillStyle;
}
context.save();
context.translate(x + barWidth/2, barBottomY - barHeight);
context.rotate(-Math.PI/2);
context.textBaseline = 'top';
if(this.labels[i]){
if(di>=0){
context.textAlign = 'left';
context.fillText(this.labels[i], this.labelMargin, 0);
}else{
context.textAlign = 'right';
context.fillText(this.labels[i], -this.labelMargin, 0);
}
}
//Draw the data value:
context.font = this.dataValueFontStyle + ' ' + this.dataValueFontHeight + 'px '+ this.dataValueFont;
context.fillStyle = this.dataValueFillStyle;
context.textBaseline = 'bottom';
if(di>=0){
context.textAlign = 'left';
context.fillText(di, this.labelMargin, 0);
}else{
context.textAlign = 'right';
context.fillText(di, -this.labelMargin, 0);
}
context.restore();
//Update x:
x = x + barWidth + this.barHGap;
}
context.restore();
}
this.animateVerticalBarChart = function() {
var aw = this,
numFrames = this.animationFrames,
currentFrame = 0,
maxData = this.data.max(),
minData = this.data.min(),
dataLen = this.data.length,
context = this.ctx,
marginLeft = this.marginLeft
marginTop = this.marginTop
marginTopCurrent = 0;
if(this.title!=null){
marginLeft += this.titleFontHeight + this.titleMargin;
}
var barWidth = (this.width - marginLeft - this.marginRight - (dataLen-1) * this.barHGap) / dataLen;
context.font = this.labelFontStyle + ' ' + this.labelFontHeight + 'px '+ this.labelFont;
var maxLabelWidth = 0;
var labelWidth = 0;
for(var i=0; i<this.labels.length; i++){
labelWidth = context.measureText(this.labels[i]).width;
if(labelWidth>maxLabelWidth){
maxLabelWidth = labelWidth;
}
}
context.font = this.dataValueFontStyle + ' ' + this.dataValueFontHeight + 'px '+ this.dataValueFont;
var maxDataValueWidth = 0;
var dataValueWidth = 0;
for(var i=0; i<dataLen; i++){
dataValueWidth = context.measureText(this.data[i]).width;
if(dataValueWidth>maxDataValueWidth){
maxDataValueWidth = dataValueWidth;
}
}
var barMaxTopY = this.marginTop + Math.max( (this.labelMargin + maxLabelWidth), (this.dataValueMargin + maxDataValueWidth) );
var barMinTopY = this.height - this.marginBottom;
var barBottomY = this.height - this.marginBottom;
if(minData<0){
barMinTopY = this.height - this.marginBottom - this.labelMargin - this.labelFontHeight - this.dataValueMargin - this.dataValueFontHeight;
barBottomY = barMinTopY + ((this.height - this.marginBottom - barMaxTopY - this.labelMargin - this.labelFontHeight - this.dataValueMargin - this.dataValueFontHeight) * minData) / (Math.abs(minData)+maxData);
}
var maxBarHeight = Math.max(Math.abs(barBottomY - barMaxTopY), Math.abs(barBottomY - barMinTopY));
var maxBarAbsData = Math.max(Math.abs(minData), Math.abs(maxData));
var belowZeroMaxBarHeight = 0;
if(minData<0){
belowZeroMaxBarHeight = Math.abs(minData * maxBarHeight / maxBarAbsData);
}
var chartAreaHeight = maxData * maxBarHeight / maxBarAbsData + belowZeroMaxBarHeight,
changeOfMarginBottom = 0,
changeOfMarginTop = 0;
this.marginBottom += belowZeroMaxBarHeight;
this.marginTop += chartAreaHeight - belowZeroMaxBarHeight;
changeOfMarginBottom = belowZeroMaxBarHeight / numFrames;
changeOfMarginTop = (chartAreaHeight - belowZeroMaxBarHeight) / numFrames;
var updateVerticalBarChart = function() {
if(currentFrame++ < numFrames) {
aw.marginBottom -= changeOfMarginBottom;
aw.marginTop -= changeOfMarginTop;
aw.ctx.clearRect(0, 0, aw.width, aw.height);
aw.drawVerticalBarChart();
marginTopCurrent = aw.marginTop;
aw.marginTop = marginTop;
aw.drawTitleAndBorders();
aw.marginTop = marginTopCurrent;
// Standard
if (typeof(window.requestAnimationFrame) == 'function') {
window.requestAnimationFrame(updateVerticalBarChart);
// IE 10+
} else if (typeof(window.msRequestAnimationFrame) == 'function') {
window.msRequestAnimationFrame(updateVerticalBarChart);
// Chrome
} else if (typeof(window.webkitRequestAnimationFrame) == 'function') {
window.webkitRequestAnimationFrame(updateVerticalBarChart);
// Firefox
} else if (window.mozRequestAnimationFrame) { // Seems rather slow in FF6 - so disabled
window.mozRequestAnimationFrame(updateVerticalBarChart);
// Default fallback to setTimeout
} else {
setTimeout(updateVerticalBarChart, 16.6666666);
}
}
}
updateVerticalBarChart();
}
this.drawPieChart = function(ring){
var context = this.ctx;
context.lineWidth = this.pieBorderWidth;
var dataSum = 0,
dataSumForStartAngle = 0,
dataLen = this.data.length;
for (var i=0; i<dataLen; i++){
dataSumForStartAngle += this.data[i];
if(this.data[i]<0){
return;
}
}
if(this.pieTotal == null){
dataSum = dataSumForStartAngle;
}else{
dataSum = this.pieTotal;
}
var pieAreaWidth = this.width - this.marginLeft - this.marginRight;
var pieAreaHeight = this.height - this.marginTop - this.marginBottom;
if(this.title){
pieAreaHeight = pieAreaHeight - this.titleFontHeight - this.titleMargin;
}
var centerX = this.width / 2;
var centerY = this.marginTop + (pieAreaHeight / 2);
if(this.title){
centerY += this.titleFontHeight + this.titleMargin;
}
var doublePI = 2 * Math.PI;
var radius = (Math.min( pieAreaWidth, pieAreaHeight) / 2);
context.font = this.labelFontStyle + ' ' + this.labelFontHeight + 'px '+ this.labelFont;
var maxLabelWidth = 0;
var labelWidth = 0;
for(var i=0; i<this.labels.length; i++){
labelWidth = context.measureText(this.labels[i]).width;
if(labelWidth>maxLabelWidth){
maxLabelWidth = labelWidth;
}
}
radius = radius - maxLabelWidth - this.labelMargin;
var startAngle = this.pieStart* doublePI / dataSumForStartAngle;
var currentAngle = startAngle;
var endAngle = 0;
var incAngleBy = 0;
for(var i=0; i<dataLen; i++){
context.beginPath();
incAngleBy = this.data[i] * doublePI / dataSum;
endAngle = currentAngle + incAngleBy;
context.moveTo(centerX, centerY);
context.arc(centerX, centerY, radius, currentAngle, endAngle, false);
context.lineTo(centerX, centerY);
currentAngle = endAngle;
if(this.colors[i]){
context.fillStyle = this.colors[i];
}else{
context.fillStyle = this.pieFillStyle;
}
context.fill();
context.strokeStyle = this.pieStrokeStyle;
context.stroke();
}
//Draw the outer shadow:
context.save();
context.shadowOffsetX = this.pieShadowOffsetX;
context.shadowOffsetY = this.pieShadowOffsetY;
context.translate(centerX, centerY);
//context.rotate(this.pieStart* doublePI / dataSum);
context.beginPath();
context.moveTo(0, 0);
context.arc(0, 0, radius, startAngle, endAngle, false);
context.shadowBlur = this.pieShadowBlur;
context.shadowColor = this.pieShadowColor;
context.globalCompositeOperation = 'destination-over';
context.fillStyle = 'rgba(0,0,0,1.0)';
context.fill();
context.restore();
//Ring-charts:
if(ring){
var ringCenterRadius = radius/2;
// draw the inner border
context.save();
context.beginPath();
context.moveTo(centerX+ringCenterRadius, centerY);
context.arc(centerX, centerY, ringCenterRadius+this.pieBorderWidth, startAngle, endAngle, false);
context.fillStyle = this.pieStrokeStyle;
context.fill();
context.restore();
// "cut" the central part:
context.save();
context.beginPath();
context.moveTo(centerX+ringCenterRadius, centerY);
context.arc(centerX, centerY, ringCenterRadius, 0, doublePI, false);
context.globalCompositeOperation = 'destination-out';
context.fillStyle = '#000';
context.fill();
context.restore();
// draw the ring's inner shadow below the ring:
context.save();
context.shadowOffsetX = this.pieShadowOffsetX;
context.shadowOffsetY = this.pieShadowOffsetY;
context.translate(centerX, centerY);
context.beginPath();
context.arc(0, 0, ringCenterRadius, startAngle, endAngle, false);
context.shadowBlur = this.pieShadowBlur;
context.shadowColor = this.pieShadowColor;
context.globalCompositeOperation = 'destination-over';
context.strokeStyle = this.pieStrokeStyle;
context.stroke();
context.restore();
}
// draw the labels:
var currentAngle = this.pieStart* doublePI / dataSumForStartAngle;
var endAngle = 0;
var incAngleBy = 0;
context.beginPath();
for(var i=0; i<this.data.length; i++){
context.save();
incAngleBy = this.data[i] * doublePI / dataSum;
endAngle = currentAngle + incAngleBy;
var mAngle = currentAngle + incAngleBy/2;
context.translate(centerX, centerY);
context.rotate(mAngle);
context.font = this.labelFontStyle + ' ' + this.labelFontHeight + 'px '+ this.labelFont;
if(this.colors[i]){
context.fillStyle = this.colors[i];
}else{
context.fillStyle = this.labelFillStyle;
}
context.textAlign = 'start';
if(this.labels[i]){
if( (mAngle>Math.PI/2) && (mAngle<=3*(Math.PI/2)) ){
var translateXBy = radius + this.labelMargin + context.measureText(this.labels[i]).width / 2;
context.translate(translateXBy, 0);
context.rotate(Math.PI);
context.translate(-translateXBy, 0);
}
context.textBaseline = 'middle';
context.fillText(this.labels[i], radius+this.labelMargin, 0);
}
context.restore();
currentAngle = endAngle;
}
}
this.drawExplodedPieChart = function(){
var context = this.ctx;
context.lineWidth = this.pieBorderWidth;
var dataSum = 0,
dataSumForStartAngle = 0,
dataLen = this.data.length;
for (var i=0; i<dataLen; i++){
dataSumForStartAngle += this.data[i];
if(this.data[i]<0){
return;
}
}
if(this.pieTotal == null){
dataSum = dataSumForStartAngle;
}else{
dataSum = this.pieTotal;
}
var pieAreaWidth = this.width - this.marginLeft - this.marginRight;
var pieAreaHeight = this.height - this.marginTop - this.marginBottom;
if(this.title!=null){
pieAreaHeight = pieAreaHeight - this.titleFontHeight - this.titleMargin;
}
var centerX = this.width / 2;
var centerY = this.marginTop + (pieAreaHeight / 2);
if(this.title){
centerY += this.titleFontHeight + this.titleMargin;
}
var doublePI = 2 * Math.PI;
var radius = (Math.min( pieAreaWidth, pieAreaHeight) / 2);
context.font = this.labelFontStyle + ' ' + this.labelFontHeight + 'px '+ this.labelFont;
var maxLabelWidth = 0;
var labelWidth = 0;
for(var i=0; i<this.labels.length; i++){
labelWidth = context.measureText(this.labels[i]).width;
if(labelWidth>maxLabelWidth){
maxLabelWidth = labelWidth;
}
}
radius = radius - maxLabelWidth - this.labelMargin;
var currentAngle = this.pieStart* doublePI / dataSumForStartAngle;
var endAngle = 0;
var incAngleBy = 0;
var halfAngle = 0;
var mAngle = 0;
for(var i=0; i<this.data.length; i++){
context.save();
incAngleBy = this.data[i] * doublePI / dataSum;
endAngle = currentAngle + incAngleBy;
halfAngle = incAngleBy/2;
mAngle = currentAngle + halfAngle;
context.translate(centerX, centerY);
context.rotate(currentAngle);
context.rotate(halfAngle);
context.translate(this.explosionOffset,0);
context.rotate(-halfAngle);
context.beginPath();
context.moveTo(0,0);
context.arc(0, 0, radius, 0, incAngleBy, false);
context.lineTo(0, 0);
context.save();
context.shadowOffsetX = this.pieShadowOffsetX;
context.shadowOffsetY = this.pieShadowOffsetY;
context.shadowBlur = this.pieShadowBlur;
context.shadowColor = this.pieShadowColor;
if(this.colors[i]){
context.fillStyle = this.colors[i];
}else{
context.fillStyle = this.pieFillStyle;
}
context.fill();
context.restore();
context.strokeStyle = this.pieStrokeStyle;
context.stroke();
// Draw the label:
context.rotate(halfAngle);
context.font = this.labelFontStyle + ' ' + this.labelFontHeight + 'px '+ this.labelFont;
if(this.colors[i]){
context.fillStyle = this.colors[i];
}else{
context.fillStyle = this.labelFillStyle;
}
context.textAlign = 'start';
if(this.labels[i]){
if( (mAngle>Math.PI/2) && (mAngle<=3*(Math.PI/2)) ){
var translateXBy = radius + this.labelMargin + context.measureText(this.labels[i]).width / 2;
context.translate(translateXBy, 0);
context.rotate(Math.PI);
context.translate(-translateXBy, 0);
}
context.textBaseline = 'middle';
context.fillText(this.labels[i], radius+this.labelMargin, 0);
}
// Restore the context:
context.restore();
currentAngle = endAngle;
}
}
this.animatePieChart = function(pieType){
var dataSum = 0,
pieTotalReal = this.pieTotal,
aw = this,
numFrames = this.animationFrames,
currentFrame = 0,
pieAreaWidth = this.width - this.marginLeft - this.marginRight,
pieAreaHeight = this.height - this.marginTop - this.marginBottom,
marginTop = this.marginTop,
marginLeft = this.marginLeft;
if(this.title){
pieAreaHeight = pieAreaHeight - this.titleFontHeight - this.titleMargin;
marginTop += this.titleFontHeight + this.titleMargin;
};
for(var i=0; i<this.data.length; i++){
dataSum += this.data[i];
if(this.data[i]<0){
return;
}
}
if(pieTotalReal == null) {
pieTotalReal = dataSum;
}
var updatePieChart = function() {
if(currentFrame++ < numFrames) {
aw.ctx.clearRect(0, 0, aw.width, aw.height);
aw.pieTotal = (dataSum * (numFrames / currentFrame)) * (pieTotalReal / dataSum);
if(pieType == "pie") {
aw.drawPieChart(false);
}else if(pieType == "ring") {
aw.drawPieChart(true);
}else if(pieType == "exploded") {
aw.drawExplodedPieChart();
}
aw.drawTitleAndBorders();
// Standard
if (typeof(window.requestAnimationFrame) == 'function') {
window.requestAnimationFrame(updatePieChart);
// IE 10+
} else if (typeof(window.msRequestAnimationFrame) == 'function') {
window.msRequestAnimationFrame(updatePieChart);
// Chrome
} else if (typeof(window.webkitRequestAnimationFrame) == 'function') {
window.webkitRequestAnimationFrame(updatePieChart);
// Firefox
} else if (window.mozRequestAnimationFrame) { // Seems rather slow in FF6 - so disabled
window.mozRequestAnimationFrame(updatePieChart);
// Default fallback to setTimeout
} else {
setTimeout(updatePieChart, 16.6666666);
}
}
}
updatePieChart();
}
this.drawParetoChart = function(){
var context = this.ctx;
var n = this.data.length;
var indices = new Array();
for (var i = 0; i < this.data.length; i++){
indices.push(i);
}
indices.numericSortReverse(this.data);
var maxData = this.data[indices[0]];
var minData = this.data[indices[indices.length-1]];
var dataSum = 0;
for (var i = 0; i < this.data.length; i++){
dataSum += this.data[indices[i]];
if(this.data[indices[i]]<0){
return;
}
}
dataSum = dataSum.toFixed(this.numberOfDecimals);
var yAxisValues = new Array();
yAxisValues.push(0);
for (var i = 1; i < 10; i++){
yAxisValues.push((dataSum * i/10).toFixed(this.numberOfDecimals));
}
yAxisValues.push(dataSum);
// Find the widest Y-axis value's width:
context.font = this.yAxisLabelFontStyle + ' ' + this.yAxisLabelFontHeight + 'px '+ this.yAxisLabelFont;
var maxYAxisLabelWidth = 0;
var yAxisLabelWidth = 0;
for(var i=0; i<yAxisValues.length; i++){
yAxisLabelWidth = context.measureText(yAxisValues[i]).width;
if(yAxisLabelWidth>maxYAxisLabelWidth){
maxYAxisLabelWidth = yAxisLabelWidth;
}
}
var perCentMaxWidth = context.measureText("100%").width;
// Calculate the chart size and position:
var chartWidth = this.width - this.marginLeft - this.marginRight - 2*this.chartMarkerSize - maxYAxisLabelWidth - perCentMaxWidth - 2*this.yAxisLabelMargin;
var chartHeight = this.height - this.marginTop - this.marginBottom;
var chartTopLeftX = this.marginLeft + this.chartMarkerSize + maxYAxisLabelWidth + this.yAxisLabelMargin;
var chartTopLeftY = this.marginTop;
if(this.title){
chartHeight -= this.titleFontHeight + this.titleMargin;
chartTopLeftY += this.titleFontHeight + this.titleMargin;
}
// Draw the chart's background:
context.save();
context.translate(chartTopLeftX, chartTopLeftY);
context.fillStyle = this.chartBackgroundFillStyle;
context.fillRect(0,0,chartWidth,chartHeight);
// Draw the markers, horizontal lines, and axis' labels:
var yStep = chartHeight / 10;
var lineY = 0;
context.lineWidth = this.chartHorizontalLineWidth;
context.font = this.yAxisLabelFontStyle + ' ' + this.yAxisLabelFontHeight + 'px '+ this.yAxisLabelFont;
for(var i=0; i<=10; i++){
lineY = i*yStep;
if( i>0 && i<10){
context.strokeStyle = this.chartHorizontalLineStrokeStyle;
context.beginPath();
context.moveTo(0,lineY);
context.lineTo(chartWidth,lineY);
context.stroke();
}
context.strokeStyle = this.chartBorderStrokeStyle;
context.beginPath();
context.moveTo(-this.chartMarkerSize,lineY);
context.lineTo(0,lineY);
context.stroke();
context.beginPath();
context.moveTo(chartWidth,lineY);
context.lineTo(chartWidth+this.chartMarkerSize,lineY);
context.stroke();
context.fillStyle = this.yAxisLabelFillStyle;
context.textAlign = 'right';
context.textBaseline = 'middle';
context.fillText(yAxisValues[10-i], -this.chartMarkerSize-this.yAxisLabelMargin, lineY);
context.textAlign = 'left';
context.fillText( ((10-i)*10)+'%', chartWidth+this.chartMarkerSize+this.yAxisLabelMargin, lineY);
}
// Draw the bars:
context.save();
context.translate(0, chartHeight);
var barWidth = (chartWidth-2*this.barHGap) / n;
var barHeight = 0;
var halfBarWidth = barWidth/2;
var y = 0;
var x = this.barHGap;
var x1 = x;
var y1 = 0;
var x2 = 0;
var y2 = 0;
for(var i=0; i<this.data.length; i++){
barHeight = this.data[indices[i]] * chartHeight / dataSum;
//Draw the bar:
if(this.colors[i]){
context.fillStyle = this.colors[i];
}else{
context.fillStyle = this.barFillStyle;
}
context.strokeStyle = this.barStrokeStyle;
context.lineWidth = this.barBorderWidth;
context.beginPath();
context.moveTo(x, y);
context.lineTo(x, y - barHeight);
context.lineTo(x + barWidth, y - barHeight);
context.lineTo(x + barWidth, y);
context.save();
context.shadowOffsetX = this.barShadowOffsetX;
context.shadowOffsetY = this.barShadowOffsetY;
context.shadowBlur = this.barShadowBlur;
context.shadowColor = this.barShadowColor;
context.fill();
context.restore();
context.stroke();
// Draw the line:
x2 = x1;
y2 = y1;
x1 = x + barWidth;
y1 -= barHeight;
if(i==this.data.length - 1){
y1 = -chartHeight;
}
context.strokeStyle = this.chartLineStrokeStyle;
context.lineWidth = this.chartLineWidth;
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
// Draw the label:
context.font = this.labelFontStyle + ' ' + this.labelFontHeight + 'px '+ this.labelFont;
if(this.colors[i]){
context.fillStyle = this.colors[i];
}else{
context.fillStyle = this.labelFillStyle;
}
context.textAlign = 'center';
if(this.labels[indices[i]]){
if(this.data[indices[i]]>=0){
context.textBaseline = 'bottom';
context.fillText(this.labels[indices[i]], x + halfBarWidth, - barHeight - this.labelMargin, barWidth);
}else{
context.textBaseline = 'top';
context.fillText(this.labels[indices[i]], x + halfBarWidth, - barHeight + this.labelMargin, barWidth);
}
}
// Draw the data value:
context.font = this.dataValueFontStyle + ' ' + this.dataValueFontHeight + 'px '+ this.dataValueFont;
context.fillStyle = this.dataValueFillStyle;
context.textAlign = 'center';
if(this.data[indices[i]]>=0){
context.textBaseline = 'bottom';
context.fillText(this.data[indices[i]], x + halfBarWidth, - barHeight - this.labelMargin - this.dataValueMargin, barWidth);
}else{
context.textBaseline = 'top';
context.fillText(this.data[indices[i]], x + halfBarWidth, - barHeight + this.labelMargin + this.dataValueMargin, barWidth);
}
// Update x:
x = x + barWidth;
}
// Draw the points:
x = this.barHGap;
x1 = x;
y1 = 0;
x2 = 0;
y2 = 0;
context.fillStyle = this.chartPointFillStyle;
context.beginPath();
context.arc(x1, y1, this.chartPointRadius, 0, 2*Math.PI, false);
context.fill();
for(var i=0; i<this.data.length; i++){
barHeight = this.data[indices[i]] * chartHeight / dataSum;
x2 = x1;
y2 = y1;
x1 = x + barWidth;
y1 -= barHeight;
if(i==this.data.length - 1){
y1 = -chartHeight;
}
context.fillStyle = this.chartPointFillStyle;
context.beginPath();
context.arc(x1, y1, this.chartPointRadius, 0, 2*Math.PI, false);
context.fill();
x = x + barWidth;
}
context.restore();
// Draw the chart's border:
context.lineWidth = this.chartBorderLineWidth;
context.strokeStyle = this.chartBorderStrokeStyle;
context.strokeRect(0,0,chartWidth,chartHeight);
context.restore();
}
}