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