%PDF- %PDF-
| Direktori : /home/tjamichg/intranet.tjamich.gob.mx/intranet/common/vendors/chart.js/ |
| Current File : /home/tjamichg/intranet.tjamich.gob.mx/intranet/common/vendors/chart.js/Chart.js |
/*!
* 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.PolarArea = function (data, options) {
chart.PolarArea.defaults = {
scaleOverlay: true,
scaleOverride: false,
scaleSteps: null,
scaleStepWidth: null,
scaleStartValue: null,
scaleShowLine: true,
scaleLineColor: "rgba(0,0,0,.1)",
scaleLineWidth: 1,
scaleShowLabels: true,
scaleLabel: "<%=value%>",
scaleFontFamily: "'Arial'",
scaleFontSize: 12,
scaleFontStyle: "normal",
scaleFontColor: "#666",
scaleShowLabelBackdrop: true,
scaleBackdropColor: "rgba(255,255,255,0.75)",
scaleBackdropPaddingY: 2,
scaleBackdropPaddingX: 2,
segmentShowStroke: true,
segmentStrokeColor: "#fff",
segmentStrokeWidth: 2,
animation: true,
animationSteps: 100,
animationEasing: "easeOutBounce",
animateRotate: true,
animateScale: false,
onAnimationComplete: null
};
var config = (options) ? mergeChartConfig(chart.PolarArea.defaults, options) : chart.PolarArea.defaults;
return new PolarArea(data, config, context);
};
this.Radar = function (data, options) {
chart.Radar.defaults = {
scaleOverlay: false,
scaleOverride: false,
scaleSteps: null,
scaleStepWidth: null,
scaleStartValue: null,
scaleShowLine: true,
scaleLineColor: "rgba(0,0,0,.1)",
scaleLineWidth: 1,
scaleShowLabels: false,
scaleLabel: "<%=value%>",
scaleFontFamily: "'Arial'",
scaleFontSize: 12,
scaleFontStyle: "normal",
scaleFontColor: "#666",
scaleShowLabelBackdrop: true,
scaleBackdropColor: "rgba(255,255,255,0.75)",
scaleBackdropPaddingY: 2,
scaleBackdropPaddingX: 2,
angleShowLineOut: true,
angleLineColor: "rgba(0,0,0,.1)",
angleLineWidth: 1,
pointLabelFontFamily: "'Arial'",
pointLabelFontStyle: "normal",
pointLabelFontSize: 12,
pointLabelFontColor: "#666",
pointDot: true,
pointDotRadius: 3,
pointDotStrokeWidth: 1,
datasetStroke: true,
datasetStrokeWidth: 2,
datasetFill: true,
animation: true,
animationSteps: 60,
animationEasing: "easeOutQuart",
onAnimationComplete: null
};
var config = (options) ? mergeChartConfig(chart.Radar.defaults, options) : chart.Radar.defaults;
return new Radar(data, config, context);
};
this.Pie = function (data, options) {
chart.Pie.defaults = {
segmentShowStroke: true,
segmentStrokeColor: "#fff",
segmentStrokeWidth: 2,
animation: true,
animationSteps: 100,
animationEasing: "easeOutBounce",
animateRotate: true,
animateScale: false,
onAnimationComplete: null
};
var config = (options) ? mergeChartConfig(chart.Pie.defaults, options) : chart.Pie.defaults;
return new Pie(data, config, context);
};
this.Doughnut = function (data, options) {
chart.Doughnut.defaults = {
segmentShowStroke: true,
segmentStrokeColor: "#fff",
segmentStrokeWidth: 2,
percentageInnerCutout: 50,
animation: true,
animationSteps: 100,
animationEasing: "easeOutBounce",
animateRotate: true,
animateScale: false,
onAnimationComplete: null
};
var config = (options) ? mergeChartConfig(chart.Doughnut.defaults, options) : chart.Doughnut.defaults;
return new Doughnut(data, config, context);
};
this.Line = function (data, options) {
chart.Line.defaults = {
scaleOverlay: false,
scaleOverride: false,
scaleSteps: null,
scaleStepWidth: null,
scaleStartValue: null,
scaleLineColor: "rgba(0,0,0,.1)",
scaleLineWidth: 1,
scaleShowLabels: true,
scaleLabel: "<%=value%>",
scaleFontFamily: "'Arial'",
scaleFontSize: 12,
scaleFontStyle: "normal",
scaleFontColor: "#666",
scaleShowGridLines: true,
scaleGridLineColor: "rgba(0,0,0,.05)",
scaleGridLineWidth: 1,
bezierCurve: true,
pointDot: true,
pointDotRadius: 4,
pointDotStrokeWidth: 2,
datasetStroke: true,
datasetStrokeWidth: 2,
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);
}
this.Bar = function (data, options) {
chart.Bar.defaults = {
scaleOverlay: false,
scaleOverride: false,
scaleSteps: null,
scaleStepWidth: null,
scaleStartValue: null,
scaleLineColor: "rgba(0,0,0,.1)",
scaleLineWidth: 1,
scaleShowLabels: true,
scaleLabel: "<%=value%>",
scaleFontFamily: "'Arial'",
scaleFontSize: 12,
scaleFontStyle: "normal",
scaleFontColor: "#666",
scaleShowGridLines: true,
scaleGridLineColor: "rgba(0,0,0,.05)",
scaleGridLineWidth: 1,
barShowStroke: true,
barStrokeWidth: 2,
barValueSpacing: 5,
barDatasetSpacing: 1,
animation: true,
animationSteps: 60,
animationEasing: "easeOutQuart",
onAnimationComplete: null
};
var config = (options) ? mergeChartConfig(chart.Bar.defaults, options) : chart.Bar.defaults;
return new Bar(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;
};
}