JAVASCRIPT   14

Chart.js

Guest on 9th May 2021 04:28:49 PM

  1. /*!
  2.  * Chart.js
  3.  * http://chartjs.org/
  4.  *
  5.  * Copyright 2013 Nick Downie
  6.  * Released under the MIT license
  7.  * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
  8.  */
  9.  
  10. //Define the global Chart Variable as a class.
  11. window.Chart = function(context){
  12.  
  13.       var chart = this;
  14.      
  15.      
  16.       //Easing functions adapted from Robert Penner's easing equations
  17.       //http://www.robertpenner.com/easing/
  18.      
  19.       var animationOptions = {
  20.             linear : function (t){
  21.                   return t;
  22.             },
  23.             easeInQuad: function (t) {
  24.                   return t*t;
  25.             },
  26.             easeOutQuad: function (t) {
  27.                   return -1 *t*(t-2);
  28.             },
  29.             easeInOutQuad: function (t) {
  30.                   if ((t/=1/2) < 1) return 1/2*t*t;
  31.                   return -1/2 * ((--t)*(t-2) - 1);
  32.             },
  33.             easeInCubic: function (t) {
  34.                   return t*t*t;
  35.             },
  36.             easeOutCubic: function (t) {
  37.                   return 1*((t=t/1-1)*t*t + 1);
  38.             },
  39.             easeInOutCubic: function (t) {
  40.                   if ((t/=1/2) < 1) return 1/2*t*t*t;
  41.                   return 1/2*((t-=2)*t*t + 2);
  42.             },
  43.             easeInQuart: function (t) {
  44.                   return t*t*t*t;
  45.             },
  46.             easeOutQuart: function (t) {
  47.                   return -1 * ((t=t/1-1)*t*t*t - 1);
  48.             },
  49.             easeInOutQuart: function (t) {
  50.                   if ((t/=1/2) < 1) return 1/2*t*t*t*t;
  51.                   return -1/2 * ((t-=2)*t*t*t - 2);
  52.             },
  53.             easeInQuint: function (t) {
  54.                   return 1*(t/=1)*t*t*t*t;
  55.             },
  56.             easeOutQuint: function (t) {
  57.                   return 1*((t=t/1-1)*t*t*t*t + 1);
  58.             },
  59.             easeInOutQuint: function (t) {
  60.                   if ((t/=1/2) < 1) return 1/2*t*t*t*t*t;
  61.                   return 1/2*((t-=2)*t*t*t*t + 2);
  62.             },
  63.             easeInSine: function (t) {
  64.                   return -1 * Math.cos(t/1 * (Math.PI/2)) + 1;
  65.             },
  66.             easeOutSine: function (t) {
  67.                   return 1 * Math.sin(t/1 * (Math.PI/2));
  68.             },
  69.             easeInOutSine: function (t) {
  70.                   return -1/2 * (Math.cos(Math.PI*t/1) - 1);
  71.             },
  72.             easeInExpo: function (t) {
  73.                   return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1));
  74.             },
  75.             easeOutExpo: function (t) {
  76.                   return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1);
  77.             },
  78.             easeInOutExpo: function (t) {
  79.                   if (t==0) return 0;
  80.                   if (t==1) return 1;
  81.                   if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1));
  82.                   return 1/2 * (-Math.pow(2, -10 * --t) + 2);
  83.                   },
  84.             easeInCirc: function (t) {
  85.                   if (t>=1) return t;
  86.                   return -1 * (Math.sqrt(1 - (t/=1)*t) - 1);
  87.             },
  88.             easeOutCirc: function (t) {
  89.                   return 1 * Math.sqrt(1 - (t=t/1-1)*t);
  90.             },
  91.             easeInOutCirc: function (t) {
  92.                   if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1);
  93.                   return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1);
  94.             },
  95.             easeInElastic: function (t) {
  96.                   var s=1.70158;var p=0;var a=1;
  97.                   if (t==0) return 0;  if ((t/=1)==1) return 1;  if (!p) p=1*.3;
  98.                   if (a < Math.abs(1)) { a=1; var s=p/4; }
  99.                   else var s = p/(2*Math.PI) * Math.asin (1/a);
  100.                   return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
  101.             },
  102.             easeOutElastic: function (t) {
  103.                   var s=1.70158;var p=0;var a=1;
  104.                   if (t==0) return 0;  if ((t/=1)==1) return 1;  if (!p) p=1*.3;
  105.                   if (a < Math.abs(1)) { a=1; var s=p/4; }
  106.                   else var s = p/(2*Math.PI) * Math.asin (1/a);
  107.                   return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1;
  108.             },
  109.             easeInOutElastic: function (t) {
  110.                   var s=1.70158;var p=0;var a=1;
  111.                   if (t==0) return 0;  if ((t/=1/2)==2) return 1;  if (!p) p=1*(.3*1.5);
  112.                   if (a < Math.abs(1)) { a=1; var s=p/4; }
  113.                   else var s = p/(2*Math.PI) * Math.asin (1/a);
  114.                   if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
  115.                   return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1;
  116.             },
  117.             easeInBack: function (t) {
  118.                   var s = 1.70158;
  119.                   return 1*(t/=1)*t*((s+1)*t - s);
  120.             },
  121.             easeOutBack: function (t) {
  122.                   var s = 1.70158;
  123.                   return 1*((t=t/1-1)*t*((s+1)*t + s) + 1);
  124.             },
  125.             easeInOutBack: function (t) {
  126.                   var s = 1.70158;
  127.                   if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s));
  128.                   return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2);
  129.             },
  130.             easeInBounce: function (t) {
  131.                   return 1 - animationOptions.easeOutBounce (1-t);
  132.             },
  133.             easeOutBounce: function (t) {
  134.                   if ((t/=1) < (1/2.75)) {
  135.                         return 1*(7.5625*t*t);
  136.                   } else if (t < (2/2.75)) {
  137.                         return 1*(7.5625*(t-=(1.5/2.75))*t + .75);
  138.                   } else if (t < (2.5/2.75)) {
  139.                         return 1*(7.5625*(t-=(2.25/2.75))*t + .9375);
  140.                   } else {
  141.                         return 1*(7.5625*(t-=(2.625/2.75))*t + .984375);
  142.                   }
  143.             },
  144.             easeInOutBounce: function (t) {
  145.                   if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5;
  146.                   return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5;
  147.             }
  148.       };
  149.  
  150.       //Variables global to the chart
  151.       var width = context.canvas.width;
  152.       var height = context.canvas.height;
  153.  
  154.  
  155.       //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
  156.       if (window.devicePixelRatio) {
  157.             context.canvas.style.width = width + "px";
  158.             context.canvas.style.height = height + "px";
  159.             context.canvas.height = height * window.devicePixelRatio;
  160.             context.canvas.width = width * window.devicePixelRatio;
  161.             context.scale(window.devicePixelRatio, window.devicePixelRatio);
  162.       }
  163.  
  164.       this.PolarArea = function(data,options){
  165.      
  166.             chart.PolarArea.defaults = {
  167.                   scaleOverlay : true,
  168.                   scaleOverride : false,
  169.                   scaleSteps : null,
  170.                   scaleStepWidth : null,
  171.                   scaleStartValue : null,
  172.                   scaleShowLine : true,
  173.                   scaleLineColor : "rgba(0,0,0,.1)",
  174.                   scaleLineWidth : 1,
  175.                   scaleShowLabels : true,
  176.                   scaleLabel : "<%=value%>",
  177.                   scaleFontFamily : "'Arial'",
  178.                   scaleFontSize : 12,
  179.                   scaleFontStyle : "normal",
  180.                   scaleFontColor : "#666",
  181.                   scaleShowLabelBackdrop : true,
  182.                   scaleBackdropColor : "rgba(255,255,255,0.75)",
  183.                   scaleBackdropPaddingY : 2,
  184.                   scaleBackdropPaddingX : 2,
  185.                   segmentShowStroke : true,
  186.                   segmentStrokeColor : "#fff",
  187.                   segmentStrokeWidth : 2,
  188.                   animation : true,
  189.                   animationSteps : 100,
  190.                   animationEasing : "easeOutBounce",
  191.                   animateRotate : true,
  192.                   animateScale : false,
  193.                   onAnimationComplete : null
  194.             };
  195.            
  196.             var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults;
  197.            
  198.             return new PolarArea(data,config,context);
  199.       };
  200.  
  201.       this.Radar = function(data,options){
  202.      
  203.             chart.Radar.defaults = {
  204.                   scaleOverlay : false,
  205.                   scaleOverride : false,
  206.                   scaleSteps : null,
  207.                   scaleStepWidth : null,
  208.                   scaleStartValue : null,
  209.                   scaleShowLine : true,
  210.                   scaleLineColor : "rgba(0,0,0,.1)",
  211.                   scaleLineWidth : 1,
  212.                   scaleShowLabels : false,
  213.                   scaleLabel : "<%=value%>",
  214.                   scaleFontFamily : "'Arial'",
  215.                   scaleFontSize : 12,
  216.                   scaleFontStyle : "normal",
  217.                   scaleFontColor : "#666",
  218.                   scaleShowLabelBackdrop : true,
  219.                   scaleBackdropColor : "rgba(255,255,255,0.75)",
  220.                   scaleBackdropPaddingY : 2,
  221.                   scaleBackdropPaddingX : 2,
  222.                   angleShowLineOut : true,
  223.                   angleLineColor : "rgba(0,0,0,.1)",
  224.                   angleLineWidth : 1,                
  225.                   pointLabelFontFamily : "'Arial'",
  226.                   pointLabelFontStyle : "normal",
  227.                   pointLabelFontSize : 12,
  228.                   pointLabelFontColor : "#666",
  229.                   pointDot : true,
  230.                   pointDotRadius : 3,
  231.                   pointDotStrokeWidth : 1,
  232.                   datasetStroke : true,
  233.                   datasetStrokeWidth : 2,
  234.                   datasetFill : true,
  235.                   animation : true,
  236.                   animationSteps : 60,
  237.                   animationEasing : "easeOutQuart",
  238.                   onAnimationComplete : null
  239.             };
  240.            
  241.             var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults;
  242.  
  243.             return new Radar(data,config,context);
  244.       };
  245.      
  246.       this.Pie = function(data,options){
  247.             chart.Pie.defaults = {
  248.                   segmentShowStroke : true,
  249.                   segmentStrokeColor : "#fff",
  250.                   segmentStrokeWidth : 2,
  251.                   animation : true,
  252.                   animationSteps : 100,
  253.                   animationEasing : "easeOutBounce",
  254.                   animateRotate : true,
  255.                   animateScale : false,
  256.                   onAnimationComplete : null
  257.             };         
  258.  
  259.             var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults;
  260.            
  261.             return new Pie(data,config,context);                       
  262.       };
  263.      
  264.       this.Doughnut = function(data,options){
  265.      
  266.             chart.Doughnut.defaults = {
  267.                   segmentShowStroke : true,
  268.                   segmentStrokeColor : "#fff",
  269.                   segmentStrokeWidth : 2,
  270.                   percentageInnerCutout : 50,
  271.                   animation : true,
  272.                   animationSteps : 100,
  273.                   animationEasing : "easeOutBounce",
  274.                   animateRotate : true,
  275.                   animateScale : false,
  276.                   onAnimationComplete : null
  277.             };         
  278.  
  279.             var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults;
  280.            
  281.             return new Doughnut(data,config,context);            
  282.            
  283.       };
  284.  
  285.       this.Line = function(data,options){
  286.      
  287.             chart.Line.defaults = {
  288.                   scaleOverlay : false,
  289.                   scaleOverride : false,
  290.                   scaleSteps : null,
  291.                   scaleStepWidth : null,
  292.                   scaleStartValue : null,
  293.                   scaleLineColor : "#ffffff",
  294.                   scaleLineWidth : 1,
  295.                   scaleShowLabels : true,
  296.                   scaleLabel : "<%=value%>",
  297.                   scaleFontFamily : "'Arial'",
  298.                   scaleFontSize : 12,
  299.                   scaleFontStyle : "normal",
  300.                   scaleFontColor : "#ffffff",
  301.                   scaleShowGridLines : false,
  302.                   scaleGridLineColor : "#ffffff",
  303.                   scaleGridLineWidth : 1,
  304.                   bezierCurve : true,
  305.                   pointDot : true,
  306.                   pointDotRadius : 4,
  307.                   pointDotStrokeWidth : 2,
  308.                   datasetStroke : true,
  309.                   datasetStrokeWidth : 2,
  310.                   datasetFill : true,
  311.                   animation : true,
  312.                   animationSteps : 60,
  313.                   animationEasing : "easeOutQuart",
  314.                   onAnimationComplete : null
  315.             };         
  316.             var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults;
  317.            
  318.             return new Line(data,config,context);
  319.       }
  320.      
  321.       this.Bar = function(data,options){
  322.             chart.Bar.defaults = {
  323.                   scaleOverlay : false,
  324.                   scaleOverride : false,
  325.                   scaleSteps : null,
  326.                   scaleStepWidth : null,
  327.                   scaleStartValue : null,
  328.                   scaleLineColor : "#fff",
  329.                   scaleLineWidth : 1,
  330.                   scaleShowLabels : true,
  331.                   scaleLabel : "<%=value%>",
  332.                   scaleFontFamily : "'Arial'",
  333.                   scaleFontSize : 12,
  334.                   scaleFontStyle : "normal",
  335.                   scaleFontColor : "#fff",
  336.                   scaleShowGridLines : false,
  337.                   scaleGridLineColor : "#fff",
  338.                   scaleGridLineWidth : 1,
  339.                   barShowStroke : true,
  340.                   barStrokeWidth : 2,
  341.                   barValueSpacing : 1,
  342.                   barDatasetSpacing : 1,
  343.                   animation : true,
  344.                   animationSteps : 60,
  345.                   animationEasing : "easeOutQuart",
  346.                   onAnimationComplete : null
  347.             };         
  348.             var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults;
  349.            
  350.             return new Bar(data,config,context);           
  351.       }
  352.      
  353.       var clear = function(c){
  354.             c.clearRect(0, 0, width, height);
  355.       };
  356.  
  357.       var PolarArea = function(data,config,ctx){
  358.             var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;        
  359.            
  360.            
  361.             calculateDrawingSizes();
  362.            
  363.             valueBounds = getValueBounds();
  364.  
  365.             labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
  366.  
  367.             //Check and set the scale
  368.             if (!config.scaleOverride){
  369.                  
  370.                   calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
  371.             }
  372.             else {
  373.                   calculatedScale = {
  374.                         steps : config.scaleSteps,
  375.                         stepValue : config.scaleStepWidth,
  376.                         graphMin : config.scaleStartValue,
  377.                         labels : []
  378.                   }
  379.                   populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
  380.             }
  381.            
  382.             scaleHop = maxSize/(calculatedScale.steps);
  383.  
  384.             //Wrap in an animation loop wrapper
  385.             animationLoop(config,drawScale,drawAllSegments,ctx);
  386.  
  387.             function calculateDrawingSizes(){
  388.                   maxSize = (Min([width,height])/2);
  389.                   //Remove whatever is larger - the font size or line width.
  390.                  
  391.                   maxSize -= Max([config.scaleFontSize*0.5,config.scaleLineWidth*0.5]);
  392.                  
  393.                   labelHeight = config.scaleFontSize*2;
  394.                   //If we're drawing the backdrop - add the Y padding to the label height and remove from drawing region.
  395.                   if (config.scaleShowLabelBackdrop){
  396.                         labelHeight += (2 * config.scaleBackdropPaddingY);
  397.                         maxSize -= config.scaleBackdropPaddingY*1.5;
  398.                   }
  399.                  
  400.                   scaleHeight = maxSize;
  401.                   //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
  402.                   labelHeight = Default(labelHeight,5);
  403.             }
  404.             function drawScale(){
  405.                   for (var i=0; i<calculatedScale.steps; i++){
  406.                         //If the line object is there
  407.                         if (config.scaleShowLine){
  408.                               ctx.beginPath();
  409.                               ctx.arc(width/2, height/2, scaleHop * (i + 1), 0, (Math.PI * 2), true);
  410.                               ctx.strokeStyle = config.scaleLineColor;
  411.                               ctx.lineWidth = config.scaleLineWidth;
  412.                               ctx.stroke();
  413.                         }
  414.  
  415.                         if (config.scaleShowLabels){
  416.                               ctx.textAlign = "center";
  417.                               ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
  418.                               var label =  calculatedScale.labels[i];
  419.                               //If the backdrop object is within the font object
  420.                               if (config.scaleShowLabelBackdrop){
  421.                                     var textWidth = ctx.measureText(label).width;
  422.                                     ctx.fillStyle = config.scaleBackdropColor;
  423.                                     ctx.beginPath();
  424.                                     ctx.rect(
  425.                                           Math.round(width/2 - textWidth/2 - config.scaleBackdropPaddingX),     //X
  426.                                           Math.round(height/2 - (scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
  427.                                           Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
  428.                                           Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
  429.                                     );
  430.                                     ctx.fill();
  431.                               }
  432.                               ctx.textBaseline = "middle";
  433.                               ctx.fillStyle = config.scaleFontColor;
  434.                               ctx.fillText(label,width/2,height/2 - (scaleHop * (i + 1)));
  435.                         }
  436.                   }
  437.             }
  438.             function drawAllSegments(animationDecimal){
  439.                   var startAngle = -Math.PI/2,
  440.                   angleStep = (Math.PI*2)/data.length,
  441.                   scaleAnimation = 1,
  442.                   rotateAnimation = 1;
  443.                   if (config.animation) {
  444.                         if (config.animateScale) {
  445.                               scaleAnimation = animationDecimal;
  446.                         }
  447.                         if (config.animateRotate){
  448.                               rotateAnimation = animationDecimal;
  449.                         }
  450.                   }
  451.  
  452.                   for (var i=0; i<data.length; i++){
  453.  
  454.                         ctx.beginPath();
  455.                         ctx.arc(width/2,height/2,scaleAnimation * calculateOffset(data[i].value,calculatedScale,scaleHop),startAngle, startAngle + rotateAnimation*angleStep, false);
  456.                         ctx.lineTo(width/2,height/2);
  457.                         ctx.closePath();
  458.                         ctx.fillStyle = data[i].color;
  459.                         ctx.fill();
  460.  
  461.                         if(config.segmentShowStroke){
  462.                               ctx.strokeStyle = config.segmentStrokeColor;
  463.                               ctx.lineWidth = config.segmentStrokeWidth;
  464.                               ctx.stroke();
  465.                         }
  466.                         startAngle += rotateAnimation*angleStep;
  467.                   }
  468.             }
  469.             function getValueBounds() {
  470.                   var upperValue = Number.MIN_VALUE;
  471.                   var lowerValue = Number.MAX_VALUE;
  472.                   for (var i=0; i<data.length; i++){
  473.                         if (data[i].value > upperValue) {upperValue = data[i].value;}
  474.                         if (data[i].value < lowerValue) {lowerValue = data[i].value;}
  475.                   };
  476.  
  477.                   var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
  478.                   var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
  479.                  
  480.                   return {
  481.                         maxValue : upperValue,
  482.                         minValue : lowerValue,
  483.                         maxSteps : maxSteps,
  484.                         minSteps : minSteps
  485.                   };
  486.                  
  487.  
  488.             }
  489.       }
  490.  
  491.       var Radar = function (data,config,ctx) {
  492.             var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;  
  493.                  
  494.             //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up.
  495.             if (!data.labels) data.labels = [];
  496.            
  497.             calculateDrawingSizes();
  498.  
  499.             var valueBounds = getValueBounds();
  500.  
  501.             labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
  502.  
  503.             //Check and set the scale
  504.             if (!config.scaleOverride){
  505.                  
  506.                   calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
  507.             }
  508.             else {
  509.                   calculatedScale = {
  510.                         steps : config.scaleSteps,
  511.                         stepValue : config.scaleStepWidth,
  512.                         graphMin : config.scaleStartValue,
  513.                         labels : []
  514.                   }
  515.                   populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
  516.             }
  517.            
  518.             scaleHop = maxSize/(calculatedScale.steps);
  519.            
  520.             animationLoop(config,drawScale,drawAllDataPoints,ctx);
  521.            
  522.             //Radar specific functions.
  523.             function drawAllDataPoints(animationDecimal){
  524.                   var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
  525.  
  526.                   ctx.save();
  527.                   //translate to the centre of the canvas.
  528.                   ctx.translate(width/2,height/2);
  529.                  
  530.                   //We accept multiple data sets for radar charts, so show loop through each set
  531.                   for (var i=0; i<data.datasets.length; i++){
  532.                         ctx.beginPath();
  533.  
  534.                         ctx.moveTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)));
  535.                         for (var j=1; j<data.datasets[i].data.length; j++){
  536.                               ctx.rotate(rotationDegree);  
  537.                               ctx.lineTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)));
  538.                  
  539.                         }
  540.                         ctx.closePath();
  541.                        
  542.                        
  543.                         ctx.fillStyle = data.datasets[i].fillColor;
  544.                         ctx.strokeStyle = data.datasets[i].strokeColor;
  545.                         ctx.lineWidth = config.datasetStrokeWidth;
  546.                         ctx.fill();
  547.                         ctx.stroke();
  548.                        
  549.                                                
  550.                         if (config.pointDot){
  551.                               ctx.fillStyle = data.datasets[i].pointColor;
  552.                               ctx.strokeStyle = data.datasets[i].pointStrokeColor;
  553.                               ctx.lineWidth = config.pointDotStrokeWidth;
  554.                               for (var k=0; k<data.datasets[i].data.length; k++){
  555.                                     ctx.rotate(rotationDegree);
  556.                                     ctx.beginPath();
  557.                                     ctx.arc(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,2*Math.PI,false);
  558.                                     ctx.fill();
  559.                                     ctx.stroke();
  560.                               }                            
  561.                              
  562.                         }
  563.                         ctx.rotate(rotationDegree);
  564.                        
  565.                   }
  566.                   ctx.restore();
  567.                  
  568.                  
  569.             }
  570.             function drawScale(){
  571.                   var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
  572.                   ctx.save();
  573.                 ctx.translate(width / 2, height / 2);
  574.                  
  575.                   if (config.angleShowLineOut){
  576.                         ctx.strokeStyle = config.angleLineColor;                 
  577.                         ctx.lineWidth = config.angleLineWidth;
  578.                         for (var h=0; h<data.datasets[0].data.length; h++){
  579.                              
  580.                             ctx.rotate(rotationDegree);
  581.                               ctx.beginPath();
  582.                               ctx.moveTo(0,0);
  583.                               ctx.lineTo(0,-maxSize);
  584.                               ctx.stroke();
  585.                         }
  586.                   }
  587.  
  588.                   for (var i=0; i<calculatedScale.steps; i++){
  589.                         ctx.beginPath();
  590.                        
  591.                         if(config.scaleShowLine){
  592.                               ctx.strokeStyle = config.scaleLineColor;
  593.                               ctx.lineWidth = config.scaleLineWidth;
  594.                               ctx.moveTo(0,-scaleHop * (i+1));                           
  595.                               for (var j=0; j<data.datasets[0].data.length; j++){
  596.                                   ctx.rotate(rotationDegree);
  597.                                     ctx.lineTo(0,-scaleHop * (i+1));
  598.                               }
  599.                               ctx.closePath();
  600.                               ctx.stroke();                
  601.                                          
  602.                         }
  603.                        
  604.                         if (config.scaleShowLabels){                   
  605.                               ctx.textAlign = 'center';
  606.                               ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
  607.                               ctx.textBaseline = "middle";
  608.                              
  609.                               if (config.scaleShowLabelBackdrop){
  610.                                     var textWidth = ctx.measureText(calculatedScale.labels[i]).width;
  611.                                     ctx.fillStyle = config.scaleBackdropColor;
  612.                                     ctx.beginPath();
  613.                                     ctx.rect(
  614.                                           Math.round(- textWidth/2 - config.scaleBackdropPaddingX),     //X
  615.                                           Math.round((-scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
  616.                                           Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
  617.                                           Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
  618.                                     );
  619.                                     ctx.fill();
  620.                               }                                  
  621.                               ctx.fillStyle = config.scaleFontColor;
  622.                               ctx.fillText(calculatedScale.labels[i],0,-scaleHop*(i+1));
  623.                         }
  624.  
  625.                   }
  626.                   for (var k=0; k<data.labels.length; k++){                  
  627.                   ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
  628.                   ctx.fillStyle = config.pointLabelFontColor;
  629.                         var opposite = Math.sin(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
  630.                         var adjacent = Math.cos(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
  631.                        
  632.                         if(rotationDegree*k == Math.PI || rotationDegree*k == 0){
  633.                               ctx.textAlign = "center";
  634.                         }
  635.                         else if(rotationDegree*k > Math.PI){
  636.                               ctx.textAlign = "right";
  637.                         }
  638.                         else{
  639.                               ctx.textAlign = "left";
  640.                         }
  641.                        
  642.                         ctx.textBaseline = "middle";
  643.                        
  644.                         ctx.fillText(data.labels[k],opposite,-adjacent);
  645.                        
  646.                   }
  647.                   ctx.restore();
  648.             };
  649.             function calculateDrawingSizes(){
  650.                   maxSize = (Min([width,height])/2);
  651.  
  652.                   labelHeight = config.scaleFontSize*2;
  653.                  
  654.                   var labelLength = 0;
  655.                   for (var i=0; i<data.labels.length; i++){
  656.                         ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
  657.                         var textMeasurement = ctx.measureText(data.labels[i]).width;
  658.                         if(textMeasurement>labelLength) labelLength = textMeasurement;
  659.                   }
  660.                  
  661.                   //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.
  662.                   maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]);                       
  663.                  
  664.                   maxSize -= config.pointLabelFontSize;
  665.                   maxSize = CapValue(maxSize, null, 0);
  666.                   scaleHeight = maxSize;
  667.                   //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
  668.                   labelHeight = Default(labelHeight,5);
  669.             };
  670.             function getValueBounds() {
  671.                   var upperValue = Number.MIN_VALUE;
  672.                   var lowerValue = Number.MAX_VALUE;
  673.                  
  674.                   for (var i=0; i<data.datasets.length; i++){
  675.                         for (var j=0; j<data.datasets[i].data.length; j++){
  676.                               if (data.datasets[i].data[j] > upperValue){upperValue = data.datasets[i].data[j]}
  677.                               if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]}
  678.                         }
  679.                   }
  680.  
  681.                   var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
  682.                   var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
  683.                  
  684.                   return {
  685.                         maxValue : upperValue,
  686.                         minValue : lowerValue,
  687.                         maxSteps : maxSteps,
  688.                         minSteps : minSteps
  689.                   };
  690.                  
  691.  
  692.             }
  693.       }
  694.  
  695.       var Pie = function(data,config,ctx){
  696.             var segmentTotal = 0;
  697.            
  698.             //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
  699.             var pieRadius = Min([height/2,width/2]) - 5;
  700.            
  701.             for (var i=0; i<data.length; i++){
  702.                   segmentTotal += data[i].value;
  703.             }
  704.            
  705.            
  706.             animationLoop(config,null,drawPieSegments,ctx);
  707.                        
  708.             function drawPieSegments (animationDecimal){
  709.                   var cumulativeAngle = -Math.PI/2,
  710.                   scaleAnimation = 1,
  711.                   rotateAnimation = 1;
  712.                   if (config.animation) {
  713.                         if (config.animateScale) {
  714.                               scaleAnimation = animationDecimal;
  715.                         }
  716.                         if (config.animateRotate){
  717.                               rotateAnimation = animationDecimal;
  718.                         }
  719.                   }
  720.                   for (var i=0; i<data.length; i++){
  721.                         var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
  722.                         ctx.beginPath();
  723.                         ctx.arc(width/2,height/2,scaleAnimation * pieRadius,cumulativeAngle,cumulativeAngle + segmentAngle);
  724.                         ctx.lineTo(width/2,height/2);
  725.                         ctx.closePath();
  726.                         ctx.fillStyle = data[i].color;
  727.                         ctx.fill();
  728.                        
  729.                         if(config.segmentShowStroke){
  730.                               ctx.lineWidth = config.segmentStrokeWidth;
  731.                               ctx.strokeStyle = config.segmentStrokeColor;
  732.                               ctx.stroke();
  733.                         }
  734.                         cumulativeAngle += segmentAngle;
  735.                   }                
  736.             }          
  737.       }
  738.  
  739.       var Doughnut = function(data,config,ctx){
  740.             var segmentTotal = 0;
  741.            
  742.             //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
  743.             var doughnutRadius = Min([height/2,width/2]) - 5;
  744.            
  745.             var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100);
  746.            
  747.             for (var i=0; i<data.length; i++){
  748.                   segmentTotal += data[i].value;
  749.             }
  750.            
  751.            
  752.             animationLoop(config,null,drawPieSegments,ctx);
  753.            
  754.            
  755.             function drawPieSegments (animationDecimal){
  756.                   var cumulativeAngle = -Math.PI/2,
  757.                   scaleAnimation = 1,
  758.                   rotateAnimation = 1;
  759.                   if (config.animation) {
  760.                         if (config.animateScale) {
  761.                               scaleAnimation = animationDecimal;
  762.                         }
  763.                         if (config.animateRotate){
  764.                               rotateAnimation = animationDecimal;
  765.                         }
  766.                   }
  767.                   for (var i=0; i<data.length; i++){
  768.                         var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
  769.                         ctx.beginPath();
  770.                         ctx.arc(width/2,height/2,scaleAnimation * doughnutRadius,cumulativeAngle,cumulativeAngle + segmentAngle,false);
  771.                         ctx.arc(width/2,height/2,scaleAnimation * cutoutRadius,cumulativeAngle + segmentAngle,cumulativeAngle,true);
  772.                         ctx.closePath();
  773.                         ctx.fillStyle = data[i].color;
  774.                         ctx.fill();
  775.                        
  776.                         if(config.segmentShowStroke){
  777.                               ctx.lineWidth = config.segmentStrokeWidth;
  778.                               ctx.strokeStyle = config.segmentStrokeColor;
  779.                               ctx.stroke();
  780.                         }
  781.                         cumulativeAngle += segmentAngle;
  782.                   }                
  783.             }                
  784.            
  785.            
  786.            
  787.       }
  788.  
  789.       var Line = function(data,config,ctx){
  790.             var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0;
  791.                  
  792.             calculateDrawingSizes();
  793.            
  794.             valueBounds = getValueBounds();
  795.             //Check and set the scale
  796.             labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
  797.             if (!config.scaleOverride){
  798.                  
  799.                   calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
  800.             }
  801.             else {
  802.                   calculatedScale = {
  803.                         steps : config.scaleSteps,
  804.                         stepValue : config.scaleStepWidth,
  805.                         graphMin : config.scaleStartValue,
  806.                         labels : []
  807.                   }
  808.                   populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
  809.             }
  810.            
  811.             scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
  812.             calculateXAxisSize();
  813.             animationLoop(config,drawScale,drawLines,ctx);       
  814.            
  815.             function drawLines(animPc){
  816.                   for (var i=0; i<data.datasets.length; i++){
  817.                         ctx.strokeStyle = data.datasets[i].strokeColor;
  818.                         ctx.lineWidth = config.datasetStrokeWidth;
  819.                         ctx.beginPath();
  820.                         ctx.moveTo(yAxisPosX, xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)))
  821.  
  822.                         for (var j=1; j<data.datasets[i].data.length; j++){
  823.                               if (config.bezierCurve){
  824.                                     ctx.bezierCurveTo(xPos(j-0.5),yPos(i,j-1),xPos(j-0.5),yPos(i,j),xPos(j),yPos(i,j));
  825.                               }
  826.                               else{
  827.                                     ctx.lineTo(xPos(j),yPos(i,j));
  828.                               }
  829.                         }
  830.                         ctx.stroke();
  831.                         if (config.datasetFill){
  832.                               ctx.lineTo(yAxisPosX + (valueHop*(data.datasets[i].data.length-1)),xAxisPosY);
  833.                               ctx.lineTo(yAxisPosX,xAxisPosY);
  834.                               ctx.closePath();
  835.                               ctx.fillStyle = data.datasets[i].fillColor;
  836.                               ctx.fill();
  837.                         }
  838.                         else{
  839.                               ctx.closePath();
  840.                         }
  841.                         if(config.pointDot){
  842.                               ctx.fillStyle = data.datasets[i].pointColor;
  843.                               ctx.strokeStyle = data.datasets[i].pointStrokeColor;
  844.                               ctx.lineWidth = config.pointDotStrokeWidth;
  845.                               for (var k=0; k<data.datasets[i].data.length; k++){
  846.                                     ctx.beginPath();
  847.                                     ctx.arc(yAxisPosX + (valueHop *k),xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,0,Math.PI*2,true);
  848.                                     ctx.fill();
  849.                                     ctx.stroke();
  850.                               }
  851.                         }
  852.                   }
  853.                  
  854.                   function yPos(dataSet,iteration){
  855.                         return xAxisPosY - animPc*(calculateOffset(data.datasets[dataSet].data[iteration],calculatedScale,scaleHop));                
  856.                   }
  857.                   function xPos(iteration){
  858.                         return yAxisPosX + (valueHop * iteration);
  859.                   }
  860.             }
  861.             function drawScale(){
  862.                   //X axis line
  863.                   ctx.lineWidth = config.scaleLineWidth;
  864.                   ctx.strokeStyle = config.scaleLineColor;
  865.                   ctx.beginPath();
  866.                   ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
  867.                   ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
  868.                   ctx.stroke();
  869.                  
  870.                  
  871.                   if (rotateLabels > 0){
  872.                         ctx.save();
  873.                         ctx.textAlign = "right";
  874.                   }
  875.                   else{
  876.                         ctx.textAlign = "center";
  877.                   }
  878.                   ctx.fillStyle = config.scaleFontColor;
  879.                   for (var i=0; i<data.labels.length; i++){
  880.                         ctx.save();
  881.                         if (rotateLabels > 0){
  882.                               ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
  883.                               ctx.rotate(-(rotateLabels * (Math.PI/180)));
  884.                               ctx.fillText(data.labels[i], 0,0);
  885.                               ctx.restore();
  886.                         }
  887.                        
  888.                         else{
  889.                               ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3);                         
  890.                         }
  891.  
  892.                         ctx.beginPath();
  893.                         ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3);
  894.                        
  895.                         //Check i isnt 0, so we dont go over the Y axis twice.
  896.                         if(config.scaleShowGridLines && i>0){
  897.                               ctx.lineWidth = config.scaleGridLineWidth;
  898.                               ctx.strokeStyle = config.scaleGridLineColor;                           
  899.                               ctx.lineTo(yAxisPosX + i * valueHop, 5);
  900.                         }
  901.                         else{
  902.                               ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3);                     
  903.                         }
  904.                         ctx.stroke();
  905.                   }
  906.                  
  907.                   //Y axis
  908.                   ctx.lineWidth = config.scaleLineWidth;
  909.                   ctx.strokeStyle = config.scaleLineColor;
  910.                   ctx.beginPath();
  911.                   ctx.moveTo(yAxisPosX,xAxisPosY+5);
  912.                   ctx.lineTo(yAxisPosX,5);
  913.                   ctx.stroke();
  914.                  
  915.                   ctx.textAlign = "right";
  916.                   ctx.textBaseline = "middle";
  917.                   for (var j=0; j<calculatedScale.steps; j++){
  918.                         ctx.beginPath();
  919.                         ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
  920.                         if (config.scaleShowGridLines){
  921.                               ctx.lineWidth = config.scaleGridLineWidth;
  922.                               ctx.strokeStyle = config.scaleGridLineColor;
  923.                               ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));                        
  924.                         }
  925.                         else{
  926.                               ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
  927.                         }
  928.                        
  929.                         ctx.stroke();
  930.                        
  931.                         if (config.scaleShowLabels){
  932.                               ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
  933.                         }
  934.                   }
  935.                  
  936.                  
  937.             }
  938.             function calculateXAxisSize(){
  939.                   var longestText = 1;
  940.                   //if we are showing the labels
  941.                   if (config.scaleShowLabels){
  942.                         ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
  943.                         for (var i=0; i<calculatedScale.labels.length; i++){
  944.                               var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
  945.                               longestText = (measuredText > longestText)? measuredText : longestText;
  946.                         }
  947.                         //Add a little extra padding from the y axis
  948.                         longestText +=10;
  949.                   }
  950.                   xAxisLength = width - longestText - widestXLabel;
  951.                   valueHop = Math.floor(xAxisLength/(data.labels.length-1)); 
  952.                        
  953.                   yAxisPosX = width-widestXLabel/2-xAxisLength;
  954.                   xAxisPosY = scaleHeight + config.scaleFontSize/2;                      
  955.             }          
  956.             function calculateDrawingSizes(){
  957.                   maxSize = height;
  958.  
  959.                   //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.
  960.                   ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
  961.                   widestXLabel = 1;
  962.                   for (var i=0; i<data.labels.length; i++){
  963.                         var textLength = ctx.measureText(data.labels[i]).width;
  964.                         //If the text length is longer - make that equal to longest text!
  965.                         widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
  966.                   }
  967.                   if (width/data.labels.length < widestXLabel){
  968.                         rotateLabels = 45;
  969.                         if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
  970.                               rotateLabels = 90;
  971.                               maxSize -= widestXLabel;
  972.                         }
  973.                         else{
  974.                               maxSize -= Math.sin(rotateLabels) * widestXLabel;
  975.                         }
  976.                   }
  977.                   else{
  978.                         maxSize -= config.scaleFontSize;
  979.                   }
  980.                  
  981.                   //Add a little padding between the x line and the text
  982.                   maxSize -= 5;
  983.                  
  984.                  
  985.                   labelHeight = config.scaleFontSize;
  986.                  
  987.                   maxSize -= labelHeight;
  988.                   //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
  989.                  
  990.                   scaleHeight = maxSize;
  991.                  
  992.                   //Then get the area above we can safely draw on.
  993.                  
  994.             }          
  995.             function getValueBounds() {
  996.                   var upperValue = Number.MIN_VALUE;
  997.                   var lowerValue = Number.MAX_VALUE;
  998.                   for (var i=0; i<data.datasets.length; i++){
  999.                         for (var j=0; j<data.datasets[i].data.length; j++){
  1000.                               if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
  1001.                               if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
  1002.                         }
  1003.                   };
  1004.      
  1005.                   var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
  1006.                   var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
  1007.                  
  1008.                   return {
  1009.                         maxValue : upperValue,
  1010.                         minValue : lowerValue,
  1011.                         maxSteps : maxSteps,
  1012.                         minSteps : minSteps
  1013.                   };
  1014.                  
  1015.      
  1016.             }
  1017.  
  1018.            
  1019.       }
  1020.      
  1021.       var Bar = function(data,config,ctx){
  1022.             var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0;
  1023.                  
  1024.             calculateDrawingSizes();
  1025.            
  1026.             valueBounds = getValueBounds();
  1027.             //Check and set the scale
  1028.             labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
  1029.             if (!config.scaleOverride){
  1030.                  
  1031.                   calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
  1032.             }
  1033.             else {
  1034.                   calculatedScale = {
  1035.                         steps : config.scaleSteps,
  1036.                         stepValue : config.scaleStepWidth,
  1037.                         graphMin : config.scaleStartValue,
  1038.                         labels : []
  1039.                   }
  1040.                   populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
  1041.             }
  1042.            
  1043.             scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
  1044.             calculateXAxisSize();
  1045.             animationLoop(config,drawScale,drawBars,ctx);        
  1046.            
  1047.             function drawBars(animPc){
  1048.                   ctx.lineWidth = config.barStrokeWidth;
  1049.                   for (var i=0; i<data.datasets.length; i++){
  1050.                               ctx.fillStyle = data.datasets[i].fillColor;
  1051.                               ctx.strokeStyle = data.datasets[i].strokeColor;
  1052.                         for (var j=0; j<data.datasets[i].data.length; j++){
  1053.                               var barOffset = yAxisPosX + config.barValueSpacing + valueHop*j + barWidth*i + config.barDatasetSpacing*i + config.barStrokeWidth*i;
  1054.                              
  1055.                               ctx.beginPath();
  1056.                               ctx.moveTo(barOffset, xAxisPosY);
  1057.                               ctx.lineTo(barOffset, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
  1058.                               ctx.lineTo(barOffset + barWidth, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
  1059.                               ctx.lineTo(barOffset + barWidth, xAxisPosY);
  1060.                               if(config.barShowStroke){
  1061.                                     ctx.stroke();
  1062.                               }
  1063.                               ctx.closePath();
  1064.                               ctx.fill();
  1065.                         }
  1066.                   }
  1067.                  
  1068.             }
  1069.             function drawScale(){
  1070.                   //X axis line
  1071.                   ctx.lineWidth = config.scaleLineWidth;
  1072.                   ctx.strokeStyle = config.scaleLineColor;
  1073.                   ctx.beginPath();
  1074.                   ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
  1075.                   ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
  1076.                   ctx.stroke();
  1077.                  
  1078.                  
  1079.                   if (rotateLabels > 0){
  1080.                         ctx.save();
  1081.                         ctx.textAlign = "right";
  1082.                   }
  1083.                   else{
  1084.                         ctx.textAlign = "center";
  1085.                   }
  1086.                   ctx.fillStyle = config.scaleFontColor;
  1087.                   for (var i=0; i<data.labels.length; i++){
  1088.                         ctx.save();
  1089.                         if (rotateLabels > 0){
  1090.                               ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
  1091.                               ctx.rotate(-(rotateLabels * (Math.PI/180)));
  1092.                               ctx.fillText(data.labels[i], 0,0);
  1093.                               ctx.restore();
  1094.                         }
  1095.                        
  1096.                         else{
  1097.                               ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3);                        
  1098.                         }
  1099.  
  1100.                         ctx.beginPath();
  1101.                         ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3);
  1102.                        
  1103.                         //Check i isnt 0, so we dont go over the Y axis twice.
  1104.                               ctx.lineWidth = config.scaleGridLineWidth;
  1105.                               ctx.strokeStyle = config.scaleGridLineColor;                           
  1106.                               ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5);
  1107.                         ctx.stroke();
  1108.                   }
  1109.                  
  1110.                   //Y axis
  1111.                   ctx.lineWidth = config.scaleLineWidth;
  1112.                   ctx.strokeStyle = config.scaleLineColor;
  1113.                   ctx.beginPath();
  1114.                   ctx.moveTo(yAxisPosX,xAxisPosY+5);
  1115.                   ctx.lineTo(yAxisPosX,5);
  1116.                   ctx.stroke();
  1117.                  
  1118.                   ctx.textAlign = "right";
  1119.                   ctx.textBaseline = "middle";
  1120.                   for (var j=0; j<calculatedScale.steps; j++){
  1121.                         ctx.beginPath();
  1122.                         ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
  1123.                         if (config.scaleShowGridLines){
  1124.                               ctx.lineWidth = config.scaleGridLineWidth;
  1125.                               ctx.strokeStyle = config.scaleGridLineColor;
  1126.                               ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));                        
  1127.                         }
  1128.                         else{
  1129.                               ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
  1130.                         }
  1131.                        
  1132.                         ctx.stroke();
  1133.                         if (config.scaleShowLabels){
  1134.                               ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
  1135.                         }
  1136.                   }
  1137.                  
  1138.                  
  1139.             }
  1140.             function calculateXAxisSize(){
  1141.                   var longestText = 1;
  1142.                   //if we are showing the labels
  1143.                   if (config.scaleShowLabels){
  1144.                         ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
  1145.                         for (var i=0; i<calculatedScale.labels.length; i++){
  1146.                               var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
  1147.                               longestText = (measuredText > longestText)? measuredText : longestText;
  1148.                         }
  1149.                         //Add a little extra padding from the y axis
  1150.                         longestText +=10;
  1151.                   }
  1152.                   xAxisLength = width - longestText - widestXLabel;
  1153.                   valueHop = Math.floor(xAxisLength/(data.labels.length));   
  1154.                  
  1155.                   barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length;
  1156.                  
  1157.                   yAxisPosX = width-widestXLabel/2-xAxisLength;
  1158.                   xAxisPosY = scaleHeight + config.scaleFontSize/2;                      
  1159.             }          
  1160.             function calculateDrawingSizes(){
  1161.                   maxSize = height;
  1162.  
  1163.                   //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.
  1164.                   ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
  1165.                   widestXLabel = 1;
  1166.                   for (var i=0; i<data.labels.length; i++){
  1167.                         var textLength = ctx.measureText(data.labels[i]).width;
  1168.                         //If the text length is longer - make that equal to longest text!
  1169.                         widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
  1170.                   }
  1171.                   if (width/data.labels.length < widestXLabel){
  1172.                         rotateLabels = 45;
  1173.                         if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
  1174.                               rotateLabels = 90;
  1175.                               maxSize -= widestXLabel;
  1176.                         }
  1177.                         else{
  1178.                               maxSize -= Math.sin(rotateLabels) * widestXLabel;
  1179.                         }
  1180.                   }
  1181.                   else{
  1182.                         maxSize -= config.scaleFontSize;
  1183.                   }
  1184.                  
  1185.                   //Add a little padding between the x line and the text
  1186.                   maxSize -= 5;
  1187.                  
  1188.                  
  1189.                   labelHeight = config.scaleFontSize;
  1190.                  
  1191.                   maxSize -= labelHeight;
  1192.                   //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
  1193.                  
  1194.                   scaleHeight = maxSize;
  1195.                  
  1196.                   //Then get the area above we can safely draw on.
  1197.                  
  1198.             }          
  1199.             function getValueBounds() {
  1200.                   var upperValue = Number.MIN_VALUE;
  1201.                   var lowerValue = Number.MAX_VALUE;
  1202.                   for (var i=0; i<data.datasets.length; i++){
  1203.                         for (var j=0; j<data.datasets[i].data.length; j++){
  1204.                               if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
  1205.                               if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
  1206.                         }
  1207.                   };
  1208.      
  1209.                   var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
  1210.                   var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
  1211.                  
  1212.                   return {
  1213.                         maxValue : upperValue,
  1214.                         minValue : lowerValue,
  1215.                         maxSteps : maxSteps,
  1216.                         minSteps : minSteps
  1217.                   };
  1218.                  
  1219.      
  1220.             }
  1221.       }
  1222.      
  1223.       function calculateOffset(val,calculatedScale,scaleHop){
  1224.             var outerValue = calculatedScale.steps * calculatedScale.stepValue;
  1225.             var adjustedValue = val - calculatedScale.graphMin;
  1226.             var scalingFactor = CapValue(adjustedValue/outerValue,1,0);
  1227.             return (scaleHop*calculatedScale.steps) * scalingFactor;
  1228.       }
  1229.      
  1230.       function animationLoop(config,drawScale,drawData,ctx){
  1231.             var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1,
  1232.                   easingFunction = animationOptions[config.animationEasing],
  1233.                   percentAnimComplete =(config.animation)? 0 : 1;
  1234.            
  1235.      
  1236.            
  1237.             if (typeof drawScale !== "function") drawScale = function(){};
  1238.            
  1239.             requestAnimFrame(animLoop);
  1240.            
  1241.             function animateFrame(){
  1242.                   var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1;
  1243.                   clear(ctx);
  1244.                   if(config.scaleOverlay){
  1245.                         drawData(easeAdjustedAnimationPercent);
  1246.                         drawScale();
  1247.                   } else {
  1248.                         drawScale();
  1249.                         drawData(easeAdjustedAnimationPercent);
  1250.                   }                      
  1251.             }
  1252.             function animLoop(){
  1253.                   //We need to check if the animation is incomplete (less than 1), or complete (1).
  1254.                         percentAnimComplete += animFrameAmount;
  1255.                         animateFrame();  
  1256.                         //Stop the loop continuing forever
  1257.                         if (percentAnimComplete <= 1){
  1258.                               requestAnimFrame(animLoop);
  1259.                         }
  1260.                         else{
  1261.                               if (typeof config.onAnimationComplete == "function") config.onAnimationComplete();
  1262.                         }
  1263.                  
  1264.             }          
  1265.            
  1266.       }
  1267.  
  1268.       //Declare global functions to be called within this namespace here.
  1269.      
  1270.      
  1271.       // shim layer with setTimeout fallback
  1272.       var requestAnimFrame = (function(){
  1273.             return window.requestAnimationFrame ||
  1274.                   window.webkitRequestAnimationFrame ||
  1275.                   window.mozRequestAnimationFrame ||
  1276.                   window.oRequestAnimationFrame ||
  1277.                   window.msRequestAnimationFrame ||
  1278.                   function(callback) {
  1279.                         window.setTimeout(callback, 1000 / 60);
  1280.                   };
  1281.       })();
  1282.  
  1283.       function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){
  1284.                   var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum;
  1285.                  
  1286.                   valueRange = maxValue - minValue;
  1287.                  
  1288.                   rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange);
  1289.  
  1290.             graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
  1291.            
  1292.             graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
  1293.            
  1294.             graphRange = graphMax - graphMin;
  1295.            
  1296.             stepValue = Math.pow(10, rangeOrderOfMagnitude);
  1297.            
  1298.               numberOfSteps = Math.round(graphRange / stepValue);
  1299.              
  1300.               //Compare number of steps to the max and min for that size graph, and add in half steps if need be.        
  1301.               while(numberOfSteps < minSteps || numberOfSteps > maxSteps) {
  1302.                   if (numberOfSteps < minSteps){
  1303.                           stepValue /= 2;
  1304.                           numberOfSteps = Math.round(graphRange/stepValue);
  1305.                     }
  1306.                     else{
  1307.                           stepValue *=2;
  1308.                           numberOfSteps = Math.round(graphRange/stepValue);
  1309.                     }
  1310.               };
  1311.  
  1312.               var labels = [];
  1313.               populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue);
  1314.            
  1315.               return {
  1316.                     steps : numberOfSteps,
  1317.                         stepValue : stepValue,
  1318.                         graphMin : graphMin,
  1319.                         labels : labels                
  1320.                    
  1321.               }
  1322.            
  1323.                   function calculateOrderOfMagnitude(val){
  1324.                     return Math.floor(Math.log(val) / Math.LN10);
  1325.                   }          
  1326.  
  1327.  
  1328.       }
  1329.  
  1330.     //Populate an array of all the labels by interpolating the string.
  1331.     function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) {
  1332.         if (labelTemplateString) {
  1333.             //Fix floating point errors by setting to fixed the on the same decimal as the stepValue.
  1334.             for (var i = 1; i < numberOfSteps + 1; i++) {
  1335.                 labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))}));
  1336.             }
  1337.         }
  1338.     }
  1339.      
  1340.       //Max value from array
  1341.       function Max( array ){
  1342.             return Math.max.apply( Math, array );
  1343.       };
  1344.       //Min value from array
  1345.       function Min( array ){
  1346.             return Math.min.apply( Math, array );
  1347.       };
  1348.       //Default if undefined
  1349.       function Default(userDeclared,valueIfFalse){
  1350.             if(!userDeclared){
  1351.                   return valueIfFalse;
  1352.             } else {
  1353.                   return userDeclared;
  1354.             }
  1355.       };
  1356.       //Is a number function
  1357.       function isNumber(n) {
  1358.             return !isNaN(parseFloat(n)) && isFinite(n);
  1359.       }
  1360.       //Apply cap a value at a high or low number
  1361.       function CapValue(valueToCap, maxValue, minValue){
  1362.             if(isNumber(maxValue)) {
  1363.                   if( valueToCap > maxValue ) {
  1364.                         return maxValue;
  1365.                   }
  1366.             }
  1367.             if(isNumber(minValue)){
  1368.                   if ( valueToCap < minValue ){
  1369.                         return minValue;
  1370.                   }
  1371.             }
  1372.             return valueToCap;
  1373.       }
  1374.       function getDecimalPlaces (num){
  1375.             var numberOfDecimalPlaces;
  1376.             if (num%1!=0){
  1377.                   return num.toString().split(".")[1].length
  1378.             }
  1379.             else{
  1380.                   return 0;
  1381.             }
  1382.            
  1383.       }
  1384.      
  1385.       function mergeChartConfig(defaults,userDefined){
  1386.             var returnObj = {};
  1387.           for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; }
  1388.           for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; }
  1389.           return returnObj;
  1390.       }
  1391.      
  1392.       //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
  1393.         var cache = {};
  1394.        
  1395.         function tmpl(str, data){
  1396.           // Figure out if we're getting a template, or if we need to
  1397.           // load the template - and be sure to cache the result.
  1398.           var fn = !/\W/.test(str) ?
  1399.             cache[str] = cache[str] ||
  1400.               tmpl(document.getElementById(str).innerHTML) :
  1401.            
  1402.             // Generate a reusable function that will serve as a template
  1403.             // generator (and which will be cached).
  1404.             new Function("obj",
  1405.               "var p=[],print=function(){p.push.apply(p,arguments);};" +
  1406.              
  1407.               // Introduce the data as local variables using with(){}
  1408.               "with(obj){p.push('" +
  1409.              
  1410.               // Convert the template into pure JavaScript
  1411.               str
  1412.                 .replace(/[\r\t\n]/g, " ")
  1413.                 .split("<%").join("\t")
  1414.                 .replace(/((^|%>)[^\t]*)'/g, "$1\r")
  1415.                 .replace(/\t=(.*?)%>/g, "',$1,'")
  1416.                 .split("\t").join("');")
  1417.                 .split("%>").join("p.push('")
  1418.                 .split("\r").join("\\'")
  1419.             + "');}return p.join('');");
  1420.          
  1421.           // Provide some basic currying to the user
  1422.           return data ? fn( data ) : fn;
  1423.         };
  1424. }

Raw Paste


Login or Register to edit or fork this paste. It's free.