PageSourceSearch

https://www.kwxx.com/wp-content/plugins/weblator_polling/public/as…tor_polling_combine_charts.js?ver=1.3.5.6_5

js kwxx.com collected 2026-09-28 12:12:56 UTC 118,718 bytes, 3,304 lines download raw bytes

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, '&amp;').replace(/"/g, '&quot;');
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.