/* * 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=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; imaxLabelWidth){ maxLabelWidth = labelWidth; } } context.font = this.dataValueFontStyle + ' ' + this.dataValueFontHeight + 'px '+ this.dataValueFont; var maxDataValueWidth = 0; var dataValueWidth = 0; for(var i=0; imaxDataValueWidth){ 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=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; imaxLabelWidth){ maxLabelWidth = labelWidth; } } context.font = this.dataValueFontStyle + ' ' + this.dataValueFontHeight + 'px '+ this.dataValueFont; var maxDataValueWidth = 0; var dataValueWidth = 0; for(var i=0; imaxDataValueWidth){ 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; imaxLabelWidth){ 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; iMath.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; imaxLabelWidth){ 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; iMath.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; imaxYAxisLabelWidth){ 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=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