mirror of
https://github.com/e107inc/e107.git
synced 2025-02-13 11:04:38 +01:00
1404 lines
47 KiB
JavaScript
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();
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|