1// Copyright 2006 Google Inc. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15 16// Known Issues: 17// 18// * Patterns only support repeat. 19// * Radial gradient are not implemented. The VML version of these look very 20// different from the canvas one. 21// * Clipping paths are not implemented. 22// * Coordsize. The width and height attribute have higher priority than the 23// width and height style values which isn't correct. 24// * Painting mode isn't implemented. 25// * Canvas width/height should is using content-box by default. IE in 26// Quirks mode will draw the canvas using border-box. Either change your 27// doctype to HTML5 28// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) 29// or use Box Sizing Behavior from WebFX 30// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) 31// * Non uniform scaling does not correctly scale strokes
vendor: 24,458 bytes, lines 31-880
31. 32// * Optimize. There is always room for speed improvements. 33 34// Only add this code if we do not already have a canvas implementation 35if (!document.createElement('canvas').getContext) { 36 37(function() { 38 39 // alias some functions to make (compiled) code shorter 40 var m = Math; 41 var mr = m.round; 42 var ms = m.sin; 43 var mc = m.cos; 44 var abs = m.abs; 45 var sqrt = m.sqrt; 46 47 // this is used for sub pixel precision 48 var Z = 10; 49 var Z2 = Z / 2; 50 51 var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1]; 52 53 /** 54 * This funtion is assigned to the <canvas> elements as element.getContext(). 55 * @this {HTMLElement} 56 * @return {CanvasRenderingContext2D_} 57 */ 58 function getContext() { 59 return this.context_ || 60 (this.context_ = new CanvasRenderingContext2D_(this)); 61 } 62 63 var slice = Array.prototype.slice; 64 65 /** 66 * Binds a function to an object. The returned function will always use the 67 * passed in {@code obj} as {@code this}. 68 * 69 * Example: 70 * 71 * g = bind(f, obj, a, b) 72 * g(c, d) // will do f.call(obj, a, b, c, d) 73 * 74 * @param {Function} f The function to bind the object to 75 * @param {Object} obj The object that should act as this when the function 76 * is called 77 * @param {*} var_args Rest arguments that will be used as the initial 78 * arguments when the function is called 79 * @return {Function} A new function that has bound this 80 */ 81 function bind(f, obj, var_args) { 82 var a = slice.call(arguments, 2); 83 return function() { 84 return f.apply(obj, a.concat(slice.call(arguments))); 85 }; 86 } 87 88 function encodeHtmlAttribute(s) { 89 return String(s).replace(/&/g, '&').replace(/"/g, '"'); 90 } 91 92 function addNamespace(doc, prefix, urn) { 93 if (!doc.namespaces[prefix]) { 94 doc.namespaces.add(prefix, urn, '#default#VML'); 95 } 96 } 97 98 function addNamespacesAndStylesheet(doc) { 99 addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml'); 100 addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office'); 101 102 // Setup default CSS. Only add one style sheet per document 103 if (!doc.styleSheets['ex_canvas_']) { 104 var ss = doc.createStyleSheet(); 105 ss.owningElement.id = 'ex_canvas_'; 106 ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + 107 // default size is 300x150 in Gecko and Opera 108 'text-align:left;width:300px;height:150px}'; 109 } 110 } 111 112 // Add namespaces and stylesheet at startup. 113 addNamespacesAndStylesheet(document); 114 115 var G_vmlCanvasManager_ = { 116 init: function(opt_doc) { 117 var doc = opt_doc || document; 118 // Create a dummy element so that IE will allow canvas elements to be 119 // recognized. 120 doc.createElement('canvas'); 121 doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); 122 }, 123 124 init_: function(doc) { 125 // find all canvas elements 126 var els = doc.getElementsByTagName('canvas'); 127 for (var i = 0; i < els.length; i++) { 128 this.initElement(els[i]); 129 } 130 }, 131 132 /** 133 * Public initializes a canvas element so that it can be used as canvas 134 * element from now on. This is called automatically before the page is 135 * loaded but if you are creating elements using createElement you need to 136 * make sure this is called on the element. 137 * @param {HTMLElement} el The canvas element to initialize. 138 * @return {HTMLElement} the element that was created. 139 */ 140 initElement: function(el) { 141 if (!el.getContext) { 142 el.getContext = getContext; 143 144 // Add namespaces and stylesheet to document of the element. 145 addNamespacesAndStylesheet(el.ownerDocument); 146 147 // Remove fallback content. There is no way to hide text nodes so we 148 // just remove all childNodes. We could hide all elements and remove 149 // text nodes but who really cares about the fallback content. 150 el.innerHTML = ''; 151 152 // do not use inline function because that will leak memory 153 el.attachEvent('onpropertychange', onPropertyChange); 154 el.attachEvent('onresize', onResize); 155 156 var attrs = el.attributes; 157 if (attrs.width && attrs.width.specified) { 158 // TODO: use runtimeStyle and coordsize 159 // el.getContext().setWidth_(attrs.width.nodeValue); 160 el.style.width = attrs.width.nodeValue + 'px'; 161 } else { 162 el.width = el.clientWidth; 163 } 164 if (attrs.height && attrs.height.specified) { 165 // TODO: use runtimeStyle and coordsize 166 // el.getContext().setHeight_(attrs.height.nodeValue); 167 el.style.height = attrs.height.nodeValue + 'px'; 168 } else { 169 el.height = el.clientHeight; 170 } 171 //el.getContext().setCoordsize_() 172 } 173 return el; 174 } 175 }; 176 177 function onPropertyChange(e) { 178 var el = e.srcElement; 179 180 switch (e.propertyName) { 181 case 'width': 182 el.getContext().clearRect(); 183 el.style.width = el.attributes.width.nodeValue + 'px'; 184 // In IE8 this does not trigger onresize. 185 el.firstChild.style.width = el.clientWidth + 'px'; 186 break; 187 case 'height': 188 el.getContext().clearRect(); 189 el.style.height = el.attributes.height.nodeValue + 'px'; 190 el.firstChild.style.height = el.clientHeight + 'px'; 191 break; 192 } 193 } 194 195 function onResize(e) { 196 var el = e.srcElement; 197 if (el.firstChild) { 198 el.firstChild.style.width = el.clientWidth + 'px'; 199 el.firstChild.style.height = el.clientHeight + 'px'; 200 } 201 } 202 203 G_vmlCanvasManager_.init(); 204 205 // precompute "00" to "FF" 206 var decToHex = []; 207 for (var i = 0; i < 16; i++) { 208 for (var j = 0; j < 16; j++) { 209 decToHex[i * 16 + j] = i.toString(16) + j.toString(16); 210 } 211 } 212 213 function createMatrixIdentity() { 214 return [ 215 [1, 0, 0], 216 [0, 1, 0], 217 [0, 0, 1] 218 ]; 219 } 220 221 function matrixMultiply(m1, m2) { 222 var result = createMatrixIdentity(); 223 224 for (var x = 0; x < 3; x++) { 225 for (var y = 0; y < 3; y++) { 226 var sum = 0; 227 228 for (var z = 0; z < 3; z++) { 229 sum += m1[x][z] * m2[z][y]; 230 } 231 232 result[x][y] = sum; 233 } 234 } 235 return result; 236 } 237 238 function copyState(o1, o2) { 239 o2.fillStyle = o1.fillStyle; 240 o2.lineCap = o1.lineCap; 241 o2.lineJoin = o1.lineJoin; 242 o2.lineWidth = o1.lineWidth; 243 o2.miterLimit = o1.miterLimit; 244 o2.shadowBlur = o1.shadowBlur; 245 o2.shadowColor = o1.shadowColor; 246 o2.shadowOffsetX = o1.shadowOffsetX; 247 o2.shadowOffsetY = o1.shadowOffsetY; 248 o2.strokeStyle = o1.strokeStyle; 249 o2.globalAlpha = o1.globalAlpha; 250 o2.font = o1.font; 251 o2.textAlign = o1.textAlign; 252 o2.textBaseline = o1.textBaseline; 253 o2.arcScaleX_ = o1.arcScaleX_; 254 o2.arcScaleY_ = o1.arcScaleY_; 255 o2.lineScale_ = o1.lineScale_; 256 } 257 258 var colorData = { 259 aliceblue: '#F0F8FF', 260 antiquewhite: '#FAEBD7', 261 aquamarine: '#7FFFD4', 262 azure: '#F0FFFF', 263 beige: '#F5F5DC', 264 bisque: '#FFE4C4', 265 black: '#000000', 266 blanchedalmond: '#FFEBCD', 267 blueviolet: '#8A2BE2', 268 brown: '#A52A2A', 269 burlywood: '#DEB887', 270 cadetblue: '#5F9EA0', 271 chartreuse: '#7FFF00', 272 chocolate: '#D2691E', 273 coral: '#FF7F50', 274 cornflowerblue: '#6495ED', 275 cornsilk: '#FFF8DC', 276 crimson: '#DC143C', 277 cyan: '#00FFFF', 278 darkblue: '#00008B', 279 darkcyan: '#008B8B', 280 darkgoldenrod: '#B8860B', 281 darkgray: '#A9A9A9', 282 darkgreen: '#006400', 283 darkgrey: '#A9A9A9', 284 darkkhaki: '#BDB76B', 285 darkmagenta: '#8B008B', 286 darkolivegreen: '#556B2F', 287 darkorange: '#FF8C00', 288 darkorchid: '#9932CC', 289 darkred: '#8B0000', 290 darksalmon: '#E9967A', 291 darkseagreen: '#8FBC8F', 292 darkslateblue: '#483D8B', 293 darkslategray: '#2F4F4F', 294 darkslategrey: '#2F4F4F', 295 darkturquoise: '#00CED1', 296 darkviolet: '#9400D3', 297 deeppink: '#FF1493', 298 deepskyblue: '#00BFFF', 299 dimgray: '#696969', 300 dimgrey: '#696969', 301 dodgerblue: '#1E90FF', 302 firebrick: '#B22222', 303 floralwhite: '#FFFAF0', 304 forestgreen: '#228B22', 305 gainsboro: '#DCDCDC', 306 ghostwhite: '#F8F8FF', 307 gold: '#FFD700', 308 goldenrod: '#DAA520', 309 grey: '#808080', 310 greenyellow: '#ADFF2F', 311 honeydew: '#F0FFF0', 312 hotpink: '#FF69B4', 313 indianred: '#CD5C5C', 314 indigo: '#4B0082', 315 ivory: '#FFFFF0', 316 khaki: '#F0E68C', 317 lavender: '#E6E6FA', 318 lavenderblush: '#FFF0F5', 319 lawngreen: '#7CFC00', 320 lemonchiffon: '#FFFACD', 321 lightblue: '#ADD8E6', 322 lightcoral: '#F08080', 323 lightcyan: '#E0FFFF', 324 lightgoldenrodyellow: '#FAFAD2', 325 lightgreen: '#90EE90', 326 lightgrey: '#D3D3D3', 327 lightpink: '#FFB6C1', 328 lightsalmon: '#FFA07A', 329 lightseagreen: '#20B2AA', 330 lightskyblue: '#87CEFA', 331 lightslategray: '#778899', 332 lightslategrey: '#778899', 333 lightsteelblue: '#B0C4DE', 334 lightyellow: '#FFFFE0', 335 limegreen: '#32CD32', 336 linen: '#FAF0E6', 337 magenta: '#FF00FF', 338 mediumaquamarine: '#66CDAA', 339 mediumblue: '#0000CD', 340 mediumorchid: '#BA55D3', 341 mediumpurple: '#9370DB', 342 mediumseagreen: '#3CB371', 343 mediumslateblue: '#7B68EE', 344 mediumspringgreen: '#00FA9A', 345 mediumturquoise: '#48D1CC', 346 mediumvioletred: '#C71585', 347 midnightblue: '#191970', 348 mintcream: '#F5FFFA', 349 mistyrose: '#FFE4E1', 350 moccasin: '#FFE4B5', 351 navajowhite: '#FFDEAD', 352 oldlace: '#FDF5E6', 353 olivedrab: '#6B8E23', 354 orange: '#FFA500', 355 orangered: '#FF4500', 356 orchid: '#DA70D6', 357 palegoldenrod: '#EEE8AA', 358 palegreen: '#98FB98', 359 paleturquoise: '#AFEEEE', 360 palevioletred: '#DB7093', 361 papayawhip: '#FFEFD5', 362 peachpuff: '#FFDAB9', 363 peru: '#CD853F', 364 pink: '#FFC0CB', 365 plum: '#DDA0DD', 366 powderblue: '#B0E0E6', 367 rosybrown: '#BC8F8F', 368 royalblue: '#4169E1', 369 saddlebrown: '#8B4513', 370 salmon: '#FA8072', 371 sandybrown: '#F4A460', 372 seagreen: '#2E8B57', 373 seashell: '#FFF5EE', 374 sienna: '#A0522D', 375 skyblue: '#87CEEB', 376 slateblue: '#6A5ACD', 377 slategray: '#708090', 378 slategrey: '#708090', 379 snow: '#FFFAFA', 380 springgreen: '#00FF7F', 381 steelblue: '#4682B4', 382 tan: '#D2B48C', 383 thistle: '#D8BFD8', 384 tomato: '#FF6347', 385 turquoise: '#40E0D0', 386 violet: '#EE82EE', 387 wheat: '#F5DEB3', 388 whitesmoke: '#F5F5F5', 389 yellowgreen: '#9ACD32' 390 }; 391 392 393 function getRgbHslContent(styleString) { 394 var start = styleString.indexOf('(', 3); 395 var end = styleString.indexOf(')', start + 1); 396 var parts = styleString.substring(start + 1, end).split(','); 397 // add alpha if needed 398 if (parts.length != 4 || styleString.charAt(3) != 'a') { 399 parts[3] = 1; 400 } 401 return parts; 402 } 403 404 function percent(s) { 405 return parseFloat(s) / 100; 406 } 407 408 function clamp(v, min, max) { 409 return Math.min(max, Math.max(min, v)); 410 } 411 412 function hslToRgb(parts){ 413 var r, g, b, h, s, l; 414 h = parseFloat(parts[0]) / 360 % 360; 415 if (h < 0) 416 h++; 417 s = clamp(percent(parts[1]), 0, 1); 418 l = clamp(percent(parts[2]), 0, 1); 419 if (s == 0) { 420 r = g = b = l; // achromatic 421 } else { 422 var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 423 var p = 2 * l - q; 424 r = hueToRgb(p, q, h + 1 / 3); 425 g = hueToRgb(p, q, h); 426 b = hueToRgb(p, q, h - 1 / 3); 427 } 428 429 return '#' + decToHex[Math.floor(r * 255)] + 430 decToHex[Math.floor(g * 255)] + 431 decToHex[Math.floor(b * 255)]; 432 } 433 434 function hueToRgb(m1, m2, h) { 435 if (h < 0) 436 h++; 437 if (h > 1) 438 h--; 439 440 if (6 * h < 1) 441 return m1 + (m2 - m1) * 6 * h; 442 else if (2 * h < 1) 443 return m2; 444 else if (3 * h < 2) 445 return m1 + (m2 - m1) * (2 / 3 - h) * 6; 446 else 447 return m1; 448 } 449 450 var processStyleCache = {}; 451 452 function processStyle(styleString) { 453 if (styleString in processStyleCache) { 454 return processStyleCache[styleString]; 455 } 456 457 var str, alpha = 1; 458 459 styleString = String(styleString); 460 if (styleString.charAt(0) == '#') { 461 str = styleString; 462 } else if (/^rgb/.test(styleString)) { 463 var parts = getRgbHslContent(styleString); 464 var str = '#', n; 465 for (var i = 0; i < 3; i++) { 466 if (parts[i].indexOf('%') != -1) { 467 n = Math.floor(percent(parts[i]) * 255); 468 } else { 469 n = +parts[i]; 470 } 471 str += decToHex[clamp(n, 0, 255)]; 472 } 473 alpha = +parts[3]; 474 } else if (/^hsl/.test(styleString)) { 475 var parts = getRgbHslContent(styleString); 476 str = hslToRgb(parts); 477 alpha = parts[3]; 478 } else { 479 str = colorData[styleString] || styleString; 480 } 481 return processStyleCache[styleString] = {color: str, alpha: alpha}; 482 } 483 484 var DEFAULT_STYLE = { 485 style: 'normal', 486 variant: 'normal', 487 weight: 'normal', 488 size: 10, 489 family: 'sans-serif' 490 }; 491 492 // Internal text style cache 493 var fontStyleCache = {}; 494 495 function processFontStyle(styleString) { 496 if (fontStyleCache[styleString]) { 497 return fontStyleCache[styleString]; 498 } 499 500 var el = document.createElement('div'); 501 var style = el.style; 502 try { 503 style.font = styleString; 504 } catch (ex) { 505 // Ignore failures to set to invalid font. 506 } 507 508 return fontStyleCache[styleString] = { 509 style: style.fontStyle || DEFAULT_STYLE.style, 510 variant: style.fontVariant || DEFAULT_STYLE.variant, 511 weight: style.fontWeight || DEFAULT_STYLE.weight, 512 size: style.fontSize || DEFAULT_STYLE.size, 513 family: style.fontFamily || DEFAULT_STYLE.family 514 }; 515 } 516 517 function getComputedStyle(style, element) { 518 var computedStyle = {}; 519 520 for (var p in style) { 521 computedStyle[p] = style[p]; 522 } 523 524 // Compute the size 525 var canvasFontSize = parseFloat(element.currentStyle.fontSize), 526 fontSize = parseFloat(style.size); 527 528 if (typeof style.size == 'number') { 529 computedStyle.size = style.size; 530 } else if (style.size.indexOf('px') != -1) { 531 computedStyle.size = fontSize; 532 } else if (style.size.indexOf('em') != -1) { 533 computedStyle.size = canvasFontSize * fontSize; 534 } else if(style.size.indexOf('%') != -1) { 535 computedStyle.size = (canvasFontSize / 100) * fontSize; 536 } else if (style.size.indexOf('pt') != -1) { 537 computedStyle.size = fontSize / .75; 538 } else { 539 computedStyle.size = canvasFontSize; 540 } 541 542 // Different scaling between normal text and VML text. This was found using 543 // trial and error to get the same size as non VML text. 544 computedStyle.size *= 0.981; 545 546 return computedStyle; 547 } 548 549 function buildStyle(style) { 550 return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + 551 style.size + 'px ' + style.family; 552 } 553 554 var lineCapMap = { 555 'butt': 'flat', 556 'round': 'round' 557 }; 558 559 function processLineCap(lineCap) { 560 return lineCapMap[lineCap] || 'square'; 561 } 562 563 /** 564 * This class implements CanvasRenderingContext2D interface as described by 565 * the WHATWG. 566 * @param {HTMLElement} canvasElement The element that the 2D context should 567 * be associated with 568 */ 569 function CanvasRenderingContext2D_(canvasElement) { 570 this.m_ = createMatrixIdentity(); 571 572 this.mStack_ = []; 573 this.aStack_ = []; 574 this.currentPath_ = []; 575 576 // Canvas context properties 577 this.strokeStyle = '#000'; 578 this.fillStyle = '#000'; 579 580 this.lineWidth = 1; 581 this.lineJoin = 'miter'; 582 this.lineCap = 'butt'; 583 this.miterLimit = Z * 1; 584 this.globalAlpha = 1; 585 this.font = '10px sans-serif'; 586 this.textAlign = 'left'; 587 this.textBaseline = 'alphabetic'; 588 this.canvas = canvasElement; 589 590 var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + 591 canvasElement.clientHeight + 'px;overflow:hidden;position:absolute'; 592 var el = canvasElement.ownerDocument.createElement('div'); 593 el.style.cssText = cssText; 594 canvasElement.appendChild(el); 595 596 var overlayEl = el.cloneNode(false); 597 // Use a non transparent background. 598 overlayEl.style.backgroundColor = 'red'; 599 overlayEl.style.filter = 'alpha(opacity=0)'; 600 canvasElement.appendChild(overlayEl); 601 602 this.element_ = el; 603 this.arcScaleX_ = 1; 604 this.arcScaleY_ = 1; 605 this.lineScale_ = 1; 606 } 607 608 var contextPrototype = CanvasRenderingContext2D_.prototype; 609 contextPrototype.clearRect = function() { 610 if (this.textMeasureEl_) { 611 this.textMeasureEl_.removeNode(true); 612 this.textMeasureEl_ = null; 613 } 614 this.element_.innerHTML = ''; 615 }; 616 617 contextPrototype.beginPath = function() { 618 // TODO: Branch current matrix so that save/restore has no effect 619 // as per safari docs. 620 this.currentPath_ = []; 621 }; 622 623 contextPrototype.moveTo = function(aX, aY) { 624 var p = getCoords(this, aX, aY); 625 this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); 626 this.currentX_ = p.x; 627 this.currentY_ = p.y; 628 }; 629 630 contextPrototype.lineTo = function(aX, aY) { 631 var p = getCoords(this, aX, aY); 632 this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); 633 634 this.currentX_ = p.x; 635 this.currentY_ = p.y; 636 }; 637 638 contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, 639 aCP2x, aCP2y, 640 aX, aY) { 641 var p = getCoords(this, aX, aY); 642 var cp1 = getCoords(this, aCP1x, aCP1y); 643 var cp2 = getCoords(this, aCP2x, aCP2y); 644 bezierCurveTo(this, cp1, cp2, p); 645 }; 646 647 // Helper function that takes the already fixed cordinates. 648 function bezierCurveTo(self, cp1, cp2, p) { 649 self.currentPath_.push({ 650 type: 'bezierCurveTo', 651 cp1x: cp1.x, 652 cp1y: cp1.y, 653 cp2x: cp2.x, 654 cp2y: cp2.y, 655 x: p.x, 656 y: p.y 657 }); 658 self.currentX_ = p.x; 659 self.currentY_ = p.y; 660 } 661 662 contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { 663 // the following is lifted almost directly from 664 // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes 665 666 var cp = getCoords(this, aCPx, aCPy); 667 var p = getCoords(this, aX, aY); 668 669 var cp1 = { 670 x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), 671 y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) 672 }; 673 var cp2 = { 674 x: cp1.x + (p.x - this.currentX_) / 3.0, 675 y: cp1.y + (p.y - this.currentY_) / 3.0 676 }; 677 678 bezierCurveTo(this, cp1, cp2, p); 679 }; 680 681 contextPrototype.arc = function(aX, aY, aRadius, 682 aStartAngle, aEndAngle, aClockwise) { 683 aRadius *= Z; 684 var arcType = aClockwise ? 'at' : 'wa'; 685 686 var xStart = aX + mc(aStartAngle) * aRadius - Z2; 687 var yStart = aY + ms(aStartAngle) * aRadius - Z2; 688 689 var xEnd = aX + mc(aEndAngle) * aRadius - Z2; 690 var yEnd = aY + ms(aEndAngle) * aRadius - Z2; 691 692 // IE won't render arches drawn counter clockwise if xStart == xEnd. 693 if (xStart == xEnd && !aClockwise) { 694 xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something 695 // that can be represented in binary 696 } 697 698 var p = getCoords(this, aX, aY); 699 var pStart = getCoords(this, xStart, yStart); 700 var pEnd = getCoords(this, xEnd, yEnd); 701 702 this.currentPath_.push({type: arcType, 703 x: p.x, 704 y: p.y, 705 radius: aRadius, 706 xStart: pStart.x, 707 yStart: pStart.y, 708 xEnd: pEnd.x, 709 yEnd: pEnd.y}); 710 711 }; 712 713 contextPrototype.rect = function(aX, aY, aWidth, aHeight) { 714 this.moveTo(aX, aY); 715 this.lineTo(aX + aWidth, aY); 716 this.lineTo(aX + aWidth, aY + aHeight); 717 this.lineTo(aX, aY + aHeight); 718 this.closePath(); 719 }; 720 721 contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { 722 var oldPath = this.currentPath_; 723 this.beginPath(); 724 725 this.moveTo(aX, aY); 726 this.lineTo(aX + aWidth, aY); 727 this.lineTo(aX + aWidth, aY + aHeight); 728 this.lineTo(aX, aY + aHeight); 729 this.closePath(); 730 this.stroke(); 731 732 this.currentPath_ = oldPath; 733 }; 734 735 contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { 736 var oldPath = this.currentPath_; 737 this.beginPath(); 738 739 this.moveTo(aX, aY); 740 this.lineTo(aX + aWidth, aY); 741 this.lineTo(aX + aWidth, aY + aHeight); 742 this.lineTo(aX, aY + aHeight); 743 this.closePath(); 744 this.fill(); 745 746 this.currentPath_ = oldPath; 747 }; 748 749 contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { 750 var gradient = new CanvasGradient_('gradient'); 751 gradient.x0_ = aX0; 752 gradient.y0_ = aY0; 753 gradient.x1_ = aX1; 754 gradient.y1_ = aY1; 755 return gradient; 756 }; 757 758 contextPrototype.createRadialGradient = function(aX0, aY0, aR0, 759 aX1, aY1, aR1) { 760 var gradient = new CanvasGradient_('gradientradial'); 761 gradient.x0_ = aX0; 762 gradient.y0_ = aY0; 763 gradient.r0_ = aR0; 764 gradient.x1_ = aX1; 765 gradient.y1_ = aY1; 766 gradient.r1_ = aR1; 767 return gradient; 768 }; 769 770 contextPrototype.drawImage = function(image, var_args) { 771 var dx, dy, dw, dh, sx, sy, sw, sh; 772 773 // to find the original width we overide the width and height 774 var oldRuntimeWidth = image.runtimeStyle.width; 775 var oldRuntimeHeight = image.runtimeStyle.height; 776 image.runtimeStyle.width = 'auto'; 777 image.runtimeStyle.height = 'auto'; 778 779 // get the original size 780 var w = image.width; 781 var h = image.height; 782 783 // and remove overides 784 image.runtimeStyle.width = oldRuntimeWidth; 785 image.runtimeStyle.height = oldRuntimeHeight; 786 787 if (arguments.length == 3) { 788 dx = arguments[1]; 789 dy = arguments[2]; 790 sx = sy = 0; 791 sw = dw = w; 792 sh = dh = h; 793 } else if (arguments.length == 5) { 794 dx = arguments[1]; 795 dy = arguments[2]; 796 dw = arguments[3]; 797 dh = arguments[4]; 798 sx = sy = 0; 799 sw = w; 800 sh = h; 801 } else if (arguments.length == 9) { 802 sx = arguments[1]; 803 sy = arguments[2]; 804 sw = arguments[3]; 805 sh = arguments[4]; 806 dx = arguments[5]; 807 dy = arguments[6]; 808 dw = arguments[7]; 809 dh = arguments[8]; 810 } else { 811 throw Error('Invalid number of arguments'); 812 } 813 814 var d = getCoords(this, dx, dy); 815 816 var w2 = sw / 2; 817 var h2 = sh / 2; 818 819 var vmlStr = []; 820 821 var W = 10; 822 var H = 10; 823 824 // For some reason that I've now forgotten, using divs didn't work 825 vmlStr.push(' <g_vml_:group', 826 ' coordsize="', Z * W, ',', Z * H, '"', 827 ' coordorigin="0,0"' , 828 ' style="width:', W, 'px;height:', H, 'px;position:absolute;'); 829 830 // If filters are necessary (rotation exists), create them 831 // filters are bog-slow, so only create them if abbsolutely necessary 832 // The following check doesn't account for skews (which don't exist 833 // in the canvas spec (yet) anyway. 834 835 if (this.m_[0][0] != 1 || this.m_[0][1] || 836 this.m_[1][1] != 1 || this.m_[1][0]) { 837 var filter = []; 838 839 // Note the 12/21 reversal 840 filter.push('M11=', this.m_[0][0], ',', 841 'M12=', this.m_[1][0], ',', 842 'M21=', this.m_[0][1], ',', 843 'M22=', this.m_[1][1], ',', 844 'Dx=', mr(d.x / Z), ',', 845 'Dy=', mr(d.y / Z), ''); 846 847 // Bounding box calculation (need to minimize displayed area so that 848 // filters don't waste time on unused pixels. 849 var max = d; 850 var c2 = getCoords(this, dx + dw, dy); 851 var c3 = getCoords(this, dx, dy + dh); 852 var c4 = getCoords(this, dx + dw, dy + dh); 853 854 max.x = m.max(max.x, c2.x, c3.x, c4.x); 855 max.y = m.max(max.y, c2.y, c3.y, c4.y); 856 857 vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z), 858 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(', 859 filter.join(''), ", sizingmethod='clip');"); 860 861 } else { 862 vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;'); 863 } 864 865 vmlStr.push(' ">' , 866 '<g_vml_:image src="', image.src, '"', 867 ' style="width:', Z * dw, 'px;', 868 ' height:', Z * dh, 'px"', 869 ' cropleft="', sx / w, '"', 870 ' croptop="', sy / h, '"', 871 ' cropright="', (w - sx - sw) / w, '"', 872 ' cropbottom="', (h - sy - sh) / h, '"', 873 ' />', 874 '</g_vml_:group>'); 875 876 this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join('')); 877 }; 878 879 contextPrototype.stroke = function(aFill) { 880 var
880lineStr = []; 881 var lineOpen = false;
vendor: 2,357 bytes, lines 882-963
882 883 var W = 10; 884 var H = 10; 885 886 lineStr.push('<g_vml_:shape', 887 ' filled="', !!aFill, '"', 888 ' style="position:absolute;width:', W, 'px;height:', H, 'px;"', 889 ' coordorigin="0,0"', 890 ' coordsize="', Z * W, ',', Z * H, '"', 891 ' stroked="', !aFill, '"', 892 ' path="'); 893 894 var newSeq = false; 895 var min = {x: null, y: null}; 896 var max = {x: null, y: null}; 897 898 for (var i = 0; i < this.currentPath_.length; i++) { 899 var p = this.currentPath_[i]; 900 var c; 901 902 switch (p.type) { 903 case 'moveTo': 904 c = p; 905 lineStr.push(' m ', mr(p.x), ',', mr(p.y)); 906 break; 907 case 'lineTo': 908 lineStr.push(' l ', mr(p.x), ',', mr(p.y)); 909 break; 910 case 'close': 911 lineStr.push(' x '); 912 p = null; 913 break; 914 case 'bezierCurveTo': 915 lineStr.push(' c ', 916 mr(p.cp1x), ',', mr(p.cp1y), ',', 917 mr(p.cp2x), ',', mr(p.cp2y), ',', 918 mr(p.x), ',', mr(p.y)); 919 break; 920 case 'at': 921 case 'wa': 922 lineStr.push(' ', p.type, ' ', 923 mr(p.x - this.arcScaleX_ * p.radius), ',', 924 mr(p.y - this.arcScaleY_ * p.radius), ' ', 925 mr(p.x + this.arcScaleX_ * p.radius), ',', 926 mr(p.y + this.arcScaleY_ * p.radius), ' ', 927 mr(p.xStart), ',', mr(p.yStart), ' ', 928 mr(p.xEnd), ',', mr(p.yEnd)); 929 break; 930 } 931 932 933 // TODO: Following is broken for curves due to 934 // move to proper paths. 935 936 // Figure out dimensions so we can do gradient fills 937 // properly 938 if (p) { 939 if (min.x == null || p.x < min.x) { 940 min.x = p.x; 941 } 942 if (max.x == null || p.x > max.x) { 943 max.x = p.x; 944 } 945 if (min.y == null || p.y < min.y) { 946 min.y = p.y; 947 } 948 if (max.y == null || p.y > max.y) { 949 max.y = p.y; 950 } 951 } 952 } 953 lineStr.push(' ">'); 954 955 if (!aFill) { 956 appendStroke(this, lineStr); 957 } else { 958 appendFill(this, lineStr, min, max); 959 } 960 961 lineStr.push('</g_vml_:shape>'); 962 963 this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
vendor: 12,990 bytes, lines 963-1416
963 964 }; 965 966 function appendStroke(ctx, lineStr) { 967 var a = processStyle(ctx.strokeStyle); 968 var color = a.color; 969 var opacity = a.alpha * ctx.globalAlpha; 970 var lineWidth = ctx.lineScale_ * ctx.lineWidth; 971 972 // VML cannot correctly render a line if the width is less than 1px. 973 // In that case, we dilute the color to make the line look thinner. 974 if (lineWidth < 1) { 975 opacity *= lineWidth; 976 } 977 978 lineStr.push( 979 '<g_vml_:stroke', 980 ' opacity="', opacity, '"', 981 ' joinstyle="', ctx.lineJoin, '"', 982 ' miterlimit="', ctx.miterLimit, '"', 983 ' endcap="', processLineCap(ctx.lineCap), '"', 984 ' weight="', lineWidth, 'px"', 985 ' color="', color, '" />' 986 ); 987 } 988 989 function appendFill(ctx, lineStr, min, max) { 990 var fillStyle = ctx.fillStyle; 991 var arcScaleX = ctx.arcScaleX_; 992 var arcScaleY = ctx.arcScaleY_; 993 var width = max.x - min.x; 994 var height = max.y - min.y; 995 if (fillStyle instanceof CanvasGradient_) { 996 // TODO: Gradients transformed with the transformation matrix. 997 var angle = 0; 998 var focus = {x: 0, y: 0}; 999 1000 // additional offset 1001 var shift = 0; 1002 // scale factor for offset 1003 var expansion = 1; 1004 1005 if (fillStyle.type_ == 'gradient') { 1006 var x0 = fillStyle.x0_ / arcScaleX; 1007 var y0 = fillStyle.y0_ / arcScaleY; 1008 var x1 = fillStyle.x1_ / arcScaleX; 1009 var y1 = fillStyle.y1_ / arcScaleY; 1010 var p0 = getCoords(ctx, x0, y0); 1011 var p1 = getCoords(ctx, x1, y1); 1012 var dx = p1.x - p0.x; 1013 var dy = p1.y - p0.y; 1014 angle = Math.atan2(dx, dy) * 180 / Math.PI; 1015 1016 // The angle should be a non-negative number. 1017 if (angle < 0) { 1018 angle += 360; 1019 } 1020 1021 // Very small angles produce an unexpected result because they are 1022 // converted to a scientific notation string. 1023 if (angle < 1e-6) { 1024 angle = 0; 1025 } 1026 } else { 1027 var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_); 1028 focus = { 1029 x: (p0.x - min.x) / width, 1030 y: (p0.y - min.y) / height 1031 }; 1032 1033 width /= arcScaleX * Z; 1034 height /= arcScaleY * Z; 1035 var dimension = m.max(width, height); 1036 shift = 2 * fillStyle.r0_ / dimension; 1037 expansion = 2 * fillStyle.r1_ / dimension - shift; 1038 } 1039 1040 // We need to sort the color stops in ascending order by offset, 1041 // otherwise IE won't interpret it correctly. 1042 var stops = fillStyle.colors_; 1043 stops.sort(function(cs1, cs2) { 1044 return cs1.offset - cs2.offset; 1045 }); 1046 1047 var length = stops.length; 1048 var color1 = stops[0].color; 1049 var color2 = stops[length - 1].color; 1050 var opacity1 = stops[0].alpha * ctx.globalAlpha; 1051 var opacity2 = stops[length - 1].alpha * ctx.globalAlpha; 1052 1053 var colors = []; 1054 for (var i = 0; i < length; i++) { 1055 var stop = stops[i]; 1056 colors.push(stop.offset * expansion + shift + ' ' + stop.color); 1057 } 1058 1059 // When colors attribute is used, the meanings of opacity and o:opacity2 1060 // are reversed. 1061 lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"', 1062 ' method="none" focus="100%"', 1063 ' color="', color1, '"', 1064 ' color2="', color2, '"', 1065 ' colors="', colors.join(','), '"', 1066 ' opacity="', opacity2, '"', 1067 ' g_o_:opacity2="', opacity1, '"', 1068 ' angle="', angle, '"', 1069 ' focusposition="', focus.x, ',', focus.y, '" />'); 1070 } else if (fillStyle instanceof CanvasPattern_) { 1071 if (width && height) { 1072 var deltaLeft = -min.x; 1073 var deltaTop = -min.y; 1074 lineStr.push('<g_vml_:fill', 1075 ' position="', 1076 deltaLeft / width * arcScaleX * arcScaleX, ',', 1077 deltaTop / height * arcScaleY * arcScaleY, '"', 1078 ' type="tile"', 1079 // TODO: Figure out the correct size to fit the scale. 1080 //' size="', w, 'px ', h, 'px"', 1081 ' src="', fillStyle.src_, '" />'); 1082 } 1083 } else { 1084 var a = processStyle(ctx.fillStyle); 1085 var color = a.color; 1086 var opacity = a.alpha * ctx.globalAlpha; 1087 lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, 1088 '" />'); 1089 } 1090 } 1091 1092 contextPrototype.fill = function() { 1093 this.stroke(true); 1094 }; 1095 1096 contextPrototype.closePath = function() { 1097 this.currentPath_.push({type: 'close'}); 1098 }; 1099 1100 function getCoords(ctx, aX, aY) { 1101 var m = ctx.m_; 1102 return { 1103 x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, 1104 y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 1105 }; 1106 }; 1107 1108 contextPrototype.save = function() { 1109 var o = {}; 1110 copyState(this, o); 1111 this.aStack_.push(o); 1112 this.mStack_.push(this.m_); 1113 this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); 1114 }; 1115 1116 contextPrototype.restore = function() { 1117 if (this.aStack_.length) { 1118 copyState(this.aStack_.pop(), this); 1119 this.m_ = this.mStack_.pop(); 1120 } 1121 }; 1122 1123 function matrixIsFinite(m) { 1124 return isFinite(m[0][0]) && isFinite(m[0][1]) && 1125 isFinite(m[1][0]) && isFinite(m[1][1]) && 1126 isFinite(m[2][0]) && isFinite(m[2][1]); 1127 } 1128 1129 function setM(ctx, m, updateLineScale) { 1130 if (!matrixIsFinite(m)) { 1131 return; 1132 } 1133 ctx.m_ = m; 1134 1135 if (updateLineScale) { 1136 // Get the line scale. 1137 // Determinant of this.m_ means how much the area is enlarged by the 1138 // transformation. So its square root can be used as a scale factor 1139 // for width. 1140 var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; 1141 ctx.lineScale_ = sqrt(abs(det)); 1142 } 1143 } 1144 1145 contextPrototype.translate = function(aX, aY) { 1146 var m1 = [ 1147 [1, 0, 0], 1148 [0, 1, 0], 1149 [aX, aY, 1] 1150 ]; 1151 1152 setM(this, matrixMultiply(m1, this.m_), false); 1153 }; 1154 1155 contextPrototype.rotate = function(aRot) { 1156 var c = mc(aRot); 1157 var s = ms(aRot); 1158 1159 var m1 = [ 1160 [c, s, 0], 1161 [-s, c, 0], 1162 [0, 0, 1] 1163 ]; 1164 1165 setM(this, matrixMultiply(m1, this.m_), false); 1166 }; 1167 1168 contextPrototype.scale = function(aX, aY) { 1169 this.arcScaleX_ *= aX; 1170 this.arcScaleY_ *= aY; 1171 var m1 = [ 1172 [aX, 0, 0], 1173 [0, aY, 0], 1174 [0, 0, 1] 1175 ]; 1176 1177 setM(this, matrixMultiply(m1, this.m_), true); 1178 }; 1179 1180 contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { 1181 var m1 = [ 1182 [m11, m12, 0], 1183 [m21, m22, 0], 1184 [dx, dy, 1] 1185 ]; 1186 1187 setM(this, matrixMultiply(m1, this.m_), true); 1188 }; 1189 1190 contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { 1191 var m = [ 1192 [m11, m12, 0], 1193 [m21, m22, 0], 1194 [dx, dy, 1] 1195 ]; 1196 1197 setM(this, m, true); 1198 }; 1199 1200 /** 1201 * The text drawing function. 1202 * The maxWidth argument isn't taken in account, since no browser supports 1203 * it yet. 1204 */ 1205 contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) { 1206 var m = this.m_, 1207 delta = 1000, 1208 left = 0, 1209 right = delta, 1210 offset = {x: 0, y: 0}, 1211 lineStr = []; 1212 1213 var fontStyle = getComputedStyle(processFontStyle(this.font), 1214 this.element_); 1215 1216 var fontStyleString = buildStyle(fontStyle); 1217 1218 var elementStyle = this.element_.currentStyle; 1219 var textAlign = this.textAlign.toLowerCase(); 1220 switch (textAlign) { 1221 case 'left': 1222 case 'center': 1223 case 'right': 1224 break; 1225 case 'end': 1226 textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left'; 1227 break; 1228 case 'start': 1229 textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left'; 1230 break; 1231 default: 1232 textAlign = 'left'; 1233 } 1234 1235 // 1.75 is an arbitrary number, as there is no info about the text baseline 1236 switch (this.textBaseline) { 1237 case 'hanging': 1238 case 'top': 1239 offset.y = fontStyle.size / 1.75; 1240 break; 1241 case 'middle': 1242 break; 1243 default: 1244 case null: 1245 case 'alphabetic': 1246 case 'ideographic': 1247 case 'bottom': 1248 offset.y = -fontStyle.size / 2.25; 1249 break; 1250 } 1251 1252 switch(textAlign) { 1253 case 'right': 1254 left = delta; 1255 right = 0.05; 1256 break; 1257 case 'center': 1258 left = right = delta / 2; 1259 break; 1260 } 1261 1262 var d = getCoords(this, x + offset.x, y + offset.y); 1263 1264 lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ', 1265 ' coordsize="100 100" coordorigin="0 0"', 1266 ' filled="', !stroke, '" stroked="', !!stroke, 1267 '" style="position:absolute;width:1px;height:1px;">'); 1268 1269 if (stroke) { 1270 appendStroke(this, lineStr); 1271 } else { 1272 // TODO: Fix the min and max params. 1273 appendFill(this, lineStr, {x: -left, y: 0}, 1274 {x: right, y: fontStyle.size}); 1275 } 1276 1277 var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + 1278 m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0'; 1279 1280 var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z); 1281 1282 lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ', 1283 ' offset="', skewOffset, '" origin="', left ,' 0" />', 1284 '<g_vml_:path textpathok="true" />', 1285 '<g_vml_:textpath on="true" string="', 1286 encodeHtmlAttribute(text), 1287 '" style="v-text-align:', textAlign, 1288 ';font:', encodeHtmlAttribute(fontStyleString), 1289 '" /></g_vml_:line>'); 1290 1291 this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); 1292 }; 1293 1294 contextPrototype.fillText = function(text, x, y, maxWidth) { 1295 this.drawText_(text, x, y, maxWidth, false); 1296 }; 1297 1298 contextPrototype.strokeText = function(text, x, y, maxWidth) { 1299 this.drawText_(text, x, y, maxWidth, true); 1300 }; 1301 1302 contextPrototype.measureText = function(text) { 1303 if (!this.textMeasureEl_) { 1304 var s = '<span style="position:absolute;' + 1305 'top:-20000px;left:0;padding:0;margin:0;border:none;' + 1306 'white-space:pre;"></span>'; 1307 this.element_.insertAdjacentHTML('beforeEnd', s); 1308 this.textMeasureEl_ = this.element_.lastChild; 1309 } 1310 var doc = this.element_.ownerDocument; 1311 this.textMeasureEl_.innerHTML = ''; 1312 this.textMeasureEl_.style.font = this.font; 1313 // Don't use innerHTML or innerText because they allow markup/whitespace. 1314 this.textMeasureEl_.appendChild(doc.createTextNode(text)); 1315 return {width: this.textMeasureEl_.offsetWidth}; 1316 }; 1317 1318 /******** STUBS ********/ 1319 contextPrototype.clip = function() { 1320 // TODO: Implement 1321 }; 1322 1323 contextPrototype.arcTo = function() { 1324 // TODO: Implement 1325 }; 1326 1327 contextPrototype.createPattern = function(image, repetition) { 1328 return new CanvasPattern_(image, repetition); 1329 }; 1330 1331 // Gradient / Pattern Stubs 1332 function CanvasGradient_(aType) { 1333 this.type_ = aType; 1334 this.x0_ = 0; 1335 this.y0_ = 0; 1336 this.r0_ = 0; 1337 this.x1_ = 0; 1338 this.y1_ = 0; 1339 this.r1_ = 0; 1340 this.colors_ = []; 1341 } 1342 1343 CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { 1344 aColor = processStyle(aColor); 1345 this.colors_.push({offset: aOffset, 1346 color: aColor.color, 1347 alpha: aColor.alpha}); 1348 }; 1349 1350 function CanvasPattern_(image, repetition) { 1351 assertImageIsValid(image); 1352 switch (repetition) { 1353 case 'repeat': 1354 case null: 1355 case '': 1356 this.repetition_ = 'repeat'; 1357 break 1358 case 'repeat-x': 1359 case 'repeat-y': 1360 case 'no-repeat': 1361 this.repetition_ = repetition; 1362 break; 1363 default: 1364 throwException('SYNTAX_ERR'); 1365 } 1366 1367 this.src_ = image.src; 1368 this.width_ = image.width; 1369 this.height_ = image.height; 1370 } 1371 1372 function throwException(s) { 1373 throw new DOMException_(s); 1374 } 1375 1376 function assertImageIsValid(img) { 1377 if (!img || img.nodeType != 1 || img.tagName != 'IMG') { 1378 throwException('TYPE_MISMATCH_ERR'); 1379 } 1380 if (img.readyState != 'complete') { 1381 throwException('INVALID_STATE_ERR'); 1382 } 1383 } 1384 1385 function DOMException_(s) { 1386 this.code = this[s]; 1387 this.message = s +': DOM Exception ' + this.code; 1388 } 1389 var p = DOMException_.prototype = new Error; 1390 p.INDEX_SIZE_ERR = 1; 1391 p.DOMSTRING_SIZE_ERR = 2; 1392 p.HIERARCHY_REQUEST_ERR = 3; 1393 p.WRONG_DOCUMENT_ERR = 4; 1394 p.INVALID_CHARACTER_ERR = 5; 1395 p.NO_DATA_ALLOWED_ERR = 6; 1396 p.NO_MODIFICATION_ALLOWED_ERR = 7; 1397 p.NOT_FOUND_ERR = 8; 1398 p.NOT_SUPPORTED_ERR = 9; 1399 p.INUSE_ATTRIBUTE_ERR = 10; 1400 p.INVALID_STATE_ERR = 11; 1401 p.SYNTAX_ERR = 12; 1402 p.INVALID_MODIFICATION_ERR = 13; 1403 p.NAMESPACE_ERR = 14; 1404 p.INVALID_ACCESS_ERR = 15; 1405 p.VALIDATION_ERR = 16; 1406 p.TYPE_MISMATCH_ERR = 17; 1407 1408 // set up externs 1409 G_vmlCanvasManager = G_vmlCanvasManager_; 1410 CanvasRenderingContext2D = CanvasRenderingContext2D_; 1411 CanvasGradient = CanvasGradient_; 1412 CanvasPattern = CanvasPattern_; 1413 DOMException = DOMException_; 1414})(); 1415 1416} // if
1417 1418 1419// ********************************************************************************* 1420 1421 1422 1423 1424/*! 1425 * Chart.js 1426 * http://chartjs.org/ 1427 * 1428 * Copyright 2013 Nick Downie 1429 * Released under the MIT license 1430 * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md 1431 */ 1432 1433//Define the global Chart Variable as a class. 1434window.Chart = function(context, options){ 1435 var chart = this; 1436 1437 //Easing functions adapted from Robert Penner's easing equations 1438 //http://www.robertpenner.com/easing/ 1439 1440 var animationOptions = { 1441 linear : function (t){ 1442 return t; 1443 }, 1444 easeInQuad: function (t) { 1445 return t*t; 1446 }, 1447 easeOutQuad: function (t) { 1448 return -1 *t*(t-2); 1449 }, 1450 easeInOutQuad: function (t) { 1451 if ((t/=1/2) < 1) return 1/2*t*t; 1452 return -1/2 * ((--t)*(t-2) - 1); 1453 }, 1454 easeInCubic: function (t) { 1455 return t*t*t; 1456 }, 1457 easeOutCubic: function (t) { 1458 return 1*((t=t/1-1)*t*t + 1); 1459 }, 1460 easeInOutCubic: function (t) { 1461 if ((t/=1/2) < 1) return 1/2*t*t*t; 1462 return 1/2*((t-=2)*t*t + 2); 1463 }, 1464 easeInQuart: function (t) { 1465 return t*t*t*t; 1466 }, 1467 easeOutQuart: function (t) { 1468 return -1 * ((t=t/1-1)*t*t*t - 1); 1469 }, 1470 easeInOutQuart: function (t) { 1471 if ((t/=1/2) < 1) return 1/2*t*t*t*t; 1472 return -1/2 * ((t-=2)*t*t*t - 2); 1473 }, 1474 easeInQuint: function (t) { 1475 return 1*(t/=1)*t*t*t*t; 1476 }, 1477 easeOutQuint: function (t) { 1478 return 1*((t=t/1-1)*t*t*t*t + 1); 1479 }, 1480 easeInOutQuint: function (t) { 1481 if ((t/=1/2) < 1) return 1/2*t*t*t*t*t; 1482 return 1/2*((t-=2)*t*t*t*t + 2); 1483 }, 1484 easeInSine: function (t) { 1485 return -1 * Math.cos(t/1 * (Math.PI/2)) + 1; 1486 }, 1487 easeOutSine: function (t) { 1488 return 1 * Math.sin(t/1 * (Math.PI/2)); 1489 }, 1490 easeInOutSine: function (t) { 1491 return -1/2 * (Math.cos(Math.PI*t/1) - 1); 1492 }, 1493 easeInExpo: function (t) { 1494 return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1)); 1495 }, 1496 easeOutExpo: function (t) { 1497 return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1); 1498 }, 1499 easeInOutExpo: function (t) { 1500 if (t==0) return 0; 1501 if (t==1) return 1; 1502 if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1)); 1503 return 1/2 * (-Math.pow(2, -10 * --t) + 2); 1504 }, 1505 easeInCirc: function (t) { 1506 if (t>=1) return t; 1507 return -1 * (Math.sqrt(1 - (t/=1)*t) - 1); 1508 }, 1509 easeOutCirc: function (t) { 1510 return 1 * Math.sqrt(1 - (t=t/1-1)*t); 1511 }, 1512 easeInOutCirc: function (t) { 1513 if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1); 1514 return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1); 1515 }, 1516 easeInElastic: function (t) { 1517 var s=1.70158;var p=0;var a=1; 1518 if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; 1519 if (a < Math.abs(1)) { a=1; var s=p/4; } 1520 else var s = p/(2*Math.PI) * Math.asin (1/a); 1521 return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); 1522 }, 1523 easeOutElastic: function (t) { 1524 var s=1.70158;var p=0;var a=1; 1525 if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; 1526 if (a < Math.abs(1)) { a=1; var s=p/4; } 1527 else var s = p/(2*Math.PI) * Math.asin (1/a); 1528 return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1; 1529 }, 1530 easeInOutElastic: function (t) { 1531 var s=1.70158;var p=0;var a=1; 1532 if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5); 1533 if (a < Math.abs(1)) { a=1; var s=p/4; } 1534 else var s = p/(2*Math.PI) * Math.asin (1/a); 1535 if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); 1536 return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1; 1537 }, 1538 easeInBack: function (t) { 1539 var s = 1.70158; 1540 return 1*(t/=1)*t*((s+1)*t - s); 1541 }, 1542 easeOutBack: function (t) { 1543 var s = 1.70158; 1544 return 1*((t=t/1-1)*t*((s+1)*t + s) + 1); 1545 }, 1546 easeInOutBack: function (t) { 1547 var s = 1.70158; 1548 if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s)); 1549 return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2); 1550 }, 1551 easeInBounce: function (t) { 1552 return 1 - animationOptions.easeOutBounce (1-t); 1553 }, 1554 easeOutBounce: function (t) { 1555 if ((t/=1) < (1/2.75)) { 1556 return 1*(7.5625*t*t); 1557 } else if (t < (2/2.75)) { 1558 return 1*(7.5625*(t-=(1.5/2.75))*t + .75); 1559 } else if (t < (2.5/2.75)) { 1560 return 1*(7.5625*(t-=(2.25/2.75))*t + .9375); 1561 } else { 1562 return 1*(7.5625*(t-=(2.625/2.75))*t + .984375); 1563 } 1564 }, 1565 easeInOutBounce: function (t) { 1566 if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5; 1567 return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5; 1568 } 1569 }; 1570 1571 this.tooltips = [], 1572 defaults = { 1573 tooltips: { 1574 background: 'black', 1575 fontFamily : "'Arial'", 1576 fontStyle : "normal", 1577 fontColor: 'white', 1578 fontSize: '12px', 1579 labelTemplate: '<%=label%> - <%=value%>', 1580 height: 24, 1581 padding: { 1582 top: 4, 1583 right: 8, 1584 bottom: 4, 1585 left: 8 1586 }, 1587 position: 'bottom center', 1588 offset: { 1589 left: 0, 1590 top: 0 1591 }, 1592 border: { 1593 width: 0, 1594 color: 'black', 1595 radius: 4 1596 }, 1597 showShadow: true, 1598 shadow: { 1599 color: 'rgba(0,0,0,0.9)', 1600 blur: 8, 1601 offsetX: 0, 1602 offsetY: 0 1603 }, 1604 showHighlight: true, 1605 highlight: { 1606 stroke: { 1607 width: 1, 1608 color: 'rgba(230,230,230,0.25)' 1609 }, 1610 fill: 'rgba(255,255,255,0.25)' 1611 } 1612 } 1613 }, 1614 options = (options) ? mergeChartConfig(defaults, options) : defaults; 1615 1616 function registerTooltip(ctx,areaObj,data,type,chart_data_type) { 1617 //alert(chart_data_type); 1618 chart.tooltips.push(new Tooltip( 1619 ctx, 1620 areaObj, 1621 data, 1622 type, 1623 chart_data_type 1624 )); 1625 } 1626 1627 var Tooltip = function(ctx, areaObj, data, type, chart_data_type) { 1628 this.ctx = ctx; 1629 this.areaObj = areaObj; 1630 this.data = data; 1631 this.savedState = null; 1632 this.highlightState = null; 1633 this.x = null; 1634 this.y = null; 1635 1636 //WD-1814 1637 if (chart_data_type && chart_data_type != 'undefined') { 1638 if (chart_data_type == 'percentage') { 1639 if (type == 'Pie' || type == 'Doughnut' || type == 'PolarArea'){ 1640 options.tooltips.labelTemplate = '<%=label%> - <%=percentageValue%> %'; 1641 } 1642 else{ 1643 options.tooltips.labelTemplate = '<%=label%> - <%=value%>'; 1644 } 1645 } 1646 else{ 1647 options.tooltips.labelTemplate = '<%=label%> - <%=value%>'; 1648 } 1649 } 1650 1651 this.inRange = function(x,y) { 1652 if(this.areaObj.type) { 1653 switch(this.areaObj.type) { 1654 case 'rect': 1655 return (x >= this.areaObj.x && x <= this.areaObj.x+this.areaObj.width) && 1656 (y >= this.areaObj.y && y <= this.areaObj.y+this.areaObj.height); 1657 break; 1658 case 'circle': 1659 return ((Math.pow(x-this.areaObj.x, 2)+Math.pow(y-this.areaObj.y, 2)) < Math.pow(this.areaObj.r,2)); 1660 break; 1661 case 'shape': 1662 var poly = this.areaObj.points; 1663 for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) 1664 ((poly[i].y <= y && y < poly[j].y) || (poly[j].y <= y && y < poly[i].y)) 1665 && (x < (poly[j].x - poly[i].x) * (y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x) 1666 && (c = !c); 1667 return c; 1668 break; 1669 } 1670 } 1671 } 1672 1673 this.render = function(x,y) { 1674 this.ctx.shadowColor = undefined; 1675 this.ctx.shadowBlur = 0; 1676 this.ctx.shadowOffsetX = 0; 1677 this.ctx.shadowOffsetY = 0; 1678 if(this.savedState == null) {
1679 this.ctx.putImageData(chart.savedState,0,0); 1680 this.savedState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height); 1681 } 1682 this.ctx.putImageData(this.savedState,0,0); 1683 if(options.tooltips.showHighlight) { 1684 if(this.highlightState == null) { 1685 this.ctx.strokeStyle = options.tooltips.highlight.stroke.color; 1686 this.ctx.lineWidth = options.tooltips.highlight.stroke.width; 1687 this.ctx.fillStyle = options.tooltips.highlight.fill; 1688 switch(this.areaObj.type) { 1689 case 'rect': 1690 this.ctx.strokeRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height); 1691 this.ctx.fillStyle = options.tooltips.highlight.fill; 1692 this.ctx.fillRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height); 1693 break; 1694 case 'circle': 1695 this.ctx.beginPath(); 1696 this.ctx.arc(this.areaObj.x, this.areaObj.y, this.areaObj.r, 0, 2*Math.PI, false); 1697 this.ctx.stroke(); 1698 this.ctx.fill(); 1699 break; 1700 case 'shape': 1701 this.ctx.beginPath(); 1702 this.ctx.moveTo(this.areaObj.points[0].x, this.areaObj.points[0].y); 1703 for(var p in this.areaObj.points) { 1704 this.ctx.lineTo(this.areaObj.points[p].x, this.areaObj.points[p].y); 1705 } 1706 this.ctx.stroke(); 1707 this.ctx.fill(); 1708 break; 1709 } 1710 this.highlightState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height); 1711 } else { 1712 this.ctx.putImageData(this.highlightState,0,0); 1713 } 1714 } 1715 var posX = x+options.tooltips.offset.left, 1716 posY = y+options.tooltips.offset.top, 1717 tpl = tmpl(options.tooltips.labelTemplate, this.data), 1718 rectWidth = options.tooltips.padding.left+this.ctx.measureText(tpl).width+options.tooltips.padding.right, 1719 position = options.tooltips.position.split(" "), 1720 height = options.tooltips.height; 1721 1722 // adjust height on fontsize 1723 if(options.tooltips.fontSize.match(/[0-9]+(.[0-9]+)?px/)) { 1724 height = parseInt(options.tooltips.fontSize); 1725 } else if(options.tooltips.fontSize.match(/[0-9]+(.[0-9]+)?(\%|em)/)) { 1726 function getDefaultFontSize(pa) { 1727 pa = pa || document.body; 1728 var who = document.createElement('div'); 1729 1730 who.style.cssText='display:inline-block; padding:0; line-height:1; position:absolute; visibility:hidden; font-size:1em'; 1731 1732 who.appendChild(document.createTextNode('M')); 1733 pa.appendChild(who); 1734 var fs = [who.offsetWidth, who.offsetHeight]; 1735 pa.removeChild(who); 1736 return fs[1]; 1737 } 1738 var size = parseFloat(options.tooltips.fontSize); 1739 if(options.tooltips.fontSize.match(/[0-9]+(.[0-9]+)?\%/)) { 1740 size /= 100; 1741 } 1742 height = size*getDefaultFontSize(this.ctx.canvas.parentNode); 1743 } 1744 1745 height += options.tooltips.padding.top+options.tooltips.padding.bottom; 1746 1747 // check relative position 1748 for(var i in position) { 1749 if(i == 0) { 1750 if(position[i] == "bottom") { 1751 posY -= height; 1752 } else if(position[i] == "center") { 1753 posY -= height/2; 1754 if(position.length == 1) { 1755 posX -= rectWidth/2; 1756 } 1757 } 1758 } 1759 if(i == 1) { 1760 if(position[i] == "right") { 1761 posX -= rectWidth; 1762 } else if(position[i] == "center") { 1763 posX -= rectWidth/2; 1764 } 1765 } 1766 } 1767 1768 // check edges 1769 if(posX + rectWidth > ctx.canvas.width) { 1770 posX -= posX+rectWidth-ctx.canvas.width; 1771 } 1772 if(posX < 0) { 1773 posX = 0; 1774 } 1775 if(posY + height > ctx.canvas.height) { 1776 posY -= posY+height-ctx.canvas.height; 1777 } 1778 if(posY < 0) { 1779 posY = 0; 1780 } 1781 this.ctx.fillStyle = options.tooltips.background; 1782 if(options.tooltips.showShadow) { 1783 this.ctx.shadowColor = options.tooltips.shadow.color; 1784 this.ctx.shadowBlur = options.tooltips.shadow.blur; 1785 this.ctx.shadowOffsetX = options.tooltips.shadow.offsetX; 1786 this.ctx.shadowOffsetY = options.tooltips.shadow.offsetY; 1787 }
1788 if(!options.tooltips.border.radius) { 1789 this.ctx.fillRect(posX, posY, rectWidth, height); 1790 if(options.tooltips.border.width > 0) { 1791 this.ctx.fillStyle = options.tooltips.border.color; 1792 this.ctx.lineWidth = options.tooltips.border.width; 1793 this.ctx.strokeRect(posX, posY, rectWidth, height); 1794 } 1795 } else { 1796 var radius = options.tooltips.border.radius > 12 ? 12 : options.tooltips.border.radius; 1797 this.ctx.beginPath(); 1798 this.ctx.moveTo(posX+radius, posY); 1799 this.ctx.lineTo(posX+rectWidth-radius, posY); 1800 this.ctx.quadraticCurveTo(posX+rectWidth, posY, posX+rectWidth, posY+radius); 1801 this.ctx.lineTo(posX+rectWidth, posY+height-radius); 1802 this.ctx.quadraticCurveTo(posX+rectWidth, posY+height, posX+rectWidth-radius, posY+height); 1803 this.ctx.lineTo(posX+radius, posY+height); 1804 this.ctx.quadraticCurveTo(posX, posY+height, posX, posY+height-radius); 1805 this.ctx.lineTo(posX, posY+radius); 1806 this.ctx.quadraticCurveTo(posX, posY, posX+radius, posY); 1807 this.ctx.fill(); 1808 if(options.tooltips.border.width > 0) { 1809 this.ctx.strokeStyle = options.tooltips.border.color; 1810 this.ctx.lineWidth = options.tooltips.border.width; 1811 this.ctx.stroke(); 1812 } 1813 this.ctx.closePath(); 1814 } 1815 this.ctx.font = options.tooltips.fontStyle+ " "+options.tooltips.fontSize+" " + options.tooltips.fontFamily; 1816 this.ctx.fillStyle = options.tooltips.fontColor; 1817 this.ctx.textAlign = 'center'; 1818 this.ctx.textBaseline = 'middle'; 1819 this.ctx.fillText(tpl, posX+rectWidth/2, posY+height/2); 1820 this.x = x; 1821 this.y = y; 1822 } 1823 } 1824 1825 //Variables global to the chart 1826 var width = context.canvas.width, 1827 height = context.canvas.height; 1828 1829 this.savedState = null; 1830 1831 function getPosition(e) { 1832 var xPosition = 0; 1833 var yPosition = 0; 1834 1835 while(e) { 1836 xPosition += (e.offsetLeft + e.clientLeft); 1837 yPosition += (e.offsetTop + e.clientTop); 1838 e = e.offsetParent; 1839 } 1840 if(window.pageXOffset > 0 || window.pageYOffset > 0) { 1841 xPosition -= window.pageXOffset; 1842 yPosition -= window.pageYOffset; 1843 } else if(document.body.scrollLeft > 0 || document.body.scrollTop > 0) { 1844 xPosition -= document.body.scrollLeft; 1845 yPosition -= document.body.scrollTop; 1846 } 1847 return { x: xPosition, y: yPosition }; 1848 } 1849 1850 function tooltipEventHandler(e) { 1851 if(chart.tooltips.length > 0) { 1852 chart.savedState = chart.savedState == null ? context.getImageData(0,0,context.canvas.width,context.canvas.height) : chart.savedState; 1853 var rendered = 0; 1854 for(var i in chart.tooltips) { 1855 var position = getPosition(context.canvas), 1856 mx = (e.clientX)-position.x, 1857 my = (e.clientY)-position.y; 1858 if(chart.tooltips[i].inRange(mx,my)) { 1859 chart.tooltips[i].render(mx,my); 1860 rendered++; 1861 } 1862 } 1863 if(rendered == 0) { 1864 context.putImageData(chart.savedState,0,0); 1865 } 1866 } 1867 } 1868 1869 function is_touch_device() { 1870 return 'ontouchstart' in window // works on most browsers 1871 || 'onmsgesturechange' in window; // works on ie10 1872 }; 1873 1874 if (is_touch_device()) { 1875 context.canvas.ontouchstart = function(e) { 1876 e.clientX = e.targetTouches[0].clientX; 1877 e.clientY = e.targetTouches[0].clientY; 1878 tooltipEventHandler(e); 1879 } 1880 context.canvas.ontouchmove = function(e) { 1881 e.clientX = e.targetTouches[0].clientX; 1882 e.clientY = e.targetTouches[0].clientY; 1883 tooltipEventHandler(e); 1884 } 1885 } else { 1886 context.canvas.onmousemove = function(e) { 1887 tooltipEventHandler(e); 1888 } 1889 } 1890 context.canvas.onmouseout = function(e) { 1891 if(chart.savedState != null) {
1892 context.putImageData(chart.savedState,0,0); 1893 } 1894 } 1895 1896 1897 //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. 1898 if (window.devicePixelRatio) { 1899 context.canvas.style.width = width + "px"; 1900 context.canvas.style.height = height + "px"; 1901 context.canvas.height = height * window.devicePixelRatio; 1902 context.canvas.width = width * window.devicePixelRatio; 1903 context.scale(window.devicePixelRatio, window.devicePixelRatio); 1904 } 1905 1906 this.PolarArea = function(data,options){ 1907 1908 chart.PolarArea.defaults = { 1909 scaleOverlay : true, 1910 scaleOverride : false, 1911 scaleSteps : null, 1912 scaleStepWidth : null, 1913 scaleStartValue : null, 1914 scaleShowLine : true, 1915 scaleLineColor : "rgba(0,0,0,.1)", 1916 scaleLineWidth : 1, 1917 scaleShowLabels : true, 1918 scaleLabel : "<%=value%>", 1919 scaleFontFamily : "'Arial'", 1920 scaleFontSize : 12, 1921 scaleFontStyle : "normal", 1922 scaleFontColor : "#666", 1923 scaleShowLabelBackdrop : true, 1924 scaleBackdropColor : "rgba(255,255,255,0.75)", 1925 scaleBackdropPaddingY : 2, 1926 scaleBackdropPaddingX : 2, 1927 segmentShowStroke : true, 1928 segmentStrokeColor : "#fff", 1929 segmentStrokeWidth : 2, 1930 animation : true, 1931 animationSteps : 100, 1932 animationEasing : "easeOutBounce", 1933 animateRotate : true, 1934 animateScale : false, 1935 onAnimationComplete : null, 1936 showTooltips : true 1937 }; 1938 1939 var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults; 1940 1941 return new PolarArea(data,config,context); 1942 }; 1943 1944 this.Radar = function(data,options){ 1945 1946 chart.Radar.defaults = { 1947 scaleOverlay : false, 1948 scaleOverride : false, 1949 scaleSteps : null, 1950 scaleStepWidth : null, 1951 scaleStartValue : null, 1952 scaleShowLine : true, 1953 scaleLineColor : "rgba(0,0,0,.1)", 1954 scaleLineWidth : 1, 1955 scaleShowLabels : false, 1956 scaleLabel : "<%=value%>", 1957 scaleFontFamily : "'Arial'", 1958 scaleFontSize : 12, 1959 scaleFontStyle : "normal", 1960 scaleFontColor : "#666", 1961 scaleShowLabelBackdrop : true, 1962 scaleBackdropColor : "rgba(255,255,255,0.75)", 1963 scaleBackdropPaddingY : 2, 1964 scaleBackdropPaddingX : 2, 1965 angleShowLineOut : true, 1966 angleLineColor : "rgba(0,0,0,.1)", 1967 angleLineWidth : 1, 1968 pointLabelFontFamily : "'Arial'", 1969 pointLabelFontStyle : "normal", 1970 pointLabelFontSize : 12, 1971 pointLabelFontColor : "#666", 1972 pointDot : true, 1973 pointDotRadius : 3, 1974 pointDotStrokeWidth : 1, 1975 datasetStroke : true, 1976 datasetStrokeWidth : 2, 1977 datasetFill : true, 1978 animation : true, 1979 animationSteps : 60, 1980 animationEasing : "easeOutQuart", 1981 onAnimationComplete : null, 1982 showTooltips : true 1983 }; 1984 1985 var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults; 1986 1987 return new Radar(data,config,context); 1988 }; 1989 1990 this.Pie = function(data,options){ 1991 chart.Pie.defaults = { 1992 segmentShowStroke : true, 1993 segmentStrokeColor : "#fff", 1994 segmentStrokeWidth : 2, 1995 animation : true, 1996 animationSteps : 100, 1997 animationEasing : "easeOutBounce", 1998 animateRotate : true, 1999 animateScale : false, 2000 onAnimationComplete : null, 2001 labelFontFamily : "'Arial'", 2002 labelFontStyle : "normal", 2003 labelFontSize : 12, 2004 labelFontColor : "#666", 2005 labelAlign : 'right', 2006 showTooltips : true 2007 }; 2008 2009 var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults; 2010 2011 return new Pie(data,config,context); 2012 }; 2013 2014 this.Doughnut = function(data,options){ 2015 2016 chart.Doughnut.defaults = { 2017 segmentShowStroke : true, 2018 segmentStrokeColor : "#fff", 2019 segmentStrokeWidth : 2, 2020 percentageInnerCutout : 50, 2021 animation : true, 2022 animationSteps : 100, 2023 animationEasing : "easeOutBounce", 2024 animateRotate : true, 2025 animateScale : false, 2026 onAnimationComplete : null, 2027 showTooltips : true 2028 }; 2029 2030 var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults; 2031 2032 return new Doughnut(data,config,context); 2033 2034 }; 2035 2036 this.Line = function(data,options){ 2037 2038 chart.Line.defaults = { 2039 scaleOverlay : false, 2040 scaleOverride : false, 2041 scaleSteps : null, 2042 scaleStepWidth : null, 2043 scaleStartValue : null, 2044 scaleLineColor : "rgba(0,0,0,.1)", 2045 scaleLineWidth : 1, 2046 scaleShowLabels : true, 2047 scaleLabel : "<%=value%>", 2048 scaleFontFamily : "'Arial'", 2049 scaleFontSize : 12, 2050 scaleFontStyle : "normal", 2051 scaleFontColor : "#666", 2052 scaleShowGridLines : true, 2053 scaleGridLineColor : "rgba(0,0,0,.05)", 2054 scaleGridLineWidth : 1, 2055 bezierCurve : true, 2056 pointDot : true, 2057 pointDotRadius : 4, 2058 pointDotStrokeWidth : 2, 2059 datasetStroke : true, 2060 datasetStrokeWidth : 2, 2061 datasetFill : true, 2062 animation : true, 2063 animationSteps : 60, 2064 animationEasing : "easeOutQuart", 2065 onAnimationComplete : null, 2066 showTooltips : true 2067 }; 2068 var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults; 2069 2070 return new Line(data,config,context); 2071 } 2072 2073 this.Bar = function(data,options){ 2074 chart.Bar.defaults = { 2075 scaleOverlay : false, 2076 scaleOverride : false, 2077 scaleSteps : null, 2078 scaleStepWidth : null, 2079 scaleStartValue : null, 2080 scaleLineColor : "rgba(0,0,0,.1)", 2081 scaleLineWidth : 1, 2082 scaleShowLabels : true, 2083 scaleLabel : "<%=value%>", 2084 scaleFontFamily : "'Arial'", 2085 scaleFontSize : 12, 2086 scaleFontStyle : "normal", 2087 scaleFontColor : "#666", 2088 scaleShowGridLines : true, 2089 scaleGridLineColor : "rgba(0,0,0,.05)", 2090 scaleGridLineWidth : 1, 2091 barShowStroke : true, 2092 barStrokeWidth : 2, 2093 barValueSpacing : 5, 2094 barDatasetSpacing : 1, 2095 animation : true, 2096 animationSteps : 60, 2097 animationEasing : "easeOutQuart", 2098 onAnimationComplete : null, 2099 showTooltips : true 2100 }; 2101 var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults; 2102 2103 //we got link this here....... checking for bar chart. 2104 //alert(options.chart_data_type); 2105 2106 return new Bar(data,config,context); 2107 } 2108 2109 var clear = function(c){ 2110 c.clearRect(0, 0, width, height); 2111 }; 2112 2113 var PolarArea = function(data,config,ctx){ 2114 var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; 2115 2116 2117 calculateDrawingSizes(); 2118 2119 valueBounds = getValueBounds(); 2120 2121 labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; 2122 2123 //Check and set the scale 2124 if (!config.scaleOverride){ 2125 2126 calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); 2127 } 2128 else { 2129 calculatedScale = { 2130 steps : config.scaleSteps, 2131 stepValue : config.scaleStepWidth, 2132 graphMin : config.scaleStartValue, 2133 labels : [] 2134 } 2135 populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); 2136 } 2137 2138 scaleHop = maxSize/(calculatedScale.steps); 2139 2140 //Wrap in an animation loop wrapper 2141 animationLoop(config,drawScale,drawAllSegments,ctx); 2142 2143 function calculateDrawingSizes(){ 2144 maxSize = (Min([width,height])/2); 2145 //Remove whatever is larger - the font size or line width. 2146 2147 maxSize -= Max([config.scaleFontSize*0.5,config.scaleLineWidth*0.5]); 2148 2149 labelHeight = config.scaleFontSize*2; 2150 //If we're drawing the backdrop - add the Y padding to the label height and remove from drawing region. 2151 if (config.scaleShowLabelBackdrop){ 2152 labelHeight += (2 * config.scaleBackdropPaddingY); 2153 maxSize -= config.scaleBackdropPaddingY*1.5; 2154 } 2155 2156 scaleHeight = maxSize; 2157 //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. 2158 labelHeight = Default(labelHeight,5); 2159 } 2160 function drawScale(){ 2161 for (var i=0; i<calculatedScale.steps; i++){ 2162 //If the line object is there 2163 if (config.scaleShowLine){ 2164 ctx.beginPath(); 2165 ctx.arc(width/2, height/2, scaleHop * (i + 1), 0, (Math.PI * 2), true); 2166 ctx.strokeStyle = config.scaleLineColor; 2167 ctx.lineWidth = config.scaleLineWidth; 2168 ctx.stroke(); 2169 } 2170 2171 if (config.scaleShowLabels){ 2172 ctx.textAlign = "center"; 2173 ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily; 2174 var label = calculatedScale.labels[i]; 2175 //If the backdrop object is within the font object 2176 if (config.scaleShowLabelBackdrop){ 2177 var textWidth = ctx.measureText(label).width; 2178 ctx.fillStyle = config.scaleBackdropColor; 2179 ctx.beginPath(); 2180 ctx.rect( 2181 Math.round(width/2 - textWidth/2 - config.scaleBackdropPaddingX), //X 2182 Math.round(height/2 - (scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y 2183 Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width 2184 Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height 2185 ); 2186 ctx.fill(); 2187 } 2188 ctx.textBaseline = "middle"; 2189 ctx.fillStyle = config.scaleFontColor; 2190 ctx.fillText(label,width/2,height/2 - (scaleHop * (i + 1))); 2191 } 2192 } 2193 } 2194 function drawAllSegments(animationDecimal){ 2195 var startAngle = -Math.PI/2,
2196 angleStep = (Math.PI*2)/data.length, 2197 scaleAnimation = 1, 2198 rotateAnimation = 1; 2199 if (config.animation) { 2200 if (config.animateScale) { 2201 scaleAnimation = animationDecimal; 2202 } 2203 if (config.animateRotate){ 2204 rotateAnimation = animationDecimal; 2205 } 2206 } 2207 2208 for (var i=0; i<data.length; i++){ 2209 2210 ctx.beginPath(); 2211 ctx.arc(width/2,height/2,scaleAnimation * calculateOffset(data[i].value,calculatedScale,scaleHop),startAngle, startAngle + rotateAnimation*angleStep, false); 2212 ctx.lineTo(width/2,height/2); 2213 ctx.closePath(); 2214 ctx.fillStyle = data[i].color; 2215 ctx.fill(); 2216 2217 if(animationDecimal >= 1 && config.showTooltips) { 2218 var points = [{x:width/2,y:height/2}], 2219 pAmount = 50, 2220 radius = calculateOffset(data[i].value,calculatedScale,scaleHop); 2221 points.push({x:width/2+radius*Math.cos(startAngle),y:height/2+radius*Math.sin(startAngle)}); 2222 for(var p = 0; p <= pAmount; p++) { 2223 points.push({x:width/2+radius*Math.cos(startAngle+p/pAmount*rotateAnimation*angleStep),y:height/2+radius*Math.sin(startAngle+p/pAmount*rotateAnimation*angleStep)}); 2224 } 2225 registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value,percentageValue:data[i].percentageValue},'PolarArea',config.chart_data_type); 2226 } 2227 2228 if(config.segmentShowStroke){ 2229 ctx.strokeStyle = config.segmentStrokeColor; 2230 ctx.lineWidth = config.segmentStrokeWidth; 2231 ctx.stroke(); 2232 } 2233 startAngle += rotateAnimation*angleStep; 2234 } 2235 } 2236 function getValueBounds() { 2237 var upperValue = Number.MIN_VALUE; 2238 var lowerValue = Number.MAX_VALUE; 2239 for (var i=0; i<data.length; i++){ 2240 if (data[i].value > upperValue) {upperValue = data[i].value;} 2241 if (data[i].value < lowerValue) {lowerValue = data[i].value;} 2242 }; 2243 2244 var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); 2245 var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); 2246 2247 return { 2248 maxValue : upperValue, 2249 minValue : lowerValue, 2250 maxSteps : maxSteps, 2251 minSteps : minSteps 2252 }; 2253 2254 2255 } 2256 } 2257 2258 var Radar = function (data,config,ctx) { 2259 var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; 2260 2261 //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up. 2262 if (!data.labels) data.labels = []; 2263 2264 calculateDrawingSizes(); 2265 2266 var valueBounds = getValueBounds(); 2267 2268 labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; 2269 2270 //Check and set the scale 2271 if (!config.scaleOverride){ 2272 2273 calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); 2274 } 2275 else { 2276 calculatedScale = { 2277 steps : config.scaleSteps, 2278 stepValue : config.scaleStepWidth, 2279 graphMin : config.scaleStartValue, 2280 labels : [] 2281 } 2282 populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); 2283 } 2284 2285 scaleHop = maxSize/(calculatedScale.steps); 2286 2287 animationLoop(config,drawScale,drawAllDataPoints,ctx); 2288 2289 //Radar specific functions. 2290 function drawAllDataPoints(animationDecimal){ 2291 var rotationDegree = (2*Math.PI)/data.datasets[0].data.length; 2292 2293 ctx.save(); 2294 //translate to the centre of the canvas. 2295 ctx.translate(width/2,height/2); 2296 //We accept multiple data sets for radar charts, so show loop through each set 2297 for (var i=0; i<data.datasets.length; i++){ 2298 var offset = calculateOffset(data.datasets[i].data[0],c
2298alculatedScale,scaleHop); 2299 ctx.beginPath(); 2300 ctx.moveTo(0,animationDecimal*(-1*offset)); 2301 if(animationDecimal >= 1 && config.showTooltips) { 2302 var curX = width/2+offset*Math.cos(0-Math.PI/2), 2303 curY = height/2+offset*Math.sin(0-Math.PI/2), 2304 pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10, 2305 ttData = data.labels[0].trim() != "" ? data.labels[0]+": "+data.datasets[i].data[0] : data.datasets[i].data[0]; 2306 registerTooltip(ctx,{type:'circle',x:curX,y:curY,r:pointRadius},{label:data.labels[0],value:data.datasets[i].data[0]},'Radar',config.chart_data_type); 2307 } 2308 for (var j=1; j<data.datasets[i].data.length; j++){ 2309 offset = calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop); 2310 ctx.rotate(rotationDegree); 2311 ctx.lineTo(0,animationDecimal*(-1*offset)); 2312 if(animationDecimal >= 1 && config.showTooltips) { 2313 var curX = width/2+offset*Math.cos(j*rotationDegree-Math.PI/2), 2314 curY = height/2+offset*Math.sin(j*rotationDegree-Math.PI/2), 2315 pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10, 2316 ttData = data.labels[j].trim() != "" ? data.labels[j]+": "+data.datasets[i].data[j] : data.datasets[i].data[j]; 2317 registerTooltip(ctx,{type:'circle',x:curX,y:curY,r:pointRadius},{label:data.labels[j],value:data.datasets[i].data[j]},'Radar',config.chart_data_type); 2318 } 2319 } 2320 ctx.closePath(); 2321 2322 2323 ctx.fillStyle = data.datasets[i].fillColor; 2324 ctx.strokeStyle = data.datasets[i].strokeColor; 2325 ctx.lineWidth = config.datasetStrokeWidth; 2326 ctx.fill(); 2327 ctx.stroke(); 2328 2329 2330 if (config.pointDot){ 2331 ctx.fillStyle = data.datasets[i].pointColor; 2332 ctx.strokeStyle = data.datasets[i].pointStrokeColor; 2333 ctx.lineWidth = config.pointDotStrokeWidth; 2334 for (var k=0; k<data.datasets[i].data.length; k++){ 2335 ctx.rotate(rotationDegree); 2336 ctx.beginPath(); 2337 ctx.arc(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,2*Math.PI,false); 2338 ctx.fill(); 2339 ctx.stroke(); 2340 } 2341 2342 } 2343 ctx.rotate(rotationDegree); 2344 } 2345 ctx.restore(); 2346 2347 2348 } 2349 function drawScale(){ 2350 var rotationDegree = (2*Math.PI)/data.datasets[0].data.length; 2351 ctx.save(); 2352 ctx.translate(width / 2, height / 2); 2353 2354 if (config.angleShowLineOut){ 2355 ctx.strokeStyle = config.angleLineColor; 2356 ctx.lineWidth = config.angleLineWidth; 2357 for (var h=0; h<data.datasets[0].data.length; h++){ 2358 2359 ctx.rotate(rotationDegree); 2360 ctx.beginPath(); 2361 ctx.moveTo(0,0); 2362 ctx.lineTo(0,-maxSize); 2363 ctx.stroke(); 2364 } 2365 } 2366 2367 for (var i=0; i<calculatedScale.steps; i++){ 2368 ctx.beginPath(); 2369 2370 if(config.scaleShowLine){ 2371 ctx.strokeStyle = config.scaleLineColor; 2372 ctx.lineWidth = config.scaleLineWidth; 2373 ctx.moveTo(0,-scaleHop * (i+1)); 2374 for (var j=0; j<data.datasets[0].data.length; j++){ 2375 ctx.rotate(rotationDegree); 2376 ctx.lineTo(0,-scaleHop * (i+1)); 2377 } 2378 ctx.closePath(); 2379 ctx.stroke(); 2380 2381 } 2382 2383 if (config.scaleShowLabels){ 2384 ctx.textAlign = 'center'; 2385 ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; 2386 ctx.textBaseline = "middle"; 2387 2388 if (config.scaleShowLabelBackdrop){ 2389 var textWidth = ctx.measureText(calculatedScale.labels[i]).width; 2390 ctx.fillStyle = config.scaleBackdropColor; 2391 ctx.beginPath(); 2392 ctx.rect( 2393 Math.round(- textWidth/2 - config.scaleBackdropPaddingX), //X 2394 Math.round((-scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y 2395 Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width 2396 Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height 2397 ); 2398 ctx.fill(); 2399 } 2400 ctx.fillStyle = config.scaleFontColor; 2401 ctx.fillText(calculatedScale.labels[i],0,-scaleHop*(i+1)); 2402 } 2403 2404 } 2405 for (var k=0; k<data.labels.length; k++){ 2406 ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily; 2407 ctx.fillStyle = config.pointLabelFontColor; 2408 var opposite = Math.sin(rotationDegree*k) * (maxSize
2408+ config.pointLabelFontSize); 2409 var adjacent = Math.cos(rotationDegree*k) * (maxSize + config.pointLabelFontSize); 2410 2411 if(rotationDegree*k == Math.PI || rotationDegree*k == 0){ 2412 ctx.textAlign = "center"; 2413 } 2414 else if(rotationDegree*k > Math.PI){ 2415 ctx.textAlign = "right"; 2416 } 2417 else{ 2418 ctx.textAlign = "left"; 2419 } 2420 2421 ctx.textBaseline = "middle"; 2422 2423 ctx.fillText(data.labels[k],opposite,-adjacent); 2424 2425 } 2426 ctx.restore(); 2427 }; 2428 function calculateDrawingSizes(){ 2429 maxSize = (Min([width,height])/2); 2430 2431 labelHeight = config.scaleFontSize*2; 2432 2433 var labelLength = 0; 2434 for (var i=0; i<data.labels.length; i++){ 2435 ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily; 2436 var textMeasurement = ctx.measureText(data.labels[i]).width; 2437 if(textMeasurement>labelLength) labelLength = textMeasurement; 2438 } 2439 2440 //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. 2441 maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]); 2442 2443 maxSize -= config.pointLabelFontSize; 2444 maxSize = CapValue(maxSize, null, 0); 2445 scaleHeight = maxSize; 2446 //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. 2447 labelHeight = Default(labelHeight,5); 2448 }; 2449 function getValueBounds() { 2450 var upperValue = Number.MIN_VALUE; 2451 var lowerValue = Number.MAX_VALUE; 2452 2453 for (var i=0; i<data.datasets.length; i++){ 2454 for (var j=0; j<data.datasets[i].data.length; j++){ 2455 if (data.datasets[i].data[j] > upperValue){upperValue = data.datasets[i].data[j]} 2456 if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]} 2457 } 2458 } 2459 2460 var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); 2461 var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); 2462 2463 return { 2464 maxValue : upperValue, 2465 minValue : lowerValue, 2466 maxSteps : maxSteps, 2467 minSteps : minSteps 2468 }; 2469 2470 2471 } 2472 } 2473 2474 var Pie = function(data,config,ctx){ 2475 var segmentTotal = 0; 2476 2477 2478 //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. 2479 var pieRadius = Min([height/2,width/2]) - 5; 2480 2481 for (var i=0; i<data.length; i++){ 2482 segmentTotal += data[i].value; 2483 2484 } 2485 ctx.fillStyle = 'black'; 2486 ctx.textBaseline = 'base'; 2487 2488 animationLoop(config,null,drawPieSegments,ctx); 2489 2490 function drawPieSegments (animationDecimal){ 2491 var cumulativeAngle = -Math.PI/2, 2492 scaleAnimation = 1, 2493 rotateAnimation = 1; 2494 if (config.animation) { 2495 if (config.animateScale) { 2496 scaleAnimation = animationDecimal; 2497 } 2498 if (config.animateRotate){ 2499 rotateAnimation = animationDecimal; 2500 } 2501 } 2502 2503 for (var i=0; i<data.length; i++){ 2504 var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2)); 2505 ctx.beginPath(); 2506 ctx.arc(width/2,height/2,scaleAnimation * pieRadius,cumulativeAngle,cumulativeAngle + segmentAngle); 2507 ctx.lineTo(width/2,height/2); 2508 ctx.closePath(); 2509 ctx.fillStyle = data[i].color; 2510 ctx.fill(); 2511 2512 if(data[i].label && scaleAnimation*pieRadius*2*segmentAngle/(2*Math.PI) > config.labelFontSize) { 2513 function getPieLabelX(align, r) { 2514 switch(align) { 2515 case 'left': 2516 return -r+20; 2517 break; 2518 case 'center': 2519 return -r/2; 2520 break; 2521 } 2522 return -10; 2523 } 2524 2525 function reversePieLabelAlign(align) { 2526 switch(align) { 2527 case 'left': return 'right'; break; 2528 case 'right': return 'left'; break; 2529 case 'center': return align; break; 2530 } 2531 } 2532 2533 var fontSize = data[i].labelFontSize || config.labelFontSize+'px'; 2534 2535 if(fontSize.match(/^[0-9]+$/g) != null) { 2536 fontSize = fontSize+'px'; 2537 } 2538 ctx.font = config.labelFontStyle+ " " +fontSize+" " + config.labelFontFamily; 2539 ctx.fillStyle = getFadeColor(animationDecimal, data[i].labelColor || 'black', data[i].color); 2540 ctx.textBaseline = 'middle'; 2541 // rotate text, so it perfectly fits in segments 2542 var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2, 2543 tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation), 2544 tY = height/2-scaleAnimation*pieRadius*Math.sin(textRotation); 2545 ctx.textAlign = data[i].labelAlign || config.labelAlign; 2546 textX = getPieLabelX(ctx.textAlign, scaleAnimation*pieRadius); 2547 if(textRotation < -Math.PI/2) { 2548 textRotation -= Math.PI; 2549 ctx.textAlign = reversePieLabelAlign(ctx.textAlign); 2550 textX = -textX; 2551 } 2552 ctx.translate(tX, tY); 2553 ctx.rotate(-textRotation); 2554 ctx.fillText(data[i].label, textX, 0); 2555 ctx.rotate(textRotation); 2556 ctx.translate(-tX, -tY); 2557 } 2558 2559 if(animationDecimal >= 1 && config.showTooltips) { 2560 var points = [{x:width/2,y:height/2}], 2561 pAmount = 50; 2562 points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle)}); 2563 for(var p = 0; p <= pAmount; p++) { 2564 points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); 2565 }
2566 registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value,percentageValue:data[i].percentageValue},'Pie',config.chart_data_type); 2567 } 2568 2569 if(config.segmentShowStroke){ 2570 ctx.lineWidth = config.segmentStrokeWidth; 2571 ctx.strokeStyle = config.segmentStrokeColor; 2572 ctx.stroke(); 2573 } 2574 cumulativeAngle += segmentAngle; 2575 } 2576 } 2577 } 2578 2579 var Doughnut = function(data,config,ctx){ 2580 var segmentTotal = 0; 2581 2582 //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. 2583 var doughnutRadius = Min([height/2,width/2]) - 5; 2584 2585 var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100); 2586 2587 for (var i=0; i<data.length; i++){ 2588 segmentTotal += data[i].value; 2589 } 2590 2591 2592 animationLoop(config,null,drawPieSegments,ctx); 2593 2594 2595 function drawPieSegments (animationDecimal){ 2596 var cumulativeAngle = -Math.PI/2, 2597 scaleAnimation = 1, 2598 rotateAnimation = 1; 2599 if (config.animation) { 2600 if (config.animateScale) { 2601 scaleAnimation = animationDecimal; 2602 } 2603 if (config.animateRotate){ 2604 rotateAnimation = animationDecimal; 2605 } 2606 } 2607 for (var i=0; i<data.length; i++){ 2608 var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2)); 2609 ctx.beginPath(); 2610 ctx.arc(width/2,height/2,scaleAnimation * doughnutRadius,cumulativeAngle,cumulativeAngle + segmentAngle,false); 2611 ctx.arc(width/2,height/2,scaleAnimation * cutoutRadius,cumulativeAngle + segmentAngle,cumulativeAngle,true); 2612 ctx.closePath(); 2613 ctx.fillStyle = data[i].color; 2614 ctx.fill(); 2615 2616 if(animationDecimal >= 1 && config.showTooltips) { 2617 var points = [], 2618 pAmount = 50; 2619 points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle)}); 2620 for(var p = 0; p <= pAmount; p++) { 2621 points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); 2622 } 2623 points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+segmentAngle)}); 2624 for(var p = pAmount; p >= 0; p--) { 2625 points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); 2626 } 2627 registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value,percentageValue:data[i].percentageValue},'Doughnut',config.chart_data_type); 2628 } 2629 2630 if(config.segmentShowStroke){ 2631 ctx.lineWidth = config.segmentStrokeWidth; 2632 ctx.strokeStyle = config.segmentStrokeColor; 2633 ctx.stroke(); 2634 } 2635 cumulativeAngle += segmentAngle; 2636 } 2637 } 2638 2639 2640 2641 } 2642 2643 var Line = function(data,config,ctx){ 2644 var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0; 2645 2646 calculateDrawingSizes(); 2647 2648 valueBounds = getValueBounds(); 2649 //Check and set the scale 2650 labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; 2651 if (!config.scaleOverride){ 2652 2653 calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); 2654 } 2655 else { 2656 calculatedScale = { 2657 steps : config.scaleSteps, 2658 stepValue : config.scaleStepWidth, 2659 graphMin : config.scaleStartValue, 2660 labels : [] 2661 } 2662 populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); 2663 } 2664 2665 scaleHop = Math.floor(scaleHeight/calculatedScale.steps); 2666 calculateXAxisSize(); 2667 animationLoop(config,drawScale,drawLines,ctx); 2668 2669 function drawLines(animPc){ 2670 for (var i=0; i<data.datasets.length; i++){ 2671 ctx.strokeStyle = data.datasets[i].strokeColor; 2672 ctx.lineWidth = config.datasetStrokeWidth; 2673 ctx.beginPath(); 2674 ctx.moveTo(yAxisPosX, xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[0],c
2674alculatedScale,scaleHop))) 2675 2676 for (var j=1; j<data.datasets[i].data.length; j++){ 2677 if (config.bezierCurve){ 2678 ctx.bezierCurveTo(xPos(j-0.5),yPos(i,j-1),xPos(j-0.5),yPos(i,j),xPos(j),yPos(i,j)); 2679 } 2680 else{ 2681 ctx.lineTo(xPos(j),yPos(i,j)); 2682 } 2683 } 2684 var pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10; 2685 for(var j = 0; j < data.datasets[i].data.length; j++) { 2686 if(animPc >= 1 && config.showTooltips) { 2687 // register tooltips 2688 registerTooltip(ctx,{type:'circle',x:xPos(j),y:yPos(i,j),r:pointRadius},{label:data.labels[j],value:data.datasets[i].data[j]},'Line',config.chart_data_type); 2689 } 2690 } 2691 ctx.stroke(); 2692 if (config.datasetFill){ 2693 ctx.lineTo(yAxisPosX + (valueHop*(data.datasets[i].data.length-1)),xAxisPosY); 2694 ctx.lineTo(yAxisPosX,xAxisPosY); 2695 ctx.closePath(); 2696 ctx.fillStyle = data.datasets[i].fillColor; 2697 ctx.fill(); 2698 } 2699 else{ 2700 ctx.closePath(); 2701 } 2702 if(config.pointDot){ 2703 ctx.fillStyle = data.datasets[i].pointColor; 2704 ctx.strokeStyle = data.datasets[i].pointStrokeColor; 2705 ctx.lineWidth = config.pointDotStrokeWidth; 2706 for (var k=0; k<data.datasets[i].data.length; k++){ 2707 ctx.beginPath(); 2708 ctx.arc(yAxisPosX + (valueHop *k),xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,0,Math.PI*2,true); 2709 ctx.fill(); 2710 ctx.stroke(); 2711 } 2712 } 2713 } 2714 2715 function yPos(dataSet,iteration){ 2716 return xAxisPosY - animPc*(calculateOffset(data.datasets[dataSet].data[iteration],calculatedScale,scaleHop)); 2717 } 2718 function xPos(iteration){ 2719 return yAxisPosX + (valueHop * iteration); 2720 } 2721 } 2722 function drawScale(){ 2723 //X axis line 2724 ctx.lineWidth = config.scaleLineWidth; 2725 ctx.strokeStyle = config.scaleLineColor; 2726 ctx.beginPath(); 2727 ctx.moveTo(width-widestXLabel/2+5,xAxisPosY); 2728 ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY); 2729 ctx.stroke(); 2730 2731 2732 if (rotateLabels > 0){ 2733 ctx.save(); 2734 ctx.textAlign = "right"; 2735 } 2736 else{ 2737 ctx.textAlign = "center"; 2738 } 2739 ctx.fillStyle = config.scaleFontColor; 2740 for (var i=0; i<data.labels.length; i++){ 2741 ctx.save(); 2742 if (rotateLabels > 0){ 2743 ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); 2744 ctx.rotate(-(rotateLabels * (Math.PI/180))); 2745 ctx.fillText(data.labels[i], 0,0); 2746 ctx.restore(); 2747 } 2748 2749 else{ 2750 ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3); 2751 } 2752 2753 ctx.beginPath(); 2754 ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3); 2755 2756 //Check i isnt 0, so we dont go over the Y axis twice. 2757 if(config.scaleShowGridLines && i>0){ 2758 ctx.lineWidth = config.scaleGridLineWidth; 2759 ctx.strokeStyle = config.scaleGridLineColor; 2760 ctx.lineTo(yAxisPosX + i * valueHop, 5); 2761 } 2762 else{ 2763 ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3); 2764 } 2765 ctx.stroke(); 2766 } 2767 2768 //Y axis 2769 ctx.lineWidth = config.scaleLineWidth; 2770 ctx.strokeStyle = config.scaleLineColor; 2771 ctx.beginPath(); 2772 ctx.moveTo(yAxisPosX,xAxisPosY+5); 2773 ctx.lineTo(yAxisPosX,5); 2774 ctx.stroke(); 2775 2776 ctx.textAlign = "right"; 2777 ctx.textBaseline = "middle"; 2778 for (var j=0; j<calculatedScale.steps; j++){ 2779 ctx.beginPath();
2780 ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop)); 2781 if (config.scaleShowGridLines){ 2782 ctx.lineWidth = config.scaleGridLineWidth; 2783 ctx.strokeStyle = config.scaleGridLineColor; 2784 ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop)); 2785 } 2786 else{ 2787 ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop)); 2788 } 2789 2790 ctx.stroke(); 2791 2792 if (config.scaleShowLabels){ 2793 ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop)); 2794 } 2795 } 2796 2797 2798 } 2799 function calculateXAxisSize(){ 2800 var longestText = 1; 2801 //if we are showing the labels 2802 if (config.scaleShowLabels){ 2803 ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; 2804 for (var i=0; i<calculatedScale.labels.length; i++){ 2805 var measuredText = ctx.measureText(calculatedScale.labels[i]).width; 2806 longestText = (measuredText > longestText)? measuredText : longestText; 2807 } 2808 //Add a little extra padding from the y axis 2809 longestText +=10; 2810 } 2811 xAxisLength = width - longestText - widestXLabel; 2812 valueHop = Math.floor(xAxisLength/(data.labels.length-1)); 2813 2814 yAxisPosX = width-widestXLabel/2-xAxisLength; 2815 xAxisPosY = scaleHeight + config.scaleFontSize/2; 2816 } 2817 function calculateDrawingSizes(){ 2818 maxSize = height; 2819 2820 //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. 2821 ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; 2822 widestXLabel = 1; 2823 for (var i=0; i<data.labels.length; i++){ 2824 var textLength = ctx.measureText(data.labels[i]).width; 2825 //If the text length is longer - make that equal to longest text! 2826 widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel; 2827 } 2828 if (width/data.labels.length < widestXLabel){ 2829 rotateLabels = 45; 2830 if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ 2831 rotateLabels = 90; 2832 maxSize -= widestXLabel; 2833 } 2834 else{ 2835 maxSize -= Math.sin(rotateLabels) * widestXLabel; 2836 } 2837 } 2838 else{ 2839 maxSize -= config.scaleFontSize; 2840 } 2841 2842 //Add a little padding between the x line and the text 2843 maxSize -= 5; 2844 2845 2846 labelHeight = config.scaleFontSize; 2847 2848 maxSize -= labelHeight; 2849 //Set 5 pixels greater than the font size to allow for a little padding from the X axis. 2850 2851 scaleHeight = maxSize; 2852 2853 //Then get the area above we can safely draw on. 2854 2855 } 2856 function getValueBounds() { 2857 var upperValue = Number.MIN_VALUE; 2858 var lowerValue = Number.MAX_VALUE; 2859 for (var i=0; i<data.datasets.length; i++){ 2860 for (var j=0; j<data.datasets[i].data.length; j++){ 2861 if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] }; 2862 if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; 2863 } 2864 }; 2865 2866 var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); 2867 var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); 2868 2869 return { 2870 maxValue : upperValue, 2871 minValue : lowerValue, 2872 maxSteps : maxSteps, 2873 minSteps : minSteps 2874 }; 2875 2876 2877 } 2878 2879 2880 } 2881 2882 var Bar = function(data,config,ctx){ 2883 var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0; 2884 2885 calculateDrawingSizes(); 2886 2887 valueBounds = getValueBounds(); 2888 //Check and set the scale 2889 labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; 2890 if (!config.scaleOverride){ 2891 2892 calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); 2893 } 2894 else { 2895 calculatedScale = { 2896 steps : config.scaleSteps, 2897 stepValue : config.scaleStepWidth, 2898 graphMin : config.scaleStartValue, 2899 labels : [] 2900 } 2901 populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); 2902 } 2903 2904 scaleHop = Math.floor(scaleHeight/calculatedScale.steps); 2905 calculateXAxisSize(); 2906 animationLoop(config,drawScale,drawBars,ctx); 2907 2908 function drawBars(animPc){ 2909 ctx.lineWidth = config.barStrokeWidth; 2910 for (var i=0; i<data.datasets.length; i++){ 2911 ctx.fillStyle = data.datasets[i].fillColor; 2912 ctx.strokeStyle = data.datasets[i].strokeColor; 2913 for (var j=0; j<data.datasets[i].data.length; j++){ 2914 var barOffset = yAxisPosX + config.barValueSpacing + valueHop*j + barWidth*i + config.barDatasetSpacing*i + config.barStrokeWidth*i; 2915 2916 ctx.beginPath();
2917 ctx.moveTo(barOffset, xAxisPosY); 2918 ctx.lineTo(barOffset, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2)); 2919 ctx.lineTo(barOffset + barWidth, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2)); 2920 ctx.lineTo(barOffset + barWidth, xAxisPosY); 2921 if(config.barShowStroke){ 2922 ctx.stroke(); 2923 } 2924 ctx.closePath(); 2925 ctx.fill(); 2926 2927 if(animPc >= 1 && config.showTooltips) { 2928 // register tooltips 2929 var x = barOffset, 2930 height = calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop), 2931 y = xAxisPosY-height, 2932 width = barWidth; 2933 registerTooltip(ctx,{type:'rect',x:x,y:y,width:width,height:height},{label:data.labels[j],value:data.datasets[i].data[j]},'Bar',config.chart_data_type); 2934 } 2935 } 2936 } 2937 2938 } 2939 function drawScale(){ 2940 //X axis line 2941 ctx.lineWidth = config.scaleLineWidth; 2942 ctx.strokeStyle = config.scaleLineColor; 2943 ctx.beginPath(); 2944 ctx.moveTo(width-widestXLabel/2+5,xAxisPosY); 2945 ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY); 2946 ctx.stroke(); 2947 2948 2949 if (rotateLabels > 0){ 2950 ctx.save(); 2951 ctx.textAlign = "right"; 2952 } 2953 else{ 2954 ctx.textAlign = "center"; 2955 } 2956 ctx.fillStyle = config.scaleFontColor; 2957 for (var i=0; i<data.labels.length; i++){ 2958 ctx.save(); 2959 if (rotateLabels > 0){ 2960 ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); 2961 ctx.rotate(-(rotateLabels * (Math.PI/180))); 2962 ctx.fillText(data.labels[i], 0,0); 2963 ctx.restore(); 2964 } 2965 2966 else{ 2967 ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3); 2968 } 2969 2970 ctx.beginPath(); 2971 ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3); 2972 2973 //Check i isnt 0, so we dont go over the Y axis twice. 2974 ctx.lineWidth = config.scaleGridLineWidth; 2975 ctx.strokeStyle = config.scaleGridLineColor; 2976 ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5); 2977 ctx.stroke(); 2978 } 2979 2980 //Y axis 2981 ctx.lineWidth = config.scaleLineWidth; 2982 ctx.strokeStyle = config.scaleLineColor; 2983 ctx.beginPath(); 2984 ctx.moveTo(yAxisPosX,xAxisPosY+5); 2985 ctx.lineTo(yAxisPosX,5); 2986 ctx.stroke(); 2987 2988 ctx.textAlign = "right"; 2989 ctx.textBaseline = "middle"; 2990 for (var j=0; j<calculatedScale.steps; j++){ 2991 ctx.beginPath(); 2992 ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop)); 2993 if (config.scaleShowGridLines){ 2994 ctx.lineWidth = config.scaleGridLineWidth; 2995 ctx.strokeStyle = config.scaleGridLineColor; 2996 ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop)); 2997 } 2998 else{ 2999 ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop)); 3000 } 3001 3002 ctx.stroke(); 3003 if (config.scaleShowLabels){ 3004 ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop)); 3005 } 3006 } 3007 3008 3009 } 3010 function calculateXAxisSize(){ 3011 var longestText = 1; 3012 //if we are showing the labels 3013 if (config.scaleShowLabels){ 3014 ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; 3015 for (var i=0; i<calculatedScale.labels.length; i++){ 3016 var measuredText = ctx.measureText(calculatedScale.labels[i]).width; 3017 longestText = (measuredText > longestText)? measuredText : longestText; 3018 } 3019 //Add a little extra padding from the y axis 3020 longestText +=10; 3021 } 3022 xAxisLength = width - longestText - widestXLabel; 3023 valueHop = Math.floor(xAxisLength/(data.labels.length)); 3024 3025 barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length; 3026 3027 yAxisPosX = width-widestXLabel/2-xAxisLength; 3028 xAxisPosY = scaleHeight + config.scaleFontSize/2; 3029 } 3030 function calculateDrawingSizes(){ 3031 maxSize = height; 3032 3033 //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. 3034 ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; 3035 widestXLabel = 1; 3036 for (var i=0; i<data.labels.length; i++){ 3037 var textLength = ctx.measureText(data.labels[i]).width; 3038 //If the text length is longer - make that equal to longest text! 3039 widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel; 3040 } 3041 if (width/data.labels.length < widestXLabel){ 3042 rotateLabels = 45;
3043 if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ 3044 rotateLabels = 90; 3045 maxSize -= widestXLabel; 3046 } 3047 else{ 3048 maxSize -= Math.sin(rotateLabels) * widestXLabel; 3049 } 3050 } 3051 else{ 3052 maxSize -= config.scaleFontSize; 3053 } 3054 3055 //Add a little padding between the x line and the text 3056 maxSize -= 5; 3057 3058 3059 labelHeight = config.scaleFontSize; 3060 3061 maxSize -= labelHeight; 3062 //Set 5 pixels greater than the font size to allow for a little padding from the X axis. 3063 3064 scaleHeight = maxSize; 3065 3066 //Then get the area above we can safely draw on. 3067 3068 } 3069 function getValueBounds() { 3070 var upperValue = Number.MIN_VALUE; 3071 var lowerValue = Number.MAX_VALUE; 3072 for (var i=0; i<data.datasets.length; i++){ 3073 for (var j=0; j<data.datasets[i].data.length; j++){ 3074 if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] }; 3075 if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; 3076 } 3077 }; 3078 3079 var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); 3080 var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); 3081 3082 return { 3083 maxValue : upperValue, 3084 minValue : lowerValue, 3085 maxSteps : maxSteps, 3086 minSteps : minSteps 3087 }; 3088 3089 3090 } 3091 } 3092 3093 function calculateOffset(val,calculatedScale,scaleHop){ 3094 var outerValue = calculatedScale.steps * calculatedScale.stepValue; 3095 var adjustedValue = val - calculatedScale.graphMin; 3096 var scalingFactor = CapValue(adjustedValue/outerValue,1,0); 3097 return (scaleHop*calculatedScale.steps) * scalingFactor; 3098 } 3099 3100 function animationLoop(config,drawScale,drawData,ctx){ 3101 var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1, 3102 easingFunction = animationOptions[config.animationEasing], 3103 percentAnimComplete =(config.animation)? 0 : 1; 3104 3105 3106 3107 if (typeof drawScale !== "function") drawScale = function(){}; 3108 3109 requestAnimFrame(animLoop); 3110 3111 function animateFrame(){ 3112 var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1; 3113 clear(ctx); 3114 if(config.scaleOverlay){ 3115 drawData(easeAdjustedAnimationPercent); 3116 drawScale(); 3117 } else { 3118 drawScale(); 3119 drawData(easeAdjustedAnimationPercent); 3120 } 3121 } 3122 function animLoop(){ 3123 //We need to check if the animation is incomplete (less than 1), or complete (1). 3124 percentAnimComplete += animFrameAmount; 3125 animateFrame(); 3126 //Stop the loop continuing forever 3127 if (percentAnimComplete <= 1){ 3128 requestAnimFrame(animLoop); 3129 } 3130 else{ 3131 if (typeof config.onAnimationComplete == "function") config.onAnimationComplete(); 3132 } 3133 3134 } 3135 3136 } 3137 3138 //Declare global functions to be called within this namespace here. 3139 3140 3141 // shim layer with setTimeout fallback 3142 var requestAnimFrame = (function(){ 3143 return window.requestAnimationFrame || 3144 window.webkitRequestAnimationFrame || 3145 window.mozRequestAnimationFrame || 3146 window.oRequestAnimationFrame || 3147 window.msRequestAnimationFrame || 3148 function(callback) { 3149 window.setTimeout(callback, 1000 / 60); 3150 }; 3151 })(); 3152 3153 function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){ 3154 var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum; 3155 valueRange = maxValue - minValue; 3156 rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); 3157 graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); 3158 graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); 3159 graphRange = graphMax - graphMin; 3160 stepValue = Math.pow(10, rangeOrderOfMagnitude); 3161 numberOfSteps = Math.round(graphRange / stepValue); 3162 3163 //Compare number of steps to the max and min for that size graph, and add in half steps if need be. 3164 while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { 3165 if (numberOfSteps < minSteps){ 3166 stepValue /= 2; 3167 numberOfSteps = Math.round(graphRange/stepValue); 3168 } 3169 else{ 3170 stepValue *=2; 3171 numberOfSteps = Math.round(graphRange/stepValue); 3172 } 3173 } 3174 3175 var labels = []; 3176 populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue); 3177 3178 return { 3179 steps : numberOfSteps, 3180 stepValue : stepValue, 3181 graphMin : graphMin, 3182 labels : labels 3183 } 3184 3185 function calculateOrderOfMagnitude(val){ 3186 return Math.floor(Math.log(val) / Math.LN10); 3187 } 3188 } 3189 3190 //Populate an array of all the labels by interpolating the string. 3191 function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) { 3192 if (labelTemplateString) { 3193 //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. 3194 for (var i = 1; i < numberOfSteps + 1; i++) { 3195 labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))})); 3196 } 3197 } 3198 } 3199 3200 //Populate an array of all the labels by interpolating the string. 3201 function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) { 3202 if (labelTemplateString) { 3203 //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. 3204 for (var i = 1; i < numberOfSteps + 1; i++) { 3205 labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))})); 3206 } 3207 } 3208 } 3209 3210 //Max value from array 3211 function Max( array ){ 3212 return Math.max.apply( Math, array ); 3213 }; 3214 //Min value from array 3215 function Min( array ){ 3216 return Math.min.apply( Math, array ); 3217 }; 3218 //Default if undefined 3219 function Default(userDeclared,valueIfFalse){ 3220 if(!userDeclared){ 3221 return valueIfFalse; 3222 } else { 3223 return userDeclared; 3224 } 3225 }; 3226 //Is a number function 3227 function isNumber(n) { 3228 return !isNaN(parseFloat(n)) && isFinite(n); 3229 } 3230 //Apply cap a value at a high or low number 3231 function CapValue(valueToCap, maxValue, minValue){ 3232 if(isNumber(maxValue)) { 3233 if( valueToCap > maxValue ) { 3234 return maxValue; 3235 } 3236 } 3237 if(isNumber(minValue)){ 3238 if ( valueToCap < minValue ){ 3239 return minValue; 3240 } 3241 } 3242 return valueToCap; 3243 } 3244 function getDecimalPlaces (num){ 3245 var numberOfDecimalPlaces; 3246 if (num%1!=0){ 3247 return num.toString().split(".")[1].length 3248 } 3249 else{ 3250 return 0; 3251 } 3252 3253 } 3254 3255 function mergeChartConfig(defaults,userDefined){ 3256 var returnObj = {}; 3257 for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; } 3258 for (var attrname in userDefined) { 3259 if(typeof(userDefined[attrname]) === "object" && defaults[attrname]) { 3260 returnObj[attrname] = mergeChartConfig(defaults[attrname], userDefined[attrname]); 3261 } else { 3262 returnObj[attrname] = userDefined[attrname]; 3263 } 3264 } 3265 return returnObj; 3266 } 3267 3268 //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ 3269 var cache = {}; 3270 3271 function tmpl(str, data){ 3272 // Figure out if we're getting a template, or if we need to 3273 // load the template - and be sure to cache the result. 3274 var fn = !/\W/.test(str) ? 3275 cache[str] = cache[str] || 3276 tmpl(document.getElementById(str).innerHTML) : 3277 3278 // Generate a reusable function that will serve as a template 3279 // generator (and which will be cached). 3280 new Function("obj",
3281 "var p=[],print=function(){p.push.apply(p,arguments);};" + 3282 3283 // Introduce the data as local variables using with(){} 3284 "with(obj){p.push('" + 3285 3286 // Convert the template into pure JavaScript 3287 str 3288 .replace(/[\r\t\n]/g, " ") 3289 .split("<%").join("\t") 3290 .replace(/((^|%>)[^\t]*)'/g, "$1\r") 3291 .replace(/\t=(.*?)%>/g, "',$1,'") 3292 .split("\t").join("');") 3293 .split("%>").join("p.push('") 3294 .split("\r").join("\\'") 3295 + "');}return p.join('');"); 3296 3297 // Provide some basic currying to the user 3298 return data ? fn( data ) : fn; 3299 }; 3300 3301 function getFadeColor(percent, primColor, secColor) { 3302 return "rgba(1,255,255, 0.0)"; 3303 } 3304}
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.