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