You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1273 lines
39 KiB
1273 lines
39 KiB
/*! |
|
* Chart.js |
|
* http://chartjs.org/ |
|
* |
|
* Copyright 2013 Nick Downie |
|
* Released under the MIT license |
|
* https://github.com/nnnick/Chart.js/blob/master/LICENSE.md |
|
*/ |
|
|
|
//Define the global Chart Variable as a class. |
|
window.Chart = function(context){ |
|
|
|
var chart = this; |
|
|
|
|
|
//Easing functions adapted from Robert Penner's easing equations |
|
//http://www.robertpenner.com/easing/ |
|
|
|
var animationOptions = { |
|
linear : function (t){ |
|
return t; |
|
}, |
|
easeInQuad: function (t) { |
|
return t*t; |
|
}, |
|
easeOutQuad: function (t) { |
|
return -1 *t*(t-2); |
|
}, |
|
easeInOutQuad: function (t) { |
|
if ((t/=1/2) < 1) return 1/2*t*t; |
|
return -1/2 * ((--t)*(t-2) - 1); |
|
}, |
|
easeInCubic: function (t) { |
|
return t*t*t; |
|
}, |
|
easeOutCubic: function (t) { |
|
return 1*((t=t/1-1)*t*t + 1); |
|
}, |
|
easeInOutCubic: function (t) { |
|
if ((t/=1/2) < 1) return 1/2*t*t*t; |
|
return 1/2*((t-=2)*t*t + 2); |
|
}, |
|
easeInQuart: function (t) { |
|
return t*t*t*t; |
|
}, |
|
easeOutQuart: function (t) { |
|
return -1 * ((t=t/1-1)*t*t*t - 1); |
|
}, |
|
easeInOutQuart: function (t) { |
|
if ((t/=1/2) < 1) return 1/2*t*t*t*t; |
|
return -1/2 * ((t-=2)*t*t*t - 2); |
|
}, |
|
easeInQuint: function (t) { |
|
return 1*(t/=1)*t*t*t*t; |
|
}, |
|
easeOutQuint: function (t) { |
|
return 1*((t=t/1-1)*t*t*t*t + 1); |
|
}, |
|
easeInOutQuint: function (t) { |
|
if ((t/=1/2) < 1) return 1/2*t*t*t*t*t; |
|
return 1/2*((t-=2)*t*t*t*t + 2); |
|
}, |
|
easeInSine: function (t) { |
|
return -1 * Math.cos(t/1 * (Math.PI/2)) + 1; |
|
}, |
|
easeOutSine: function (t) { |
|
return 1 * Math.sin(t/1 * (Math.PI/2)); |
|
}, |
|
easeInOutSine: function (t) { |
|
return -1/2 * (Math.cos(Math.PI*t/1) - 1); |
|
}, |
|
easeInExpo: function (t) { |
|
return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1)); |
|
}, |
|
easeOutExpo: function (t) { |
|
return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1); |
|
}, |
|
easeInOutExpo: function (t) { |
|
if (t==0) return 0; |
|
if (t==1) return 1; |
|
if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1)); |
|
return 1/2 * (-Math.pow(2, -10 * --t) + 2); |
|
}, |
|
easeInCirc: function (t) { |
|
if (t>=1) return t; |
|
return -1 * (Math.sqrt(1 - (t/=1)*t) - 1); |
|
}, |
|
easeOutCirc: function (t) { |
|
return 1 * Math.sqrt(1 - (t=t/1-1)*t); |
|
}, |
|
easeInOutCirc: function (t) { |
|
if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1); |
|
return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1); |
|
}, |
|
easeInElastic: function (t) { |
|
var s=1.70158;var p=0;var a=1; |
|
if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; |
|
if (a < Math.abs(1)) { a=1; var s=p/4; } |
|
else var s = p/(2*Math.PI) * Math.asin (1/a); |
|
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); |
|
}, |
|
easeOutElastic: function (t) { |
|
var s=1.70158;var p=0;var a=1; |
|
if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; |
|
if (a < Math.abs(1)) { a=1; var s=p/4; } |
|
else var s = p/(2*Math.PI) * Math.asin (1/a); |
|
return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1; |
|
}, |
|
easeInOutElastic: function (t) { |
|
var s=1.70158;var p=0;var a=1; |
|
if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5); |
|
if (a < Math.abs(1)) { a=1; var s=p/4; } |
|
else var s = p/(2*Math.PI) * Math.asin (1/a); |
|
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); |
|
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1; |
|
}, |
|
easeInBack: function (t) { |
|
var s = 1.70158; |
|
return 1*(t/=1)*t*((s+1)*t - s); |
|
}, |
|
easeOutBack: function (t) { |
|
var s = 1.70158; |
|
return 1*((t=t/1-1)*t*((s+1)*t + s) + 1); |
|
}, |
|
easeInOutBack: function (t) { |
|
var s = 1.70158; |
|
if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s)); |
|
return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2); |
|
}, |
|
easeInBounce: function (t) { |
|
return 1 - animationOptions.easeOutBounce (1-t); |
|
}, |
|
easeOutBounce: function (t) { |
|
if ((t/=1) < (1/2.75)) { |
|
return 1*(7.5625*t*t); |
|
} else if (t < (2/2.75)) { |
|
return 1*(7.5625*(t-=(1.5/2.75))*t + .75); |
|
} else if (t < (2.5/2.75)) { |
|
return 1*(7.5625*(t-=(2.25/2.75))*t + .9375); |
|
} else { |
|
return 1*(7.5625*(t-=(2.625/2.75))*t + .984375); |
|
} |
|
}, |
|
easeInOutBounce: function (t) { |
|
if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5; |
|
return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5; |
|
} |
|
}; |
|
|
|
//Variables global to the chart |
|
var width = context.canvas.width; |
|
var height = context.canvas.height; |
|
|
|
|
|
//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. |
|
if (window.devicePixelRatio) { |
|
context.canvas.style.width = width + "px"; |
|
context.canvas.style.height = height + "px"; |
|
context.canvas.height = height * window.devicePixelRatio; |
|
context.canvas.width = width * window.devicePixelRatio; |
|
context.scale(window.devicePixelRatio, window.devicePixelRatio); |
|
} |
|
|
|
this.Line = function(data,options){ |
|
|
|
chart.Line.defaults = { |
|
scaleOverlay : false, |
|
scaleOverride : false, |
|
scaleSteps : null, |
|
scaleStepWidth : null, |
|
scaleStartValue : null, |
|
scaleLineColor : "#46abdb", |
|
scaleLineWidth : 1, |
|
scaleShowLabels : true, |
|
scaleLabel : "<%=value%>", |
|
scaleFontFamily : "'Arial'", |
|
scaleFontSize : 12, |
|
scaleFontStyle : "normal", |
|
scaleFontColor : "#FFF", |
|
scaleShowGridLines : true, |
|
scaleGridLineColor : "#46abdb", |
|
scaleGridLineWidth : 1, |
|
bezierCurve : true, |
|
pointDot : true, |
|
pointDotRadius :0, |
|
pointDotStrokeWidth : 2, |
|
datasetStroke : true, |
|
datasetStrokeWidth : 4, |
|
datasetFill : true, |
|
animation : true, |
|
animationSteps : 60, |
|
animationEasing : "easeOutQuart", |
|
onAnimationComplete : null |
|
}; |
|
var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults; |
|
|
|
return new Line(data,config,context); |
|
} |
|
|
|
var clear = function(c){ |
|
c.clearRect(0, 0, width, height); |
|
}; |
|
|
|
var PolarArea = function(data,config,ctx){ |
|
var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; |
|
|
|
|
|
calculateDrawingSizes(); |
|
|
|
valueBounds = getValueBounds(); |
|
|
|
labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; |
|
|
|
//Check and set the scale |
|
if (!config.scaleOverride){ |
|
|
|
calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); |
|
} |
|
else { |
|
calculatedScale = { |
|
steps : config.scaleSteps, |
|
stepValue : config.scaleStepWidth, |
|
graphMin : config.scaleStartValue, |
|
labels : [] |
|
} |
|
populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); |
|
} |
|
|
|
scaleHop = maxSize/(calculatedScale.steps); |
|
|
|
//Wrap in an animation loop wrapper |
|
animationLoop(config,drawScale,drawAllSegments,ctx); |
|
|
|
function calculateDrawingSizes(){ |
|
maxSize = (Min([width,height])/2); |
|
//Remove whatever is larger - the font size or line width. |
|
|
|
maxSize -= Max([config.scaleFontSize*0.5,config.scaleLineWidth*0.5]); |
|
|
|
labelHeight = config.scaleFontSize*2; |
|
//If we're drawing the backdrop - add the Y padding to the label height and remove from drawing region. |
|
if (config.scaleShowLabelBackdrop){ |
|
labelHeight += (2 * config.scaleBackdropPaddingY); |
|
maxSize -= config.scaleBackdropPaddingY*1.5; |
|
} |
|
|
|
scaleHeight = maxSize; |
|
//If the label height is less than 5, set it to 5 so we don't have lines on top of each other. |
|
labelHeight = Default(labelHeight,5); |
|
} |
|
function drawScale(){ |
|
for (var i=0; i<calculatedScale.steps; i++){ |
|
//If the line object is there |
|
if (config.scaleShowLine){ |
|
ctx.beginPath(); |
|
ctx.arc(width/2, height/2, scaleHop * (i + 1), 0, (Math.PI * 2), true); |
|
ctx.strokeStyle = config.scaleLineColor; |
|
ctx.lineWidth = config.scaleLineWidth; |
|
ctx.stroke(); |
|
} |
|
|
|
if (config.scaleShowLabels){ |
|
ctx.textAlign = "center"; |
|
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily; |
|
var label = calculatedScale.labels[i]; |
|
//If the backdrop object is within the font object |
|
if (config.scaleShowLabelBackdrop){ |
|
var textWidth = ctx.measureText(label).width; |
|
ctx.fillStyle = config.scaleBackdropColor; |
|
ctx.beginPath(); |
|
ctx.rect( |
|
Math.round(width/2 - textWidth/2 - config.scaleBackdropPaddingX), //X |
|
Math.round(height/2 - (scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y |
|
Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width |
|
Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height |
|
); |
|
ctx.fill(); |
|
} |
|
ctx.textBaseline = "middle"; |
|
ctx.fillStyle = config.scaleFontColor; |
|
ctx.fillText(label,width/2,height/2 - (scaleHop * (i + 1))); |
|
} |
|
} |
|
} |
|
function drawAllSegments(animationDecimal){ |
|
var startAngle = -Math.PI/2, |
|
angleStep = (Math.PI*2)/data.length, |
|
scaleAnimation = 1, |
|
rotateAnimation = 1; |
|
if (config.animation) { |
|
if (config.animateScale) { |
|
scaleAnimation = animationDecimal; |
|
} |
|
if (config.animateRotate){ |
|
rotateAnimation = animationDecimal; |
|
} |
|
} |
|
|
|
for (var i=0; i<data.length; i++){ |
|
|
|
ctx.beginPath(); |
|
ctx.arc(width/2,height/2,scaleAnimation * calculateOffset(data[i].value,calculatedScale,scaleHop),startAngle, startAngle + rotateAnimation*angleStep, false); |
|
ctx.lineTo(width/2,height/2); |
|
ctx.closePath(); |
|
ctx.fillStyle = data[i].color; |
|
ctx.fill(); |
|
|
|
if(config.segmentShowStroke){ |
|
ctx.strokeStyle = config.segmentStrokeColor; |
|
ctx.lineWidth = config.segmentStrokeWidth; |
|
ctx.stroke(); |
|
} |
|
startAngle += rotateAnimation*angleStep; |
|
} |
|
} |
|
function getValueBounds() { |
|
var upperValue = Number.MIN_VALUE; |
|
var lowerValue = Number.MAX_VALUE; |
|
for (var i=0; i<data.length; i++){ |
|
if (data[i].value > upperValue) {upperValue = data[i].value;} |
|
if (data[i].value < lowerValue) {lowerValue = data[i].value;} |
|
}; |
|
|
|
var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); |
|
var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); |
|
|
|
return { |
|
maxValue : upperValue, |
|
minValue : lowerValue, |
|
maxSteps : maxSteps, |
|
minSteps : minSteps |
|
}; |
|
|
|
|
|
} |
|
} |
|
|
|
var Radar = function (data,config,ctx) { |
|
var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; |
|
|
|
//If no labels are defined set to an empty array, so referencing length for looping doesn't blow up. |
|
if (!data.labels) data.labels = []; |
|
|
|
calculateDrawingSizes(); |
|
|
|
var valueBounds = getValueBounds(); |
|
|
|
labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; |
|
|
|
//Check and set the scale |
|
if (!config.scaleOverride){ |
|
|
|
calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); |
|
} |
|
else { |
|
calculatedScale = { |
|
steps : config.scaleSteps, |
|
stepValue : config.scaleStepWidth, |
|
graphMin : config.scaleStartValue, |
|
labels : [] |
|
} |
|
populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); |
|
} |
|
|
|
scaleHop = maxSize/(calculatedScale.steps); |
|
|
|
animationLoop(config,drawScale,drawAllDataPoints,ctx); |
|
|
|
//Radar specific functions. |
|
function drawAllDataPoints(animationDecimal){ |
|
var rotationDegree = (2*Math.PI)/data.datasets[0].data.length; |
|
|
|
ctx.save(); |
|
//translate to the centre of the canvas. |
|
ctx.translate(width/2,height/2); |
|
|
|
//We accept multiple data sets for radar charts, so show loop through each set |
|
for (var i=0; i<data.datasets.length; i++){ |
|
ctx.beginPath(); |
|
|
|
ctx.moveTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop))); |
|
for (var j=1; j<data.datasets[i].data.length; j++){ |
|
ctx.rotate(rotationDegree); |
|
ctx.lineTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop))); |
|
|
|
} |
|
ctx.closePath(); |
|
|
|
|
|
ctx.fillStyle = data.datasets[i].fillColor; |
|
ctx.strokeStyle = data.datasets[i].strokeColor; |
|
ctx.lineWidth = config.datasetStrokeWidth; |
|
ctx.fill(); |
|
ctx.stroke(); |
|
|
|
|
|
if (config.pointDot){ |
|
ctx.fillStyle = data.datasets[i].pointColor; |
|
ctx.strokeStyle = data.datasets[i].pointStrokeColor; |
|
ctx.lineWidth = config.pointDotStrokeWidth; |
|
for (var k=0; k<data.datasets[i].data.length; k++){ |
|
ctx.rotate(rotationDegree); |
|
ctx.beginPath(); |
|
ctx.arc(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,2*Math.PI,false); |
|
ctx.fill(); |
|
ctx.stroke(); |
|
} |
|
|
|
} |
|
ctx.rotate(rotationDegree); |
|
|
|
} |
|
ctx.restore(); |
|
|
|
|
|
} |
|
function drawScale(){ |
|
var rotationDegree = (2*Math.PI)/data.datasets[0].data.length; |
|
ctx.save(); |
|
ctx.translate(width / 2, height / 2); |
|
|
|
if (config.angleShowLineOut){ |
|
ctx.strokeStyle = config.angleLineColor; |
|
ctx.lineWidth = config.angleLineWidth; |
|
for (var h=0; h<data.datasets[0].data.length; h++){ |
|
|
|
ctx.rotate(rotationDegree); |
|
ctx.beginPath(); |
|
ctx.moveTo(0,0); |
|
ctx.lineTo(0,-maxSize); |
|
ctx.stroke(); |
|
} |
|
} |
|
|
|
for (var i=0; i<calculatedScale.steps; i++){ |
|
ctx.beginPath(); |
|
|
|
if(config.scaleShowLine){ |
|
ctx.strokeStyle = config.scaleLineColor; |
|
ctx.lineWidth = config.scaleLineWidth; |
|
ctx.moveTo(0,-scaleHop * (i+1)); |
|
for (var j=0; j<data.datasets[0].data.length; j++){ |
|
ctx.rotate(rotationDegree); |
|
ctx.lineTo(0,-scaleHop * (i+1)); |
|
} |
|
ctx.closePath(); |
|
ctx.stroke(); |
|
|
|
} |
|
|
|
if (config.scaleShowLabels){ |
|
ctx.textAlign = 'center'; |
|
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; |
|
ctx.textBaseline = "middle"; |
|
|
|
if (config.scaleShowLabelBackdrop){ |
|
var textWidth = ctx.measureText(calculatedScale.labels[i]).width; |
|
ctx.fillStyle = config.scaleBackdropColor; |
|
ctx.beginPath(); |
|
ctx.rect( |
|
Math.round(- textWidth/2 - config.scaleBackdropPaddingX), //X |
|
Math.round((-scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y |
|
Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width |
|
Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height |
|
); |
|
ctx.fill(); |
|
} |
|
ctx.fillStyle = config.scaleFontColor; |
|
ctx.fillText(calculatedScale.labels[i],0,-scaleHop*(i+1)); |
|
} |
|
|
|
} |
|
for (var k=0; k<data.labels.length; k++){ |
|
ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily; |
|
ctx.fillStyle = config.pointLabelFontColor; |
|
var opposite = Math.sin(rotationDegree*k) * (maxSize + config.pointLabelFontSize); |
|
var adjacent = Math.cos(rotationDegree*k) * (maxSize + config.pointLabelFontSize); |
|
|
|
if(rotationDegree*k == Math.PI || rotationDegree*k == 0){ |
|
ctx.textAlign = "center"; |
|
} |
|
else if(rotationDegree*k > Math.PI){ |
|
ctx.textAlign = "right"; |
|
} |
|
else{ |
|
ctx.textAlign = "left"; |
|
} |
|
|
|
ctx.textBaseline = "middle"; |
|
|
|
ctx.fillText(data.labels[k],opposite,-adjacent); |
|
|
|
} |
|
ctx.restore(); |
|
}; |
|
function calculateDrawingSizes(){ |
|
maxSize = (Min([width,height])/2); |
|
|
|
labelHeight = config.scaleFontSize*2; |
|
|
|
var labelLength = 0; |
|
for (var i=0; i<data.labels.length; i++){ |
|
ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily; |
|
var textMeasurement = ctx.measureText(data.labels[i]).width; |
|
if(textMeasurement>labelLength) labelLength = textMeasurement; |
|
} |
|
|
|
//Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size. |
|
maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]); |
|
|
|
maxSize -= config.pointLabelFontSize; |
|
maxSize = CapValue(maxSize, null, 0); |
|
scaleHeight = maxSize; |
|
//If the label height is less than 5, set it to 5 so we don't have lines on top of each other. |
|
labelHeight = Default(labelHeight,5); |
|
}; |
|
function getValueBounds() { |
|
var upperValue = Number.MIN_VALUE; |
|
var lowerValue = Number.MAX_VALUE; |
|
|
|
for (var i=0; i<data.datasets.length; i++){ |
|
for (var j=0; j<data.datasets[i].data.length; j++){ |
|
if (data.datasets[i].data[j] > upperValue){upperValue = data.datasets[i].data[j]} |
|
if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]} |
|
} |
|
} |
|
|
|
var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); |
|
var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); |
|
|
|
return { |
|
maxValue : upperValue, |
|
minValue : lowerValue, |
|
maxSteps : maxSteps, |
|
minSteps : minSteps |
|
}; |
|
|
|
|
|
} |
|
} |
|
|
|
var Pie = function(data,config,ctx){ |
|
var segmentTotal = 0; |
|
|
|
//In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. |
|
var pieRadius = Min([height/2,width/2]) - 5; |
|
|
|
for (var i=0; i<data.length; i++){ |
|
segmentTotal += data[i].value; |
|
} |
|
|
|
|
|
animationLoop(config,null,drawPieSegments,ctx); |
|
|
|
function drawPieSegments (animationDecimal){ |
|
var cumulativeAngle = -Math.PI/2, |
|
scaleAnimation = 1, |
|
rotateAnimation = 1; |
|
if (config.animation) { |
|
if (config.animateScale) { |
|
scaleAnimation = animationDecimal; |
|
} |
|
if (config.animateRotate){ |
|
rotateAnimation = animationDecimal; |
|
} |
|
} |
|
for (var i=0; i<data.length; i++){ |
|
var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2)); |
|
ctx.beginPath(); |
|
ctx.arc(width/2,height/2,scaleAnimation * pieRadius,cumulativeAngle,cumulativeAngle + segmentAngle); |
|
ctx.lineTo(width/2,height/2); |
|
ctx.closePath(); |
|
ctx.fillStyle = data[i].color; |
|
ctx.fill(); |
|
|
|
if(config.segmentShowStroke){ |
|
ctx.lineWidth = config.segmentStrokeWidth; |
|
ctx.strokeStyle = config.segmentStrokeColor; |
|
ctx.stroke(); |
|
} |
|
cumulativeAngle += segmentAngle; |
|
} |
|
} |
|
} |
|
|
|
var Doughnut = function(data,config,ctx){ |
|
var segmentTotal = 0; |
|
|
|
//In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. |
|
var doughnutRadius = Min([height/2,width/2]) - 5; |
|
|
|
var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100); |
|
|
|
for (var i=0; i<data.length; i++){ |
|
segmentTotal += data[i].value; |
|
} |
|
|
|
|
|
animationLoop(config,null,drawPieSegments,ctx); |
|
|
|
|
|
function drawPieSegments (animationDecimal){ |
|
var cumulativeAngle = -Math.PI/2, |
|
scaleAnimation = 1, |
|
rotateAnimation = 1; |
|
if (config.animation) { |
|
if (config.animateScale) { |
|
scaleAnimation = animationDecimal; |
|
} |
|
if (config.animateRotate){ |
|
rotateAnimation = animationDecimal; |
|
} |
|
} |
|
for (var i=0; i<data.length; i++){ |
|
var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2)); |
|
ctx.beginPath(); |
|
ctx.arc(width/2,height/2,scaleAnimation * doughnutRadius,cumulativeAngle,cumulativeAngle + segmentAngle,false); |
|
ctx.arc(width/2,height/2,scaleAnimation * cutoutRadius,cumulativeAngle + segmentAngle,cumulativeAngle,true); |
|
ctx.closePath(); |
|
ctx.fillStyle = data[i].color; |
|
ctx.fill(); |
|
|
|
if(config.segmentShowStroke){ |
|
ctx.lineWidth = config.segmentStrokeWidth; |
|
ctx.strokeStyle = config.segmentStrokeColor; |
|
ctx.stroke(); |
|
} |
|
cumulativeAngle += segmentAngle; |
|
} |
|
} |
|
|
|
|
|
|
|
} |
|
|
|
var Line = function(data,config,ctx){ |
|
var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0; |
|
|
|
calculateDrawingSizes(); |
|
|
|
valueBounds = getValueBounds(); |
|
//Check and set the scale |
|
labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; |
|
if (!config.scaleOverride){ |
|
|
|
calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); |
|
} |
|
else { |
|
calculatedScale = { |
|
steps : config.scaleSteps, |
|
stepValue : config.scaleStepWidth, |
|
graphMin : config.scaleStartValue, |
|
labels : [] |
|
} |
|
populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); |
|
} |
|
|
|
scaleHop = Math.floor(scaleHeight/calculatedScale.steps); |
|
calculateXAxisSize(); |
|
animationLoop(config,drawScale,drawLines,ctx); |
|
|
|
function drawLines(animPc){ |
|
for (var i=0; i<data.datasets.length; i++){ |
|
ctx.strokeStyle = data.datasets[i].strokeColor; |
|
ctx.lineWidth = config.datasetStrokeWidth; |
|
ctx.beginPath(); |
|
ctx.moveTo(yAxisPosX, xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop))) |
|
|
|
for (var j=1; j<data.datasets[i].data.length; j++){ |
|
if (config.bezierCurve){ |
|
ctx.bezierCurveTo(xPos(j-0.5),yPos(i,j-1),xPos(j-0.5),yPos(i,j),xPos(j),yPos(i,j)); |
|
} |
|
else{ |
|
ctx.lineTo(xPos(j),yPos(i,j)); |
|
} |
|
} |
|
ctx.stroke(); |
|
if (config.datasetFill){ |
|
ctx.lineTo(yAxisPosX + (valueHop*(data.datasets[i].data.length-1)),xAxisPosY); |
|
ctx.lineTo(yAxisPosX,xAxisPosY); |
|
ctx.closePath(); |
|
ctx.fillStyle = data.datasets[i].fillColor; |
|
ctx.fill(); |
|
} |
|
else{ |
|
ctx.closePath(); |
|
} |
|
if(config.pointDot){ |
|
ctx.fillStyle = data.datasets[i].pointColor; |
|
ctx.strokeStyle = data.datasets[i].pointStrokeColor; |
|
ctx.lineWidth = config.pointDotStrokeWidth; |
|
for (var k=0; k<data.datasets[i].data.length; k++){ |
|
ctx.beginPath(); |
|
ctx.arc(yAxisPosX + (valueHop *k),xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,0,Math.PI*2,true); |
|
ctx.fill(); |
|
ctx.stroke(); |
|
} |
|
} |
|
} |
|
|
|
function yPos(dataSet,iteration){ |
|
return xAxisPosY - animPc*(calculateOffset(data.datasets[dataSet].data[iteration],calculatedScale,scaleHop)); |
|
} |
|
function xPos(iteration){ |
|
return yAxisPosX + (valueHop * iteration); |
|
} |
|
} |
|
function drawScale(){ |
|
//X axis line |
|
ctx.lineWidth = config.scaleLineWidth; |
|
ctx.strokeStyle = config.scaleLineColor; |
|
ctx.beginPath(); |
|
ctx.moveTo(width-widestXLabel/2+5,xAxisPosY); |
|
ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY); |
|
ctx.stroke(); |
|
|
|
|
|
if (rotateLabels > 0){ |
|
ctx.save(); |
|
ctx.textAlign = "right"; |
|
} |
|
else{ |
|
ctx.textAlign = "center"; |
|
} |
|
ctx.fillStyle = config.scaleFontColor; |
|
for (var i=0; i<data.labels.length; i++){ |
|
ctx.save(); |
|
if (rotateLabels > 0){ |
|
ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); |
|
ctx.rotate(-(rotateLabels * (Math.PI/180))); |
|
ctx.fillText(data.labels[i], 0,0); |
|
ctx.restore(); |
|
} |
|
|
|
else{ |
|
ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3); |
|
} |
|
|
|
ctx.beginPath(); |
|
ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3); |
|
|
|
//Check i isnt 0, so we dont go over the Y axis twice. |
|
if(config.scaleShowGridLines && i>0){ |
|
ctx.lineWidth = config.scaleGridLineWidth; |
|
ctx.strokeStyle = config.scaleGridLineColor; |
|
ctx.lineTo(yAxisPosX + i * valueHop, 5); |
|
} |
|
else{ |
|
ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3); |
|
} |
|
ctx.stroke(); |
|
} |
|
|
|
//Y axis |
|
ctx.lineWidth = config.scaleLineWidth; |
|
ctx.strokeStyle = config.scaleLineColor; |
|
ctx.beginPath(); |
|
ctx.moveTo(yAxisPosX,xAxisPosY+5); |
|
ctx.lineTo(yAxisPosX,5); |
|
ctx.stroke(); |
|
|
|
ctx.textAlign = "right"; |
|
ctx.textBaseline = "middle"; |
|
for (var j=0; j<calculatedScale.steps; j++){ |
|
ctx.beginPath(); |
|
ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop)); |
|
if (config.scaleShowGridLines){ |
|
ctx.lineWidth = config.scaleGridLineWidth; |
|
ctx.strokeStyle = config.scaleGridLineColor; |
|
ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop)); |
|
} |
|
else{ |
|
ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop)); |
|
} |
|
|
|
ctx.stroke(); |
|
|
|
if (config.scaleShowLabels){ |
|
ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop)); |
|
} |
|
} |
|
|
|
|
|
} |
|
function calculateXAxisSize(){ |
|
var longestText = 1; |
|
//if we are showing the labels |
|
if (config.scaleShowLabels){ |
|
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; |
|
for (var i=0; i<calculatedScale.labels.length; i++){ |
|
var measuredText = ctx.measureText(calculatedScale.labels[i]).width; |
|
longestText = (measuredText > longestText)? measuredText : longestText; |
|
} |
|
//Add a little extra padding from the y axis |
|
longestText +=10; |
|
} |
|
xAxisLength = width - longestText - widestXLabel; |
|
valueHop = Math.floor(xAxisLength/(data.labels.length-1)); |
|
|
|
yAxisPosX = width-widestXLabel/2-xAxisLength; |
|
xAxisPosY = scaleHeight + config.scaleFontSize/2; |
|
} |
|
function calculateDrawingSizes(){ |
|
maxSize = height; |
|
|
|
//Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. |
|
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; |
|
widestXLabel = 1; |
|
for (var i=0; i<data.labels.length; i++){ |
|
var textLength = ctx.measureText(data.labels[i]).width; |
|
//If the text length is longer - make that equal to longest text! |
|
widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel; |
|
} |
|
if (width/data.labels.length < widestXLabel){ |
|
rotateLabels = 45; |
|
if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ |
|
rotateLabels = 90; |
|
maxSize -= widestXLabel; |
|
} |
|
else{ |
|
maxSize -= Math.sin(rotateLabels) * widestXLabel; |
|
} |
|
} |
|
else{ |
|
maxSize -= config.scaleFontSize; |
|
} |
|
|
|
//Add a little padding between the x line and the text |
|
maxSize -= 5; |
|
|
|
|
|
labelHeight = config.scaleFontSize; |
|
|
|
maxSize -= labelHeight; |
|
//Set 5 pixels greater than the font size to allow for a little padding from the X axis. |
|
|
|
scaleHeight = maxSize; |
|
|
|
//Then get the area above we can safely draw on. |
|
|
|
} |
|
function getValueBounds() { |
|
var upperValue = Number.MIN_VALUE; |
|
var lowerValue = Number.MAX_VALUE; |
|
for (var i=0; i<data.datasets.length; i++){ |
|
for (var j=0; j<data.datasets[i].data.length; j++){ |
|
if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] }; |
|
if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; |
|
} |
|
}; |
|
|
|
var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); |
|
var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); |
|
|
|
return { |
|
maxValue : upperValue, |
|
minValue : lowerValue, |
|
maxSteps : maxSteps, |
|
minSteps : minSteps |
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
var Bar = function(data,config,ctx){ |
|
var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0; |
|
|
|
calculateDrawingSizes(); |
|
|
|
valueBounds = getValueBounds(); |
|
//Check and set the scale |
|
labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; |
|
if (!config.scaleOverride){ |
|
|
|
calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); |
|
} |
|
else { |
|
calculatedScale = { |
|
steps : config.scaleSteps, |
|
stepValue : config.scaleStepWidth, |
|
graphMin : config.scaleStartValue, |
|
labels : [] |
|
} |
|
populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); |
|
} |
|
|
|
scaleHop = Math.floor(scaleHeight/calculatedScale.steps); |
|
calculateXAxisSize(); |
|
animationLoop(config,drawScale,drawBars,ctx); |
|
|
|
function drawBars(animPc){ |
|
ctx.lineWidth = config.barStrokeWidth; |
|
for (var i=0; i<data.datasets.length; i++){ |
|
ctx.fillStyle = data.datasets[i].fillColor; |
|
ctx.strokeStyle = data.datasets[i].strokeColor; |
|
for (var j=0; j<data.datasets[i].data.length; j++){ |
|
var barOffset = yAxisPosX + config.barValueSpacing + valueHop*j + barWidth*i + config.barDatasetSpacing*i + config.barStrokeWidth*i; |
|
|
|
ctx.beginPath(); |
|
ctx.moveTo(barOffset, xAxisPosY); |
|
ctx.lineTo(barOffset, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2)); |
|
ctx.lineTo(barOffset + barWidth, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2)); |
|
ctx.lineTo(barOffset + barWidth, xAxisPosY); |
|
if(config.barShowStroke){ |
|
ctx.stroke(); |
|
} |
|
ctx.closePath(); |
|
ctx.fill(); |
|
} |
|
} |
|
|
|
} |
|
function drawScale(){ |
|
//X axis line |
|
ctx.lineWidth = config.scaleLineWidth; |
|
ctx.strokeStyle = config.scaleLineColor; |
|
ctx.beginPath(); |
|
ctx.moveTo(width-widestXLabel/2+5,xAxisPosY); |
|
ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY); |
|
ctx.stroke(); |
|
|
|
|
|
if (rotateLabels > 0){ |
|
ctx.save(); |
|
ctx.textAlign = "right"; |
|
} |
|
else{ |
|
ctx.textAlign = "center"; |
|
} |
|
ctx.fillStyle = config.scaleFontColor; |
|
for (var i=0; i<data.labels.length; i++){ |
|
ctx.save(); |
|
if (rotateLabels > 0){ |
|
ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); |
|
ctx.rotate(-(rotateLabels * (Math.PI/180))); |
|
ctx.fillText(data.labels[i], 0,0); |
|
ctx.restore(); |
|
} |
|
|
|
else{ |
|
ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3); |
|
} |
|
|
|
ctx.beginPath(); |
|
ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3); |
|
|
|
//Check i isnt 0, so we dont go over the Y axis twice. |
|
ctx.lineWidth = config.scaleGridLineWidth; |
|
ctx.strokeStyle = config.scaleGridLineColor; |
|
ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5); |
|
ctx.stroke(); |
|
} |
|
|
|
//Y axis |
|
ctx.lineWidth = config.scaleLineWidth; |
|
ctx.strokeStyle = config.scaleLineColor; |
|
ctx.beginPath(); |
|
ctx.moveTo(yAxisPosX,xAxisPosY+5); |
|
ctx.lineTo(yAxisPosX,5); |
|
ctx.stroke(); |
|
|
|
ctx.textAlign = "right"; |
|
ctx.textBaseline = "middle"; |
|
for (var j=0; j<calculatedScale.steps; j++){ |
|
ctx.beginPath(); |
|
ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop)); |
|
if (config.scaleShowGridLines){ |
|
ctx.lineWidth = config.scaleGridLineWidth; |
|
ctx.strokeStyle = config.scaleGridLineColor; |
|
ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop)); |
|
} |
|
else{ |
|
ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop)); |
|
} |
|
|
|
ctx.stroke(); |
|
if (config.scaleShowLabels){ |
|
ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop)); |
|
} |
|
} |
|
|
|
|
|
} |
|
function calculateXAxisSize(){ |
|
var longestText = 1; |
|
//if we are showing the labels |
|
if (config.scaleShowLabels){ |
|
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; |
|
for (var i=0; i<calculatedScale.labels.length; i++){ |
|
var measuredText = ctx.measureText(calculatedScale.labels[i]).width; |
|
longestText = (measuredText > longestText)? measuredText : longestText; |
|
} |
|
//Add a little extra padding from the y axis |
|
longestText +=10; |
|
} |
|
xAxisLength = width - longestText - widestXLabel; |
|
valueHop = Math.floor(xAxisLength/(data.labels.length)); |
|
|
|
barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length; |
|
|
|
yAxisPosX = width-widestXLabel/2-xAxisLength; |
|
xAxisPosY = scaleHeight + config.scaleFontSize/2; |
|
} |
|
function calculateDrawingSizes(){ |
|
maxSize = height; |
|
|
|
//Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. |
|
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; |
|
widestXLabel = 1; |
|
for (var i=0; i<data.labels.length; i++){ |
|
var textLength = ctx.measureText(data.labels[i]).width; |
|
//If the text length is longer - make that equal to longest text! |
|
widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel; |
|
} |
|
if (width/data.labels.length < widestXLabel){ |
|
rotateLabels = 45; |
|
if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ |
|
rotateLabels = 90; |
|
maxSize -= widestXLabel; |
|
} |
|
else{ |
|
maxSize -= Math.sin(rotateLabels) * widestXLabel; |
|
} |
|
} |
|
else{ |
|
maxSize -= config.scaleFontSize; |
|
} |
|
|
|
//Add a little padding between the x line and the text |
|
maxSize -= 5; |
|
|
|
|
|
labelHeight = config.scaleFontSize; |
|
|
|
maxSize -= labelHeight; |
|
//Set 5 pixels greater than the font size to allow for a little padding from the X axis. |
|
|
|
scaleHeight = maxSize; |
|
|
|
//Then get the area above we can safely draw on. |
|
|
|
} |
|
function getValueBounds() { |
|
var upperValue = Number.MIN_VALUE; |
|
var lowerValue = Number.MAX_VALUE; |
|
for (var i=0; i<data.datasets.length; i++){ |
|
for (var j=0; j<data.datasets[i].data.length; j++){ |
|
if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] }; |
|
if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; |
|
} |
|
}; |
|
|
|
var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); |
|
var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); |
|
|
|
return { |
|
maxValue : upperValue, |
|
minValue : lowerValue, |
|
maxSteps : maxSteps, |
|
minSteps : minSteps |
|
}; |
|
|
|
|
|
} |
|
} |
|
|
|
function calculateOffset(val,calculatedScale,scaleHop){ |
|
var outerValue = calculatedScale.steps * calculatedScale.stepValue; |
|
var adjustedValue = val - calculatedScale.graphMin; |
|
var scalingFactor = CapValue(adjustedValue/outerValue,1,0); |
|
return (scaleHop*calculatedScale.steps) * scalingFactor; |
|
} |
|
|
|
function animationLoop(config,drawScale,drawData,ctx){ |
|
var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1, |
|
easingFunction = animationOptions[config.animationEasing], |
|
percentAnimComplete =(config.animation)? 0 : 1; |
|
|
|
|
|
|
|
if (typeof drawScale !== "function") drawScale = function(){}; |
|
|
|
requestAnimFrame(animLoop); |
|
|
|
function animateFrame(){ |
|
var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1; |
|
clear(ctx); |
|
if(config.scaleOverlay){ |
|
drawData(easeAdjustedAnimationPercent); |
|
drawScale(); |
|
} else { |
|
drawScale(); |
|
drawData(easeAdjustedAnimationPercent); |
|
} |
|
} |
|
function animLoop(){ |
|
//We need to check if the animation is incomplete (less than 1), or complete (1). |
|
percentAnimComplete += animFrameAmount; |
|
animateFrame(); |
|
//Stop the loop continuing forever |
|
if (percentAnimComplete <= 1){ |
|
requestAnimFrame(animLoop); |
|
} |
|
else{ |
|
if (typeof config.onAnimationComplete == "function") config.onAnimationComplete(); |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
//Declare global functions to be called within this namespace here. |
|
|
|
|
|
// shim layer with setTimeout fallback |
|
var requestAnimFrame = (function(){ |
|
return window.requestAnimationFrame || |
|
window.webkitRequestAnimationFrame || |
|
window.mozRequestAnimationFrame || |
|
window.oRequestAnimationFrame || |
|
window.msRequestAnimationFrame || |
|
function(callback) { |
|
window.setTimeout(callback, 1000 / 60); |
|
}; |
|
})(); |
|
|
|
function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){ |
|
var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum; |
|
|
|
valueRange = maxValue - minValue; |
|
|
|
rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); |
|
|
|
graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); |
|
|
|
graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); |
|
|
|
graphRange = graphMax - graphMin; |
|
|
|
stepValue = Math.pow(10, rangeOrderOfMagnitude); |
|
|
|
numberOfSteps = Math.round(graphRange / stepValue); |
|
|
|
//Compare number of steps to the max and min for that size graph, and add in half steps if need be. |
|
while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { |
|
if (numberOfSteps < minSteps){ |
|
stepValue /= 2; |
|
numberOfSteps = Math.round(graphRange/stepValue); |
|
} |
|
else{ |
|
stepValue *=2; |
|
numberOfSteps = Math.round(graphRange/stepValue); |
|
} |
|
}; |
|
|
|
var labels = []; |
|
populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue); |
|
|
|
return { |
|
steps : numberOfSteps, |
|
stepValue : stepValue, |
|
graphMin : graphMin, |
|
labels : labels |
|
|
|
} |
|
|
|
function calculateOrderOfMagnitude(val){ |
|
return Math.floor(Math.log(val) / Math.LN10); |
|
} |
|
|
|
|
|
} |
|
|
|
//Populate an array of all the labels by interpolating the string. |
|
function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) { |
|
if (labelTemplateString) { |
|
//Fix floating point errors by setting to fixed the on the same decimal as the stepValue. |
|
for (var i = 1; i < numberOfSteps + 1; i++) { |
|
labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))})); |
|
} |
|
} |
|
} |
|
|
|
//Max value from array |
|
function Max( array ){ |
|
return Math.max.apply( Math, array ); |
|
}; |
|
//Min value from array |
|
function Min( array ){ |
|
return Math.min.apply( Math, array ); |
|
}; |
|
//Default if undefined |
|
function Default(userDeclared,valueIfFalse){ |
|
if(!userDeclared){ |
|
return valueIfFalse; |
|
} else { |
|
return userDeclared; |
|
} |
|
}; |
|
//Is a number function |
|
function isNumber(n) { |
|
return !isNaN(parseFloat(n)) && isFinite(n); |
|
} |
|
//Apply cap a value at a high or low number |
|
function CapValue(valueToCap, maxValue, minValue){ |
|
if(isNumber(maxValue)) { |
|
if( valueToCap > maxValue ) { |
|
return maxValue; |
|
} |
|
} |
|
if(isNumber(minValue)){ |
|
if ( valueToCap < minValue ){ |
|
return minValue; |
|
} |
|
} |
|
return valueToCap; |
|
} |
|
function getDecimalPlaces (num){ |
|
var numberOfDecimalPlaces; |
|
if (num%1!=0){ |
|
return num.toString().split(".")[1].length |
|
} |
|
else{ |
|
return 0; |
|
} |
|
|
|
} |
|
|
|
function mergeChartConfig(defaults,userDefined){ |
|
var returnObj = {}; |
|
for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; } |
|
for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; } |
|
return returnObj; |
|
} |
|
|
|
//Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ |
|
var cache = {}; |
|
|
|
function tmpl(str, data){ |
|
// Figure out if we're getting a template, or if we need to |
|
// load the template - and be sure to cache the result. |
|
var fn = !/\W/.test(str) ? |
|
cache[str] = cache[str] || |
|
tmpl(document.getElementById(str).innerHTML) : |
|
|
|
// Generate a reusable function that will serve as a template |
|
// generator (and which will be cached). |
|
new Function("obj", |
|
"var p=[],print=function(){p.push.apply(p,arguments);};" + |
|
|
|
// Introduce the data as local variables using with(){} |
|
"with(obj){p.push('" + |
|
|
|
// Convert the template into pure JavaScript |
|
str |
|
.replace(/[\r\t\n]/g, " ") |
|
.split("<%").join("\t") |
|
.replace(/((^|%>)[^\t]*)'/g, "$1\r") |
|
.replace(/\t=(.*?)%>/g, "',$1,'") |
|
.split("\t").join("');") |
|
.split("%>").join("p.push('") |
|
.split("\r").join("\\'") |
|
+ "');}return p.join('');"); |
|
|
|
// Provide some basic currying to the user |
|
return data ? fn( data ) : fn; |
|
}; |
|
} |
|
|
|
|
|
|