[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /*! 2 * Chart.js 3 * http://chartjs.org/ 4 * Version: 2.1.6 5 * 6 * Copyright 2016 Nick Downie 7 * Released under the MIT license 8 * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md 9 */ 10 11 /** 12 * Description of import into Moodle: 13 * 14 * - Download from http://www.chartjs.org/docs/#getting-started-download-chart-js. 15 * - Copy Chart.js to lib/amd/src/chartjs.js. 16 * - Add these instructions to the file. 17 * - Add the jshint ignore rules. 18 */ 19 20 (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Chart = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 21 22 },{}],2:[function(require,module,exports){ 23 /* MIT license */ 24 var colorNames = require(6); 25 26 module.exports = { 27 getRgba: getRgba, 28 getHsla: getHsla, 29 getRgb: getRgb, 30 getHsl: getHsl, 31 getHwb: getHwb, 32 getAlpha: getAlpha, 33 34 hexString: hexString, 35 rgbString: rgbString, 36 rgbaString: rgbaString, 37 percentString: percentString, 38 percentaString: percentaString, 39 hslString: hslString, 40 hslaString: hslaString, 41 hwbString: hwbString, 42 keyword: keyword 43 } 44 45 function getRgba(string) { 46 if (!string) { 47 return; 48 } 49 var abbr = /^#([a-fA-F0-9]{3})$/, 50 hex = /^#([a-fA-F0-9]{6})$/, 51 rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/, 52 per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/, 53 keyword = /(\w+)/; 54 55 var rgb = [0, 0, 0], 56 a = 1, 57 match = string.match(abbr); 58 if (match) { 59 match = match[1]; 60 for (var i = 0; i < rgb.length; i++) { 61 rgb[i] = parseInt(match[i] + match[i], 16); 62 } 63 } 64 else if (match = string.match(hex)) { 65 match = match[1]; 66 for (var i = 0; i < rgb.length; i++) { 67 rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); 68 } 69 } 70 else if (match = string.match(rgba)) { 71 for (var i = 0; i < rgb.length; i++) { 72 rgb[i] = parseInt(match[i + 1]); 73 } 74 a = parseFloat(match[4]); 75 } 76 else if (match = string.match(per)) { 77 for (var i = 0; i < rgb.length; i++) { 78 rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); 79 } 80 a = parseFloat(match[4]); 81 } 82 else if (match = string.match(keyword)) { 83 if (match[1] == "transparent") { 84 return [0, 0, 0, 0]; 85 } 86 rgb = colorNames[match[1]]; 87 if (!rgb) { 88 return; 89 } 90 } 91 92 for (var i = 0; i < rgb.length; i++) { 93 rgb[i] = scale(rgb[i], 0, 255); 94 } 95 if (!a && a != 0) { 96 a = 1; 97 } 98 else { 99 a = scale(a, 0, 1); 100 } 101 rgb[3] = a; 102 return rgb; 103 } 104 105 function getHsla(string) { 106 if (!string) { 107 return; 108 } 109 var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; 110 var match = string.match(hsl); 111 if (match) { 112 var alpha = parseFloat(match[4]); 113 var h = scale(parseInt(match[1]), 0, 360), 114 s = scale(parseFloat(match[2]), 0, 100), 115 l = scale(parseFloat(match[3]), 0, 100), 116 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); 117 return [h, s, l, a]; 118 } 119 } 120 121 function getHwb(string) { 122 if (!string) { 123 return; 124 } 125 var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; 126 var match = string.match(hwb); 127 if (match) { 128 var alpha = parseFloat(match[4]); 129 var h = scale(parseInt(match[1]), 0, 360), 130 w = scale(parseFloat(match[2]), 0, 100), 131 b = scale(parseFloat(match[3]), 0, 100), 132 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); 133 return [h, w, b, a]; 134 } 135 } 136 137 function getRgb(string) { 138 var rgba = getRgba(string); 139 return rgba && rgba.slice(0, 3); 140 } 141 142 function getHsl(string) { 143 var hsla = getHsla(string); 144 return hsla && hsla.slice(0, 3); 145 } 146 147 function getAlpha(string) { 148 var vals = getRgba(string); 149 if (vals) { 150 return vals[3]; 151 } 152 else if (vals = getHsla(string)) { 153 return vals[3]; 154 } 155 else if (vals = getHwb(string)) { 156 return vals[3]; 157 } 158 } 159 160 // generators 161 function hexString(rgb) { 162 return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1]) 163 + hexDouble(rgb[2]); 164 } 165 166 function rgbString(rgba, alpha) { 167 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { 168 return rgbaString(rgba, alpha); 169 } 170 return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; 171 } 172 173 function rgbaString(rgba, alpha) { 174 if (alpha === undefined) { 175 alpha = (rgba[3] !== undefined ? rgba[3] : 1); 176 } 177 return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] 178 + ", " + alpha + ")"; 179 } 180 181 function percentString(rgba, alpha) { 182 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { 183 return percentaString(rgba, alpha); 184 } 185 var r = Math.round(rgba[0]/255 * 100), 186 g = Math.round(rgba[1]/255 * 100), 187 b = Math.round(rgba[2]/255 * 100); 188 189 return "rgb(" + r + "%, " + g + "%, " + b + "%)"; 190 } 191 192 function percentaString(rgba, alpha) { 193 var r = Math.round(rgba[0]/255 * 100), 194 g = Math.round(rgba[1]/255 * 100), 195 b = Math.round(rgba[2]/255 * 100); 196 return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; 197 } 198 199 function hslString(hsla, alpha) { 200 if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { 201 return hslaString(hsla, alpha); 202 } 203 return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; 204 } 205 206 function hslaString(hsla, alpha) { 207 if (alpha === undefined) { 208 alpha = (hsla[3] !== undefined ? hsla[3] : 1); 209 } 210 return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " 211 + alpha + ")"; 212 } 213 214 // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax 215 // (hwb have alpha optional & 1 is default value) 216 function hwbString(hwb, alpha) { 217 if (alpha === undefined) { 218 alpha = (hwb[3] !== undefined ? hwb[3] : 1); 219 } 220 return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" 221 + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; 222 } 223 224 function keyword(rgb) { 225 return reverseNames[rgb.slice(0, 3)]; 226 } 227 228 // helpers 229 function scale(num, min, max) { 230 return Math.min(Math.max(min, num), max); 231 } 232 233 function hexDouble(num) { 234 var str = num.toString(16).toUpperCase(); 235 return (str.length < 2) ? "0" + str : str; 236 } 237 238 239 //create a list of reverse color names 240 var reverseNames = {}; 241 for (var name in colorNames) { 242 reverseNames[colorNames[name]] = name; 243 } 244 245 },{"6":6}],3:[function(require,module,exports){ 246 /* MIT license */ 247 var convert = require(5); 248 var string = require(2); 249 250 var Color = function (obj) { 251 if (obj instanceof Color) { 252 return obj; 253 } 254 if (!(this instanceof Color)) { 255 return new Color(obj); 256 } 257 258 this.values = { 259 rgb: [0, 0, 0], 260 hsl: [0, 0, 0], 261 hsv: [0, 0, 0], 262 hwb: [0, 0, 0], 263 cmyk: [0, 0, 0, 0], 264 alpha: 1 265 }; 266 267 // parse Color() argument 268 var vals; 269 if (typeof obj === 'string') { 270 vals = string.getRgba(obj); 271 if (vals) { 272 this.setValues('rgb', vals); 273 } else if (vals = string.getHsla(obj)) { 274 this.setValues('hsl', vals); 275 } else if (vals = string.getHwb(obj)) { 276 this.setValues('hwb', vals); 277 } else { 278 throw new Error('Unable to parse color from string "' + obj + '"'); 279 } 280 } else if (typeof obj === 'object') { 281 vals = obj; 282 if (vals.r !== undefined || vals.red !== undefined) { 283 this.setValues('rgb', vals); 284 } else if (vals.l !== undefined || vals.lightness !== undefined) { 285 this.setValues('hsl', vals); 286 } else if (vals.v !== undefined || vals.value !== undefined) { 287 this.setValues('hsv', vals); 288 } else if (vals.w !== undefined || vals.whiteness !== undefined) { 289 this.setValues('hwb', vals); 290 } else if (vals.c !== undefined || vals.cyan !== undefined) { 291 this.setValues('cmyk', vals); 292 } else { 293 throw new Error('Unable to parse color from object ' + JSON.stringify(obj)); 294 } 295 } 296 }; 297 298 Color.prototype = { 299 rgb: function () { 300 return this.setSpace('rgb', arguments); 301 }, 302 hsl: function () { 303 return this.setSpace('hsl', arguments); 304 }, 305 hsv: function () { 306 return this.setSpace('hsv', arguments); 307 }, 308 hwb: function () { 309 return this.setSpace('hwb', arguments); 310 }, 311 cmyk: function () { 312 return this.setSpace('cmyk', arguments); 313 }, 314 315 rgbArray: function () { 316 return this.values.rgb; 317 }, 318 hslArray: function () { 319 return this.values.hsl; 320 }, 321 hsvArray: function () { 322 return this.values.hsv; 323 }, 324 hwbArray: function () { 325 var values = this.values; 326 if (values.alpha !== 1) { 327 return values.hwb.concat([values.alpha]); 328 } 329 return values.hwb; 330 }, 331 cmykArray: function () { 332 return this.values.cmyk; 333 }, 334 rgbaArray: function () { 335 var values = this.values; 336 return values.rgb.concat([values.alpha]); 337 }, 338 hslaArray: function () { 339 var values = this.values; 340 return values.hsl.concat([values.alpha]); 341 }, 342 alpha: function (val) { 343 if (val === undefined) { 344 return this.values.alpha; 345 } 346 this.setValues('alpha', val); 347 return this; 348 }, 349 350 red: function (val) { 351 return this.setChannel('rgb', 0, val); 352 }, 353 green: function (val) { 354 return this.setChannel('rgb', 1, val); 355 }, 356 blue: function (val) { 357 return this.setChannel('rgb', 2, val); 358 }, 359 hue: function (val) { 360 if (val) { 361 val %= 360; 362 val = val < 0 ? 360 + val : val; 363 } 364 return this.setChannel('hsl', 0, val); 365 }, 366 saturation: function (val) { 367 return this.setChannel('hsl', 1, val); 368 }, 369 lightness: function (val) { 370 return this.setChannel('hsl', 2, val); 371 }, 372 saturationv: function (val) { 373 return this.setChannel('hsv', 1, val); 374 }, 375 whiteness: function (val) { 376 return this.setChannel('hwb', 1, val); 377 }, 378 blackness: function (val) { 379 return this.setChannel('hwb', 2, val); 380 }, 381 value: function (val) { 382 return this.setChannel('hsv', 2, val); 383 }, 384 cyan: function (val) { 385 return this.setChannel('cmyk', 0, val); 386 }, 387 magenta: function (val) { 388 return this.setChannel('cmyk', 1, val); 389 }, 390 yellow: function (val) { 391 return this.setChannel('cmyk', 2, val); 392 }, 393 black: function (val) { 394 return this.setChannel('cmyk', 3, val); 395 }, 396 397 hexString: function () { 398 return string.hexString(this.values.rgb); 399 }, 400 rgbString: function () { 401 return string.rgbString(this.values.rgb, this.values.alpha); 402 }, 403 rgbaString: function () { 404 return string.rgbaString(this.values.rgb, this.values.alpha); 405 }, 406 percentString: function () { 407 return string.percentString(this.values.rgb, this.values.alpha); 408 }, 409 hslString: function () { 410 return string.hslString(this.values.hsl, this.values.alpha); 411 }, 412 hslaString: function () { 413 return string.hslaString(this.values.hsl, this.values.alpha); 414 }, 415 hwbString: function () { 416 return string.hwbString(this.values.hwb, this.values.alpha); 417 }, 418 keyword: function () { 419 return string.keyword(this.values.rgb, this.values.alpha); 420 }, 421 422 rgbNumber: function () { 423 var rgb = this.values.rgb; 424 return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; 425 }, 426 427 luminosity: function () { 428 // http://www.w3.org/TR/WCAG20/#relativeluminancedef 429 var rgb = this.values.rgb; 430 var lum = []; 431 for (var i = 0; i < rgb.length; i++) { 432 var chan = rgb[i] / 255; 433 lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); 434 } 435 return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; 436 }, 437 438 contrast: function (color2) { 439 // http://www.w3.org/TR/WCAG20/#contrast-ratiodef 440 var lum1 = this.luminosity(); 441 var lum2 = color2.luminosity(); 442 if (lum1 > lum2) { 443 return (lum1 + 0.05) / (lum2 + 0.05); 444 } 445 return (lum2 + 0.05) / (lum1 + 0.05); 446 }, 447 448 level: function (color2) { 449 var contrastRatio = this.contrast(color2); 450 if (contrastRatio >= 7.1) { 451 return 'AAA'; 452 } 453 454 return (contrastRatio >= 4.5) ? 'AA' : ''; 455 }, 456 457 dark: function () { 458 // YIQ equation from http://24ways.org/2010/calculating-color-contrast 459 var rgb = this.values.rgb; 460 var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; 461 return yiq < 128; 462 }, 463 464 light: function () { 465 return !this.dark(); 466 }, 467 468 negate: function () { 469 var rgb = []; 470 for (var i = 0; i < 3; i++) { 471 rgb[i] = 255 - this.values.rgb[i]; 472 } 473 this.setValues('rgb', rgb); 474 return this; 475 }, 476 477 lighten: function (ratio) { 478 var hsl = this.values.hsl; 479 hsl[2] += hsl[2] * ratio; 480 this.setValues('hsl', hsl); 481 return this; 482 }, 483 484 darken: function (ratio) { 485 var hsl = this.values.hsl; 486 hsl[2] -= hsl[2] * ratio; 487 this.setValues('hsl', hsl); 488 return this; 489 }, 490 491 saturate: function (ratio) { 492 var hsl = this.values.hsl; 493 hsl[1] += hsl[1] * ratio; 494 this.setValues('hsl', hsl); 495 return this; 496 }, 497 498 desaturate: function (ratio) { 499 var hsl = this.values.hsl; 500 hsl[1] -= hsl[1] * ratio; 501 this.setValues('hsl', hsl); 502 return this; 503 }, 504 505 whiten: function (ratio) { 506 var hwb = this.values.hwb; 507 hwb[1] += hwb[1] * ratio; 508 this.setValues('hwb', hwb); 509 return this; 510 }, 511 512 blacken: function (ratio) { 513 var hwb = this.values.hwb; 514 hwb[2] += hwb[2] * ratio; 515 this.setValues('hwb', hwb); 516 return this; 517 }, 518 519 greyscale: function () { 520 var rgb = this.values.rgb; 521 // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale 522 var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; 523 this.setValues('rgb', [val, val, val]); 524 return this; 525 }, 526 527 clearer: function (ratio) { 528 var alpha = this.values.alpha; 529 this.setValues('alpha', alpha - (alpha * ratio)); 530 return this; 531 }, 532 533 opaquer: function (ratio) { 534 var alpha = this.values.alpha; 535 this.setValues('alpha', alpha + (alpha * ratio)); 536 return this; 537 }, 538 539 rotate: function (degrees) { 540 var hsl = this.values.hsl; 541 var hue = (hsl[0] + degrees) % 360; 542 hsl[0] = hue < 0 ? 360 + hue : hue; 543 this.setValues('hsl', hsl); 544 return this; 545 }, 546 547 /** 548 * Ported from sass implementation in C 549 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 550 */ 551 mix: function (mixinColor, weight) { 552 var color1 = this; 553 var color2 = mixinColor; 554 var p = weight === undefined ? 0.5 : weight; 555 556 var w = 2 * p - 1; 557 var a = color1.alpha() - color2.alpha(); 558 559 var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; 560 var w2 = 1 - w1; 561 562 return this 563 .rgb( 564 w1 * color1.red() + w2 * color2.red(), 565 w1 * color1.green() + w2 * color2.green(), 566 w1 * color1.blue() + w2 * color2.blue() 567 ) 568 .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); 569 }, 570 571 toJSON: function () { 572 return this.rgb(); 573 }, 574 575 clone: function () { 576 // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, 577 // making the final build way to big to embed in Chart.js. So let's do it manually, 578 // assuming that values to clone are 1 dimension arrays containing only numbers, 579 // except 'alpha' which is a number. 580 var result = new Color(); 581 var source = this.values; 582 var target = result.values; 583 var value, type; 584 585 for (var prop in source) { 586 if (source.hasOwnProperty(prop)) { 587 value = source[prop]; 588 type = ({}).toString.call(value); 589 if (type === '[object Array]') { 590 target[prop] = value.slice(0); 591 } else if (type === '[object Number]') { 592 target[prop] = value; 593 } else { 594 console.error('unexpected color value:', value); 595 } 596 } 597 } 598 599 return result; 600 } 601 }; 602 603 Color.prototype.spaces = { 604 rgb: ['red', 'green', 'blue'], 605 hsl: ['hue', 'saturation', 'lightness'], 606 hsv: ['hue', 'saturation', 'value'], 607 hwb: ['hue', 'whiteness', 'blackness'], 608 cmyk: ['cyan', 'magenta', 'yellow', 'black'] 609 }; 610 611 Color.prototype.maxes = { 612 rgb: [255, 255, 255], 613 hsl: [360, 100, 100], 614 hsv: [360, 100, 100], 615 hwb: [360, 100, 100], 616 cmyk: [100, 100, 100, 100] 617 }; 618 619 Color.prototype.getValues = function (space) { 620 var values = this.values; 621 var vals = {}; 622 623 for (var i = 0; i < space.length; i++) { 624 vals[space.charAt(i)] = values[space][i]; 625 } 626 627 if (values.alpha !== 1) { 628 vals.a = values.alpha; 629 } 630 631 // {r: 255, g: 255, b: 255, a: 0.4} 632 return vals; 633 }; 634 635 Color.prototype.setValues = function (space, vals) { 636 var values = this.values; 637 var spaces = this.spaces; 638 var maxes = this.maxes; 639 var alpha = 1; 640 var i; 641 642 if (space === 'alpha') { 643 alpha = vals; 644 } else if (vals.length) { 645 // [10, 10, 10] 646 values[space] = vals.slice(0, space.length); 647 alpha = vals[space.length]; 648 } else if (vals[space.charAt(0)] !== undefined) { 649 // {r: 10, g: 10, b: 10} 650 for (i = 0; i < space.length; i++) { 651 values[space][i] = vals[space.charAt(i)]; 652 } 653 654 alpha = vals.a; 655 } else if (vals[spaces[space][0]] !== undefined) { 656 // {red: 10, green: 10, blue: 10} 657 var chans = spaces[space]; 658 659 for (i = 0; i < space.length; i++) { 660 values[space][i] = vals[chans[i]]; 661 } 662 663 alpha = vals.alpha; 664 } 665 666 values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); 667 668 if (space === 'alpha') { 669 return false; 670 } 671 672 var capped; 673 674 // cap values of the space prior converting all values 675 for (i = 0; i < space.length; i++) { 676 capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); 677 values[space][i] = Math.round(capped); 678 } 679 680 // convert to all the other color spaces 681 for (var sname in spaces) { 682 if (sname !== space) { 683 values[sname] = convert[space][sname](values[space]); 684 } 685 } 686 687 return true; 688 }; 689 690 Color.prototype.setSpace = function (space, args) { 691 var vals = args[0]; 692 693 if (vals === undefined) { 694 // color.rgb() 695 return this.getValues(space); 696 } 697 698 // color.rgb(10, 10, 10) 699 if (typeof vals === 'number') { 700 vals = Array.prototype.slice.call(args); 701 } 702 703 this.setValues(space, vals); 704 return this; 705 }; 706 707 Color.prototype.setChannel = function (space, index, val) { 708 var svalues = this.values[space]; 709 if (val === undefined) { 710 // color.red() 711 return svalues[index]; 712 } else if (val === svalues[index]) { 713 // color.red(color.red()) 714 return this; 715 } 716 717 // color.red(100) 718 svalues[index] = val; 719 this.setValues(space, svalues); 720 721 return this; 722 }; 723 724 if (typeof window !== 'undefined') { 725 window.Color = Color; 726 } 727 728 module.exports = Color; 729 730 },{"2":2,"5":5}],4:[function(require,module,exports){ 731 /* MIT license */ 732 733 module.exports = { 734 rgb2hsl: rgb2hsl, 735 rgb2hsv: rgb2hsv, 736 rgb2hwb: rgb2hwb, 737 rgb2cmyk: rgb2cmyk, 738 rgb2keyword: rgb2keyword, 739 rgb2xyz: rgb2xyz, 740 rgb2lab: rgb2lab, 741 rgb2lch: rgb2lch, 742 743 hsl2rgb: hsl2rgb, 744 hsl2hsv: hsl2hsv, 745 hsl2hwb: hsl2hwb, 746 hsl2cmyk: hsl2cmyk, 747 hsl2keyword: hsl2keyword, 748 749 hsv2rgb: hsv2rgb, 750 hsv2hsl: hsv2hsl, 751 hsv2hwb: hsv2hwb, 752 hsv2cmyk: hsv2cmyk, 753 hsv2keyword: hsv2keyword, 754 755 hwb2rgb: hwb2rgb, 756 hwb2hsl: hwb2hsl, 757 hwb2hsv: hwb2hsv, 758 hwb2cmyk: hwb2cmyk, 759 hwb2keyword: hwb2keyword, 760 761 cmyk2rgb: cmyk2rgb, 762 cmyk2hsl: cmyk2hsl, 763 cmyk2hsv: cmyk2hsv, 764 cmyk2hwb: cmyk2hwb, 765 cmyk2keyword: cmyk2keyword, 766 767 keyword2rgb: keyword2rgb, 768 keyword2hsl: keyword2hsl, 769 keyword2hsv: keyword2hsv, 770 keyword2hwb: keyword2hwb, 771 keyword2cmyk: keyword2cmyk, 772 keyword2lab: keyword2lab, 773 keyword2xyz: keyword2xyz, 774 775 xyz2rgb: xyz2rgb, 776 xyz2lab: xyz2lab, 777 xyz2lch: xyz2lch, 778 779 lab2xyz: lab2xyz, 780 lab2rgb: lab2rgb, 781 lab2lch: lab2lch, 782 783 lch2lab: lch2lab, 784 lch2xyz: lch2xyz, 785 lch2rgb: lch2rgb 786 } 787 788 789 function rgb2hsl(rgb) { 790 var r = rgb[0]/255, 791 g = rgb[1]/255, 792 b = rgb[2]/255, 793 min = Math.min(r, g, b), 794 max = Math.max(r, g, b), 795 delta = max - min, 796 h, s, l; 797 798 if (max == min) 799 h = 0; 800 else if (r == max) 801 h = (g - b) / delta; 802 else if (g == max) 803 h = 2 + (b - r) / delta; 804 else if (b == max) 805 h = 4 + (r - g)/ delta; 806 807 h = Math.min(h * 60, 360); 808 809 if (h < 0) 810 h += 360; 811 812 l = (min + max) / 2; 813 814 if (max == min) 815 s = 0; 816 else if (l <= 0.5) 817 s = delta / (max + min); 818 else 819 s = delta / (2 - max - min); 820 821 return [h, s * 100, l * 100]; 822 } 823 824 function rgb2hsv(rgb) { 825 var r = rgb[0], 826 g = rgb[1], 827 b = rgb[2], 828 min = Math.min(r, g, b), 829 max = Math.max(r, g, b), 830 delta = max - min, 831 h, s, v; 832 833 if (max == 0) 834 s = 0; 835 else 836 s = (delta/max * 1000)/10; 837 838 if (max == min) 839 h = 0; 840 else if (r == max) 841 h = (g - b) / delta; 842 else if (g == max) 843 h = 2 + (b - r) / delta; 844 else if (b == max) 845 h = 4 + (r - g) / delta; 846 847 h = Math.min(h * 60, 360); 848 849 if (h < 0) 850 h += 360; 851 852 v = ((max / 255) * 1000) / 10; 853 854 return [h, s, v]; 855 } 856 857 function rgb2hwb(rgb) { 858 var r = rgb[0], 859 g = rgb[1], 860 b = rgb[2], 861 h = rgb2hsl(rgb)[0], 862 w = 1/255 * Math.min(r, Math.min(g, b)), 863 b = 1 - 1/255 * Math.max(r, Math.max(g, b)); 864 865 return [h, w * 100, b * 100]; 866 } 867 868 function rgb2cmyk(rgb) { 869 var r = rgb[0] / 255, 870 g = rgb[1] / 255, 871 b = rgb[2] / 255, 872 c, m, y, k; 873 874 k = Math.min(1 - r, 1 - g, 1 - b); 875 c = (1 - r - k) / (1 - k) || 0; 876 m = (1 - g - k) / (1 - k) || 0; 877 y = (1 - b - k) / (1 - k) || 0; 878 return [c * 100, m * 100, y * 100, k * 100]; 879 } 880 881 function rgb2keyword(rgb) { 882 return reverseKeywords[JSON.stringify(rgb)]; 883 } 884 885 function rgb2xyz(rgb) { 886 var r = rgb[0] / 255, 887 g = rgb[1] / 255, 888 b = rgb[2] / 255; 889 890 // assume sRGB 891 r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); 892 g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); 893 b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); 894 895 var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); 896 var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); 897 var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); 898 899 return [x * 100, y *100, z * 100]; 900 } 901 902 function rgb2lab(rgb) { 903 var xyz = rgb2xyz(rgb), 904 x = xyz[0], 905 y = xyz[1], 906 z = xyz[2], 907 l, a, b; 908 909 x /= 95.047; 910 y /= 100; 911 z /= 108.883; 912 913 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); 914 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); 915 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); 916 917 l = (116 * y) - 16; 918 a = 500 * (x - y); 919 b = 200 * (y - z); 920 921 return [l, a, b]; 922 } 923 924 function rgb2lch(args) { 925 return lab2lch(rgb2lab(args)); 926 } 927 928 function hsl2rgb(hsl) { 929 var h = hsl[0] / 360, 930 s = hsl[1] / 100, 931 l = hsl[2] / 100, 932 t1, t2, t3, rgb, val; 933 934 if (s == 0) { 935 val = l * 255; 936 return [val, val, val]; 937 } 938 939 if (l < 0.5) 940 t2 = l * (1 + s); 941 else 942 t2 = l + s - l * s; 943 t1 = 2 * l - t2; 944 945 rgb = [0, 0, 0]; 946 for (var i = 0; i < 3; i++) { 947 t3 = h + 1 / 3 * - (i - 1); 948 t3 < 0 && t3++; 949 t3 > 1 && t3--; 950 951 if (6 * t3 < 1) 952 val = t1 + (t2 - t1) * 6 * t3; 953 else if (2 * t3 < 1) 954 val = t2; 955 else if (3 * t3 < 2) 956 val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; 957 else 958 val = t1; 959 960 rgb[i] = val * 255; 961 } 962 963 return rgb; 964 } 965 966 function hsl2hsv(hsl) { 967 var h = hsl[0], 968 s = hsl[1] / 100, 969 l = hsl[2] / 100, 970 sv, v; 971 972 if(l === 0) { 973 // no need to do calc on black 974 // also avoids divide by 0 error 975 return [0, 0, 0]; 976 } 977 978 l *= 2; 979 s *= (l <= 1) ? l : 2 - l; 980 v = (l + s) / 2; 981 sv = (2 * s) / (l + s); 982 return [h, sv * 100, v * 100]; 983 } 984 985 function hsl2hwb(args) { 986 return rgb2hwb(hsl2rgb(args)); 987 } 988 989 function hsl2cmyk(args) { 990 return rgb2cmyk(hsl2rgb(args)); 991 } 992 993 function hsl2keyword(args) { 994 return rgb2keyword(hsl2rgb(args)); 995 } 996 997 998 function hsv2rgb(hsv) { 999 var h = hsv[0] / 60, 1000 s = hsv[1] / 100, 1001 v = hsv[2] / 100, 1002 hi = Math.floor(h) % 6; 1003 1004 var f = h - Math.floor(h), 1005 p = 255 * v * (1 - s), 1006 q = 255 * v * (1 - (s * f)), 1007 t = 255 * v * (1 - (s * (1 - f))), 1008 v = 255 * v; 1009 1010 switch(hi) { 1011 case 0: 1012 return [v, t, p]; 1013 case 1: 1014 return [q, v, p]; 1015 case 2: 1016 return [p, v, t]; 1017 case 3: 1018 return [p, q, v]; 1019 case 4: 1020 return [t, p, v]; 1021 case 5: 1022 return [v, p, q]; 1023 } 1024 } 1025 1026 function hsv2hsl(hsv) { 1027 var h = hsv[0], 1028 s = hsv[1] / 100, 1029 v = hsv[2] / 100, 1030 sl, l; 1031 1032 l = (2 - s) * v; 1033 sl = s * v; 1034 sl /= (l <= 1) ? l : 2 - l; 1035 sl = sl || 0; 1036 l /= 2; 1037 return [h, sl * 100, l * 100]; 1038 } 1039 1040 function hsv2hwb(args) { 1041 return rgb2hwb(hsv2rgb(args)) 1042 } 1043 1044 function hsv2cmyk(args) { 1045 return rgb2cmyk(hsv2rgb(args)); 1046 } 1047 1048 function hsv2keyword(args) { 1049 return rgb2keyword(hsv2rgb(args)); 1050 } 1051 1052 // http://dev.w3.org/csswg/css-color/#hwb-to-rgb 1053 function hwb2rgb(hwb) { 1054 var h = hwb[0] / 360, 1055 wh = hwb[1] / 100, 1056 bl = hwb[2] / 100, 1057 ratio = wh + bl, 1058 i, v, f, n; 1059 1060 // wh + bl cant be > 1 1061 if (ratio > 1) { 1062 wh /= ratio; 1063 bl /= ratio; 1064 } 1065 1066 i = Math.floor(6 * h); 1067 v = 1 - bl; 1068 f = 6 * h - i; 1069 if ((i & 0x01) != 0) { 1070 f = 1 - f; 1071 } 1072 n = wh + f * (v - wh); // linear interpolation 1073 1074 switch (i) { 1075 default: 1076 case 6: 1077 case 0: r = v; g = n; b = wh; break; 1078 case 1: r = n; g = v; b = wh; break; 1079 case 2: r = wh; g = v; b = n; break; 1080 case 3: r = wh; g = n; b = v; break; 1081 case 4: r = n; g = wh; b = v; break; 1082 case 5: r = v; g = wh; b = n; break; 1083 } 1084 1085 return [r * 255, g * 255, b * 255]; 1086 } 1087 1088 function hwb2hsl(args) { 1089 return rgb2hsl(hwb2rgb(args)); 1090 } 1091 1092 function hwb2hsv(args) { 1093 return rgb2hsv(hwb2rgb(args)); 1094 } 1095 1096 function hwb2cmyk(args) { 1097 return rgb2cmyk(hwb2rgb(args)); 1098 } 1099 1100 function hwb2keyword(args) { 1101 return rgb2keyword(hwb2rgb(args)); 1102 } 1103 1104 function cmyk2rgb(cmyk) { 1105 var c = cmyk[0] / 100, 1106 m = cmyk[1] / 100, 1107 y = cmyk[2] / 100, 1108 k = cmyk[3] / 100, 1109 r, g, b; 1110 1111 r = 1 - Math.min(1, c * (1 - k) + k); 1112 g = 1 - Math.min(1, m * (1 - k) + k); 1113 b = 1 - Math.min(1, y * (1 - k) + k); 1114 return [r * 255, g * 255, b * 255]; 1115 } 1116 1117 function cmyk2hsl(args) { 1118 return rgb2hsl(cmyk2rgb(args)); 1119 } 1120 1121 function cmyk2hsv(args) { 1122 return rgb2hsv(cmyk2rgb(args)); 1123 } 1124 1125 function cmyk2hwb(args) { 1126 return rgb2hwb(cmyk2rgb(args)); 1127 } 1128 1129 function cmyk2keyword(args) { 1130 return rgb2keyword(cmyk2rgb(args)); 1131 } 1132 1133 1134 function xyz2rgb(xyz) { 1135 var x = xyz[0] / 100, 1136 y = xyz[1] / 100, 1137 z = xyz[2] / 100, 1138 r, g, b; 1139 1140 r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); 1141 g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); 1142 b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); 1143 1144 // assume sRGB 1145 r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) 1146 : r = (r * 12.92); 1147 1148 g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) 1149 : g = (g * 12.92); 1150 1151 b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) 1152 : b = (b * 12.92); 1153 1154 r = Math.min(Math.max(0, r), 1); 1155 g = Math.min(Math.max(0, g), 1); 1156 b = Math.min(Math.max(0, b), 1); 1157 1158 return [r * 255, g * 255, b * 255]; 1159 } 1160 1161 function xyz2lab(xyz) { 1162 var x = xyz[0], 1163 y = xyz[1], 1164 z = xyz[2], 1165 l, a, b; 1166 1167 x /= 95.047; 1168 y /= 100; 1169 z /= 108.883; 1170 1171 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); 1172 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); 1173 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); 1174 1175 l = (116 * y) - 16; 1176 a = 500 * (x - y); 1177 b = 200 * (y - z); 1178 1179 return [l, a, b]; 1180 } 1181 1182 function xyz2lch(args) { 1183 return lab2lch(xyz2lab(args)); 1184 } 1185 1186 function lab2xyz(lab) { 1187 var l = lab[0], 1188 a = lab[1], 1189 b = lab[2], 1190 x, y, z, y2; 1191 1192 if (l <= 8) { 1193 y = (l * 100) / 903.3; 1194 y2 = (7.787 * (y / 100)) + (16 / 116); 1195 } else { 1196 y = 100 * Math.pow((l + 16) / 116, 3); 1197 y2 = Math.pow(y / 100, 1/3); 1198 } 1199 1200 x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3); 1201 1202 z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3); 1203 1204 return [x, y, z]; 1205 } 1206 1207 function lab2lch(lab) { 1208 var l = lab[0], 1209 a = lab[1], 1210 b = lab[2], 1211 hr, h, c; 1212 1213 hr = Math.atan2(b, a); 1214 h = hr * 360 / 2 / Math.PI; 1215 if (h < 0) { 1216 h += 360; 1217 } 1218 c = Math.sqrt(a * a + b * b); 1219 return [l, c, h]; 1220 } 1221 1222 function lab2rgb(args) { 1223 return xyz2rgb(lab2xyz(args)); 1224 } 1225 1226 function lch2lab(lch) { 1227 var l = lch[0], 1228 c = lch[1], 1229 h = lch[2], 1230 a, b, hr; 1231 1232 hr = h / 360 * 2 * Math.PI; 1233 a = c * Math.cos(hr); 1234 b = c * Math.sin(hr); 1235 return [l, a, b]; 1236 } 1237 1238 function lch2xyz(args) { 1239 return lab2xyz(lch2lab(args)); 1240 } 1241 1242 function lch2rgb(args) { 1243 return lab2rgb(lch2lab(args)); 1244 } 1245 1246 function keyword2rgb(keyword) { 1247 return cssKeywords[keyword]; 1248 } 1249 1250 function keyword2hsl(args) { 1251 return rgb2hsl(keyword2rgb(args)); 1252 } 1253 1254 function keyword2hsv(args) { 1255 return rgb2hsv(keyword2rgb(args)); 1256 } 1257 1258 function keyword2hwb(args) { 1259 return rgb2hwb(keyword2rgb(args)); 1260 } 1261 1262 function keyword2cmyk(args) { 1263 return rgb2cmyk(keyword2rgb(args)); 1264 } 1265 1266 function keyword2lab(args) { 1267 return rgb2lab(keyword2rgb(args)); 1268 } 1269 1270 function keyword2xyz(args) { 1271 return rgb2xyz(keyword2rgb(args)); 1272 } 1273 1274 var cssKeywords = { 1275 aliceblue: [240,248,255], 1276 antiquewhite: [250,235,215], 1277 aqua: [0,255,255], 1278 aquamarine: [127,255,212], 1279 azure: [240,255,255], 1280 beige: [245,245,220], 1281 bisque: [255,228,196], 1282 black: [0,0,0], 1283 blanchedalmond: [255,235,205], 1284 blue: [0,0,255], 1285 blueviolet: [138,43,226], 1286 brown: [165,42,42], 1287 burlywood: [222,184,135], 1288 cadetblue: [95,158,160], 1289 chartreuse: [127,255,0], 1290 chocolate: [210,105,30], 1291 coral: [255,127,80], 1292 cornflowerblue: [100,149,237], 1293 cornsilk: [255,248,220], 1294 crimson: [220,20,60], 1295 cyan: [0,255,255], 1296 darkblue: [0,0,139], 1297 darkcyan: [0,139,139], 1298 darkgoldenrod: [184,134,11], 1299 darkgray: [169,169,169], 1300 darkgreen: [0,100,0], 1301 darkgrey: [169,169,169], 1302 darkkhaki: [189,183,107], 1303 darkmagenta: [139,0,139], 1304 darkolivegreen: [85,107,47], 1305 darkorange: [255,140,0], 1306 darkorchid: [153,50,204], 1307 darkred: [139,0,0], 1308 darksalmon: [233,150,122], 1309 darkseagreen: [143,188,143], 1310 darkslateblue: [72,61,139], 1311 darkslategray: [47,79,79], 1312 darkslategrey: [47,79,79], 1313 darkturquoise: [0,206,209], 1314 darkviolet: [148,0,211], 1315 deeppink: [255,20,147], 1316 deepskyblue: [0,191,255], 1317 dimgray: [105,105,105], 1318 dimgrey: [105,105,105], 1319 dodgerblue: [30,144,255], 1320 firebrick: [178,34,34], 1321 floralwhite: [255,250,240], 1322 forestgreen: [34,139,34], 1323 fuchsia: [255,0,255], 1324 gainsboro: [220,220,220], 1325 ghostwhite: [248,248,255], 1326 gold: [255,215,0], 1327 goldenrod: [218,165,32], 1328 gray: [128,128,128], 1329 green: [0,128,0], 1330 greenyellow: [173,255,47], 1331 grey: [128,128,128], 1332 honeydew: [240,255,240], 1333 hotpink: [255,105,180], 1334 indianred: [205,92,92], 1335 indigo: [75,0,130], 1336 ivory: [255,255,240], 1337 khaki: [240,230,140], 1338 lavender: [230,230,250], 1339 lavenderblush: [255,240,245], 1340 lawngreen: [124,252,0], 1341 lemonchiffon: [255,250,205], 1342 lightblue: [173,216,230], 1343 lightcoral: [240,128,128], 1344 lightcyan: [224,255,255], 1345 lightgoldenrodyellow: [250,250,210], 1346 lightgray: [211,211,211], 1347 lightgreen: [144,238,144], 1348 lightgrey: [211,211,211], 1349 lightpink: [255,182,193], 1350 lightsalmon: [255,160,122], 1351 lightseagreen: [32,178,170], 1352 lightskyblue: [135,206,250], 1353 lightslategray: [119,136,153], 1354 lightslategrey: [119,136,153], 1355 lightsteelblue: [176,196,222], 1356 lightyellow: [255,255,224], 1357 lime: [0,255,0], 1358 limegreen: [50,205,50], 1359 linen: [250,240,230], 1360 magenta: [255,0,255], 1361 maroon: [128,0,0], 1362 mediumaquamarine: [102,205,170], 1363 mediumblue: [0,0,205], 1364 mediumorchid: [186,85,211], 1365 mediumpurple: [147,112,219], 1366 mediumseagreen: [60,179,113], 1367 mediumslateblue: [123,104,238], 1368 mediumspringgreen: [0,250,154], 1369 mediumturquoise: [72,209,204], 1370 mediumvioletred: [199,21,133], 1371 midnightblue: [25,25,112], 1372 mintcream: [245,255,250], 1373 mistyrose: [255,228,225], 1374 moccasin: [255,228,181], 1375 navajowhite: [255,222,173], 1376 navy: [0,0,128], 1377 oldlace: [253,245,230], 1378 olive: [128,128,0], 1379 olivedrab: [107,142,35], 1380 orange: [255,165,0], 1381 orangered: [255,69,0], 1382 orchid: [218,112,214], 1383 palegoldenrod: [238,232,170], 1384 palegreen: [152,251,152], 1385 paleturquoise: [175,238,238], 1386 palevioletred: [219,112,147], 1387 papayawhip: [255,239,213], 1388 peachpuff: [255,218,185], 1389 peru: [205,133,63], 1390 pink: [255,192,203], 1391 plum: [221,160,221], 1392 powderblue: [176,224,230], 1393 purple: [128,0,128], 1394 rebeccapurple: [102, 51, 153], 1395 red: [255,0,0], 1396 rosybrown: [188,143,143], 1397 royalblue: [65,105,225], 1398 saddlebrown: [139,69,19], 1399 salmon: [250,128,114], 1400 sandybrown: [244,164,96], 1401 seagreen: [46,139,87], 1402 seashell: [255,245,238], 1403 sienna: [160,82,45], 1404 silver: [192,192,192], 1405 skyblue: [135,206,235], 1406 slateblue: [106,90,205], 1407 slategray: [112,128,144], 1408 slategrey: [112,128,144], 1409 snow: [255,250,250], 1410 springgreen: [0,255,127], 1411 steelblue: [70,130,180], 1412 tan: [210,180,140], 1413 teal: [0,128,128], 1414 thistle: [216,191,216], 1415 tomato: [255,99,71], 1416 turquoise: [64,224,208], 1417 violet: [238,130,238], 1418 wheat: [245,222,179], 1419 white: [255,255,255], 1420 whitesmoke: [245,245,245], 1421 yellow: [255,255,0], 1422 yellowgreen: [154,205,50] 1423 }; 1424 1425 var reverseKeywords = {}; 1426 for (var key in cssKeywords) { 1427 reverseKeywords[JSON.stringify(cssKeywords[key])] = key; 1428 } 1429 1430 },{}],5:[function(require,module,exports){ 1431 var conversions = require(4); 1432 1433 var convert = function() { 1434 return new Converter(); 1435 } 1436 1437 for (var func in conversions) { 1438 // export Raw versions 1439 convert[func + "Raw"] = (function(func) { 1440 // accept array or plain args 1441 return function(arg) { 1442 if (typeof arg == "number") 1443 arg = Array.prototype.slice.call(arguments); 1444 return conversions[func](arg); 1445 } 1446 })(func); 1447 1448 var pair = /(\w+)2(\w+)/.exec(func), 1449 from = pair[1], 1450 to = pair[2]; 1451 1452 // export rgb2hsl and ["rgb"]["hsl"] 1453 convert[from] = convert[from] || {}; 1454 1455 convert[from][to] = convert[func] = (function(func) { 1456 return function(arg) { 1457 if (typeof arg == "number") 1458 arg = Array.prototype.slice.call(arguments); 1459 1460 var val = conversions[func](arg); 1461 if (typeof val == "string" || val === undefined) 1462 return val; // keyword 1463 1464 for (var i = 0; i < val.length; i++) 1465 val[i] = Math.round(val[i]); 1466 return val; 1467 } 1468 })(func); 1469 } 1470 1471 1472 /* Converter does lazy conversion and caching */ 1473 var Converter = function() { 1474 this.convs = {}; 1475 }; 1476 1477 /* Either get the values for a space or 1478 set the values for a space, depending on args */ 1479 Converter.prototype.routeSpace = function(space, args) { 1480 var values = args[0]; 1481 if (values === undefined) { 1482 // color.rgb() 1483 return this.getValues(space); 1484 } 1485 // color.rgb(10, 10, 10) 1486 if (typeof values == "number") { 1487 values = Array.prototype.slice.call(args); 1488 } 1489 1490 return this.setValues(space, values); 1491 }; 1492 1493 /* Set the values for a space, invalidating cache */ 1494 Converter.prototype.setValues = function(space, values) { 1495 this.space = space; 1496 this.convs = {}; 1497 this.convs[space] = values; 1498 return this; 1499 }; 1500 1501 /* Get the values for a space. If there's already 1502 a conversion for the space, fetch it, otherwise 1503 compute it */ 1504 Converter.prototype.getValues = function(space) { 1505 var vals = this.convs[space]; 1506 if (!vals) { 1507 var fspace = this.space, 1508 from = this.convs[fspace]; 1509 vals = convert[fspace][space](from); 1510 1511 this.convs[space] = vals; 1512 } 1513 return vals; 1514 }; 1515 1516 ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) { 1517 Converter.prototype[space] = function(vals) { 1518 return this.routeSpace(space, arguments); 1519 } 1520 }); 1521 1522 module.exports = convert; 1523 },{"4":4}],6:[function(require,module,exports){ 1524 module.exports = { 1525 "aliceblue": [240, 248, 255], 1526 "antiquewhite": [250, 235, 215], 1527 "aqua": [0, 255, 255], 1528 "aquamarine": [127, 255, 212], 1529 "azure": [240, 255, 255], 1530 "beige": [245, 245, 220], 1531 "bisque": [255, 228, 196], 1532 "black": [0, 0, 0], 1533 "blanchedalmond": [255, 235, 205], 1534 "blue": [0, 0, 255], 1535 "blueviolet": [138, 43, 226], 1536 "brown": [165, 42, 42], 1537 "burlywood": [222, 184, 135], 1538 "cadetblue": [95, 158, 160], 1539 "chartreuse": [127, 255, 0], 1540 "chocolate": [210, 105, 30], 1541 "coral": [255, 127, 80], 1542 "cornflowerblue": [100, 149, 237], 1543 "cornsilk": [255, 248, 220], 1544 "crimson": [220, 20, 60], 1545 "cyan": [0, 255, 255], 1546 "darkblue": [0, 0, 139], 1547 "darkcyan": [0, 139, 139], 1548 "darkgoldenrod": [184, 134, 11], 1549 "darkgray": [169, 169, 169], 1550 "darkgreen": [0, 100, 0], 1551 "darkgrey": [169, 169, 169], 1552 "darkkhaki": [189, 183, 107], 1553 "darkmagenta": [139, 0, 139], 1554 "darkolivegreen": [85, 107, 47], 1555 "darkorange": [255, 140, 0], 1556 "darkorchid": [153, 50, 204], 1557 "darkred": [139, 0, 0], 1558 "darksalmon": [233, 150, 122], 1559 "darkseagreen": [143, 188, 143], 1560 "darkslateblue": [72, 61, 139], 1561 "darkslategray": [47, 79, 79], 1562 "darkslategrey": [47, 79, 79], 1563 "darkturquoise": [0, 206, 209], 1564 "darkviolet": [148, 0, 211], 1565 "deeppink": [255, 20, 147], 1566 "deepskyblue": [0, 191, 255], 1567 "dimgray": [105, 105, 105], 1568 "dimgrey": [105, 105, 105], 1569 "dodgerblue": [30, 144, 255], 1570 "firebrick": [178, 34, 34], 1571 "floralwhite": [255, 250, 240], 1572 "forestgreen": [34, 139, 34], 1573 "fuchsia": [255, 0, 255], 1574 "gainsboro": [220, 220, 220], 1575 "ghostwhite": [248, 248, 255], 1576 "gold": [255, 215, 0], 1577 "goldenrod": [218, 165, 32], 1578 "gray": [128, 128, 128], 1579 "green": [0, 128, 0], 1580 "greenyellow": [173, 255, 47], 1581 "grey": [128, 128, 128], 1582 "honeydew": [240, 255, 240], 1583 "hotpink": [255, 105, 180], 1584 "indianred": [205, 92, 92], 1585 "indigo": [75, 0, 130], 1586 "ivory": [255, 255, 240], 1587 "khaki": [240, 230, 140], 1588 "lavender": [230, 230, 250], 1589 "lavenderblush": [255, 240, 245], 1590 "lawngreen": [124, 252, 0], 1591 "lemonchiffon": [255, 250, 205], 1592 "lightblue": [173, 216, 230], 1593 "lightcoral": [240, 128, 128], 1594 "lightcyan": [224, 255, 255], 1595 "lightgoldenrodyellow": [250, 250, 210], 1596 "lightgray": [211, 211, 211], 1597 "lightgreen": [144, 238, 144], 1598 "lightgrey": [211, 211, 211], 1599 "lightpink": [255, 182, 193], 1600 "lightsalmon": [255, 160, 122], 1601 "lightseagreen": [32, 178, 170], 1602 "lightskyblue": [135, 206, 250], 1603 "lightslategray": [119, 136, 153], 1604 "lightslategrey": [119, 136, 153], 1605 "lightsteelblue": [176, 196, 222], 1606 "lightyellow": [255, 255, 224], 1607 "lime": [0, 255, 0], 1608 "limegreen": [50, 205, 50], 1609 "linen": [250, 240, 230], 1610 "magenta": [255, 0, 255], 1611 "maroon": [128, 0, 0], 1612 "mediumaquamarine": [102, 205, 170], 1613 "mediumblue": [0, 0, 205], 1614 "mediumorchid": [186, 85, 211], 1615 "mediumpurple": [147, 112, 219], 1616 "mediumseagreen": [60, 179, 113], 1617 "mediumslateblue": [123, 104, 238], 1618 "mediumspringgreen": [0, 250, 154], 1619 "mediumturquoise": [72, 209, 204], 1620 "mediumvioletred": [199, 21, 133], 1621 "midnightblue": [25, 25, 112], 1622 "mintcream": [245, 255, 250], 1623 "mistyrose": [255, 228, 225], 1624 "moccasin": [255, 228, 181], 1625 "navajowhite": [255, 222, 173], 1626 "navy": [0, 0, 128], 1627 "oldlace": [253, 245, 230], 1628 "olive": [128, 128, 0], 1629 "olivedrab": [107, 142, 35], 1630 "orange": [255, 165, 0], 1631 "orangered": [255, 69, 0], 1632 "orchid": [218, 112, 214], 1633 "palegoldenrod": [238, 232, 170], 1634 "palegreen": [152, 251, 152], 1635 "paleturquoise": [175, 238, 238], 1636 "palevioletred": [219, 112, 147], 1637 "papayawhip": [255, 239, 213], 1638 "peachpuff": [255, 218, 185], 1639 "peru": [205, 133, 63], 1640 "pink": [255, 192, 203], 1641 "plum": [221, 160, 221], 1642 "powderblue": [176, 224, 230], 1643 "purple": [128, 0, 128], 1644 "rebeccapurple": [102, 51, 153], 1645 "red": [255, 0, 0], 1646 "rosybrown": [188, 143, 143], 1647 "royalblue": [65, 105, 225], 1648 "saddlebrown": [139, 69, 19], 1649 "salmon": [250, 128, 114], 1650 "sandybrown": [244, 164, 96], 1651 "seagreen": [46, 139, 87], 1652 "seashell": [255, 245, 238], 1653 "sienna": [160, 82, 45], 1654 "silver": [192, 192, 192], 1655 "skyblue": [135, 206, 235], 1656 "slateblue": [106, 90, 205], 1657 "slategray": [112, 128, 144], 1658 "slategrey": [112, 128, 144], 1659 "snow": [255, 250, 250], 1660 "springgreen": [0, 255, 127], 1661 "steelblue": [70, 130, 180], 1662 "tan": [210, 180, 140], 1663 "teal": [0, 128, 128], 1664 "thistle": [216, 191, 216], 1665 "tomato": [255, 99, 71], 1666 "turquoise": [64, 224, 208], 1667 "violet": [238, 130, 238], 1668 "wheat": [245, 222, 179], 1669 "white": [255, 255, 255], 1670 "whitesmoke": [245, 245, 245], 1671 "yellow": [255, 255, 0], 1672 "yellowgreen": [154, 205, 50] 1673 }; 1674 },{}],7:[function(require,module,exports){ 1675 /** 1676 * @namespace Chart 1677 */ 1678 var Chart = require(26)(); 1679 1680 require(25)(Chart); 1681 require(24)(Chart); 1682 require(21)(Chart); 1683 require(22)(Chart); 1684 require(23)(Chart); 1685 require(27)(Chart); 1686 require(31)(Chart); 1687 require(29)(Chart); 1688 require(30)(Chart); 1689 require(32)(Chart); 1690 require(28)(Chart); 1691 require(33)(Chart); 1692 1693 require(34)(Chart); 1694 require(35)(Chart); 1695 require(36)(Chart); 1696 require(37)(Chart); 1697 1698 require(40)(Chart); 1699 require(38)(Chart); 1700 require(39)(Chart); 1701 require(41)(Chart); 1702 require(42)(Chart); 1703 require(43)(Chart); 1704 1705 // Controllers must be loaded after elements 1706 // See Chart.core.datasetController.dataElementType 1707 require(15)(Chart); 1708 require(16)(Chart); 1709 require(17)(Chart); 1710 require(18)(Chart); 1711 require(19)(Chart); 1712 require(20)(Chart); 1713 1714 require(8)(Chart); 1715 require(9)(Chart); 1716 require(10)(Chart); 1717 require(11)(Chart); 1718 require(12)(Chart); 1719 require(13)(Chart); 1720 require(14)(Chart); 1721 1722 window.Chart = module.exports = Chart; 1723 1724 },{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"8":8,"9":9}],8:[function(require,module,exports){ 1725 "use strict"; 1726 1727 module.exports = function(Chart) { 1728 1729 Chart.Bar = function(context, config) { 1730 config.type = 'bar'; 1731 1732 return new Chart(context, config); 1733 }; 1734 1735 }; 1736 },{}],9:[function(require,module,exports){ 1737 "use strict"; 1738 1739 module.exports = function(Chart) { 1740 1741 Chart.Bubble = function(context, config) { 1742 config.type = 'bubble'; 1743 return new Chart(context, config); 1744 }; 1745 1746 }; 1747 },{}],10:[function(require,module,exports){ 1748 "use strict"; 1749 1750 module.exports = function(Chart) { 1751 1752 Chart.Doughnut = function(context, config) { 1753 config.type = 'doughnut'; 1754 1755 return new Chart(context, config); 1756 }; 1757 1758 }; 1759 },{}],11:[function(require,module,exports){ 1760 "use strict"; 1761 1762 module.exports = function(Chart) { 1763 1764 Chart.Line = function(context, config) { 1765 config.type = 'line'; 1766 1767 return new Chart(context, config); 1768 }; 1769 1770 }; 1771 },{}],12:[function(require,module,exports){ 1772 "use strict"; 1773 1774 module.exports = function(Chart) { 1775 1776 Chart.PolarArea = function(context, config) { 1777 config.type = 'polarArea'; 1778 1779 return new Chart(context, config); 1780 }; 1781 1782 }; 1783 },{}],13:[function(require,module,exports){ 1784 "use strict"; 1785 1786 module.exports = function(Chart) { 1787 1788 Chart.Radar = function(context, config) { 1789 config.options = Chart.helpers.configMerge({ aspectRatio: 1 }, config.options); 1790 config.type = 'radar'; 1791 1792 return new Chart(context, config); 1793 }; 1794 1795 }; 1796 1797 },{}],14:[function(require,module,exports){ 1798 "use strict"; 1799 1800 module.exports = function(Chart) { 1801 1802 var defaultConfig = { 1803 hover: { 1804 mode: 'single' 1805 }, 1806 1807 scales: { 1808 xAxes: [{ 1809 type: "linear", // scatter should not use a category axis 1810 position: "bottom", 1811 id: "x-axis-1" // need an ID so datasets can reference the scale 1812 }], 1813 yAxes: [{ 1814 type: "linear", 1815 position: "left", 1816 id: "y-axis-1" 1817 }] 1818 }, 1819 1820 tooltips: { 1821 callbacks: { 1822 title: function(tooltipItems, data) { 1823 // Title doesn't make sense for scatter since we format the data as a point 1824 return ''; 1825 }, 1826 label: function(tooltipItem, data) { 1827 return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')'; 1828 } 1829 } 1830 } 1831 }; 1832 1833 // Register the default config for this type 1834 Chart.defaults.scatter = defaultConfig; 1835 1836 // Scatter charts use line controllers 1837 Chart.controllers.scatter = Chart.controllers.line; 1838 1839 Chart.Scatter = function(context, config) { 1840 config.type = 'scatter'; 1841 return new Chart(context, config); 1842 }; 1843 1844 }; 1845 },{}],15:[function(require,module,exports){ 1846 "use strict"; 1847 1848 module.exports = function(Chart) { 1849 1850 var helpers = Chart.helpers; 1851 1852 Chart.defaults.bar = { 1853 hover: { 1854 mode: "label" 1855 }, 1856 1857 scales: { 1858 xAxes: [{ 1859 type: "category", 1860 1861 // Specific to Bar Controller 1862 categoryPercentage: 0.8, 1863 barPercentage: 0.9, 1864 1865 // grid line settings 1866 gridLines: { 1867 offsetGridLines: true 1868 } 1869 }], 1870 yAxes: [{ 1871 type: "linear" 1872 }] 1873 } 1874 }; 1875 1876 Chart.controllers.bar = Chart.DatasetController.extend({ 1877 1878 dataElementType: Chart.elements.Rectangle, 1879 1880 initialize: function(chart, datasetIndex) { 1881 Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex); 1882 1883 // Use this to indicate that this is a bar dataset. 1884 this.getMeta().bar = true; 1885 }, 1886 1887 // Get the number of datasets that display bars. We use this to correctly calculate the bar width 1888 getBarCount: function getBarCount() { 1889 var me = this; 1890 var barCount = 0; 1891 helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { 1892 var meta = me.chart.getDatasetMeta(datasetIndex); 1893 if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) { 1894 ++barCount; 1895 } 1896 }, me); 1897 return barCount; 1898 }, 1899 1900 update: function update(reset) { 1901 var me = this; 1902 helpers.each(me.getMeta().data, function(rectangle, index) { 1903 me.updateElement(rectangle, index, reset); 1904 }, me); 1905 }, 1906 1907 updateElement: function updateElement(rectangle, index, reset) { 1908 var me = this; 1909 var meta = me.getMeta(); 1910 var xScale = me.getScaleForId(meta.xAxisID); 1911 var yScale = me.getScaleForId(meta.yAxisID); 1912 var scaleBase = yScale.getBasePixel(); 1913 var rectangleElementOptions = me.chart.options.elements.rectangle; 1914 var custom = rectangle.custom || {}; 1915 var dataset = me.getDataset(); 1916 1917 helpers.extend(rectangle, { 1918 // Utility 1919 _xScale: xScale, 1920 _yScale: yScale, 1921 _datasetIndex: me.index, 1922 _index: index, 1923 1924 // Desired view properties 1925 _model: { 1926 x: me.calculateBarX(index, me.index), 1927 y: reset ? scaleBase : me.calculateBarY(index, me.index), 1928 1929 // Tooltip 1930 label: me.chart.data.labels[index], 1931 datasetLabel: dataset.label, 1932 1933 // Appearance 1934 base: reset ? scaleBase : me.calculateBarBase(me.index, index), 1935 width: me.calculateBarWidth(index), 1936 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), 1937 borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped, 1938 borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor), 1939 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth) 1940 } 1941 }); 1942 rectangle.pivot(); 1943 }, 1944 1945 calculateBarBase: function(datasetIndex, index) { 1946 var me = this; 1947 var meta = me.getMeta(); 1948 var yScale = me.getScaleForId(meta.yAxisID); 1949 var base = 0; 1950 1951 if (yScale.options.stacked) { 1952 var chart = me.chart; 1953 var datasets = chart.data.datasets; 1954 var value = datasets[datasetIndex].data[index]; 1955 1956 if (value < 0) { 1957 for (var i = 0; i < datasetIndex; i++) { 1958 var negDS = datasets[i]; 1959 var negDSMeta = chart.getDatasetMeta(i); 1960 if (negDSMeta.bar && negDSMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { 1961 base += negDS.data[index] < 0 ? negDS.data[index] : 0; 1962 } 1963 } 1964 } else { 1965 for (var j = 0; j < datasetIndex; j++) { 1966 var posDS = datasets[j]; 1967 var posDSMeta = chart.getDatasetMeta(j); 1968 if (posDSMeta.bar && posDSMeta.yAxisID === yScale.id && chart.isDatasetVisible(j)) { 1969 base += posDS.data[index] > 0 ? posDS.data[index] : 0; 1970 } 1971 } 1972 } 1973 1974 return yScale.getPixelForValue(base); 1975 } 1976 1977 return yScale.getBasePixel(); 1978 }, 1979 1980 getRuler: function(index) { 1981 var me = this; 1982 var meta = me.getMeta(); 1983 var xScale = me.getScaleForId(meta.xAxisID); 1984 var datasetCount = me.getBarCount(); 1985 1986 var tickWidth; 1987 1988 if (xScale.options.type === 'category') { 1989 tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index); 1990 } else { 1991 // Average width 1992 tickWidth = xScale.width / xScale.ticks.length; 1993 } 1994 var categoryWidth = tickWidth * xScale.options.categoryPercentage; 1995 var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2; 1996 var fullBarWidth = categoryWidth / datasetCount; 1997 1998 if (xScale.ticks.length !== me.chart.data.labels.length) { 1999 var perc = xScale.ticks.length / me.chart.data.labels.length; 2000 fullBarWidth = fullBarWidth * perc; 2001 } 2002 2003 var barWidth = fullBarWidth * xScale.options.barPercentage; 2004 var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage); 2005 2006 return { 2007 datasetCount: datasetCount, 2008 tickWidth: tickWidth, 2009 categoryWidth: categoryWidth, 2010 categorySpacing: categorySpacing, 2011 fullBarWidth: fullBarWidth, 2012 barWidth: barWidth, 2013 barSpacing: barSpacing 2014 }; 2015 }, 2016 2017 calculateBarWidth: function(index) { 2018 var xScale = this.getScaleForId(this.getMeta().xAxisID); 2019 var ruler = this.getRuler(index); 2020 return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth; 2021 }, 2022 2023 // Get bar index from the given dataset index accounting for the fact that not all bars are visible 2024 getBarIndex: function(datasetIndex) { 2025 var barIndex = 0; 2026 var meta, j; 2027 2028 for (j = 0; j < datasetIndex; ++j) { 2029 meta = this.chart.getDatasetMeta(j); 2030 if (meta.bar && this.chart.isDatasetVisible(j)) { 2031 ++barIndex; 2032 } 2033 } 2034 2035 return barIndex; 2036 }, 2037 2038 calculateBarX: function(index, datasetIndex) { 2039 var me = this; 2040 var meta = me.getMeta(); 2041 var xScale = me.getScaleForId(meta.xAxisID); 2042 var barIndex = me.getBarIndex(datasetIndex); 2043 2044 var ruler = me.getRuler(index); 2045 var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); 2046 leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0; 2047 2048 if (xScale.options.stacked) { 2049 return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing; 2050 } 2051 2052 return leftTick + 2053 (ruler.barWidth / 2) + 2054 ruler.categorySpacing + 2055 (ruler.barWidth * barIndex) + 2056 (ruler.barSpacing / 2) + 2057 (ruler.barSpacing * barIndex); 2058 }, 2059 2060 calculateBarY: function(index, datasetIndex) { 2061 var me = this; 2062 var meta = me.getMeta(); 2063 var yScale = me.getScaleForId(meta.yAxisID); 2064 var value = me.getDataset().data[index]; 2065 2066 if (yScale.options.stacked) { 2067 2068 var sumPos = 0, 2069 sumNeg = 0; 2070 2071 for (var i = 0; i < datasetIndex; i++) { 2072 var ds = me.chart.data.datasets[i]; 2073 var dsMeta = me.chart.getDatasetMeta(i); 2074 if (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i)) { 2075 if (ds.data[index] < 0) { 2076 sumNeg += ds.data[index] || 0; 2077 } else { 2078 sumPos += ds.data[index] || 0; 2079 } 2080 } 2081 } 2082 2083 if (value < 0) { 2084 return yScale.getPixelForValue(sumNeg + value); 2085 } else { 2086 return yScale.getPixelForValue(sumPos + value); 2087 } 2088 } 2089 2090 return yScale.getPixelForValue(value); 2091 }, 2092 2093 draw: function(ease) { 2094 var me = this; 2095 var easingDecimal = ease || 1; 2096 helpers.each(me.getMeta().data, function(rectangle, index) { 2097 var d = me.getDataset().data[index]; 2098 if (d !== null && d !== undefined && !isNaN(d)) { 2099 rectangle.transition(easingDecimal).draw(); 2100 } 2101 }, me); 2102 }, 2103 2104 setHoverStyle: function(rectangle) { 2105 var dataset = this.chart.data.datasets[rectangle._datasetIndex]; 2106 var index = rectangle._index; 2107 2108 var custom = rectangle.custom || {}; 2109 var model = rectangle._model; 2110 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); 2111 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor)); 2112 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); 2113 }, 2114 2115 removeHoverStyle: function(rectangle) { 2116 var dataset = this.chart.data.datasets[rectangle._datasetIndex]; 2117 var index = rectangle._index; 2118 var custom = rectangle.custom || {}; 2119 var model = rectangle._model; 2120 var rectangleElementOptions = this.chart.options.elements.rectangle; 2121 2122 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor); 2123 model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor); 2124 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth); 2125 } 2126 2127 }); 2128 2129 2130 // including horizontalBar in the bar file, instead of a file of its own 2131 // it extends bar (like pie extends doughnut) 2132 Chart.defaults.horizontalBar = { 2133 hover: { 2134 mode: "label" 2135 }, 2136 2137 scales: { 2138 xAxes: [{ 2139 type: "linear", 2140 position: "bottom" 2141 }], 2142 yAxes: [{ 2143 position: "left", 2144 type: "category", 2145 2146 // Specific to Horizontal Bar Controller 2147 categoryPercentage: 0.8, 2148 barPercentage: 0.9, 2149 2150 // grid line settings 2151 gridLines: { 2152 offsetGridLines: true 2153 } 2154 }] 2155 }, 2156 elements: { 2157 rectangle: { 2158 borderSkipped: 'left' 2159 } 2160 }, 2161 tooltips: { 2162 callbacks: { 2163 title: function(tooltipItems, data) { 2164 // Pick first xLabel for now 2165 var title = ''; 2166 2167 if (tooltipItems.length > 0) { 2168 if (tooltipItems[0].yLabel) { 2169 title = tooltipItems[0].yLabel; 2170 } else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) { 2171 title = data.labels[tooltipItems[0].index]; 2172 } 2173 } 2174 2175 return title; 2176 }, 2177 label: function(tooltipItem, data) { 2178 var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; 2179 return datasetLabel + ': ' + tooltipItem.xLabel; 2180 } 2181 } 2182 } 2183 }; 2184 2185 Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ 2186 updateElement: function updateElement(rectangle, index, reset, numBars) { 2187 var me = this; 2188 var meta = me.getMeta(); 2189 var xScale = me.getScaleForId(meta.xAxisID); 2190 var yScale = me.getScaleForId(meta.yAxisID); 2191 var scaleBase = xScale.getBasePixel(); 2192 var custom = rectangle.custom || {}; 2193 var dataset = me.getDataset(); 2194 var rectangleElementOptions = me.chart.options.elements.rectangle; 2195 2196 helpers.extend(rectangle, { 2197 // Utility 2198 _xScale: xScale, 2199 _yScale: yScale, 2200 _datasetIndex: me.index, 2201 _index: index, 2202 2203 // Desired view properties 2204 _model: { 2205 x: reset ? scaleBase : me.calculateBarX(index, me.index), 2206 y: me.calculateBarY(index, me.index), 2207 2208 // Tooltip 2209 label: me.chart.data.labels[index], 2210 datasetLabel: dataset.label, 2211 2212 // Appearance 2213 base: reset ? scaleBase : me.calculateBarBase(me.index, index), 2214 height: me.calculateBarHeight(index), 2215 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), 2216 borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped, 2217 borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor), 2218 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth) 2219 }, 2220 2221 draw: function () { 2222 var ctx = this._chart.ctx; 2223 var vm = this._view; 2224 2225 var halfHeight = vm.height / 2, 2226 topY = vm.y - halfHeight, 2227 bottomY = vm.y + halfHeight, 2228 right = vm.base - (vm.base - vm.x), 2229 halfStroke = vm.borderWidth / 2; 2230 2231 // Canvas doesn't allow us to stroke inside the width so we can 2232 // adjust the sizes to fit if we're setting a stroke on the line 2233 if (vm.borderWidth) { 2234 topY += halfStroke; 2235 bottomY -= halfStroke; 2236 right += halfStroke; 2237 } 2238 2239 ctx.beginPath(); 2240 2241 ctx.fillStyle = vm.backgroundColor; 2242 ctx.strokeStyle = vm.borderColor; 2243 ctx.lineWidth = vm.borderWidth; 2244 2245 // Corner points, from bottom-left to bottom-right clockwise 2246 // | 1 2 | 2247 // | 0 3 | 2248 var corners = [ 2249 [vm.base, bottomY], 2250 [vm.base, topY], 2251 [right, topY], 2252 [right, bottomY] 2253 ]; 2254 2255 // Find first (starting) corner with fallback to 'bottom' 2256 var borders = ['bottom', 'left', 'top', 'right']; 2257 var startCorner = borders.indexOf(vm.borderSkipped, 0); 2258 if (startCorner === -1) 2259 startCorner = 0; 2260 2261 function cornerAt(index) { 2262 return corners[(startCorner + index) % 4]; 2263 } 2264 2265 // Draw rectangle from 'startCorner' 2266 ctx.moveTo.apply(ctx, cornerAt(0)); 2267 for (var i = 1; i < 4; i++) 2268 ctx.lineTo.apply(ctx, cornerAt(i)); 2269 2270 ctx.fill(); 2271 if (vm.borderWidth) { 2272 ctx.stroke(); 2273 } 2274 }, 2275 2276 inRange: function (mouseX, mouseY) { 2277 var vm = this._view; 2278 var inRange = false; 2279 2280 if (vm) { 2281 if (vm.x < vm.base) { 2282 inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.x && mouseX <= vm.base); 2283 } else { 2284 inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.base && mouseX <= vm.x); 2285 } 2286 } 2287 2288 return inRange; 2289 } 2290 }); 2291 2292 rectangle.pivot(); 2293 }, 2294 2295 calculateBarBase: function (datasetIndex, index) { 2296 var me = this; 2297 var meta = me.getMeta(); 2298 var xScale = me.getScaleForId(meta.xAxisID); 2299 var base = 0; 2300 2301 if (xScale.options.stacked) { 2302 2303 var value = me.chart.data.datasets[datasetIndex].data[index]; 2304 2305 if (value < 0) { 2306 for (var i = 0; i < datasetIndex; i++) { 2307 var negDS = me.chart.data.datasets[i]; 2308 var negDSMeta = me.chart.getDatasetMeta(i); 2309 if (negDSMeta.bar && negDSMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i)) { 2310 base += negDS.data[index] < 0 ? negDS.data[index] : 0; 2311 } 2312 } 2313 } else { 2314 for (var j = 0; j < datasetIndex; j++) { 2315 var posDS = me.chart.data.datasets[j]; 2316 var posDSMeta = me.chart.getDatasetMeta(j); 2317 if (posDSMeta.bar && posDSMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(j)) { 2318 base += posDS.data[index] > 0 ? posDS.data[index] : 0; 2319 } 2320 } 2321 } 2322 2323 return xScale.getPixelForValue(base); 2324 } 2325 2326 return xScale.getBasePixel(); 2327 }, 2328 2329 getRuler: function (index) { 2330 var me = this; 2331 var meta = me.getMeta(); 2332 var yScale = me.getScaleForId(meta.yAxisID); 2333 var datasetCount = me.getBarCount(); 2334 2335 var tickHeight; 2336 if (yScale.options.type === 'category') { 2337 tickHeight = yScale.getPixelForTick(index + 1) - yScale.getPixelForTick(index); 2338 } else { 2339 // Average width 2340 tickHeight = yScale.width / yScale.ticks.length; 2341 } 2342 var categoryHeight = tickHeight * yScale.options.categoryPercentage; 2343 var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2; 2344 var fullBarHeight = categoryHeight / datasetCount; 2345 2346 if (yScale.ticks.length !== me.chart.data.labels.length) { 2347 var perc = yScale.ticks.length / me.chart.data.labels.length; 2348 fullBarHeight = fullBarHeight * perc; 2349 } 2350 2351 var barHeight = fullBarHeight * yScale.options.barPercentage; 2352 var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage); 2353 2354 return { 2355 datasetCount: datasetCount, 2356 tickHeight: tickHeight, 2357 categoryHeight: categoryHeight, 2358 categorySpacing: categorySpacing, 2359 fullBarHeight: fullBarHeight, 2360 barHeight: barHeight, 2361 barSpacing: barSpacing, 2362 }; 2363 }, 2364 2365 calculateBarHeight: function (index) { 2366 var me = this; 2367 var yScale = me.getScaleForId(me.getMeta().yAxisID); 2368 var ruler = me.getRuler(index); 2369 return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight; 2370 }, 2371 2372 calculateBarX: function (index, datasetIndex) { 2373 var me = this; 2374 var meta = me.getMeta(); 2375 var xScale = me.getScaleForId(meta.xAxisID); 2376 var value = me.getDataset().data[index]; 2377 2378 if (xScale.options.stacked) { 2379 2380 var sumPos = 0, 2381 sumNeg = 0; 2382 2383 for (var i = 0; i < datasetIndex; i++) { 2384 var ds = me.chart.data.datasets[i]; 2385 var dsMeta = me.chart.getDatasetMeta(i); 2386 if (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i)) { 2387 if (ds.data[index] < 0) { 2388 sumNeg += ds.data[index] || 0; 2389 } else { 2390 sumPos += ds.data[index] || 0; 2391 } 2392 } 2393 } 2394 2395 if (value < 0) { 2396 return xScale.getPixelForValue(sumNeg + value); 2397 } else { 2398 return xScale.getPixelForValue(sumPos + value); 2399 } 2400 } 2401 2402 return xScale.getPixelForValue(value); 2403 }, 2404 2405 calculateBarY: function (index, datasetIndex) { 2406 var me = this; 2407 var meta = me.getMeta(); 2408 var yScale = me.getScaleForId(meta.yAxisID); 2409 var barIndex = me.getBarIndex(datasetIndex); 2410 2411 var ruler = me.getRuler(index); 2412 var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); 2413 topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0; 2414 2415 if (yScale.options.stacked) { 2416 return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing; 2417 } 2418 2419 return topTick + 2420 (ruler.barHeight / 2) + 2421 ruler.categorySpacing + 2422 (ruler.barHeight * barIndex) + 2423 (ruler.barSpacing / 2) + 2424 (ruler.barSpacing * barIndex); 2425 } 2426 }); 2427 }; 2428 2429 },{}],16:[function(require,module,exports){ 2430 "use strict"; 2431 2432 module.exports = function(Chart) { 2433 2434 var helpers = Chart.helpers; 2435 2436 Chart.defaults.bubble = { 2437 hover: { 2438 mode: "single" 2439 }, 2440 2441 scales: { 2442 xAxes: [{ 2443 type: "linear", // bubble should probably use a linear scale by default 2444 position: "bottom", 2445 id: "x-axis-0" // need an ID so datasets can reference the scale 2446 }], 2447 yAxes: [{ 2448 type: "linear", 2449 position: "left", 2450 id: "y-axis-0" 2451 }] 2452 }, 2453 2454 tooltips: { 2455 callbacks: { 2456 title: function(tooltipItems, data) { 2457 // Title doesn't make sense for scatter since we format the data as a point 2458 return ''; 2459 }, 2460 label: function(tooltipItem, data) { 2461 var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; 2462 var dataPoint = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; 2463 return datasetLabel + ': (' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.r + ')'; 2464 } 2465 } 2466 } 2467 }; 2468 2469 Chart.controllers.bubble = Chart.DatasetController.extend({ 2470 2471 dataElementType: Chart.elements.Point, 2472 2473 update: function update(reset) { 2474 var me = this; 2475 var meta = me.getMeta(); 2476 var points = meta.data; 2477 2478 // Update Points 2479 helpers.each(points, function(point, index) { 2480 me.updateElement(point, index, reset); 2481 }); 2482 }, 2483 2484 updateElement: function(point, index, reset) { 2485 var me = this; 2486 var meta = me.getMeta(); 2487 var xScale = me.getScaleForId(meta.xAxisID); 2488 var yScale = me.getScaleForId(meta.yAxisID); 2489 2490 var custom = point.custom || {}; 2491 var dataset = me.getDataset(); 2492 var data = dataset.data[index]; 2493 var pointElementOptions = me.chart.options.elements.point; 2494 var dsIndex = me.index; 2495 2496 helpers.extend(point, { 2497 // Utility 2498 _xScale: xScale, 2499 _yScale: yScale, 2500 _datasetIndex: dsIndex, 2501 _index: index, 2502 2503 // Desired view properties 2504 _model: { 2505 x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(data, index, dsIndex, me.chart.isCombo), 2506 y: reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex), 2507 // Appearance 2508 radius: reset ? 0 : custom.radius ? custom.radius : me.getRadius(data), 2509 2510 // Tooltip 2511 hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius) 2512 } 2513 }); 2514 2515 // Trick to reset the styles of the point 2516 Chart.DatasetController.prototype.removeHoverStyle.call(me, point, pointElementOptions); 2517 2518 var model = point._model; 2519 model.skip = custom.skip ? custom.skip : (isNaN(model.x) || isNaN(model.y)); 2520 2521 point.pivot(); 2522 }, 2523 2524 getRadius: function(value) { 2525 return value.r || this.chart.options.elements.point.radius; 2526 }, 2527 2528 setHoverStyle: function(point) { 2529 var me = this; 2530 Chart.DatasetController.prototype.setHoverStyle.call(me, point); 2531 2532 // Radius 2533 var dataset = me.chart.data.datasets[point._datasetIndex]; 2534 var index = point._index; 2535 var custom = point.custom || {}; 2536 var model = point._model; 2537 model.radius = custom.hoverRadius ? custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, me.chart.options.elements.point.hoverRadius)) + me.getRadius(dataset.data[index]); 2538 }, 2539 2540 removeHoverStyle: function(point) { 2541 var me = this; 2542 Chart.DatasetController.prototype.removeHoverStyle.call(me, point, me.chart.options.elements.point); 2543 2544 var dataVal = me.chart.data.datasets[point._datasetIndex].data[point._index]; 2545 var custom = point.custom || {}; 2546 var model = point._model; 2547 2548 model.radius = custom.radius ? custom.radius : me.getRadius(dataVal); 2549 } 2550 }); 2551 }; 2552 2553 },{}],17:[function(require,module,exports){ 2554 "use strict"; 2555 2556 module.exports = function(Chart) { 2557 2558 var helpers = Chart.helpers, 2559 defaults = Chart.defaults; 2560 2561 defaults.doughnut = { 2562 animation: { 2563 //Boolean - Whether we animate the rotation of the Doughnut 2564 animateRotate: true, 2565 //Boolean - Whether we animate scaling the Doughnut from the centre 2566 animateScale: false 2567 }, 2568 aspectRatio: 1, 2569 hover: { 2570 mode: 'single' 2571 }, 2572 legendCallback: function(chart) { 2573 var text = []; 2574 text.push('<ul class="' + chart.id + '-legend">'); 2575 2576 var data = chart.data; 2577 var datasets = data.datasets; 2578 var labels = data.labels; 2579 2580 if (datasets.length) { 2581 for (var i = 0; i < datasets[0].data.length; ++i) { 2582 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); 2583 if (labels[i]) { 2584 text.push(labels[i]); 2585 } 2586 text.push('</li>'); 2587 } 2588 } 2589 2590 text.push('</ul>'); 2591 return text.join(""); 2592 }, 2593 legend: { 2594 labels: { 2595 generateLabels: function(chart) { 2596 var data = chart.data; 2597 if (data.labels.length && data.datasets.length) { 2598 return data.labels.map(function(label, i) { 2599 var meta = chart.getDatasetMeta(0); 2600 var ds = data.datasets[0]; 2601 var arc = meta.data[i]; 2602 var custom = arc.custom || {}; 2603 var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; 2604 var arcOpts = chart.options.elements.arc; 2605 var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); 2606 var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); 2607 var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); 2608 2609 return { 2610 text: label, 2611 fillStyle: fill, 2612 strokeStyle: stroke, 2613 lineWidth: bw, 2614 hidden: isNaN(ds.data[i]) || meta.data[i].hidden, 2615 2616 // Extra data used for toggling the correct item 2617 index: i 2618 }; 2619 }); 2620 } else { 2621 return []; 2622 } 2623 } 2624 }, 2625 2626 onClick: function(e, legendItem) { 2627 var index = legendItem.index; 2628 var chart = this.chart; 2629 var i, ilen, meta; 2630 2631 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { 2632 meta = chart.getDatasetMeta(i); 2633 meta.data[index].hidden = !meta.data[index].hidden; 2634 } 2635 2636 chart.update(); 2637 } 2638 }, 2639 2640 //The percentage of the chart that we cut out of the middle. 2641 cutoutPercentage: 50, 2642 2643 //The rotation of the chart, where the first data arc begins. 2644 rotation: Math.PI * -0.5, 2645 2646 //The total circumference of the chart. 2647 circumference: Math.PI * 2.0, 2648 2649 // Need to override these to give a nice default 2650 tooltips: { 2651 callbacks: { 2652 title: function() { 2653 return ''; 2654 }, 2655 label: function(tooltipItem, data) { 2656 return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; 2657 } 2658 } 2659 } 2660 }; 2661 2662 defaults.pie = helpers.clone(defaults.doughnut); 2663 helpers.extend(defaults.pie, { 2664 cutoutPercentage: 0 2665 }); 2666 2667 2668 Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ 2669 2670 dataElementType: Chart.elements.Arc, 2671 2672 linkScales: helpers.noop, 2673 2674 // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly 2675 getRingIndex: function getRingIndex(datasetIndex) { 2676 var ringIndex = 0; 2677 2678 for (var j = 0; j < datasetIndex; ++j) { 2679 if (this.chart.isDatasetVisible(j)) { 2680 ++ringIndex; 2681 } 2682 } 2683 2684 return ringIndex; 2685 }, 2686 2687 update: function update(reset) { 2688 var me = this; 2689 var chart = me.chart, 2690 chartArea = chart.chartArea, 2691 opts = chart.options, 2692 arcOpts = opts.elements.arc, 2693 availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth, 2694 availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth, 2695 minSize = Math.min(availableWidth, availableHeight), 2696 offset = { 2697 x: 0, 2698 y: 0 2699 }, 2700 meta = me.getMeta(), 2701 cutoutPercentage = opts.cutoutPercentage, 2702 circumference = opts.circumference; 2703 2704 // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc 2705 if (circumference < Math.PI * 2.0) { 2706 var startAngle = opts.rotation % (Math.PI * 2.0); 2707 startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); 2708 var endAngle = startAngle + circumference; 2709 var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; 2710 var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; 2711 var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); 2712 var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); 2713 var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); 2714 var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); 2715 var cutout = cutoutPercentage / 100.0; 2716 var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))}; 2717 var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))}; 2718 var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; 2719 minSize = Math.min(availableWidth / size.width, availableHeight / size.height); 2720 offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; 2721 } 2722 2723 chart.outerRadius = Math.max(minSize / 2, 0); 2724 chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 1, 0); 2725 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); 2726 chart.offsetX = offset.x * chart.outerRadius; 2727 chart.offsetY = offset.y * chart.outerRadius; 2728 2729 meta.total = me.calculateTotal(); 2730 2731 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); 2732 me.innerRadius = me.outerRadius - chart.radiusLength; 2733 2734 helpers.each(meta.data, function(arc, index) { 2735 me.updateElement(arc, index, reset); 2736 }); 2737 }, 2738 2739 updateElement: function(arc, index, reset) { 2740 var me = this; 2741 var chart = me.chart, 2742 chartArea = chart.chartArea, 2743 opts = chart.options, 2744 animationOpts = opts.animation, 2745 arcOpts = opts.elements.arc, 2746 centerX = (chartArea.left + chartArea.right) / 2, 2747 centerY = (chartArea.top + chartArea.bottom) / 2, 2748 startAngle = opts.rotation, // non reset case handled later 2749 endAngle = opts.rotation, // non reset case handled later 2750 dataset = me.getDataset(), 2751 circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)), 2752 innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius, 2753 outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius, 2754 custom = arc.custom || {}, 2755 valueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; 2756 2757 helpers.extend(arc, { 2758 // Utility 2759 _datasetIndex: me.index, 2760 _index: index, 2761 2762 // Desired view properties 2763 _model: { 2764 x: centerX + chart.offsetX, 2765 y: centerY + chart.offsetY, 2766 startAngle: startAngle, 2767 endAngle: endAngle, 2768 circumference: circumference, 2769 outerRadius: outerRadius, 2770 innerRadius: innerRadius, 2771 label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) 2772 } 2773 }); 2774 2775 var model = arc._model; 2776 // Resets the visual styles 2777 this.removeHoverStyle(arc); 2778 2779 // Set correct angles if not resetting 2780 if (!reset || !animationOpts.animateRotate) { 2781 if (index === 0) { 2782 model.startAngle = opts.rotation; 2783 } else { 2784 model.startAngle = me.getMeta().data[index - 1]._model.endAngle; 2785 } 2786 2787 model.endAngle = model.startAngle + model.circumference; 2788 } 2789 2790 arc.pivot(); 2791 }, 2792 2793 removeHoverStyle: function(arc) { 2794 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); 2795 }, 2796 2797 calculateTotal: function() { 2798 var dataset = this.getDataset(); 2799 var meta = this.getMeta(); 2800 var total = 0; 2801 var value; 2802 2803 helpers.each(meta.data, function(element, index) { 2804 value = dataset.data[index]; 2805 if (!isNaN(value) && !element.hidden) { 2806 total += Math.abs(value); 2807 } 2808 }); 2809 2810 return total; 2811 }, 2812 2813 calculateCircumference: function(value) { 2814 var total = this.getMeta().total; 2815 if (total > 0 && !isNaN(value)) { 2816 return (Math.PI * 2.0) * (value / total); 2817 } else { 2818 return 0; 2819 } 2820 } 2821 }); 2822 }; 2823 2824 },{}],18:[function(require,module,exports){ 2825 "use strict"; 2826 2827 module.exports = function(Chart) { 2828 2829 var helpers = Chart.helpers; 2830 2831 Chart.defaults.line = { 2832 showLines: true, 2833 2834 hover: { 2835 mode: "label" 2836 }, 2837 2838 scales: { 2839 xAxes: [{ 2840 type: "category", 2841 id: 'x-axis-0' 2842 }], 2843 yAxes: [{ 2844 type: "linear", 2845 id: 'y-axis-0' 2846 }] 2847 } 2848 }; 2849 2850 function lineEnabled(dataset, options) { 2851 return helpers.getValueOrDefault(dataset.showLine, options.showLines); 2852 } 2853 2854 Chart.controllers.line = Chart.DatasetController.extend({ 2855 2856 datasetElementType: Chart.elements.Line, 2857 2858 dataElementType: Chart.elements.Point, 2859 2860 addElementAndReset: function(index) { 2861 var me = this; 2862 var options = me.chart.options; 2863 var meta = me.getMeta(); 2864 2865 Chart.DatasetController.prototype.addElementAndReset.call(me, index); 2866 2867 // Make sure bezier control points are updated 2868 if (lineEnabled(me.getDataset(), options) && meta.dataset._model.tension !== 0) { 2869 me.updateBezierControlPoints(); 2870 } 2871 }, 2872 2873 update: function update(reset) { 2874 var me = this; 2875 var meta = me.getMeta(); 2876 var line = meta.dataset; 2877 var points = meta.data || []; 2878 var options = me.chart.options; 2879 var lineElementOptions = options.elements.line; 2880 var scale = me.getScaleForId(meta.yAxisID); 2881 var i, ilen, custom; 2882 var dataset = me.getDataset(); 2883 var showLine = lineEnabled(dataset, options); 2884 2885 // Update Line 2886 if (showLine) { 2887 custom = line.custom || {}; 2888 2889 // Compatibility: If the properties are defined with only the old name, use those values 2890 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { 2891 dataset.lineTension = dataset.tension; 2892 } 2893 2894 // Utility 2895 line._scale = scale; 2896 line._datasetIndex = me.index; 2897 // Data 2898 line._children = points; 2899 // Model 2900 line._model = { 2901 // Appearance 2902 // The default behavior of lines is to break at null values, according 2903 // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 2904 // This option gives linse the ability to span gaps 2905 spanGaps: dataset.spanGaps ? dataset.spanGaps : false, 2906 tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension), 2907 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), 2908 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), 2909 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), 2910 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), 2911 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), 2912 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), 2913 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), 2914 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), 2915 // Scale 2916 scaleTop: scale.top, 2917 scaleBottom: scale.bottom, 2918 scaleZero: scale.getBasePixel() 2919 }; 2920 2921 line.pivot(); 2922 } 2923 2924 // Update Points 2925 for (i=0, ilen=points.length; i<ilen; ++i) { 2926 me.updateElement(points[i], i, reset); 2927 } 2928 2929 if (showLine && line._model.tension !== 0) { 2930 me.updateBezierControlPoints(); 2931 } 2932 2933 // Now pivot the point for animation 2934 for (i=0, ilen=points.length; i<ilen; ++i) { 2935 points[i].pivot(); 2936 } 2937 }, 2938 2939 getPointBackgroundColor: function(point, index) { 2940 var backgroundColor = this.chart.options.elements.point.backgroundColor; 2941 var dataset = this.getDataset(); 2942 var custom = point.custom || {}; 2943 2944 if (custom.backgroundColor) { 2945 backgroundColor = custom.backgroundColor; 2946 } else if (dataset.pointBackgroundColor) { 2947 backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); 2948 } else if (dataset.backgroundColor) { 2949 backgroundColor = dataset.backgroundColor; 2950 } 2951 2952 return backgroundColor; 2953 }, 2954 2955 getPointBorderColor: function(point, index) { 2956 var borderColor = this.chart.options.elements.point.borderColor; 2957 var dataset = this.getDataset(); 2958 var custom = point.custom || {}; 2959 2960 if (custom.borderColor) { 2961 borderColor = custom.borderColor; 2962 } else if (dataset.pointBorderColor) { 2963 borderColor = helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); 2964 } else if (dataset.borderColor) { 2965 borderColor = dataset.borderColor; 2966 } 2967 2968 return borderColor; 2969 }, 2970 2971 getPointBorderWidth: function(point, index) { 2972 var borderWidth = this.chart.options.elements.point.borderWidth; 2973 var dataset = this.getDataset(); 2974 var custom = point.custom || {}; 2975 2976 if (custom.borderWidth) { 2977 borderWidth = custom.borderWidth; 2978 } else if (dataset.pointBorderWidth) { 2979 borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); 2980 } else if (dataset.borderWidth) { 2981 borderWidth = dataset.borderWidth; 2982 } 2983 2984 return borderWidth; 2985 }, 2986 2987 updateElement: function(point, index, reset) { 2988 var me = this; 2989 var meta = me.getMeta(); 2990 var custom = point.custom || {}; 2991 var dataset = me.getDataset(); 2992 var datasetIndex = me.index; 2993 var value = dataset.data[index]; 2994 var yScale = me.getScaleForId(meta.yAxisID); 2995 var xScale = me.getScaleForId(meta.xAxisID); 2996 var pointOptions = me.chart.options.elements.point; 2997 var x, y; 2998 2999 // Compatibility: If the properties are defined with only the old name, use those values 3000 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { 3001 dataset.pointRadius = dataset.radius; 3002 } 3003 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { 3004 dataset.pointHitRadius = dataset.hitRadius; 3005 } 3006 3007 x = xScale.getPixelForValue(value, index, datasetIndex, me.chart.isCombo); 3008 y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex, me.chart.isCombo); 3009 3010 // Utility 3011 point._xScale = xScale; 3012 point._yScale = yScale; 3013 point._datasetIndex = datasetIndex; 3014 point._index = index; 3015 3016 // Desired view properties 3017 point._model = { 3018 x: x, 3019 y: y, 3020 skip: custom.skip || isNaN(x) || isNaN(y), 3021 // Appearance 3022 radius: custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), 3023 pointStyle: custom.pointStyle || helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), 3024 backgroundColor: me.getPointBackgroundColor(point, index), 3025 borderColor: me.getPointBorderColor(point, index), 3026 borderWidth: me.getPointBorderWidth(point, index), 3027 tension: meta.dataset._model ? meta.dataset._model.tension : 0, 3028 // Tooltip 3029 hitRadius: custom.hitRadius || helpers.getValueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) 3030 }; 3031 }, 3032 3033 calculatePointY: function(value, index, datasetIndex, isCombo) { 3034 var me = this; 3035 var chart = me.chart; 3036 var meta = me.getMeta(); 3037 var yScale = me.getScaleForId(meta.yAxisID); 3038 var sumPos = 0; 3039 var sumNeg = 0; 3040 var i, ds, dsMeta; 3041 3042 if (yScale.options.stacked) { 3043 for (i = 0; i < datasetIndex; i++) { 3044 ds = chart.data.datasets[i]; 3045 dsMeta = chart.getDatasetMeta(i); 3046 if (dsMeta.type === 'line' && chart.isDatasetVisible(i)) { 3047 if (ds.data[index] < 0) { 3048 sumNeg += ds.data[index] || 0; 3049 } else { 3050 sumPos += ds.data[index] || 0; 3051 } 3052 } 3053 } 3054 3055 if (value < 0) { 3056 return yScale.getPixelForValue(sumNeg + value); 3057 } else { 3058 return yScale.getPixelForValue(sumPos + value); 3059 } 3060 } 3061 3062 return yScale.getPixelForValue(value); 3063 }, 3064 3065 updateBezierControlPoints: function() { 3066 var meta = this.getMeta(); 3067 var area = this.chart.chartArea; 3068 var points = meta.data || []; 3069 var i, ilen, point, model, controlPoints; 3070 3071 for (i=0, ilen=points.length; i<ilen; ++i) { 3072 point = points[i]; 3073 model = point._model; 3074 controlPoints = helpers.splineCurve( 3075 helpers.previousItem(points, i)._model, 3076 model, 3077 helpers.nextItem(points, i)._model, 3078 meta.dataset._model.tension 3079 ); 3080 3081 model.controlPointPreviousX = controlPoints.previous.x; 3082 model.controlPointPreviousY = controlPoints.previous.y; 3083 model.controlPointNextX = controlPoints.next.x; 3084 model.controlPointNextY = controlPoints.next.y; 3085 } 3086 }, 3087 3088 draw: function(ease) { 3089 var me = this; 3090 var meta = me.getMeta(); 3091 var points = meta.data || []; 3092 var easingDecimal = ease || 1; 3093 var i, ilen; 3094 3095 // Transition Point Locations 3096 for (i=0, ilen=points.length; i<ilen; ++i) { 3097 points[i].transition(easingDecimal); 3098 } 3099 3100 // Transition and Draw the line 3101 if (lineEnabled(me.getDataset(), me.chart.options)) { 3102 meta.dataset.transition(easingDecimal).draw(); 3103 } 3104 3105 // Draw the points 3106 for (i=0, ilen=points.length; i<ilen; ++i) { 3107 points[i].draw(); 3108 } 3109 }, 3110 3111 setHoverStyle: function(point) { 3112 // Point 3113 var dataset = this.chart.data.datasets[point._datasetIndex]; 3114 var index = point._index; 3115 var custom = point.custom || {}; 3116 var model = point._model; 3117 3118 model.radius = custom.hoverRadius || helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); 3119 model.backgroundColor = custom.hoverBackgroundColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); 3120 model.borderColor = custom.hoverBorderColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); 3121 model.borderWidth = custom.hoverBorderWidth || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); 3122 }, 3123 3124 removeHoverStyle: function(point) { 3125 var me = this; 3126 var dataset = me.chart.data.datasets[point._datasetIndex]; 3127 var index = point._index; 3128 var custom = point.custom || {}; 3129 var model = point._model; 3130 3131 // Compatibility: If the properties are defined with only the old name, use those values 3132 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { 3133 dataset.pointRadius = dataset.radius; 3134 } 3135 3136 model.radius = custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius); 3137 model.backgroundColor = me.getPointBackgroundColor(point, index); 3138 model.borderColor = me.getPointBorderColor(point, index); 3139 model.borderWidth = me.getPointBorderWidth(point, index); 3140 } 3141 }); 3142 }; 3143 3144 },{}],19:[function(require,module,exports){ 3145 "use strict"; 3146 3147 module.exports = function(Chart) { 3148 3149 var helpers = Chart.helpers; 3150 3151 Chart.defaults.polarArea = { 3152 3153 scale: { 3154 type: "radialLinear", 3155 lineArc: true // so that lines are circular 3156 }, 3157 3158 //Boolean - Whether to animate the rotation of the chart 3159 animation: { 3160 animateRotate: true, 3161 animateScale: true 3162 }, 3163 3164 aspectRatio: 1, 3165 legendCallback: function(chart) { 3166 var text = []; 3167 text.push('<ul class="' + chart.id + '-legend">'); 3168 3169 var data = chart.data; 3170 var datasets = data.datasets; 3171 var labels = data.labels; 3172 3173 if (datasets.length) { 3174 for (var i = 0; i < datasets[0].data.length; ++i) { 3175 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '">'); 3176 if (labels[i]) { 3177 text.push(labels[i]); 3178 } 3179 text.push('</span></li>'); 3180 } 3181 } 3182 3183 text.push('</ul>'); 3184 return text.join(""); 3185 }, 3186 legend: { 3187 labels: { 3188 generateLabels: function(chart) { 3189 var data = chart.data; 3190 if (data.labels.length && data.datasets.length) { 3191 return data.labels.map(function(label, i) { 3192 var meta = chart.getDatasetMeta(0); 3193 var ds = data.datasets[0]; 3194 var arc = meta.data[i]; 3195 var custom = arc.custom || {}; 3196 var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; 3197 var arcOpts = chart.options.elements.arc; 3198 var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); 3199 var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); 3200 var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); 3201 3202 return { 3203 text: label, 3204 fillStyle: fill, 3205 strokeStyle: stroke, 3206 lineWidth: bw, 3207 hidden: isNaN(ds.data[i]) || meta.data[i].hidden, 3208 3209 // Extra data used for toggling the correct item 3210 index: i 3211 }; 3212 }); 3213 } else { 3214 return []; 3215 } 3216 } 3217 }, 3218 3219 onClick: function(e, legendItem) { 3220 var index = legendItem.index; 3221 var chart = this.chart; 3222 var i, ilen, meta; 3223 3224 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { 3225 meta = chart.getDatasetMeta(i); 3226 meta.data[index].hidden = !meta.data[index].hidden; 3227 } 3228 3229 chart.update(); 3230 } 3231 }, 3232 3233 // Need to override these to give a nice default 3234 tooltips: { 3235 callbacks: { 3236 title: function() { 3237 return ''; 3238 }, 3239 label: function(tooltipItem, data) { 3240 return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel; 3241 } 3242 } 3243 } 3244 }; 3245 3246 Chart.controllers.polarArea = Chart.DatasetController.extend({ 3247 3248 dataElementType: Chart.elements.Arc, 3249 3250 linkScales: helpers.noop, 3251 3252 update: function update(reset) { 3253 var me = this; 3254 var chart = me.chart; 3255 var chartArea = chart.chartArea; 3256 var meta = me.getMeta(); 3257 var opts = chart.options; 3258 var arcOpts = opts.elements.arc; 3259 var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); 3260 chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0); 3261 chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); 3262 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); 3263 3264 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); 3265 me.innerRadius = me.outerRadius - chart.radiusLength; 3266 3267 meta.count = me.countVisibleElements(); 3268 3269 helpers.each(meta.data, function(arc, index) { 3270 me.updateElement(arc, index, reset); 3271 }); 3272 }, 3273 3274 updateElement: function(arc, index, reset) { 3275 var me = this; 3276 var chart = me.chart; 3277 var chartArea = chart.chartArea; 3278 var dataset = me.getDataset(); 3279 var opts = chart.options; 3280 var animationOpts = opts.animation; 3281 var arcOpts = opts.elements.arc; 3282 var custom = arc.custom || {}; 3283 var scale = chart.scale; 3284 var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; 3285 var labels = chart.data.labels; 3286 3287 var circumference = me.calculateCircumference(dataset.data[index]); 3288 var centerX = (chartArea.left + chartArea.right) / 2; 3289 var centerY = (chartArea.top + chartArea.bottom) / 2; 3290 3291 // If there is NaN data before us, we need to calculate the starting angle correctly. 3292 // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data 3293 var visibleCount = 0; 3294 var meta = me.getMeta(); 3295 for (var i = 0; i < index; ++i) { 3296 if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) { 3297 ++visibleCount; 3298 } 3299 } 3300 3301 var negHalfPI = -0.5 * Math.PI; 3302 var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); 3303 var startAngle = (negHalfPI) + (circumference * visibleCount); 3304 var endAngle = startAngle + (arc.hidden ? 0 : circumference); 3305 3306 var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); 3307 3308 helpers.extend(arc, { 3309 // Utility 3310 _datasetIndex: me.index, 3311 _index: index, 3312 _scale: scale, 3313 3314 // Desired view properties 3315 _model: { 3316 x: centerX, 3317 y: centerY, 3318 innerRadius: 0, 3319 outerRadius: reset ? resetRadius : distance, 3320 startAngle: reset && animationOpts.animateRotate ? negHalfPI : startAngle, 3321 endAngle: reset && animationOpts.animateRotate ? negHalfPI : endAngle, 3322 label: getValueAtIndexOrDefault(labels, index, labels[index]) 3323 } 3324 }); 3325 3326 // Apply border and fill style 3327 me.removeHoverStyle(arc); 3328 3329 arc.pivot(); 3330 }, 3331 3332 removeHoverStyle: function(arc) { 3333 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); 3334 }, 3335 3336 countVisibleElements: function() { 3337 var dataset = this.getDataset(); 3338 var meta = this.getMeta(); 3339 var count = 0; 3340 3341 helpers.each(meta.data, function(element, index) { 3342 if (!isNaN(dataset.data[index]) && !element.hidden) { 3343 count++; 3344 } 3345 }); 3346 3347 return count; 3348 }, 3349 3350 calculateCircumference: function(value) { 3351 var count = this.getMeta().count; 3352 if (count > 0 && !isNaN(value)) { 3353 return (2 * Math.PI) / count; 3354 } else { 3355 return 0; 3356 } 3357 } 3358 }); 3359 }; 3360 3361 },{}],20:[function(require,module,exports){ 3362 "use strict"; 3363 3364 module.exports = function(Chart) { 3365 3366 var helpers = Chart.helpers; 3367 3368 Chart.defaults.radar = { 3369 scale: { 3370 type: "radialLinear" 3371 }, 3372 elements: { 3373 line: { 3374 tension: 0 // no bezier in radar 3375 } 3376 } 3377 }; 3378 3379 Chart.controllers.radar = Chart.DatasetController.extend({ 3380 3381 datasetElementType: Chart.elements.Line, 3382 3383 dataElementType: Chart.elements.Point, 3384 3385 linkScales: helpers.noop, 3386 3387 addElementAndReset: function(index) { 3388 Chart.DatasetController.prototype.addElementAndReset.call(this, index); 3389 3390 // Make sure bezier control points are updated 3391 this.updateBezierControlPoints(); 3392 }, 3393 3394 update: function update(reset) { 3395 var me = this; 3396 var meta = me.getMeta(); 3397 var line = meta.dataset; 3398 var points = meta.data; 3399 var custom = line.custom || {}; 3400 var dataset = me.getDataset(); 3401 var lineElementOptions = me.chart.options.elements.line; 3402 var scale = me.chart.scale; 3403 3404 // Compatibility: If the properties are defined with only the old name, use those values 3405 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { 3406 dataset.lineTension = dataset.tension; 3407 } 3408 3409 helpers.extend(meta.dataset, { 3410 // Utility 3411 _datasetIndex: me.index, 3412 // Data 3413 _children: points, 3414 _loop: true, 3415 // Model 3416 _model: { 3417 // Appearance 3418 tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension), 3419 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), 3420 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), 3421 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), 3422 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), 3423 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), 3424 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), 3425 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), 3426 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), 3427 3428 // Scale 3429 scaleTop: scale.top, 3430 scaleBottom: scale.bottom, 3431 scaleZero: scale.getBasePosition() 3432 } 3433 }); 3434 3435 meta.dataset.pivot(); 3436 3437 // Update Points 3438 helpers.each(points, function(point, index) { 3439 me.updateElement(point, index, reset); 3440 }, me); 3441 3442 3443 // Update bezier control points 3444 me.updateBezierControlPoints(); 3445 }, 3446 updateElement: function(point, index, reset) { 3447 var me = this; 3448 var custom = point.custom || {}; 3449 var dataset = me.getDataset(); 3450 var scale = me.chart.scale; 3451 var pointElementOptions = me.chart.options.elements.point; 3452 var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); 3453 3454 helpers.extend(point, { 3455 // Utility 3456 _datasetIndex: me.index, 3457 _index: index, 3458 _scale: scale, 3459 3460 // Desired view properties 3461 _model: { 3462 x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales 3463 y: reset ? scale.yCenter : pointPosition.y, 3464 3465 // Appearance 3466 tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.tension, me.chart.options.elements.line.tension), 3467 radius: custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), 3468 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), 3469 borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), 3470 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), 3471 pointStyle: custom.pointStyle ? custom.pointStyle : helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), 3472 3473 // Tooltip 3474 hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius) 3475 } 3476 }); 3477 3478 point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); 3479 }, 3480 updateBezierControlPoints: function() { 3481 var chartArea = this.chart.chartArea; 3482 var meta = this.getMeta(); 3483 3484 helpers.each(meta.data, function(point, index) { 3485 var model = point._model; 3486 var controlPoints = helpers.splineCurve( 3487 helpers.previousItem(meta.data, index, true)._model, 3488 model, 3489 helpers.nextItem(meta.data, index, true)._model, 3490 model.tension 3491 ); 3492 3493 // Prevent the bezier going outside of the bounds of the graph 3494 model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); 3495 model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); 3496 3497 model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); 3498 model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); 3499 3500 // Now pivot the point for animation 3501 point.pivot(); 3502 }); 3503 }, 3504 3505 draw: function(ease) { 3506 var meta = this.getMeta(); 3507 var easingDecimal = ease || 1; 3508 3509 // Transition Point Locations 3510 helpers.each(meta.data, function(point, index) { 3511 point.transition(easingDecimal); 3512 }); 3513 3514 // Transition and Draw the line 3515 meta.dataset.transition(easingDecimal).draw(); 3516 3517 // Draw the points 3518 helpers.each(meta.data, function(point) { 3519 point.draw(); 3520 }); 3521 }, 3522 3523 setHoverStyle: function(point) { 3524 // Point 3525 var dataset = this.chart.data.datasets[point._datasetIndex]; 3526 var custom = point.custom || {}; 3527 var index = point._index; 3528 var model = point._model; 3529 3530 model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); 3531 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); 3532 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); 3533 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); 3534 }, 3535 3536 removeHoverStyle: function(point) { 3537 var dataset = this.chart.data.datasets[point._datasetIndex]; 3538 var custom = point.custom || {}; 3539 var index = point._index; 3540 var model = point._model; 3541 var pointElementOptions = this.chart.options.elements.point; 3542 3543 model.radius = custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.radius, index, pointElementOptions.radius); 3544 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor); 3545 model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor); 3546 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth); 3547 } 3548 }); 3549 }; 3550 3551 },{}],21:[function(require,module,exports){ 3552 /*global window: false */ 3553 "use strict"; 3554 3555 module.exports = function(Chart) { 3556 3557 var helpers = Chart.helpers; 3558 3559 Chart.defaults.global.animation = { 3560 duration: 1000, 3561 easing: "easeOutQuart", 3562 onProgress: helpers.noop, 3563 onComplete: helpers.noop 3564 }; 3565 3566 Chart.Animation = Chart.Element.extend({ 3567 currentStep: null, // the current animation step 3568 numSteps: 60, // default number of steps 3569 easing: "", // the easing to use for this animation 3570 render: null, // render function used by the animation service 3571 3572 onAnimationProgress: null, // user specified callback to fire on each step of the animation 3573 onAnimationComplete: null // user specified callback to fire when the animation finishes 3574 }); 3575 3576 Chart.animationService = { 3577 frameDuration: 17, 3578 animations: [], 3579 dropFrames: 0, 3580 request: null, 3581 addAnimation: function(chartInstance, animationObject, duration, lazy) { 3582 var me = this; 3583 3584 if (!lazy) { 3585 chartInstance.animating = true; 3586 } 3587 3588 for (var index = 0; index < me.animations.length; ++index) { 3589 if (me.animations[index].chartInstance === chartInstance) { 3590 // replacing an in progress animation 3591 me.animations[index].animationObject = animationObject; 3592 return; 3593 } 3594 } 3595 3596 me.animations.push({ 3597 chartInstance: chartInstance, 3598 animationObject: animationObject 3599 }); 3600 3601 // If there are no animations queued, manually kickstart a digest, for lack of a better word 3602 if (me.animations.length === 1) { 3603 me.requestAnimationFrame(); 3604 } 3605 }, 3606 // Cancel the animation for a given chart instance 3607 cancelAnimation: function(chartInstance) { 3608 var index = helpers.findIndex(this.animations, function(animationWrapper) { 3609 return animationWrapper.chartInstance === chartInstance; 3610 }); 3611 3612 if (index !== -1) { 3613 this.animations.splice(index, 1); 3614 chartInstance.animating = false; 3615 } 3616 }, 3617 requestAnimationFrame: function() { 3618 var me = this; 3619 if (me.request === null) { 3620 // Skip animation frame requests until the active one is executed. 3621 // This can happen when processing mouse events, e.g. 'mousemove' 3622 // and 'mouseout' events will trigger multiple renders. 3623 me.request = helpers.requestAnimFrame.call(window, function() { 3624 me.request = null; 3625 me.startDigest(); 3626 }); 3627 } 3628 }, 3629 startDigest: function() { 3630 var me = this; 3631 3632 var startTime = Date.now(); 3633 var framesToDrop = 0; 3634 3635 if (me.dropFrames > 1) { 3636 framesToDrop = Math.floor(me.dropFrames); 3637 me.dropFrames = me.dropFrames % 1; 3638 } 3639 3640 var i = 0; 3641 while (i < me.animations.length) { 3642 if (me.animations[i].animationObject.currentStep === null) { 3643 me.animations[i].animationObject.currentStep = 0; 3644 } 3645 3646 me.animations[i].animationObject.currentStep += 1 + framesToDrop; 3647 3648 if (me.animations[i].animationObject.currentStep > me.animations[i].animationObject.numSteps) { 3649 me.animations[i].animationObject.currentStep = me.animations[i].animationObject.numSteps; 3650 } 3651 3652 me.animations[i].animationObject.render(me.animations[i].chartInstance, me.animations[i].animationObject); 3653 if (me.animations[i].animationObject.onAnimationProgress && me.animations[i].animationObject.onAnimationProgress.call) { 3654 me.animations[i].animationObject.onAnimationProgress.call(me.animations[i].chartInstance, me.animations[i]); 3655 } 3656 3657 if (me.animations[i].animationObject.currentStep === me.animations[i].animationObject.numSteps) { 3658 if (me.animations[i].animationObject.onAnimationComplete && me.animations[i].animationObject.onAnimationComplete.call) { 3659 me.animations[i].animationObject.onAnimationComplete.call(me.animations[i].chartInstance, me.animations[i]); 3660 } 3661 3662 // executed the last frame. Remove the animation. 3663 me.animations[i].chartInstance.animating = false; 3664 3665 me.animations.splice(i, 1); 3666 } else { 3667 ++i; 3668 } 3669 } 3670 3671 var endTime = Date.now(); 3672 var dropFrames = (endTime - startTime) / me.frameDuration; 3673 3674 me.dropFrames += dropFrames; 3675 3676 // Do we have more stuff to animate? 3677 if (me.animations.length > 0) { 3678 me.requestAnimationFrame(); 3679 } 3680 } 3681 }; 3682 }; 3683 },{}],22:[function(require,module,exports){ 3684 "use strict"; 3685 3686 module.exports = function(Chart) { 3687 3688 var helpers = Chart.helpers; 3689 //Create a dictionary of chart types, to allow for extension of existing types 3690 Chart.types = {}; 3691 3692 //Store a reference to each instance - allowing us to globally resize chart instances on window resize. 3693 //Destroy method on the chart will remove the instance of the chart from this reference. 3694 Chart.instances = {}; 3695 3696 // Controllers available for dataset visualization eg. bar, line, slice, etc. 3697 Chart.controllers = {}; 3698 3699 /** 3700 * @class Chart.Controller 3701 * The main controller of a chart. 3702 */ 3703 Chart.Controller = function(instance) { 3704 3705 this.chart = instance; 3706 this.config = instance.config; 3707 this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {}); 3708 this.id = helpers.uid(); 3709 3710 Object.defineProperty(this, 'data', { 3711 get: function() { 3712 return this.config.data; 3713 } 3714 }); 3715 3716 //Add the chart instance to the global namespace 3717 Chart.instances[this.id] = this; 3718 3719 if (this.options.responsive) { 3720 // Silent resize before chart draws 3721 this.resize(true); 3722 } 3723 3724 this.initialize(); 3725 3726 return this; 3727 }; 3728 3729 helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ { 3730 3731 initialize: function initialize() { 3732 var me = this; 3733 // Before init plugin notification 3734 Chart.plugins.notify('beforeInit', [me]); 3735 3736 me.bindEvents(); 3737 3738 // Make sure controllers are built first so that each dataset is bound to an axis before the scales 3739 // are built 3740 me.ensureScalesHaveIDs(); 3741 me.buildOrUpdateControllers(); 3742 me.buildScales(); 3743 me.updateLayout(); 3744 me.resetElements(); 3745 me.initToolTip(); 3746 me.update(); 3747 3748 // After init plugin notification 3749 Chart.plugins.notify('afterInit', [me]); 3750 3751 return me; 3752 }, 3753 3754 clear: function clear() { 3755 helpers.clear(this.chart); 3756 return this; 3757 }, 3758 3759 stop: function stop() { 3760 // Stops any current animation loop occuring 3761 Chart.animationService.cancelAnimation(this); 3762 return this; 3763 }, 3764 3765 resize: function resize(silent) { 3766 var me = this; 3767 var chart = me.chart; 3768 var canvas = chart.canvas; 3769 var newWidth = helpers.getMaximumWidth(canvas); 3770 var aspectRatio = chart.aspectRatio; 3771 var newHeight = (me.options.maintainAspectRatio && isNaN(aspectRatio) === false && isFinite(aspectRatio) && aspectRatio !== 0) ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas); 3772 3773 var sizeChanged = chart.width !== newWidth || chart.height !== newHeight; 3774 3775 if (!sizeChanged) { 3776 return me; 3777 } 3778 3779 canvas.width = chart.width = newWidth; 3780 canvas.height = chart.height = newHeight; 3781 3782 helpers.retinaScale(chart); 3783 3784 // Notify any plugins about the resize 3785 var newSize = { width: newWidth, height: newHeight }; 3786 Chart.plugins.notify('resize', [me, newSize]); 3787 3788 // Notify of resize 3789 if (me.options.onResize) { 3790 me.options.onResize(me, newSize); 3791 } 3792 3793 if (!silent) { 3794 me.stop(); 3795 me.update(me.options.responsiveAnimationDuration); 3796 } 3797 3798 return me; 3799 }, 3800 3801 ensureScalesHaveIDs: function ensureScalesHaveIDs() { 3802 var options = this.options; 3803 var scalesOptions = options.scales || {}; 3804 var scaleOptions = options.scale; 3805 3806 helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) { 3807 xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); 3808 }); 3809 3810 helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) { 3811 yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); 3812 }); 3813 3814 if (scaleOptions) { 3815 scaleOptions.id = scaleOptions.id || 'scale'; 3816 } 3817 }, 3818 3819 /** 3820 * Builds a map of scale ID to scale object for future lookup. 3821 */ 3822 buildScales: function buildScales() { 3823 var me = this; 3824 var options = me.options; 3825 var scales = me.scales = {}; 3826 var items = []; 3827 3828 if (options.scales) { 3829 items = items.concat( 3830 (options.scales.xAxes || []).map(function(xAxisOptions) { 3831 return { options: xAxisOptions, dtype: 'category' }; }), 3832 (options.scales.yAxes || []).map(function(yAxisOptions) { 3833 return { options: yAxisOptions, dtype: 'linear' }; })); 3834 } 3835 3836 if (options.scale) { 3837 items.push({ options: options.scale, dtype: 'radialLinear', isDefault: true }); 3838 } 3839 3840 helpers.each(items, function(item, index) { 3841 var scaleOptions = item.options; 3842 var scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype); 3843 var scaleClass = Chart.scaleService.getScaleConstructor(scaleType); 3844 if (!scaleClass) { 3845 return; 3846 } 3847 3848 var scale = new scaleClass({ 3849 id: scaleOptions.id, 3850 options: scaleOptions, 3851 ctx: me.chart.ctx, 3852 chart: me 3853 }); 3854 3855 scales[scale.id] = scale; 3856 3857 // TODO(SB): I think we should be able to remove this custom case (options.scale) 3858 // and consider it as a regular scale part of the "scales"" map only! This would 3859 // make the logic easier and remove some useless? custom code. 3860 if (item.isDefault) { 3861 me.scale = scale; 3862 } 3863 }); 3864 3865 Chart.scaleService.addScalesToLayout(this); 3866 }, 3867 3868 updateLayout: function() { 3869 Chart.layoutService.update(this, this.chart.width, this.chart.height); 3870 }, 3871 3872 buildOrUpdateControllers: function buildOrUpdateControllers() { 3873 var me = this; 3874 var types = []; 3875 var newControllers = []; 3876 3877 helpers.each(me.data.datasets, function(dataset, datasetIndex) { 3878 var meta = me.getDatasetMeta(datasetIndex); 3879 if (!meta.type) { 3880 meta.type = dataset.type || me.config.type; 3881 } 3882 3883 types.push(meta.type); 3884 3885 if (meta.controller) { 3886 meta.controller.updateIndex(datasetIndex); 3887 } else { 3888 meta.controller = new Chart.controllers[meta.type](me, datasetIndex); 3889 newControllers.push(meta.controller); 3890 } 3891 }, me); 3892 3893 if (types.length > 1) { 3894 for (var i = 1; i < types.length; i++) { 3895 if (types[i] !== types[i - 1]) { 3896 me.isCombo = true; 3897 break; 3898 } 3899 } 3900 } 3901 3902 return newControllers; 3903 }, 3904 3905 resetElements: function resetElements() { 3906 var me = this; 3907 helpers.each(me.data.datasets, function(dataset, datasetIndex) { 3908 me.getDatasetMeta(datasetIndex).controller.reset(); 3909 }, me); 3910 }, 3911 3912 update: function update(animationDuration, lazy) { 3913 var me = this; 3914 Chart.plugins.notify('beforeUpdate', [me]); 3915 3916 // In case the entire data object changed 3917 me.tooltip._data = me.data; 3918 3919 // Make sure dataset controllers are updated and new controllers are reset 3920 var newControllers = me.buildOrUpdateControllers(); 3921 3922 // Make sure all dataset controllers have correct meta data counts 3923 helpers.each(me.data.datasets, function(dataset, datasetIndex) { 3924 me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); 3925 }, me); 3926 3927 Chart.layoutService.update(me, me.chart.width, me.chart.height); 3928 3929 // Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages 3930 Chart.plugins.notify('afterScaleUpdate', [me]); 3931 3932 // Can only reset the new controllers after the scales have been updated 3933 helpers.each(newControllers, function(controller) { 3934 controller.reset(); 3935 }); 3936 3937 me.updateDatasets(); 3938 3939 // Do this before render so that any plugins that need final scale updates can use it 3940 Chart.plugins.notify('afterUpdate', [me]); 3941 3942 me.render(animationDuration, lazy); 3943 }, 3944 3945 /** 3946 * @method beforeDatasetsUpdate 3947 * @description Called before all datasets are updated. If a plugin returns false, 3948 * the datasets update will be cancelled until another chart update is triggered. 3949 * @param {Object} instance the chart instance being updated. 3950 * @returns {Boolean} false to cancel the datasets update. 3951 * @memberof Chart.PluginBase 3952 * @since version 2.1.5 3953 * @instance 3954 */ 3955 3956 /** 3957 * @method afterDatasetsUpdate 3958 * @description Called after all datasets have been updated. Note that this 3959 * extension will not be called if the datasets update has been cancelled. 3960 * @param {Object} instance the chart instance being updated. 3961 * @memberof Chart.PluginBase 3962 * @since version 2.1.5 3963 * @instance 3964 */ 3965 3966 /** 3967 * Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate 3968 * extension, in which case no datasets will be updated and the afterDatasetsUpdate 3969 * notification will be skipped. 3970 * @protected 3971 * @instance 3972 */ 3973 updateDatasets: function() { 3974 var me = this; 3975 var i, ilen; 3976 3977 if (Chart.plugins.notify('beforeDatasetsUpdate', [ me ])) { 3978 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { 3979 me.getDatasetMeta(i).controller.update(); 3980 } 3981 3982 Chart.plugins.notify('afterDatasetsUpdate', [ me ]); 3983 } 3984 }, 3985 3986 render: function render(duration, lazy) { 3987 var me = this; 3988 Chart.plugins.notify('beforeRender', [me]); 3989 3990 var animationOptions = me.options.animation; 3991 if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) { 3992 var animation = new Chart.Animation(); 3993 animation.numSteps = (duration || animationOptions.duration) / 16.66; //60 fps 3994 animation.easing = animationOptions.easing; 3995 3996 // render function 3997 animation.render = function(chartInstance, animationObject) { 3998 var easingFunction = helpers.easingEffects[animationObject.easing]; 3999 var stepDecimal = animationObject.currentStep / animationObject.numSteps; 4000 var easeDecimal = easingFunction(stepDecimal); 4001 4002 chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); 4003 }; 4004 4005 // user events 4006 animation.onAnimationProgress = animationOptions.onProgress; 4007 animation.onAnimationComplete = animationOptions.onComplete; 4008 4009 Chart.animationService.addAnimation(me, animation, duration, lazy); 4010 } else { 4011 me.draw(); 4012 if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) { 4013 animationOptions.onComplete.call(me); 4014 } 4015 } 4016 return me; 4017 }, 4018 4019 draw: function(ease) { 4020 var me = this; 4021 var easingDecimal = ease || 1; 4022 me.clear(); 4023 4024 Chart.plugins.notify('beforeDraw', [me, easingDecimal]); 4025 4026 // Draw all the scales 4027 helpers.each(me.boxes, function(box) { 4028 box.draw(me.chartArea); 4029 }, me); 4030 if (me.scale) { 4031 me.scale.draw(); 4032 } 4033 4034 Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]); 4035 4036 // Draw each dataset via its respective controller (reversed to support proper line stacking) 4037 helpers.each(me.data.datasets, function(dataset, datasetIndex) { 4038 if (me.isDatasetVisible(datasetIndex)) { 4039 me.getDatasetMeta(datasetIndex).controller.draw(ease); 4040 } 4041 }, me, true); 4042 4043 Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]); 4044 4045 // Finally draw the tooltip 4046 me.tooltip.transition(easingDecimal).draw(); 4047 4048 Chart.plugins.notify('afterDraw', [me, easingDecimal]); 4049 }, 4050 4051 // Get the single element that was clicked on 4052 // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw 4053 getElementAtEvent: function(e) { 4054 var me = this; 4055 var eventPosition = helpers.getRelativePosition(e, me.chart); 4056 var elementsArray = []; 4057 4058 helpers.each(me.data.datasets, function(dataset, datasetIndex) { 4059 if (me.isDatasetVisible(datasetIndex)) { 4060 var meta = me.getDatasetMeta(datasetIndex); 4061 helpers.each(meta.data, function(element, index) { 4062 if (element.inRange(eventPosition.x, eventPosition.y)) { 4063 elementsArray.push(element); 4064 return elementsArray; 4065 } 4066 }); 4067 } 4068 }); 4069 4070 return elementsArray; 4071 }, 4072 4073 getElementsAtEvent: function(e) { 4074 var me = this; 4075 var eventPosition = helpers.getRelativePosition(e, me.chart); 4076 var elementsArray = []; 4077 4078 var found = (function() { 4079 if (me.data.datasets) { 4080 for (var i = 0; i < me.data.datasets.length; i++) { 4081 var meta = me.getDatasetMeta(i); 4082 if (me.isDatasetVisible(i)) { 4083 for (var j = 0; j < meta.data.length; j++) { 4084 if (meta.data[j].inRange(eventPosition.x, eventPosition.y)) { 4085 return meta.data[j]; 4086 } 4087 } 4088 } 4089 } 4090 } 4091 }).call(me); 4092 4093 if (!found) { 4094 return elementsArray; 4095 } 4096 4097 helpers.each(me.data.datasets, function(dataset, datasetIndex) { 4098 if (me.isDatasetVisible(datasetIndex)) { 4099 var meta = me.getDatasetMeta(datasetIndex); 4100 elementsArray.push(meta.data[found._index]); 4101 } 4102 }, me); 4103 4104 return elementsArray; 4105 }, 4106 4107 getElementsAtEventForMode: function(e, mode) { 4108 var me = this; 4109 switch (mode) { 4110 case 'single': 4111 return me.getElementAtEvent(e); 4112 case 'label': 4113 return me.getElementsAtEvent(e); 4114 case 'dataset': 4115 return me.getDatasetAtEvent(e); 4116 default: 4117 return e; 4118 } 4119 }, 4120 4121 getDatasetAtEvent: function(e) { 4122 var elementsArray = this.getElementAtEvent(e); 4123 4124 if (elementsArray.length > 0) { 4125 elementsArray = this.getDatasetMeta(elementsArray[0]._datasetIndex).data; 4126 } 4127 4128 return elementsArray; 4129 }, 4130 4131 getDatasetMeta: function(datasetIndex) { 4132 var me = this; 4133 var dataset = me.data.datasets[datasetIndex]; 4134 if (!dataset._meta) { 4135 dataset._meta = {}; 4136 } 4137 4138 var meta = dataset._meta[me.id]; 4139 if (!meta) { 4140 meta = dataset._meta[me.id] = { 4141 type: null, 4142 data: [], 4143 dataset: null, 4144 controller: null, 4145 hidden: null, // See isDatasetVisible() comment 4146 xAxisID: null, 4147 yAxisID: null 4148 }; 4149 } 4150 4151 return meta; 4152 }, 4153 4154 getVisibleDatasetCount: function() { 4155 var count = 0; 4156 for (var i = 0, ilen = this.data.datasets.length; i<ilen; ++i) { 4157 if (this.isDatasetVisible(i)) { 4158 count++; 4159 } 4160 } 4161 return count; 4162 }, 4163 4164 isDatasetVisible: function(datasetIndex) { 4165 var meta = this.getDatasetMeta(datasetIndex); 4166 4167 // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, 4168 // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. 4169 return typeof meta.hidden === 'boolean'? !meta.hidden : !this.data.datasets[datasetIndex].hidden; 4170 }, 4171 4172 generateLegend: function generateLegend() { 4173 return this.options.legendCallback(this); 4174 }, 4175 4176 destroy: function destroy() { 4177 var me = this; 4178 me.stop(); 4179 me.clear(); 4180 helpers.unbindEvents(me, me.events); 4181 helpers.removeResizeListener(me.chart.canvas.parentNode); 4182 4183 // Reset canvas height/width attributes 4184 var canvas = me.chart.canvas; 4185 canvas.width = me.chart.width; 4186 canvas.height = me.chart.height; 4187 4188 // if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here 4189 if (me.chart.originalDevicePixelRatio !== undefined) { 4190 me.chart.ctx.scale(1 / me.chart.originalDevicePixelRatio, 1 / me.chart.originalDevicePixelRatio); 4191 } 4192 4193 // Reset to the old style since it may have been changed by the device pixel ratio changes 4194 canvas.style.width = me.chart.originalCanvasStyleWidth; 4195 canvas.style.height = me.chart.originalCanvasStyleHeight; 4196 4197 Chart.plugins.notify('destroy', [me]); 4198 4199 delete Chart.instances[me.id]; 4200 }, 4201 4202 toBase64Image: function toBase64Image() { 4203 return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); 4204 }, 4205 4206 initToolTip: function initToolTip() { 4207 var me = this; 4208 me.tooltip = new Chart.Tooltip({ 4209 _chart: me.chart, 4210 _chartInstance: me, 4211 _data: me.data, 4212 _options: me.options.tooltips 4213 }, me); 4214 }, 4215 4216 bindEvents: function bindEvents() { 4217 var me = this; 4218 helpers.bindEvents(me, me.options.events, function(evt) { 4219 me.eventHandler(evt); 4220 }); 4221 }, 4222 4223 updateHoverStyle: function(elements, mode, enabled) { 4224 var method = enabled? 'setHoverStyle' : 'removeHoverStyle'; 4225 var element, i, ilen; 4226 4227 switch (mode) { 4228 case 'single': 4229 elements = [ elements[0] ]; 4230 break; 4231 case 'label': 4232 case 'dataset': 4233 // elements = elements; 4234 break; 4235 default: 4236 // unsupported mode 4237 return; 4238 } 4239 4240 for (i=0, ilen=elements.length; i<ilen; ++i) { 4241 element = elements[i]; 4242 if (element) { 4243 this.getDatasetMeta(element._datasetIndex).controller[method](element); 4244 } 4245 } 4246 }, 4247 4248 eventHandler: function eventHandler(e) { 4249 var me = this; 4250 var tooltip = me.tooltip; 4251 var options = me.options || {}; 4252 var hoverOptions = options.hover; 4253 var tooltipsOptions = options.tooltips; 4254 4255 me.lastActive = me.lastActive || []; 4256 me.lastTooltipActive = me.lastTooltipActive || []; 4257 4258 // Find Active Elements for hover and tooltips 4259 if (e.type === 'mouseout') { 4260 me.active = []; 4261 me.tooltipActive = []; 4262 } else { 4263 me.active = me.getElementsAtEventForMode(e, hoverOptions.mode); 4264 me.tooltipActive = me.getElementsAtEventForMode(e, tooltipsOptions.mode); 4265 } 4266 4267 // On Hover hook 4268 if (hoverOptions.onHover) { 4269 hoverOptions.onHover.call(me, me.active); 4270 } 4271 4272 if (e.type === 'mouseup' || e.type === 'click') { 4273 if (options.onClick) { 4274 options.onClick.call(me, e, me.active); 4275 } 4276 if (me.legend && me.legend.handleEvent) { 4277 me.legend.handleEvent(e); 4278 } 4279 } 4280 4281 // Remove styling for last active (even if it may still be active) 4282 if (me.lastActive.length) { 4283 me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); 4284 } 4285 4286 // Built in hover styling 4287 if (me.active.length && hoverOptions.mode) { 4288 me.updateHoverStyle(me.active, hoverOptions.mode, true); 4289 } 4290 4291 // Built in Tooltips 4292 if (tooltipsOptions.enabled || tooltipsOptions.custom) { 4293 tooltip.initialize(); 4294 tooltip._active = me.tooltipActive; 4295 tooltip.update(true); 4296 } 4297 4298 // Hover animations 4299 tooltip.pivot(); 4300 4301 if (!me.animating) { 4302 // If entering, leaving, or changing elements, animate the change via pivot 4303 if (!helpers.arrayEquals(me.active, me.lastActive) || 4304 !helpers.arrayEquals(me.tooltipActive, me.lastTooltipActive)) { 4305 4306 me.stop(); 4307 4308 if (tooltipsOptions.enabled || tooltipsOptions.custom) { 4309 tooltip.update(true); 4310 } 4311 4312 // We only need to render at this point. Updating will cause scales to be 4313 // recomputed generating flicker & using more memory than necessary. 4314 me.render(hoverOptions.animationDuration, true); 4315 } 4316 } 4317 4318 // Remember Last Actives 4319 me.lastActive = me.active; 4320 me.lastTooltipActive = me.tooltipActive; 4321 return me; 4322 } 4323 }); 4324 }; 4325 4326 },{}],23:[function(require,module,exports){ 4327 "use strict"; 4328 4329 module.exports = function(Chart) { 4330 4331 var helpers = Chart.helpers; 4332 var noop = helpers.noop; 4333 4334 // Base class for all dataset controllers (line, bar, etc) 4335 Chart.DatasetController = function(chart, datasetIndex) { 4336 this.initialize.call(this, chart, datasetIndex); 4337 }; 4338 4339 helpers.extend(Chart.DatasetController.prototype, { 4340 4341 /** 4342 * Element type used to generate a meta dataset (e.g. Chart.element.Line). 4343 * @type {Chart.core.element} 4344 */ 4345 datasetElementType: null, 4346 4347 /** 4348 * Element type used to generate a meta data (e.g. Chart.element.Point). 4349 * @type {Chart.core.element} 4350 */ 4351 dataElementType: null, 4352 4353 initialize: function(chart, datasetIndex) { 4354 var me = this; 4355 me.chart = chart; 4356 me.index = datasetIndex; 4357 me.linkScales(); 4358 me.addElements(); 4359 }, 4360 4361 updateIndex: function(datasetIndex) { 4362 this.index = datasetIndex; 4363 }, 4364 4365 linkScales: function() { 4366 var me = this; 4367 var meta = me.getMeta(); 4368 var dataset = me.getDataset(); 4369 4370 if (meta.xAxisID === null) { 4371 meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; 4372 } 4373 if (meta.yAxisID === null) { 4374 meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; 4375 } 4376 }, 4377 4378 getDataset: function() { 4379 return this.chart.data.datasets[this.index]; 4380 }, 4381 4382 getMeta: function() { 4383 return this.chart.getDatasetMeta(this.index); 4384 }, 4385 4386 getScaleForId: function(scaleID) { 4387 return this.chart.scales[scaleID]; 4388 }, 4389 4390 reset: function() { 4391 this.update(true); 4392 }, 4393 4394 createMetaDataset: function() { 4395 var me = this; 4396 var type = me.datasetElementType; 4397 return type && new type({ 4398 _chart: me.chart.chart, 4399 _datasetIndex: me.index 4400 }); 4401 }, 4402 4403 createMetaData: function(index) { 4404 var me = this; 4405 var type = me.dataElementType; 4406 return type && new type({ 4407 _chart: me.chart.chart, 4408 _datasetIndex: me.index, 4409 _index: index 4410 }); 4411 }, 4412 4413 addElements: function() { 4414 var me = this; 4415 var meta = me.getMeta(); 4416 var data = me.getDataset().data || []; 4417 var metaData = meta.data; 4418 var i, ilen; 4419 4420 for (i=0, ilen=data.length; i<ilen; ++i) { 4421 metaData[i] = metaData[i] || me.createMetaData(meta, i); 4422 } 4423 4424 meta.dataset = meta.dataset || me.createMetaDataset(); 4425 }, 4426 4427 addElementAndReset: function(index) { 4428 var me = this; 4429 var element = me.createMetaData(index); 4430 me.getMeta().data.splice(index, 0, element); 4431 me.updateElement(element, index, true); 4432 }, 4433 4434 buildOrUpdateElements: function buildOrUpdateElements() { 4435 // Handle the number of data points changing 4436 var meta = this.getMeta(), 4437 md = meta.data, 4438 numData = this.getDataset().data.length, 4439 numMetaData = md.length; 4440 4441 // Make sure that we handle number of datapoints changing 4442 if (numData < numMetaData) { 4443 // Remove excess bars for data points that have been removed 4444 md.splice(numData, numMetaData - numData); 4445 } else if (numData > numMetaData) { 4446 // Add new elements 4447 for (var index = numMetaData; index < numData; ++index) { 4448 this.addElementAndReset(index); 4449 } 4450 } 4451 }, 4452 4453 update: noop, 4454 4455 draw: function(ease) { 4456 var easingDecimal = ease || 1; 4457 helpers.each(this.getMeta().data, function(element, index) { 4458 element.transition(easingDecimal).draw(); 4459 }); 4460 }, 4461 4462 removeHoverStyle: function(element, elementOpts) { 4463 var dataset = this.chart.data.datasets[element._datasetIndex], 4464 index = element._index, 4465 custom = element.custom || {}, 4466 valueOrDefault = helpers.getValueAtIndexOrDefault, 4467 color = helpers.color, 4468 model = element._model; 4469 4470 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); 4471 model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); 4472 model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); 4473 }, 4474 4475 setHoverStyle: function(element) { 4476 var dataset = this.chart.data.datasets[element._datasetIndex], 4477 index = element._index, 4478 custom = element.custom || {}, 4479 valueOrDefault = helpers.getValueAtIndexOrDefault, 4480 color = helpers.color, 4481 getHoverColor = helpers.getHoverColor, 4482 model = element._model; 4483 4484 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); 4485 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); 4486 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); 4487 } 4488 }); 4489 4490 Chart.DatasetController.extend = helpers.inherits; 4491 }; 4492 },{}],24:[function(require,module,exports){ 4493 "use strict"; 4494 4495 module.exports = function(Chart) { 4496 4497 var helpers = Chart.helpers; 4498 4499 Chart.elements = {}; 4500 4501 Chart.Element = function(configuration) { 4502 helpers.extend(this, configuration); 4503 this.initialize.apply(this, arguments); 4504 }; 4505 4506 helpers.extend(Chart.Element.prototype, { 4507 4508 initialize: function() { 4509 this.hidden = false; 4510 }, 4511 4512 pivot: function() { 4513 var me = this; 4514 if (!me._view) { 4515 me._view = helpers.clone(me._model); 4516 } 4517 me._start = helpers.clone(me._view); 4518 return me; 4519 }, 4520 4521 transition: function(ease) { 4522 var me = this; 4523 4524 if (!me._view) { 4525 me._view = helpers.clone(me._model); 4526 } 4527 4528 // No animation -> No Transition 4529 if (ease === 1) { 4530 me._view = me._model; 4531 me._start = null; 4532 return me; 4533 } 4534 4535 if (!me._start) { 4536 me.pivot(); 4537 } 4538 4539 helpers.each(me._model, function(value, key) { 4540 4541 if (key[0] === '_') { 4542 // Only non-underscored properties 4543 } 4544 4545 // Init if doesn't exist 4546 else if (!me._view.hasOwnProperty(key)) { 4547 if (typeof value === 'number' && !isNaN(me._view[key])) { 4548 me._view[key] = value * ease; 4549 } else { 4550 me._view[key] = value; 4551 } 4552 } 4553 4554 // No unnecessary computations 4555 else if (value === me._view[key]) { 4556 // It's the same! Woohoo! 4557 } 4558 4559 // Color transitions if possible 4560 else if (typeof value === 'string') { 4561 try { 4562 var color = helpers.color(me._model[key]).mix(helpers.color(me._start[key]), ease); 4563 me._view[key] = color.rgbString(); 4564 } catch (err) { 4565 me._view[key] = value; 4566 } 4567 } 4568 // Number transitions 4569 else if (typeof value === 'number') { 4570 var startVal = me._start[key] !== undefined && isNaN(me._start[key]) === false ? me._start[key] : 0; 4571 me._view[key] = ((me._model[key] - startVal) * ease) + startVal; 4572 } 4573 // Everything else 4574 else { 4575 me._view[key] = value; 4576 } 4577 }, me); 4578 4579 return me; 4580 }, 4581 4582 tooltipPosition: function() { 4583 return { 4584 x: this._model.x, 4585 y: this._model.y 4586 }; 4587 }, 4588 4589 hasValue: function() { 4590 return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); 4591 } 4592 }); 4593 4594 Chart.Element.extend = helpers.inherits; 4595 4596 }; 4597 4598 },{}],25:[function(require,module,exports){ 4599 /*global window: false */ 4600 /*global document: false */ 4601 "use strict"; 4602 4603 var color = require(3); 4604 4605 module.exports = function(Chart) { 4606 //Global Chart helpers object for utility methods and classes 4607 var helpers = Chart.helpers = {}; 4608 4609 //-- Basic js utility methods 4610 helpers.each = function(loopable, callback, self, reverse) { 4611 // Check to see if null or undefined firstly. 4612 var i, len; 4613 if (helpers.isArray(loopable)) { 4614 len = loopable.length; 4615 if (reverse) { 4616 for (i = len - 1; i >= 0; i--) { 4617 callback.call(self, loopable[i], i); 4618 } 4619 } else { 4620 for (i = 0; i < len; i++) { 4621 callback.call(self, loopable[i], i); 4622 } 4623 } 4624 } else if (typeof loopable === 'object') { 4625 var keys = Object.keys(loopable); 4626 len = keys.length; 4627 for (i = 0; i < len; i++) { 4628 callback.call(self, loopable[keys[i]], keys[i]); 4629 } 4630 } 4631 }; 4632 helpers.clone = function(obj) { 4633 var objClone = {}; 4634 helpers.each(obj, function(value, key) { 4635 if (helpers.isArray(value)) { 4636 objClone[key] = value.slice(0); 4637 } else if (typeof value === 'object' && value !== null) { 4638 objClone[key] = helpers.clone(value); 4639 } else { 4640 objClone[key] = value; 4641 } 4642 }); 4643 return objClone; 4644 }; 4645 helpers.extend = function(base) { 4646 var setFn = function(value, key) { base[key] = value; }; 4647 for (var i = 1, ilen = arguments.length; i < ilen; i++) { 4648 helpers.each(arguments[i], setFn); 4649 } 4650 return base; 4651 }; 4652 // Need a special merge function to chart configs since they are now grouped 4653 helpers.configMerge = function(_base) { 4654 var base = helpers.clone(_base); 4655 helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) { 4656 helpers.each(extension, function(value, key) { 4657 if (key === 'scales') { 4658 // Scale config merging is complex. Add out own function here for that 4659 base[key] = helpers.scaleMerge(base.hasOwnProperty(key) ? base[key] : {}, value); 4660 4661 } else if (key === 'scale') { 4662 // Used in polar area & radar charts since there is only one scale 4663 base[key] = helpers.configMerge(base.hasOwnProperty(key) ? base[key] : {}, Chart.scaleService.getScaleDefaults(value.type), value); 4664 } else if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) { 4665 // In this case we have an array of objects replacing another array. Rather than doing a strict replace, 4666 // merge. This allows easy scale option merging 4667 var baseArray = base[key]; 4668 4669 helpers.each(value, function(valueObj, index) { 4670 4671 if (index < baseArray.length) { 4672 if (typeof baseArray[index] === 'object' && baseArray[index] !== null && typeof valueObj === 'object' && valueObj !== null) { 4673 // Two objects are coming together. Do a merge of them. 4674 baseArray[index] = helpers.configMerge(baseArray[index], valueObj); 4675 } else { 4676 // Just overwrite in this case since there is nothing to merge 4677 baseArray[index] = valueObj; 4678 } 4679 } else { 4680 baseArray.push(valueObj); // nothing to merge 4681 } 4682 }); 4683 4684 } else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") { 4685 // If we are overwriting an object with an object, do a merge of the properties. 4686 base[key] = helpers.configMerge(base[key], value); 4687 4688 } else { 4689 // can just overwrite the value in this case 4690 base[key] = value; 4691 } 4692 }); 4693 }); 4694 4695 return base; 4696 }; 4697 helpers.scaleMerge = function(_base, extension) { 4698 var base = helpers.clone(_base); 4699 4700 helpers.each(extension, function(value, key) { 4701 if (key === 'xAxes' || key === 'yAxes') { 4702 // These properties are arrays of items 4703 if (base.hasOwnProperty(key)) { 4704 helpers.each(value, function(valueObj, index) { 4705 var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); 4706 var axisDefaults = Chart.scaleService.getScaleDefaults(axisType); 4707 if (index >= base[key].length || !base[key][index].type) { 4708 base[key].push(helpers.configMerge(axisDefaults, valueObj)); 4709 } else if (valueObj.type && valueObj.type !== base[key][index].type) { 4710 // Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults 4711 base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj); 4712 } else { 4713 // Type is the same 4714 base[key][index] = helpers.configMerge(base[key][index], valueObj); 4715 } 4716 }); 4717 } else { 4718 base[key] = []; 4719 helpers.each(value, function(valueObj) { 4720 var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); 4721 base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj)); 4722 }); 4723 } 4724 } else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") { 4725 // If we are overwriting an object with an object, do a merge of the properties. 4726 base[key] = helpers.configMerge(base[key], value); 4727 4728 } else { 4729 // can just overwrite the value in this case 4730 base[key] = value; 4731 } 4732 }); 4733 4734 return base; 4735 }; 4736 helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) { 4737 if (value === undefined || value === null) { 4738 return defaultValue; 4739 } 4740 4741 if (helpers.isArray(value)) { 4742 return index < value.length ? value[index] : defaultValue; 4743 } 4744 4745 return value; 4746 }; 4747 helpers.getValueOrDefault = function(value, defaultValue) { 4748 return value === undefined ? defaultValue : value; 4749 }; 4750 helpers.indexOf = Array.prototype.indexOf? 4751 function(array, item) { return array.indexOf(item); } : 4752 function(array, item) { 4753 for (var i = 0, ilen = array.length; i < ilen; ++i) { 4754 if (array[i] === item) { 4755 return i; 4756 } 4757 } 4758 return -1; 4759 }; 4760 helpers.where = function(collection, filterCallback) { 4761 if (helpers.isArray(collection) && Array.prototype.filter) { 4762 return collection.filter(filterCallback); 4763 } else { 4764 var filtered = []; 4765 4766 helpers.each(collection, function(item) { 4767 if (filterCallback(item)) { 4768 filtered.push(item); 4769 } 4770 }); 4771 4772 return filtered; 4773 } 4774 }; 4775 helpers.findIndex = Array.prototype.findIndex? 4776 function(array, callback, scope) { return array.findIndex(callback, scope); } : 4777 function(array, callback, scope) { 4778 scope = scope === undefined? array : scope; 4779 for (var i = 0, ilen = array.length; i < ilen; ++i) { 4780 if (callback.call(scope, array[i], i, array)) { 4781 return i; 4782 } 4783 } 4784 return -1; 4785 }; 4786 helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { 4787 // Default to start of the array 4788 if (startIndex === undefined || startIndex === null) { 4789 startIndex = -1; 4790 } 4791 for (var i = startIndex + 1; i < arrayToSearch.length; i++) { 4792 var currentItem = arrayToSearch[i]; 4793 if (filterCallback(currentItem)) { 4794 return currentItem; 4795 } 4796 } 4797 }; 4798 helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { 4799 // Default to end of the array 4800 if (startIndex === undefined || startIndex === null) { 4801 startIndex = arrayToSearch.length; 4802 } 4803 for (var i = startIndex - 1; i >= 0; i--) { 4804 var currentItem = arrayToSearch[i]; 4805 if (filterCallback(currentItem)) { 4806 return currentItem; 4807 } 4808 } 4809 }; 4810 helpers.inherits = function(extensions) { 4811 //Basic javascript inheritance based on the model created in Backbone.js 4812 var parent = this; 4813 var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() { 4814 return parent.apply(this, arguments); 4815 }; 4816 4817 var Surrogate = function() { 4818 this.constructor = ChartElement; 4819 }; 4820 Surrogate.prototype = parent.prototype; 4821 ChartElement.prototype = new Surrogate(); 4822 4823 ChartElement.extend = helpers.inherits; 4824 4825 if (extensions) { 4826 helpers.extend(ChartElement.prototype, extensions); 4827 } 4828 4829 ChartElement.__super__ = parent.prototype; 4830 4831 return ChartElement; 4832 }; 4833 helpers.noop = function() {}; 4834 helpers.uid = (function() { 4835 var id = 0; 4836 return function() { 4837 return id++; 4838 }; 4839 })(); 4840 //-- Math methods 4841 helpers.isNumber = function(n) { 4842 return !isNaN(parseFloat(n)) && isFinite(n); 4843 }; 4844 helpers.almostEquals = function(x, y, epsilon) { 4845 return Math.abs(x - y) < epsilon; 4846 }; 4847 helpers.max = function(array) { 4848 return array.reduce(function(max, value) { 4849 if (!isNaN(value)) { 4850 return Math.max(max, value); 4851 } else { 4852 return max; 4853 } 4854 }, Number.NEGATIVE_INFINITY); 4855 }; 4856 helpers.min = function(array) { 4857 return array.reduce(function(min, value) { 4858 if (!isNaN(value)) { 4859 return Math.min(min, value); 4860 } else { 4861 return min; 4862 } 4863 }, Number.POSITIVE_INFINITY); 4864 }; 4865 helpers.sign = Math.sign? 4866 function(x) { return Math.sign(x); } : 4867 function(x) { 4868 x = +x; // convert to a number 4869 if (x === 0 || isNaN(x)) { 4870 return x; 4871 } 4872 return x > 0 ? 1 : -1; 4873 }; 4874 helpers.log10 = Math.log10? 4875 function(x) { return Math.log10(x); } : 4876 function(x) { 4877 return Math.log(x) / Math.LN10; 4878 }; 4879 helpers.toRadians = function(degrees) { 4880 return degrees * (Math.PI / 180); 4881 }; 4882 helpers.toDegrees = function(radians) { 4883 return radians * (180 / Math.PI); 4884 }; 4885 // Gets the angle from vertical upright to the point about a centre. 4886 helpers.getAngleFromPoint = function(centrePoint, anglePoint) { 4887 var distanceFromXCenter = anglePoint.x - centrePoint.x, 4888 distanceFromYCenter = anglePoint.y - centrePoint.y, 4889 radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); 4890 4891 var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); 4892 4893 if (angle < (-0.5 * Math.PI)) { 4894 angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] 4895 } 4896 4897 return { 4898 angle: angle, 4899 distance: radialDistanceFromCenter 4900 }; 4901 }; 4902 helpers.aliasPixel = function(pixelWidth) { 4903 return (pixelWidth % 2 === 0) ? 0 : 0.5; 4904 }; 4905 helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { 4906 //Props to Rob Spencer at scaled innovation for his post on splining between points 4907 //http://scaledinnovation.com/analytics/splines/aboutSplines.html 4908 4909 // This function must also respect "skipped" points 4910 4911 var previous = firstPoint.skip ? middlePoint : firstPoint, 4912 current = middlePoint, 4913 next = afterPoint.skip ? middlePoint : afterPoint; 4914 4915 var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); 4916 var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); 4917 4918 var s01 = d01 / (d01 + d12); 4919 var s12 = d12 / (d01 + d12); 4920 4921 // If all points are the same, s01 & s02 will be inf 4922 s01 = isNaN(s01) ? 0 : s01; 4923 s12 = isNaN(s12) ? 0 : s12; 4924 4925 var fa = t * s01; // scaling factor for triangle Ta 4926 var fb = t * s12; 4927 4928 return { 4929 previous: { 4930 x: current.x - fa * (next.x - previous.x), 4931 y: current.y - fa * (next.y - previous.y) 4932 }, 4933 next: { 4934 x: current.x + fb * (next.x - previous.x), 4935 y: current.y + fb * (next.y - previous.y) 4936 } 4937 }; 4938 }; 4939 helpers.nextItem = function(collection, index, loop) { 4940 if (loop) { 4941 return index >= collection.length - 1 ? collection[0] : collection[index + 1]; 4942 } 4943 4944 return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; 4945 }; 4946 helpers.previousItem = function(collection, index, loop) { 4947 if (loop) { 4948 return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; 4949 } 4950 return index <= 0 ? collection[0] : collection[index - 1]; 4951 }; 4952 // Implementation of the nice number algorithm used in determining where axis labels will go 4953 helpers.niceNum = function(range, round) { 4954 var exponent = Math.floor(helpers.log10(range)); 4955 var fraction = range / Math.pow(10, exponent); 4956 var niceFraction; 4957 4958 if (round) { 4959 if (fraction < 1.5) { 4960 niceFraction = 1; 4961 } else if (fraction < 3) { 4962 niceFraction = 2; 4963 } else if (fraction < 7) { 4964 niceFraction = 5; 4965 } else { 4966 niceFraction = 10; 4967 } 4968 } else { 4969 if (fraction <= 1.0) { 4970 niceFraction = 1; 4971 } else if (fraction <= 2) { 4972 niceFraction = 2; 4973 } else if (fraction <= 5) { 4974 niceFraction = 5; 4975 } else { 4976 niceFraction = 10; 4977 } 4978 } 4979 4980 return niceFraction * Math.pow(10, exponent); 4981 }; 4982 //Easing functions adapted from Robert Penner's easing equations 4983 //http://www.robertpenner.com/easing/ 4984 var easingEffects = helpers.easingEffects = { 4985 linear: function(t) { 4986 return t; 4987 }, 4988 easeInQuad: function(t) { 4989 return t * t; 4990 }, 4991 easeOutQuad: function(t) { 4992 return -1 * t * (t - 2); 4993 }, 4994 easeInOutQuad: function(t) { 4995 if ((t /= 1 / 2) < 1) { 4996 return 1 / 2 * t * t; 4997 } 4998 return -1 / 2 * ((--t) * (t - 2) - 1); 4999 }, 5000 easeInCubic: function(t) { 5001 return t * t * t; 5002 }, 5003 easeOutCubic: function(t) { 5004 return 1 * ((t = t / 1 - 1) * t * t + 1); 5005 }, 5006 easeInOutCubic: function(t) { 5007 if ((t /= 1 / 2) < 1) { 5008 return 1 / 2 * t * t * t; 5009 } 5010 return 1 / 2 * ((t -= 2) * t * t + 2); 5011 }, 5012 easeInQuart: function(t) { 5013 return t * t * t * t; 5014 }, 5015 easeOutQuart: function(t) { 5016 return -1 * ((t = t / 1 - 1) * t * t * t - 1); 5017 }, 5018 easeInOutQuart: function(t) { 5019 if ((t /= 1 / 2) < 1) { 5020 return 1 / 2 * t * t * t * t; 5021 } 5022 return -1 / 2 * ((t -= 2) * t * t * t - 2); 5023 }, 5024 easeInQuint: function(t) { 5025 return 1 * (t /= 1) * t * t * t * t; 5026 }, 5027 easeOutQuint: function(t) { 5028 return 1 * ((t = t / 1 - 1) * t * t * t * t + 1); 5029 }, 5030 easeInOutQuint: function(t) { 5031 if ((t /= 1 / 2) < 1) { 5032 return 1 / 2 * t * t * t * t * t; 5033 } 5034 return 1 / 2 * ((t -= 2) * t * t * t * t + 2); 5035 }, 5036 easeInSine: function(t) { 5037 return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1; 5038 }, 5039 easeOutSine: function(t) { 5040 return 1 * Math.sin(t / 1 * (Math.PI / 2)); 5041 }, 5042 easeInOutSine: function(t) { 5043 return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1); 5044 }, 5045 easeInExpo: function(t) { 5046 return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1)); 5047 }, 5048 easeOutExpo: function(t) { 5049 return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1); 5050 }, 5051 easeInOutExpo: function(t) { 5052 if (t === 0) { 5053 return 0; 5054 } 5055 if (t === 1) { 5056 return 1; 5057 } 5058 if ((t /= 1 / 2) < 1) { 5059 return 1 / 2 * Math.pow(2, 10 * (t - 1)); 5060 } 5061 return 1 / 2 * (-Math.pow(2, -10 * --t) + 2); 5062 }, 5063 easeInCirc: function(t) { 5064 if (t >= 1) { 5065 return t; 5066 } 5067 return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1); 5068 }, 5069 easeOutCirc: function(t) { 5070 return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t); 5071 }, 5072 easeInOutCirc: function(t) { 5073 if ((t /= 1 / 2) < 1) { 5074 return -1 / 2 * (Math.sqrt(1 - t * t) - 1); 5075 } 5076 return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); 5077 }, 5078 easeInElastic: function(t) { 5079 var s = 1.70158; 5080 var p = 0; 5081 var a = 1; 5082 if (t === 0) { 5083 return 0; 5084 } 5085 if ((t /= 1) === 1) { 5086 return 1; 5087 } 5088 if (!p) { 5089 p = 1 * 0.3; 5090 } 5091 if (a < Math.abs(1)) { 5092 a = 1; 5093 s = p / 4; 5094 } else { 5095 s = p / (2 * Math.PI) * Math.asin(1 / a); 5096 } 5097 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); 5098 }, 5099 easeOutElastic: function(t) { 5100 var s = 1.70158; 5101 var p = 0; 5102 var a = 1; 5103 if (t === 0) { 5104 return 0; 5105 } 5106 if ((t /= 1) === 1) { 5107 return 1; 5108 } 5109 if (!p) { 5110 p = 1 * 0.3; 5111 } 5112 if (a < Math.abs(1)) { 5113 a = 1; 5114 s = p / 4; 5115 } else { 5116 s = p / (2 * Math.PI) * Math.asin(1 / a); 5117 } 5118 return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; 5119 }, 5120 easeInOutElastic: function(t) { 5121 var s = 1.70158; 5122 var p = 0; 5123 var a = 1; 5124 if (t === 0) { 5125 return 0; 5126 } 5127 if ((t /= 1 / 2) === 2) { 5128 return 1; 5129 } 5130 if (!p) { 5131 p = 1 * (0.3 * 1.5); 5132 } 5133 if (a < Math.abs(1)) { 5134 a = 1; 5135 s = p / 4; 5136 } else { 5137 s = p / (2 * Math.PI) * Math.asin(1 / a); 5138 } 5139 if (t < 1) { 5140 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); 5141 } 5142 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1; 5143 }, 5144 easeInBack: function(t) { 5145 var s = 1.70158; 5146 return 1 * (t /= 1) * t * ((s + 1) * t - s); 5147 }, 5148 easeOutBack: function(t) { 5149 var s = 1.70158; 5150 return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1); 5151 }, 5152 easeInOutBack: function(t) { 5153 var s = 1.70158; 5154 if ((t /= 1 / 2) < 1) { 5155 return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)); 5156 } 5157 return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); 5158 }, 5159 easeInBounce: function(t) { 5160 return 1 - easingEffects.easeOutBounce(1 - t); 5161 }, 5162 easeOutBounce: function(t) { 5163 if ((t /= 1) < (1 / 2.75)) { 5164 return 1 * (7.5625 * t * t); 5165 } else if (t < (2 / 2.75)) { 5166 return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75); 5167 } else if (t < (2.5 / 2.75)) { 5168 return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375); 5169 } else { 5170 return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375); 5171 } 5172 }, 5173 easeInOutBounce: function(t) { 5174 if (t < 1 / 2) { 5175 return easingEffects.easeInBounce(t * 2) * 0.5; 5176 } 5177 return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5; 5178 } 5179 }; 5180 //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ 5181 helpers.requestAnimFrame = (function() { 5182 return window.requestAnimationFrame || 5183 window.webkitRequestAnimationFrame || 5184 window.mozRequestAnimationFrame || 5185 window.oRequestAnimationFrame || 5186 window.msRequestAnimationFrame || 5187 function(callback) { 5188 return window.setTimeout(callback, 1000 / 60); 5189 }; 5190 })(); 5191 helpers.cancelAnimFrame = (function() { 5192 return window.cancelAnimationFrame || 5193 window.webkitCancelAnimationFrame || 5194 window.mozCancelAnimationFrame || 5195 window.oCancelAnimationFrame || 5196 window.msCancelAnimationFrame || 5197 function(callback) { 5198 return window.clearTimeout(callback, 1000 / 60); 5199 }; 5200 })(); 5201 //-- DOM methods 5202 helpers.getRelativePosition = function(evt, chart) { 5203 var mouseX, mouseY; 5204 var e = evt.originalEvent || evt, 5205 canvas = evt.currentTarget || evt.srcElement, 5206 boundingRect = canvas.getBoundingClientRect(); 5207 5208 var touches = e.touches; 5209 if (touches && touches.length > 0) { 5210 mouseX = touches[0].clientX; 5211 mouseY = touches[0].clientY; 5212 5213 } else { 5214 mouseX = e.clientX; 5215 mouseY = e.clientY; 5216 } 5217 5218 // Scale mouse coordinates into canvas coordinates 5219 // by following the pattern laid out by 'jerryj' in the comments of 5220 // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ 5221 var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left')); 5222 var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top')); 5223 var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right')); 5224 var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom')); 5225 var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; 5226 var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; 5227 5228 // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However 5229 // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here 5230 mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); 5231 mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); 5232 5233 return { 5234 x: mouseX, 5235 y: mouseY 5236 }; 5237 5238 }; 5239 helpers.addEvent = function(node, eventType, method) { 5240 if (node.addEventListener) { 5241 node.addEventListener(eventType, method); 5242 } else if (node.attachEvent) { 5243 node.attachEvent("on" + eventType, method); 5244 } else { 5245 node["on" + eventType] = method; 5246 } 5247 }; 5248 helpers.removeEvent = function(node, eventType, handler) { 5249 if (node.removeEventListener) { 5250 node.removeEventListener(eventType, handler, false); 5251 } else if (node.detachEvent) { 5252 node.detachEvent("on" + eventType, handler); 5253 } else { 5254 node["on" + eventType] = helpers.noop; 5255 } 5256 }; 5257 helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) { 5258 // Create the events object if it's not already present 5259 var events = chartInstance.events = chartInstance.events || {}; 5260 5261 helpers.each(arrayOfEvents, function(eventName) { 5262 events[eventName] = function() { 5263 handler.apply(chartInstance, arguments); 5264 }; 5265 helpers.addEvent(chartInstance.chart.canvas, eventName, events[eventName]); 5266 }); 5267 }; 5268 helpers.unbindEvents = function(chartInstance, arrayOfEvents) { 5269 var canvas = chartInstance.chart.canvas; 5270 helpers.each(arrayOfEvents, function(handler, eventName) { 5271 helpers.removeEvent(canvas, eventName, handler); 5272 }); 5273 }; 5274 5275 // Private helper function to convert max-width/max-height values that may be percentages into a number 5276 function parseMaxStyle(styleValue, node, parentProperty) { 5277 var valueInPixels; 5278 if (typeof(styleValue) === 'string') { 5279 valueInPixels = parseInt(styleValue, 10); 5280 5281 if (styleValue.indexOf('%') != -1) { 5282 // percentage * size in dimension 5283 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; 5284 } 5285 } else { 5286 valueInPixels = styleValue; 5287 } 5288 5289 return valueInPixels; 5290 } 5291 5292 /** 5293 * Returns if the given value contains an effective constraint. 5294 * @private 5295 */ 5296 function isConstrainedValue(value) { 5297 return value !== undefined && value !== null && value !== 'none'; 5298 } 5299 5300 // Private helper to get a constraint dimension 5301 // @param domNode : the node to check the constraint on 5302 // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight) 5303 // @param percentageProperty : property of parent to use when calculating width as a percentage 5304 // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser 5305 function getConstraintDimension(domNode, maxStyle, percentageProperty) { 5306 var view = document.defaultView; 5307 var parentNode = domNode.parentNode; 5308 var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; 5309 var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; 5310 var hasCNode = isConstrainedValue(constrainedNode); 5311 var hasCContainer = isConstrainedValue(constrainedContainer); 5312 var infinity = Number.POSITIVE_INFINITY; 5313 5314 if (hasCNode || hasCContainer) { 5315 return Math.min( 5316 hasCNode? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, 5317 hasCContainer? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); 5318 } 5319 5320 return 'none'; 5321 } 5322 // returns Number or undefined if no constraint 5323 helpers.getConstraintWidth = function(domNode) { 5324 return getConstraintDimension(domNode, 'max-width', 'clientWidth'); 5325 }; 5326 // returns Number or undefined if no constraint 5327 helpers.getConstraintHeight = function(domNode) { 5328 return getConstraintDimension(domNode, 'max-height', 'clientHeight'); 5329 }; 5330 helpers.getMaximumWidth = function(domNode) { 5331 var container = domNode.parentNode; 5332 var padding = parseInt(helpers.getStyle(container, 'padding-left')) + parseInt(helpers.getStyle(container, 'padding-right')); 5333 var w = container.clientWidth - padding; 5334 var cw = helpers.getConstraintWidth(domNode); 5335 return isNaN(cw)? w : Math.min(w, cw); 5336 }; 5337 helpers.getMaximumHeight = function(domNode) { 5338 var container = domNode.parentNode; 5339 var padding = parseInt(helpers.getStyle(container, 'padding-top')) + parseInt(helpers.getStyle(container, 'padding-bottom')); 5340 var h = container.clientHeight - padding; 5341 var ch = helpers.getConstraintHeight(domNode); 5342 return isNaN(ch)? h : Math.min(h, ch); 5343 }; 5344 helpers.getStyle = function(el, property) { 5345 return el.currentStyle ? 5346 el.currentStyle[property] : 5347 document.defaultView.getComputedStyle(el, null).getPropertyValue(property); 5348 }; 5349 helpers.retinaScale = function(chart) { 5350 var ctx = chart.ctx; 5351 var canvas = chart.canvas; 5352 var width = canvas.width; 5353 var height = canvas.height; 5354 var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1; 5355 5356 if (pixelRatio !== 1) { 5357 canvas.height = height * pixelRatio; 5358 canvas.width = width * pixelRatio; 5359 ctx.scale(pixelRatio, pixelRatio); 5360 5361 // Store the device pixel ratio so that we can go backwards in `destroy`. 5362 // The devicePixelRatio changes with zoom, so there are no guarantees that it is the same 5363 // when destroy is called 5364 chart.originalDevicePixelRatio = chart.originalDevicePixelRatio || pixelRatio; 5365 } 5366 5367 canvas.style.width = width + 'px'; 5368 canvas.style.height = height + 'px'; 5369 }; 5370 //-- Canvas methods 5371 helpers.clear = function(chart) { 5372 chart.ctx.clearRect(0, 0, chart.width, chart.height); 5373 }; 5374 helpers.fontString = function(pixelSize, fontStyle, fontFamily) { 5375 return fontStyle + " " + pixelSize + "px " + fontFamily; 5376 }; 5377 helpers.longestText = function(ctx, font, arrayOfThings, cache) { 5378 cache = cache || {}; 5379 var data = cache.data = cache.data || {}; 5380 var gc = cache.garbageCollect = cache.garbageCollect || []; 5381 5382 if (cache.font !== font) { 5383 data = cache.data = {}; 5384 gc = cache.garbageCollect = []; 5385 cache.font = font; 5386 } 5387 5388 ctx.font = font; 5389 var longest = 0; 5390 helpers.each(arrayOfThings, function(thing) { 5391 // Undefined strings and arrays should not be measured 5392 if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) { 5393 longest = helpers.measureText(ctx, data, gc, longest, thing); 5394 } else if (helpers.isArray(thing)) { 5395 // if it is an array lets measure each element 5396 // to do maybe simplify this function a bit so we can do this more recursively? 5397 helpers.each(thing, function(nestedThing) { 5398 // Undefined strings and arrays should not be measured 5399 if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) { 5400 longest = helpers.measureText(ctx, data, gc, longest, nestedThing); 5401 } 5402 }); 5403 } 5404 }); 5405 5406 var gcLen = gc.length / 2; 5407 if (gcLen > arrayOfThings.length) { 5408 for (var i = 0; i < gcLen; i++) { 5409 delete data[gc[i]]; 5410 } 5411 gc.splice(0, gcLen); 5412 } 5413 return longest; 5414 }; 5415 helpers.measureText = function (ctx, data, gc, longest, string) { 5416 var textWidth = data[string]; 5417 if (!textWidth) { 5418 textWidth = data[string] = ctx.measureText(string).width; 5419 gc.push(string); 5420 } 5421 if (textWidth > longest) { 5422 longest = textWidth; 5423 } 5424 return longest; 5425 }; 5426 helpers.numberOfLabelLines = function(arrayOfThings) { 5427 var numberOfLines = 1; 5428 helpers.each(arrayOfThings, function(thing) { 5429 if (helpers.isArray(thing)) { 5430 if (thing.length > numberOfLines) { 5431 numberOfLines = thing.length; 5432 } 5433 } 5434 }); 5435 return numberOfLines; 5436 }; 5437 helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) { 5438 ctx.beginPath(); 5439 ctx.moveTo(x + radius, y); 5440 ctx.lineTo(x + width - radius, y); 5441 ctx.quadraticCurveTo(x + width, y, x + width, y + radius); 5442 ctx.lineTo(x + width, y + height - radius); 5443 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); 5444 ctx.lineTo(x + radius, y + height); 5445 ctx.quadraticCurveTo(x, y + height, x, y + height - radius); 5446 ctx.lineTo(x, y + radius); 5447 ctx.quadraticCurveTo(x, y, x + radius, y); 5448 ctx.closePath(); 5449 }; 5450 helpers.color = function(c) { 5451 if (!color) { 5452 console.log('Color.js not found!'); 5453 return c; 5454 } 5455 5456 /* global CanvasGradient */ 5457 if (c instanceof CanvasGradient) { 5458 return color(Chart.defaults.global.defaultColor); 5459 } 5460 5461 return color(c); 5462 }; 5463 helpers.addResizeListener = function(node, callback) { 5464 // Hide an iframe before the node 5465 var hiddenIframe = document.createElement('iframe'); 5466 var hiddenIframeClass = 'chartjs-hidden-iframe'; 5467 5468 if (hiddenIframe.classlist) { 5469 // can use classlist 5470 hiddenIframe.classlist.add(hiddenIframeClass); 5471 } else { 5472 hiddenIframe.setAttribute('class', hiddenIframeClass); 5473 } 5474 5475 // Set the style 5476 var style = hiddenIframe.style; 5477 style.width = '100%'; 5478 style.display = 'block'; 5479 style.border = 0; 5480 style.height = 0; 5481 style.margin = 0; 5482 style.position = 'absolute'; 5483 style.left = 0; 5484 style.right = 0; 5485 style.top = 0; 5486 style.bottom = 0; 5487 5488 // Insert the iframe so that contentWindow is available 5489 node.insertBefore(hiddenIframe, node.firstChild); 5490 5491 (hiddenIframe.contentWindow || hiddenIframe).onresize = function() { 5492 if (callback) { 5493 callback(); 5494 } 5495 }; 5496 }; 5497 helpers.removeResizeListener = function(node) { 5498 var hiddenIframe = node.querySelector('.chartjs-hidden-iframe'); 5499 5500 // Remove the resize detect iframe 5501 if (hiddenIframe) { 5502 hiddenIframe.parentNode.removeChild(hiddenIframe); 5503 } 5504 }; 5505 helpers.isArray = Array.isArray? 5506 function(obj) { return Array.isArray(obj); } : 5507 function(obj) { 5508 return Object.prototype.toString.call(obj) === '[object Array]'; 5509 }; 5510 //! @see http://stackoverflow.com/a/14853974 5511 helpers.arrayEquals = function(a0, a1) { 5512 var i, ilen, v0, v1; 5513 5514 if (!a0 || !a1 || a0.length != a1.length) { 5515 return false; 5516 } 5517 5518 for (i = 0, ilen=a0.length; i < ilen; ++i) { 5519 v0 = a0[i]; 5520 v1 = a1[i]; 5521 5522 if (v0 instanceof Array && v1 instanceof Array) { 5523 if (!helpers.arrayEquals(v0, v1)) { 5524 return false; 5525 } 5526 } else if (v0 != v1) { 5527 // NOTE: two different object instances will never be equal: {x:20} != {x:20} 5528 return false; 5529 } 5530 } 5531 5532 return true; 5533 }; 5534 helpers.callCallback = function(fn, args, _tArg) { 5535 if (fn && typeof fn.call === 'function') { 5536 fn.apply(_tArg, args); 5537 } 5538 }; 5539 helpers.getHoverColor = function(color) { 5540 /* global CanvasPattern */ 5541 return (color instanceof CanvasPattern) ? 5542 color : 5543 helpers.color(color).saturate(0.5).darken(0.1).rgbString(); 5544 }; 5545 }; 5546 5547 },{"3":3}],26:[function(require,module,exports){ 5548 "use strict"; 5549 5550 module.exports = function() { 5551 5552 //Occupy the global variable of Chart, and create a simple base class 5553 var Chart = function(context, config) { 5554 var me = this; 5555 var helpers = Chart.helpers; 5556 me.config = config; 5557 5558 // Support a jQuery'd canvas element 5559 if (context.length && context[0].getContext) { 5560 context = context[0]; 5561 } 5562 5563 // Support a canvas domnode 5564 if (context.getContext) { 5565 context = context.getContext("2d"); 5566 } 5567 5568 me.ctx = context; 5569 me.canvas = context.canvas; 5570 5571 context.canvas.style.display = context.canvas.style.display || 'block'; 5572 5573 // Figure out what the size of the chart will be. 5574 // If the canvas has a specified width and height, we use those else 5575 // we look to see if the canvas node has a CSS width and height. 5576 // If there is still no height, fill the parent container 5577 me.width = context.canvas.width || parseInt(helpers.getStyle(context.canvas, 'width'), 10) || helpers.getMaximumWidth(context.canvas); 5578 me.height = context.canvas.height || parseInt(helpers.getStyle(context.canvas, 'height'), 10) || helpers.getMaximumHeight(context.canvas); 5579 5580 me.aspectRatio = me.width / me.height; 5581 5582 if (isNaN(me.aspectRatio) || isFinite(me.aspectRatio) === false) { 5583 // If the canvas has no size, try and figure out what the aspect ratio will be. 5584 // Some charts prefer square canvases (pie, radar, etc). If that is specified, use that 5585 // else use the canvas default ratio of 2 5586 me.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2; 5587 } 5588 5589 // Store the original style of the element so we can set it back 5590 me.originalCanvasStyleWidth = context.canvas.style.width; 5591 me.originalCanvasStyleHeight = context.canvas.style.height; 5592 5593 // High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. 5594 helpers.retinaScale(me); 5595 5596 if (config) { 5597 me.controller = new Chart.Controller(me); 5598 } 5599 5600 // Always bind this so that if the responsive state changes we still work 5601 helpers.addResizeListener(context.canvas.parentNode, function() { 5602 if (me.controller && me.controller.config.options.responsive) { 5603 me.controller.resize(); 5604 } 5605 }); 5606 5607 return me.controller ? me.controller : me; 5608 5609 }; 5610 5611 //Globally expose the defaults to allow for user updating/changing 5612 Chart.defaults = { 5613 global: { 5614 responsive: true, 5615 responsiveAnimationDuration: 0, 5616 maintainAspectRatio: true, 5617 events: ["mousemove", "mouseout", "click", "touchstart", "touchmove"], 5618 hover: { 5619 onHover: null, 5620 mode: 'single', 5621 animationDuration: 400 5622 }, 5623 onClick: null, 5624 defaultColor: 'rgba(0,0,0,0.1)', 5625 defaultFontColor: '#666', 5626 defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", 5627 defaultFontSize: 12, 5628 defaultFontStyle: 'normal', 5629 showLines: true, 5630 5631 // Element defaults defined in element extensions 5632 elements: {}, 5633 5634 // Legend callback string 5635 legendCallback: function(chart) { 5636 var text = []; 5637 text.push('<ul class="' + chart.id + '-legend">'); 5638 for (var i = 0; i < chart.data.datasets.length; i++) { 5639 text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>'); 5640 if (chart.data.datasets[i].label) { 5641 text.push(chart.data.datasets[i].label); 5642 } 5643 text.push('</li>'); 5644 } 5645 text.push('</ul>'); 5646 5647 return text.join(""); 5648 } 5649 } 5650 }; 5651 5652 Chart.Chart = Chart; 5653 5654 return Chart; 5655 5656 }; 5657 5658 },{}],27:[function(require,module,exports){ 5659 "use strict"; 5660 5661 module.exports = function(Chart) { 5662 5663 var helpers = Chart.helpers; 5664 5665 // The layout service is very self explanatory. It's responsible for the layout within a chart. 5666 // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need 5667 // It is this service's responsibility of carrying out that layout. 5668 Chart.layoutService = { 5669 defaults: {}, 5670 5671 // Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins. 5672 addBox: function(chartInstance, box) { 5673 if (!chartInstance.boxes) { 5674 chartInstance.boxes = []; 5675 } 5676 chartInstance.boxes.push(box); 5677 }, 5678 5679 removeBox: function(chartInstance, box) { 5680 if (!chartInstance.boxes) { 5681 return; 5682 } 5683 chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1); 5684 }, 5685 5686 // The most important function 5687 update: function(chartInstance, width, height) { 5688 5689 if (!chartInstance) { 5690 return; 5691 } 5692 5693 var xPadding = 0; 5694 var yPadding = 0; 5695 5696 var leftBoxes = helpers.where(chartInstance.boxes, function(box) { 5697 return box.options.position === "left"; 5698 }); 5699 var rightBoxes = helpers.where(chartInstance.boxes, function(box) { 5700 return box.options.position === "right"; 5701 }); 5702 var topBoxes = helpers.where(chartInstance.boxes, function(box) { 5703 return box.options.position === "top"; 5704 }); 5705 var bottomBoxes = helpers.where(chartInstance.boxes, function(box) { 5706 return box.options.position === "bottom"; 5707 }); 5708 5709 // Boxes that overlay the chartarea such as the radialLinear scale 5710 var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) { 5711 return box.options.position === "chartArea"; 5712 }); 5713 5714 // Ensure that full width boxes are at the very top / bottom 5715 topBoxes.sort(function(a, b) { 5716 return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0); 5717 }); 5718 bottomBoxes.sort(function(a, b) { 5719 return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0); 5720 }); 5721 5722 // Essentially we now have any number of boxes on each of the 4 sides. 5723 // Our canvas looks like the following. 5724 // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and 5725 // B1 is the bottom axis 5726 // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays 5727 // These locations are single-box locations only, when trying to register a chartArea location that is already taken, 5728 // an error will be thrown. 5729 // 5730 // |----------------------------------------------------| 5731 // | T1 (Full Width) | 5732 // |----------------------------------------------------| 5733 // | | | T2 | | 5734 // | |----|-------------------------------------|----| 5735 // | | | C1 | | C2 | | 5736 // | | |----| |----| | 5737 // | | | | | 5738 // | L1 | L2 | ChartArea (C0) | R1 | 5739 // | | | | | 5740 // | | |----| |----| | 5741 // | | | C3 | | C4 | | 5742 // | |----|-------------------------------------|----| 5743 // | | | B1 | | 5744 // |----------------------------------------------------| 5745 // | B2 (Full Width) | 5746 // |----------------------------------------------------| 5747 // 5748 // What we do to find the best sizing, we do the following 5749 // 1. Determine the minimum size of the chart area. 5750 // 2. Split the remaining width equally between each vertical axis 5751 // 3. Split the remaining height equally between each horizontal axis 5752 // 4. Give each layout the maximum size it can be. The layout will return it's minimum size 5753 // 5. Adjust the sizes of each axis based on it's minimum reported size. 5754 // 6. Refit each axis 5755 // 7. Position each axis in the final location 5756 // 8. Tell the chart the final location of the chart area 5757 // 9. Tell any axes that overlay the chart area the positions of the chart area 5758 5759 // Step 1 5760 var chartWidth = width - (2 * xPadding); 5761 var chartHeight = height - (2 * yPadding); 5762 var chartAreaWidth = chartWidth / 2; // min 50% 5763 var chartAreaHeight = chartHeight / 2; // min 50% 5764 5765 // Step 2 5766 var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); 5767 5768 // Step 3 5769 var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); 5770 5771 // Step 4 5772 var maxChartAreaWidth = chartWidth; 5773 var maxChartAreaHeight = chartHeight; 5774 var minBoxSizes = []; 5775 5776 helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); 5777 5778 function getMinimumBoxSize(box) { 5779 var minSize; 5780 var isHorizontal = box.isHorizontal(); 5781 5782 if (isHorizontal) { 5783 minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); 5784 maxChartAreaHeight -= minSize.height; 5785 } else { 5786 minSize = box.update(verticalBoxWidth, chartAreaHeight); 5787 maxChartAreaWidth -= minSize.width; 5788 } 5789 5790 minBoxSizes.push({ 5791 horizontal: isHorizontal, 5792 minSize: minSize, 5793 box: box 5794 }); 5795 } 5796 5797 // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could 5798 // be if the axes are drawn at their minimum sizes. 5799 5800 // Steps 5 & 6 5801 var totalLeftBoxesWidth = xPadding; 5802 var totalRightBoxesWidth = xPadding; 5803 var totalTopBoxesHeight = yPadding; 5804 var totalBottomBoxesHeight = yPadding; 5805 5806 // Update, and calculate the left and right margins for the horizontal boxes 5807 helpers.each(leftBoxes.concat(rightBoxes), fitBox); 5808 5809 helpers.each(leftBoxes, function(box) { 5810 totalLeftBoxesWidth += box.width; 5811 }); 5812 5813 helpers.each(rightBoxes, function(box) { 5814 totalRightBoxesWidth += box.width; 5815 }); 5816 5817 // Set the Left and Right margins for the horizontal boxes 5818 helpers.each(topBoxes.concat(bottomBoxes), fitBox); 5819 5820 // Function to fit a box 5821 function fitBox(box) { 5822 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { 5823 return minBoxSize.box === box; 5824 }); 5825 5826 if (minBoxSize) { 5827 if (box.isHorizontal()) { 5828 var scaleMargin = { 5829 left: totalLeftBoxesWidth, 5830 right: totalRightBoxesWidth, 5831 top: 0, 5832 bottom: 0 5833 }; 5834 5835 // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends 5836 // on the margin. Sometimes they need to increase in size slightly 5837 box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); 5838 } else { 5839 box.update(minBoxSize.minSize.width, maxChartAreaHeight); 5840 } 5841 } 5842 } 5843 5844 // Figure out how much margin is on the top and bottom of the vertical boxes 5845 helpers.each(topBoxes, function(box) { 5846 totalTopBoxesHeight += box.height; 5847 }); 5848 5849 helpers.each(bottomBoxes, function(box) { 5850 totalBottomBoxesHeight += box.height; 5851 }); 5852 5853 // Let the left layout know the final margin 5854 helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); 5855 5856 function finalFitVerticalBox(box) { 5857 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { 5858 return minBoxSize.box === box; 5859 }); 5860 5861 var scaleMargin = { 5862 left: 0, 5863 right: 0, 5864 top: totalTopBoxesHeight, 5865 bottom: totalBottomBoxesHeight 5866 }; 5867 5868 if (minBoxSize) { 5869 box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); 5870 } 5871 } 5872 5873 // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) 5874 totalLeftBoxesWidth = xPadding; 5875 totalRightBoxesWidth = xPadding; 5876 totalTopBoxesHeight = yPadding; 5877 totalBottomBoxesHeight = yPadding; 5878 5879 helpers.each(leftBoxes, function(box) { 5880 totalLeftBoxesWidth += box.width; 5881 }); 5882 5883 helpers.each(rightBoxes, function(box) { 5884 totalRightBoxesWidth += box.width; 5885 }); 5886 5887 helpers.each(topBoxes, function(box) { 5888 totalTopBoxesHeight += box.height; 5889 }); 5890 helpers.each(bottomBoxes, function(box) { 5891 totalBottomBoxesHeight += box.height; 5892 }); 5893 5894 // Figure out if our chart area changed. This would occur if the dataset layout label rotation 5895 // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do 5896 // without calling `fit` again 5897 var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; 5898 var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; 5899 5900 if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { 5901 helpers.each(leftBoxes, function(box) { 5902 box.height = newMaxChartAreaHeight; 5903 }); 5904 5905 helpers.each(rightBoxes, function(box) { 5906 box.height = newMaxChartAreaHeight; 5907 }); 5908 5909 helpers.each(topBoxes, function(box) { 5910 if (!box.options.fullWidth) { 5911 box.width = newMaxChartAreaWidth; 5912 } 5913 }); 5914 5915 helpers.each(bottomBoxes, function(box) { 5916 if (!box.options.fullWidth) { 5917 box.width = newMaxChartAreaWidth; 5918 } 5919 }); 5920 5921 maxChartAreaHeight = newMaxChartAreaHeight; 5922 maxChartAreaWidth = newMaxChartAreaWidth; 5923 } 5924 5925 // Step 7 - Position the boxes 5926 var left = xPadding; 5927 var top = yPadding; 5928 var right = 0; 5929 var bottom = 0; 5930 5931 helpers.each(leftBoxes.concat(topBoxes), placeBox); 5932 5933 // Account for chart width and height 5934 left += maxChartAreaWidth; 5935 top += maxChartAreaHeight; 5936 5937 helpers.each(rightBoxes, placeBox); 5938 helpers.each(bottomBoxes, placeBox); 5939 5940 function placeBox(box) { 5941 if (box.isHorizontal()) { 5942 box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth; 5943 box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth; 5944 box.top = top; 5945 box.bottom = top + box.height; 5946 5947 // Move to next point 5948 top = box.bottom; 5949 5950 } else { 5951 5952 box.left = left; 5953 box.right = left + box.width; 5954 box.top = totalTopBoxesHeight; 5955 box.bottom = totalTopBoxesHeight + maxChartAreaHeight; 5956 5957 // Move to next point 5958 left = box.right; 5959 } 5960 } 5961 5962 // Step 8 5963 chartInstance.chartArea = { 5964 left: totalLeftBoxesWidth, 5965 top: totalTopBoxesHeight, 5966 right: totalLeftBoxesWidth + maxChartAreaWidth, 5967 bottom: totalTopBoxesHeight + maxChartAreaHeight 5968 }; 5969 5970 // Step 9 5971 helpers.each(chartAreaBoxes, function(box) { 5972 box.left = chartInstance.chartArea.left; 5973 box.top = chartInstance.chartArea.top; 5974 box.right = chartInstance.chartArea.right; 5975 box.bottom = chartInstance.chartArea.bottom; 5976 5977 box.update(maxChartAreaWidth, maxChartAreaHeight); 5978 }); 5979 } 5980 }; 5981 }; 5982 5983 },{}],28:[function(require,module,exports){ 5984 "use strict"; 5985 5986 module.exports = function(Chart) { 5987 5988 var helpers = Chart.helpers; 5989 var noop = helpers.noop; 5990 5991 Chart.defaults.global.legend = { 5992 5993 display: true, 5994 position: 'top', 5995 fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) 5996 reverse: false, 5997 5998 // a callback that will handle 5999 onClick: function(e, legendItem) { 6000 var index = legendItem.datasetIndex; 6001 var ci = this.chart; 6002 var meta = ci.getDatasetMeta(index); 6003 6004 // See controller.isDatasetVisible comment 6005 meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null; 6006 6007 // We hid a dataset ... rerender the chart 6008 ci.update(); 6009 }, 6010 6011 labels: { 6012 boxWidth: 40, 6013 padding: 10, 6014 // Generates labels shown in the legend 6015 // Valid properties to return: 6016 // text : text to display 6017 // fillStyle : fill of coloured box 6018 // strokeStyle: stroke of coloured box 6019 // hidden : if this legend item refers to a hidden item 6020 // lineCap : cap style for line 6021 // lineDash 6022 // lineDashOffset : 6023 // lineJoin : 6024 // lineWidth : 6025 generateLabels: function(chart) { 6026 var data = chart.data; 6027 return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) { 6028 return { 6029 text: dataset.label, 6030 fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), 6031 hidden: !chart.isDatasetVisible(i), 6032 lineCap: dataset.borderCapStyle, 6033 lineDash: dataset.borderDash, 6034 lineDashOffset: dataset.borderDashOffset, 6035 lineJoin: dataset.borderJoinStyle, 6036 lineWidth: dataset.borderWidth, 6037 strokeStyle: dataset.borderColor, 6038 6039 // Below is extra data used for toggling the datasets 6040 datasetIndex: i 6041 }; 6042 }, this) : []; 6043 } 6044 } 6045 }; 6046 6047 Chart.Legend = Chart.Element.extend({ 6048 6049 initialize: function(config) { 6050 helpers.extend(this, config); 6051 6052 // Contains hit boxes for each dataset (in dataset order) 6053 this.legendHitBoxes = []; 6054 6055 // Are we in doughnut mode which has a different data type 6056 this.doughnutMode = false; 6057 }, 6058 6059 // These methods are ordered by lifecyle. Utilities then follow. 6060 // Any function defined here is inherited by all legend types. 6061 // Any function can be extended by the legend type 6062 6063 beforeUpdate: noop, 6064 update: function(maxWidth, maxHeight, margins) { 6065 var me = this; 6066 6067 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 6068 me.beforeUpdate(); 6069 6070 // Absorb the master measurements 6071 me.maxWidth = maxWidth; 6072 me.maxHeight = maxHeight; 6073 me.margins = margins; 6074 6075 // Dimensions 6076 me.beforeSetDimensions(); 6077 me.setDimensions(); 6078 me.afterSetDimensions(); 6079 // Labels 6080 me.beforeBuildLabels(); 6081 me.buildLabels(); 6082 me.afterBuildLabels(); 6083 6084 // Fit 6085 me.beforeFit(); 6086 me.fit(); 6087 me.afterFit(); 6088 // 6089 me.afterUpdate(); 6090 6091 return me.minSize; 6092 }, 6093 afterUpdate: noop, 6094 6095 // 6096 6097 beforeSetDimensions: noop, 6098 setDimensions: function() { 6099 var me = this; 6100 // Set the unconstrained dimension before label rotation 6101 if (me.isHorizontal()) { 6102 // Reset position before calculating rotation 6103 me.width = me.maxWidth; 6104 me.left = 0; 6105 me.right = me.width; 6106 } else { 6107 me.height = me.maxHeight; 6108 6109 // Reset position before calculating rotation 6110 me.top = 0; 6111 me.bottom = me.height; 6112 } 6113 6114 // Reset padding 6115 me.paddingLeft = 0; 6116 me.paddingTop = 0; 6117 me.paddingRight = 0; 6118 me.paddingBottom = 0; 6119 6120 // Reset minSize 6121 me.minSize = { 6122 width: 0, 6123 height: 0 6124 }; 6125 }, 6126 afterSetDimensions: noop, 6127 6128 // 6129 6130 beforeBuildLabels: noop, 6131 buildLabels: function() { 6132 var me = this; 6133 me.legendItems = me.options.labels.generateLabels.call(me, me.chart); 6134 if(me.options.reverse){ 6135 me.legendItems.reverse(); 6136 } 6137 }, 6138 afterBuildLabels: noop, 6139 6140 // 6141 6142 beforeFit: noop, 6143 fit: function() { 6144 var me = this; 6145 var opts = me.options; 6146 var labelOpts = opts.labels; 6147 var display = opts.display; 6148 6149 var ctx = me.ctx; 6150 6151 var globalDefault = Chart.defaults.global, 6152 itemOrDefault = helpers.getValueOrDefault, 6153 fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize), 6154 fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle), 6155 fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily), 6156 labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); 6157 6158 // Reset hit boxes 6159 var hitboxes = me.legendHitBoxes = []; 6160 6161 var minSize = me.minSize; 6162 var isHorizontal = me.isHorizontal(); 6163 6164 if (isHorizontal) { 6165 minSize.width = me.maxWidth; // fill all the width 6166 minSize.height = display ? 10 : 0; 6167 } else { 6168 minSize.width = display ? 10 : 0; 6169 minSize.height = me.maxHeight; // fill all the height 6170 } 6171 6172 // Increase sizes here 6173 if (display) { 6174 ctx.font = labelFont; 6175 6176 if (isHorizontal) { 6177 // Labels 6178 6179 // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one 6180 var lineWidths = me.lineWidths = [0]; 6181 var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0; 6182 6183 ctx.textAlign = "left"; 6184 ctx.textBaseline = 'top'; 6185 6186 helpers.each(me.legendItems, function(legendItem, i) { 6187 var width = labelOpts.boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; 6188 if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) { 6189 totalHeight += fontSize + (labelOpts.padding); 6190 lineWidths[lineWidths.length] = me.left; 6191 } 6192 6193 // Store the hitbox width and height here. Final position will be updated in `draw` 6194 hitboxes[i] = { 6195 left: 0, 6196 top: 0, 6197 width: width, 6198 height: fontSize 6199 }; 6200 6201 lineWidths[lineWidths.length - 1] += width + labelOpts.padding; 6202 }); 6203 6204 minSize.height += totalHeight; 6205 6206 } else { 6207 var vPadding = labelOpts.padding; 6208 var columnWidths = me.columnWidths = []; 6209 var totalWidth = labelOpts.padding; 6210 var currentColWidth = 0; 6211 var currentColHeight = 0; 6212 var itemHeight = fontSize + vPadding; 6213 6214 helpers.each(me.legendItems, function(legendItem, i) { 6215 var itemWidth = labelOpts.boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; 6216 6217 // If too tall, go to new column 6218 if (currentColHeight + itemHeight > minSize.height) { 6219 totalWidth += currentColWidth + labelOpts.padding; 6220 columnWidths.push(currentColWidth); // previous column width 6221 6222 currentColWidth = 0; 6223 currentColHeight = 0; 6224 } 6225 6226 // Get max width 6227 currentColWidth = Math.max(currentColWidth, itemWidth); 6228 currentColHeight += itemHeight; 6229 6230 // Store the hitbox width and height here. Final position will be updated in `draw` 6231 hitboxes[i] = { 6232 left: 0, 6233 top: 0, 6234 width: itemWidth, 6235 height: fontSize 6236 }; 6237 }); 6238 6239 totalWidth += currentColWidth; 6240 columnWidths.push(currentColWidth); 6241 minSize.width += totalWidth; 6242 } 6243 } 6244 6245 me.width = minSize.width; 6246 me.height = minSize.height; 6247 }, 6248 afterFit: noop, 6249 6250 // Shared Methods 6251 isHorizontal: function() { 6252 return this.options.position === "top" || this.options.position === "bottom"; 6253 }, 6254 6255 // Actualy draw the legend on the canvas 6256 draw: function() { 6257 var me = this; 6258 var opts = me.options; 6259 var labelOpts = opts.labels; 6260 var globalDefault = Chart.defaults.global, 6261 lineDefault = globalDefault.elements.line, 6262 legendWidth = me.width, 6263 legendHeight = me.height, 6264 lineWidths = me.lineWidths; 6265 6266 if (opts.display) { 6267 var ctx = me.ctx, 6268 cursor, 6269 itemOrDefault = helpers.getValueOrDefault, 6270 fontColor = itemOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor), 6271 fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize), 6272 fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle), 6273 fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily), 6274 labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); 6275 6276 // Canvas setup 6277 ctx.textAlign = "left"; 6278 ctx.textBaseline = 'top'; 6279 ctx.lineWidth = 0.5; 6280 ctx.strokeStyle = fontColor; // for strikethrough effect 6281 ctx.fillStyle = fontColor; // render in correct colour 6282 ctx.font = labelFont; 6283 6284 var boxWidth = labelOpts.boxWidth, 6285 hitboxes = me.legendHitBoxes; 6286 6287 // current position 6288 var drawLegendBox = function(x, y, legendItem) { 6289 // Set the ctx for the box 6290 ctx.save(); 6291 6292 ctx.fillStyle = itemOrDefault(legendItem.fillStyle, globalDefault.defaultColor); 6293 ctx.lineCap = itemOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); 6294 ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); 6295 ctx.lineJoin = itemOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); 6296 ctx.lineWidth = itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth); 6297 ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, globalDefault.defaultColor); 6298 6299 if (ctx.setLineDash) { 6300 // IE 9 and 10 do not support line dash 6301 ctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash)); 6302 } 6303 6304 // Draw the box 6305 ctx.strokeRect(x, y, boxWidth, fontSize); 6306 ctx.fillRect(x, y, boxWidth, fontSize); 6307 6308 ctx.restore(); 6309 }; 6310 var fillText = function(x, y, legendItem, textWidth) { 6311 ctx.fillText(legendItem.text, boxWidth + (fontSize / 2) + x, y); 6312 6313 if (legendItem.hidden) { 6314 // Strikethrough the text if hidden 6315 ctx.beginPath(); 6316 ctx.lineWidth = 2; 6317 ctx.moveTo(boxWidth + (fontSize / 2) + x, y + (fontSize / 2)); 6318 ctx.lineTo(boxWidth + (fontSize / 2) + x + textWidth, y + (fontSize / 2)); 6319 ctx.stroke(); 6320 } 6321 }; 6322 6323 // Horizontal 6324 var isHorizontal = me.isHorizontal(); 6325 if (isHorizontal) { 6326 cursor = { 6327 x: me.left + ((legendWidth - lineWidths[0]) / 2), 6328 y: me.top + labelOpts.padding, 6329 line: 0 6330 }; 6331 } else { 6332 cursor = { 6333 x: me.left + labelOpts.padding, 6334 y: me.top, 6335 line: 0 6336 }; 6337 } 6338 6339 var itemHeight = fontSize + labelOpts.padding; 6340 helpers.each(me.legendItems, function(legendItem, i) { 6341 var textWidth = ctx.measureText(legendItem.text).width, 6342 width = boxWidth + (fontSize / 2) + textWidth, 6343 x = cursor.x, 6344 y = cursor.y; 6345 6346 if (isHorizontal) { 6347 if (x + width >= legendWidth) { 6348 y = cursor.y += fontSize + (labelOpts.padding); 6349 cursor.line++; 6350 x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2); 6351 } 6352 } else { 6353 if (y + itemHeight > me.bottom) { 6354 x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; 6355 y = cursor.y = me.top; 6356 cursor.line++; 6357 } 6358 } 6359 6360 6361 drawLegendBox(x, y, legendItem); 6362 6363 hitboxes[i].left = x; 6364 hitboxes[i].top = y; 6365 6366 // Fill the actual label 6367 fillText(x, y, legendItem, textWidth); 6368 6369 if (isHorizontal) { 6370 cursor.x += width + (labelOpts.padding); 6371 } else { 6372 cursor.y += itemHeight; 6373 } 6374 6375 }); 6376 } 6377 }, 6378 6379 // Handle an event 6380 handleEvent: function(e) { 6381 var me = this; 6382 var position = helpers.getRelativePosition(e, me.chart.chart), 6383 x = position.x, 6384 y = position.y, 6385 opts = me.options; 6386 6387 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { 6388 // See if we are touching one of the dataset boxes 6389 var lh = me.legendHitBoxes; 6390 for (var i = 0; i < lh.length; ++i) { 6391 var hitBox = lh[i]; 6392 6393 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { 6394 // Touching an element 6395 if (opts.onClick) { 6396 opts.onClick.call(me, e, me.legendItems[i]); 6397 } 6398 break; 6399 } 6400 } 6401 } 6402 } 6403 }); 6404 6405 // Register the legend plugin 6406 Chart.plugins.register({ 6407 beforeInit: function(chartInstance) { 6408 var opts = chartInstance.options; 6409 var legendOpts = opts.legend; 6410 6411 if (legendOpts) { 6412 chartInstance.legend = new Chart.Legend({ 6413 ctx: chartInstance.chart.ctx, 6414 options: legendOpts, 6415 chart: chartInstance 6416 }); 6417 6418 Chart.layoutService.addBox(chartInstance, chartInstance.legend); 6419 } 6420 } 6421 }); 6422 }; 6423 6424 },{}],29:[function(require,module,exports){ 6425 "use strict"; 6426 6427 module.exports = function(Chart) { 6428 6429 var noop = Chart.helpers.noop; 6430 6431 /** 6432 * The plugin service singleton 6433 * @namespace Chart.plugins 6434 * @since 2.1.0 6435 */ 6436 Chart.plugins = { 6437 _plugins: [], 6438 6439 /** 6440 * Registers the given plugin(s) if not already registered. 6441 * @param {Array|Object} plugins plugin instance(s). 6442 */ 6443 register: function(plugins) { 6444 var p = this._plugins; 6445 ([]).concat(plugins).forEach(function(plugin) { 6446 if (p.indexOf(plugin) === -1) { 6447 p.push(plugin); 6448 } 6449 }); 6450 }, 6451 6452 /** 6453 * Unregisters the given plugin(s) only if registered. 6454 * @param {Array|Object} plugins plugin instance(s). 6455 */ 6456 unregister: function(plugins) { 6457 var p = this._plugins; 6458 ([]).concat(plugins).forEach(function(plugin) { 6459 var idx = p.indexOf(plugin); 6460 if (idx !== -1) { 6461 p.splice(idx, 1); 6462 } 6463 }); 6464 }, 6465 6466 /** 6467 * Remove all registered p^lugins. 6468 * @since 2.1.5 6469 */ 6470 clear: function() { 6471 this._plugins = []; 6472 }, 6473 6474 /** 6475 * Returns the number of registered plugins? 6476 * @returns {Number} 6477 * @since 2.1.5 6478 */ 6479 count: function() { 6480 return this._plugins.length; 6481 }, 6482 6483 /** 6484 * Returns all registered plugin intances. 6485 * @returns {Array} array of plugin objects. 6486 * @since 2.1.5 6487 */ 6488 getAll: function() { 6489 return this._plugins; 6490 }, 6491 6492 /** 6493 * Calls registered plugins on the specified extension, with the given args. This 6494 * method immediately returns as soon as a plugin explicitly returns false. The 6495 * returned value can be used, for instance, to interrupt the current action. 6496 * @param {String} extension the name of the plugin method to call (e.g. 'beforeUpdate'). 6497 * @param {Array} [args] extra arguments to apply to the extension call. 6498 * @returns {Boolean} false if any of the plugins return false, else returns true. 6499 */ 6500 notify: function(extension, args) { 6501 var plugins = this._plugins; 6502 var ilen = plugins.length; 6503 var i, plugin; 6504 6505 for (i=0; i<ilen; ++i) { 6506 plugin = plugins[i]; 6507 if (typeof plugin[extension] === 'function') { 6508 if (plugin[extension].apply(plugin, args || []) === false) { 6509 return false; 6510 } 6511 } 6512 } 6513 6514 return true; 6515 } 6516 }; 6517 6518 /** 6519 * Plugin extension methods. 6520 * @interface Chart.PluginBase 6521 * @since 2.1.0 6522 */ 6523 Chart.PluginBase = Chart.Element.extend({ 6524 // Called at start of chart init 6525 beforeInit: noop, 6526 6527 // Called at end of chart init 6528 afterInit: noop, 6529 6530 // Called at start of update 6531 beforeUpdate: noop, 6532 6533 // Called at end of update 6534 afterUpdate: noop, 6535 6536 // Called at start of draw 6537 beforeDraw: noop, 6538 6539 // Called at end of draw 6540 afterDraw: noop, 6541 6542 // Called during destroy 6543 destroy: noop 6544 }); 6545 6546 /** 6547 * Provided for backward compatibility, use Chart.plugins instead 6548 * @namespace Chart.pluginService 6549 * @deprecated since version 2.1.5 6550 * @todo remove me at version 3 6551 */ 6552 Chart.pluginService = Chart.plugins; 6553 }; 6554 6555 },{}],30:[function(require,module,exports){ 6556 "use strict"; 6557 6558 module.exports = function(Chart) { 6559 6560 var helpers = Chart.helpers; 6561 6562 Chart.defaults.scale = { 6563 display: true, 6564 position: "left", 6565 6566 // grid line settings 6567 gridLines: { 6568 display: true, 6569 color: "rgba(0, 0, 0, 0.1)", 6570 lineWidth: 1, 6571 drawBorder: true, 6572 drawOnChartArea: true, 6573 drawTicks: true, 6574 tickMarkLength: 10, 6575 zeroLineWidth: 1, 6576 zeroLineColor: "rgba(0,0,0,0.25)", 6577 offsetGridLines: false 6578 }, 6579 6580 // scale label 6581 scaleLabel: { 6582 // actual label 6583 labelString: '', 6584 6585 // display property 6586 display: false 6587 }, 6588 6589 // label settings 6590 ticks: { 6591 beginAtZero: false, 6592 minRotation: 0, 6593 maxRotation: 50, 6594 mirror: false, 6595 padding: 10, 6596 reverse: false, 6597 display: true, 6598 autoSkip: true, 6599 autoSkipPadding: 0, 6600 labelOffset: 0, 6601 // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. 6602 callback: function(value) { 6603 return helpers.isArray(value) ? value : '' + value; 6604 } 6605 } 6606 }; 6607 6608 Chart.Scale = Chart.Element.extend({ 6609 6610 // These methods are ordered by lifecyle. Utilities then follow. 6611 // Any function defined here is inherited by all scale types. 6612 // Any function can be extended by the scale type 6613 6614 beforeUpdate: function() { 6615 helpers.callCallback(this.options.beforeUpdate, [this]); 6616 }, 6617 update: function(maxWidth, maxHeight, margins) { 6618 var me = this; 6619 6620 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 6621 me.beforeUpdate(); 6622 6623 // Absorb the master measurements 6624 me.maxWidth = maxWidth; 6625 me.maxHeight = maxHeight; 6626 me.margins = helpers.extend({ 6627 left: 0, 6628 right: 0, 6629 top: 0, 6630 bottom: 0 6631 }, margins); 6632 6633 // Dimensions 6634 me.beforeSetDimensions(); 6635 me.setDimensions(); 6636 me.afterSetDimensions(); 6637 6638 // Data min/max 6639 me.beforeDataLimits(); 6640 me.determineDataLimits(); 6641 me.afterDataLimits(); 6642 6643 // Ticks 6644 me.beforeBuildTicks(); 6645 me.buildTicks(); 6646 me.afterBuildTicks(); 6647 6648 me.beforeTickToLabelConversion(); 6649 me.convertTicksToLabels(); 6650 me.afterTickToLabelConversion(); 6651 6652 // Tick Rotation 6653 me.beforeCalculateTickRotation(); 6654 me.calculateTickRotation(); 6655 me.afterCalculateTickRotation(); 6656 // Fit 6657 me.beforeFit(); 6658 me.fit(); 6659 me.afterFit(); 6660 // 6661 me.afterUpdate(); 6662 6663 return me.minSize; 6664 6665 }, 6666 afterUpdate: function() { 6667 helpers.callCallback(this.options.afterUpdate, [this]); 6668 }, 6669 6670 // 6671 6672 beforeSetDimensions: function() { 6673 helpers.callCallback(this.options.beforeSetDimensions, [this]); 6674 }, 6675 setDimensions: function() { 6676 var me = this; 6677 // Set the unconstrained dimension before label rotation 6678 if (me.isHorizontal()) { 6679 // Reset position before calculating rotation 6680 me.width = me.maxWidth; 6681 me.left = 0; 6682 me.right = me.width; 6683 } else { 6684 me.height = me.maxHeight; 6685 6686 // Reset position before calculating rotation 6687 me.top = 0; 6688 me.bottom = me.height; 6689 } 6690 6691 // Reset padding 6692 me.paddingLeft = 0; 6693 me.paddingTop = 0; 6694 me.paddingRight = 0; 6695 me.paddingBottom = 0; 6696 }, 6697 afterSetDimensions: function() { 6698 helpers.callCallback(this.options.afterSetDimensions, [this]); 6699 }, 6700 6701 // Data limits 6702 beforeDataLimits: function() { 6703 helpers.callCallback(this.options.beforeDataLimits, [this]); 6704 }, 6705 determineDataLimits: helpers.noop, 6706 afterDataLimits: function() { 6707 helpers.callCallback(this.options.afterDataLimits, [this]); 6708 }, 6709 6710 // 6711 beforeBuildTicks: function() { 6712 helpers.callCallback(this.options.beforeBuildTicks, [this]); 6713 }, 6714 buildTicks: helpers.noop, 6715 afterBuildTicks: function() { 6716 helpers.callCallback(this.options.afterBuildTicks, [this]); 6717 }, 6718 6719 beforeTickToLabelConversion: function() { 6720 helpers.callCallback(this.options.beforeTickToLabelConversion, [this]); 6721 }, 6722 convertTicksToLabels: function() { 6723 var me = this; 6724 // Convert ticks to strings 6725 me.ticks = me.ticks.map(function(numericalTick, index, ticks) { 6726 if (me.options.ticks.userCallback) { 6727 return me.options.ticks.userCallback(numericalTick, index, ticks); 6728 } 6729 return me.options.ticks.callback(numericalTick, index, ticks); 6730 }, 6731 me); 6732 }, 6733 afterTickToLabelConversion: function() { 6734 helpers.callCallback(this.options.afterTickToLabelConversion, [this]); 6735 }, 6736 6737 // 6738 6739 beforeCalculateTickRotation: function() { 6740 helpers.callCallback(this.options.beforeCalculateTickRotation, [this]); 6741 }, 6742 calculateTickRotation: function() { 6743 var me = this; 6744 var context = me.ctx; 6745 var globalDefaults = Chart.defaults.global; 6746 var optionTicks = me.options.ticks; 6747 6748 //Get the width of each grid by calculating the difference 6749 //between x offsets between 0 and 1. 6750 var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize); 6751 var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle); 6752 var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily); 6753 var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); 6754 context.font = tickLabelFont; 6755 6756 var firstWidth = context.measureText(me.ticks[0]).width; 6757 var lastWidth = context.measureText(me.ticks[me.ticks.length - 1]).width; 6758 var firstRotated; 6759 6760 me.labelRotation = optionTicks.minRotation || 0; 6761 me.paddingRight = 0; 6762 me.paddingLeft = 0; 6763 6764 if (me.options.display) { 6765 if (me.isHorizontal()) { 6766 me.paddingRight = lastWidth / 2 + 3; 6767 me.paddingLeft = firstWidth / 2 + 3; 6768 6769 if (!me.longestTextCache) { 6770 me.longestTextCache = {}; 6771 } 6772 var originalLabelWidth = helpers.longestText(context, tickLabelFont, me.ticks, me.longestTextCache); 6773 var labelWidth = originalLabelWidth; 6774 var cosRotation; 6775 var sinRotation; 6776 6777 // Allow 3 pixels x2 padding either side for label readability 6778 // only the index matters for a dataset scale, but we want a consistent interface between scales 6779 var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; 6780 6781 //Max label rotation can be set or default to 90 - also act as a loop counter 6782 while (labelWidth > tickWidth && me.labelRotation < optionTicks.maxRotation) { 6783 cosRotation = Math.cos(helpers.toRadians(me.labelRotation)); 6784 sinRotation = Math.sin(helpers.toRadians(me.labelRotation)); 6785 6786 firstRotated = cosRotation * firstWidth; 6787 6788 // We're right aligning the text now. 6789 if (firstRotated + tickFontSize / 2 > me.yLabelWidth) { 6790 me.paddingLeft = firstRotated + tickFontSize / 2; 6791 } 6792 6793 me.paddingRight = tickFontSize / 2; 6794 6795 if (sinRotation * originalLabelWidth > me.maxHeight) { 6796 // go back one step 6797 me.labelRotation--; 6798 break; 6799 } 6800 6801 me.labelRotation++; 6802 labelWidth = cosRotation * originalLabelWidth; 6803 } 6804 } 6805 } 6806 6807 if (me.margins) { 6808 me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); 6809 me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); 6810 } 6811 }, 6812 afterCalculateTickRotation: function() { 6813 helpers.callCallback(this.options.afterCalculateTickRotation, [this]); 6814 }, 6815 6816 // 6817 6818 beforeFit: function() { 6819 helpers.callCallback(this.options.beforeFit, [this]); 6820 }, 6821 fit: function() { 6822 var me = this; 6823 // Reset 6824 var minSize = me.minSize = { 6825 width: 0, 6826 height: 0 6827 }; 6828 6829 var opts = me.options; 6830 var globalDefaults = Chart.defaults.global; 6831 var tickOpts = opts.ticks; 6832 var scaleLabelOpts = opts.scaleLabel; 6833 var display = opts.display; 6834 var isHorizontal = me.isHorizontal(); 6835 6836 var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); 6837 var tickFontStyle = helpers.getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); 6838 var tickFontFamily = helpers.getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); 6839 var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); 6840 6841 var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabelOpts.fontSize, globalDefaults.defaultFontSize); 6842 var scaleLabelFontStyle = helpers.getValueOrDefault(scaleLabelOpts.fontStyle, globalDefaults.defaultFontStyle); 6843 var scaleLabelFontFamily = helpers.getValueOrDefault(scaleLabelOpts.fontFamily, globalDefaults.defaultFontFamily); 6844 var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily); 6845 6846 var tickMarkLength = opts.gridLines.tickMarkLength; 6847 6848 // Width 6849 if (isHorizontal) { 6850 // subtract the margins to line up with the chartArea if we are a full width scale 6851 minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; 6852 } else { 6853 minSize.width = display ? tickMarkLength : 0; 6854 } 6855 6856 // height 6857 if (isHorizontal) { 6858 minSize.height = display ? tickMarkLength : 0; 6859 } else { 6860 minSize.height = me.maxHeight; // fill all the height 6861 } 6862 6863 // Are we showing a title for the scale? 6864 if (scaleLabelOpts.display && display) { 6865 if (isHorizontal) { 6866 minSize.height += (scaleLabelFontSize * 1.5); 6867 } else { 6868 minSize.width += (scaleLabelFontSize * 1.5); 6869 } 6870 } 6871 6872 if (tickOpts.display && display) { 6873 // Don't bother fitting the ticks if we are not showing them 6874 if (!me.longestTextCache) { 6875 me.longestTextCache = {}; 6876 } 6877 6878 var largestTextWidth = helpers.longestText(me.ctx, tickLabelFont, me.ticks, me.longestTextCache); 6879 var tallestLabelHeightInLines = helpers.numberOfLabelLines(me.ticks); 6880 var lineSpace = tickFontSize * 0.5; 6881 6882 if (isHorizontal) { 6883 // A horizontal axis is more constrained by the height. 6884 me.longestLabelWidth = largestTextWidth; 6885 6886 // TODO - improve this calculation 6887 var labelHeight = (Math.sin(helpers.toRadians(me.labelRotation)) * me.longestLabelWidth) + (tickFontSize * tallestLabelHeightInLines) + (lineSpace * tallestLabelHeightInLines); 6888 6889 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight); 6890 me.ctx.font = tickLabelFont; 6891 6892 var firstLabelWidth = me.ctx.measureText(me.ticks[0]).width; 6893 var lastLabelWidth = me.ctx.measureText(me.ticks[me.ticks.length - 1]).width; 6894 6895 // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated 6896 // by the font height 6897 var cosRotation = Math.cos(helpers.toRadians(me.labelRotation)); 6898 var sinRotation = Math.sin(helpers.toRadians(me.labelRotation)); 6899 me.paddingLeft = me.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges 6900 me.paddingRight = me.labelRotation !== 0 ? (sinRotation * (tickFontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated 6901 } else { 6902 // A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first 6903 var maxLabelWidth = me.maxWidth - minSize.width; 6904 6905 // Account for padding 6906 var mirror = tickOpts.mirror; 6907 if (!mirror) { 6908 largestTextWidth += me.options.ticks.padding; 6909 } else { 6910 // If mirrored text is on the inside so don't expand 6911 largestTextWidth = 0; 6912 } 6913 6914 if (largestTextWidth < maxLabelWidth) { 6915 // We don't need all the room 6916 minSize.width += largestTextWidth; 6917 } else { 6918 // Expand to max size 6919 minSize.width = me.maxWidth; 6920 } 6921 6922 me.paddingTop = tickFontSize / 2; 6923 me.paddingBottom = tickFontSize / 2; 6924 } 6925 } 6926 6927 if (me.margins) { 6928 me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); 6929 me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); 6930 me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); 6931 me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); 6932 } 6933 6934 me.width = minSize.width; 6935 me.height = minSize.height; 6936 6937 }, 6938 afterFit: function() { 6939 helpers.callCallback(this.options.afterFit, [this]); 6940 }, 6941 6942 // Shared Methods 6943 isHorizontal: function() { 6944 return this.options.position === "top" || this.options.position === "bottom"; 6945 }, 6946 isFullWidth: function() { 6947 return (this.options.fullWidth); 6948 }, 6949 6950 // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not 6951 getRightValue: function getRightValue(rawValue) { 6952 // Null and undefined values first 6953 if (rawValue === null || typeof(rawValue) === 'undefined') { 6954 return NaN; 6955 } 6956 // isNaN(object) returns true, so make sure NaN is checking for a number 6957 if (typeof(rawValue) === 'number' && isNaN(rawValue)) { 6958 return NaN; 6959 } 6960 // If it is in fact an object, dive in one more level 6961 if (typeof(rawValue) === "object") { 6962 if ((rawValue instanceof Date) || (rawValue.isValid)) { 6963 return rawValue; 6964 } else { 6965 return getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y); 6966 } 6967 } 6968 6969 // Value is good, return it 6970 return rawValue; 6971 }, 6972 6973 // Used to get the value to display in the tooltip for the data at the given index 6974 // function getLabelForIndex(index, datasetIndex) 6975 getLabelForIndex: helpers.noop, 6976 6977 // Used to get data value locations. Value can either be an index or a numerical value 6978 getPixelForValue: helpers.noop, 6979 6980 // Used to get the data value from a given pixel. This is the inverse of getPixelForValue 6981 getValueForPixel: helpers.noop, 6982 6983 // Used for tick location, should 6984 getPixelForTick: function(index, includeOffset) { 6985 var me = this; 6986 if (me.isHorizontal()) { 6987 var innerWidth = me.width - (me.paddingLeft + me.paddingRight); 6988 var tickWidth = innerWidth / Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); 6989 var pixel = (tickWidth * index) + me.paddingLeft; 6990 6991 if (includeOffset) { 6992 pixel += tickWidth / 2; 6993 } 6994 6995 var finalVal = me.left + Math.round(pixel); 6996 finalVal += me.isFullWidth() ? me.margins.left : 0; 6997 return finalVal; 6998 } else { 6999 var innerHeight = me.height - (me.paddingTop + me.paddingBottom); 7000 return me.top + (index * (innerHeight / (me.ticks.length - 1))); 7001 } 7002 }, 7003 7004 // Utility for getting the pixel location of a percentage of scale 7005 getPixelForDecimal: function(decimal /*, includeOffset*/ ) { 7006 var me = this; 7007 if (me.isHorizontal()) { 7008 var innerWidth = me.width - (me.paddingLeft + me.paddingRight); 7009 var valueOffset = (innerWidth * decimal) + me.paddingLeft; 7010 7011 var finalVal = me.left + Math.round(valueOffset); 7012 finalVal += me.isFullWidth() ? me.margins.left : 0; 7013 return finalVal; 7014 } else { 7015 return me.top + (decimal * me.height); 7016 } 7017 }, 7018 7019 getBasePixel: function() { 7020 var me = this; 7021 var min = me.min; 7022 var max = me.max; 7023 7024 return me.getPixelForValue( 7025 me.beginAtZero? 0: 7026 min < 0 && max < 0? max : 7027 min > 0 && max > 0? min : 7028 0); 7029 }, 7030 7031 // Actualy draw the scale on the canvas 7032 // @param {rectangle} chartArea : the area of the chart to draw full grid lines on 7033 draw: function(chartArea) { 7034 var me = this; 7035 var options = me.options; 7036 if (!options.display) { 7037 return; 7038 } 7039 7040 var context = me.ctx; 7041 var globalDefaults = Chart.defaults.global; 7042 var optionTicks = options.ticks; 7043 var gridLines = options.gridLines; 7044 var scaleLabel = options.scaleLabel; 7045 7046 var isRotated = me.labelRotation !== 0; 7047 var skipRatio; 7048 var useAutoskipper = optionTicks.autoSkip; 7049 var isHorizontal = me.isHorizontal(); 7050 7051 // figure out the maximum number of gridlines to show 7052 var maxTicks; 7053 if (optionTicks.maxTicksLimit) { 7054 maxTicks = optionTicks.maxTicksLimit; 7055 } 7056 7057 var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); 7058 var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize); 7059 var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle); 7060 var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily); 7061 var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); 7062 var tl = gridLines.tickMarkLength; 7063 7064 var scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); 7065 var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabel.fontSize, globalDefaults.defaultFontSize); 7066 var scaleLabelFontStyle = helpers.getValueOrDefault(scaleLabel.fontStyle, globalDefaults.defaultFontStyle); 7067 var scaleLabelFontFamily = helpers.getValueOrDefault(scaleLabel.fontFamily, globalDefaults.defaultFontFamily); 7068 var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily); 7069 7070 var labelRotationRadians = helpers.toRadians(me.labelRotation); 7071 var cosRotation = Math.cos(labelRotationRadians); 7072 var sinRotation = Math.sin(labelRotationRadians); 7073 var longestRotatedLabel = me.longestLabelWidth * cosRotation; 7074 var rotatedLabelHeight = tickFontSize * sinRotation; 7075 7076 // Make sure we draw text in the correct color and font 7077 context.fillStyle = tickFontColor; 7078 7079 var itemsToDraw = []; 7080 7081 if (isHorizontal) { 7082 skipRatio = false; 7083 7084 // Only calculate the skip ratio with the half width of longestRotateLabel if we got an actual rotation 7085 // See #2584 7086 if (isRotated) { 7087 longestRotatedLabel /= 2; 7088 } 7089 7090 if ((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length > (me.width - (me.paddingLeft + me.paddingRight))) { 7091 skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length) / (me.width - (me.paddingLeft + me.paddingRight))); 7092 } 7093 7094 // if they defined a max number of optionTicks, 7095 // increase skipRatio until that number is met 7096 if (maxTicks && me.ticks.length > maxTicks) { 7097 while (!skipRatio || me.ticks.length / (skipRatio || 1) > maxTicks) { 7098 if (!skipRatio) { 7099 skipRatio = 1; 7100 } 7101 skipRatio += 1; 7102 } 7103 } 7104 7105 if (!useAutoskipper) { 7106 skipRatio = false; 7107 } 7108 } 7109 7110 7111 var xTickStart = options.position === "right" ? me.left : me.right - tl; 7112 var xTickEnd = options.position === "right" ? me.left + tl : me.right; 7113 var yTickStart = options.position === "bottom" ? me.top : me.bottom - tl; 7114 var yTickEnd = options.position === "bottom" ? me.top + tl : me.bottom; 7115 7116 helpers.each(me.ticks, function(label, index) { 7117 // If the callback returned a null or undefined value, do not draw this line 7118 if (label === undefined || label === null) { 7119 return; 7120 } 7121 7122 var isLastTick = me.ticks.length === index + 1; 7123 7124 // Since we always show the last tick,we need may need to hide the last shown one before 7125 var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio >= me.ticks.length); 7126 if (shouldSkip && !isLastTick || (label === undefined || label === null)) { 7127 return; 7128 } 7129 7130 var lineWidth, lineColor; 7131 if (index === (typeof me.zeroLineIndex !== 'undefined' ? me.zeroLineIndex : 0)) { 7132 // Draw the first index specially 7133 lineWidth = gridLines.zeroLineWidth; 7134 lineColor = gridLines.zeroLineColor; 7135 } else { 7136 lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, index); 7137 lineColor = helpers.getValueAtIndexOrDefault(gridLines.color, index); 7138 } 7139 7140 // Common properties 7141 var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY; 7142 var textAlign, textBaseline = 'middle'; 7143 7144 if (isHorizontal) { 7145 if (!isRotated) { 7146 textBaseline = options.position === 'top' ? 'bottom' : 'top'; 7147 } 7148 7149 textAlign = isRotated ? 'right' : 'center'; 7150 7151 var xLineValue = me.getPixelForTick(index) + helpers.aliasPixel(lineWidth); // xvalues for grid lines 7152 labelX = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option) 7153 labelY = (isRotated) ? me.top + 12 : options.position === 'top' ? me.bottom - tl : me.top + tl; 7154 7155 tx1 = tx2 = x1 = x2 = xLineValue; 7156 ty1 = yTickStart; 7157 ty2 = yTickEnd; 7158 y1 = chartArea.top; 7159 y2 = chartArea.bottom; 7160 } else { 7161 if (options.position === 'left') { 7162 if (optionTicks.mirror) { 7163 labelX = me.right + optionTicks.padding; 7164 textAlign = 'left'; 7165 } else { 7166 labelX = me.right - optionTicks.padding; 7167 textAlign = 'right'; 7168 } 7169 } else { 7170 // right side 7171 if (optionTicks.mirror) { 7172 labelX = me.left - optionTicks.padding; 7173 textAlign = 'right'; 7174 } else { 7175 labelX = me.left + optionTicks.padding; 7176 textAlign = 'left'; 7177 } 7178 } 7179 7180 var yLineValue = me.getPixelForTick(index); // xvalues for grid lines 7181 yLineValue += helpers.aliasPixel(lineWidth); 7182 labelY = me.getPixelForTick(index, gridLines.offsetGridLines); 7183 7184 tx1 = xTickStart; 7185 tx2 = xTickEnd; 7186 x1 = chartArea.left; 7187 x2 = chartArea.right; 7188 ty1 = ty2 = y1 = y2 = yLineValue; 7189 } 7190 7191 itemsToDraw.push({ 7192 tx1: tx1, 7193 ty1: ty1, 7194 tx2: tx2, 7195 ty2: ty2, 7196 x1: x1, 7197 y1: y1, 7198 x2: x2, 7199 y2: y2, 7200 labelX: labelX, 7201 labelY: labelY, 7202 glWidth: lineWidth, 7203 glColor: lineColor, 7204 rotation: -1 * labelRotationRadians, 7205 label: label, 7206 textBaseline: textBaseline, 7207 textAlign: textAlign 7208 }); 7209 }); 7210 7211 // Draw all of the tick labels, tick marks, and grid lines at the correct places 7212 helpers.each(itemsToDraw, function(itemToDraw) { 7213 if (gridLines.display) { 7214 context.lineWidth = itemToDraw.glWidth; 7215 context.strokeStyle = itemToDraw.glColor; 7216 7217 context.beginPath(); 7218 7219 if (gridLines.drawTicks) { 7220 context.moveTo(itemToDraw.tx1, itemToDraw.ty1); 7221 context.lineTo(itemToDraw.tx2, itemToDraw.ty2); 7222 } 7223 7224 if (gridLines.drawOnChartArea) { 7225 context.moveTo(itemToDraw.x1, itemToDraw.y1); 7226 context.lineTo(itemToDraw.x2, itemToDraw.y2); 7227 } 7228 7229 context.stroke(); 7230 } 7231 7232 if (optionTicks.display) { 7233 context.save(); 7234 context.translate(itemToDraw.labelX, itemToDraw.labelY); 7235 context.rotate(itemToDraw.rotation); 7236 context.font = tickLabelFont; 7237 context.textBaseline = itemToDraw.textBaseline; 7238 context.textAlign = itemToDraw.textAlign; 7239 7240 var label = itemToDraw.label; 7241 if (helpers.isArray(label)) { 7242 for (var i = 0, y = 0; i < label.length; ++i) { 7243 // We just make sure the multiline element is a string here.. 7244 context.fillText('' + label[i], 0, y); 7245 // apply same lineSpacing as calculated @ L#320 7246 y += (tickFontSize * 1.5); 7247 } 7248 } else { 7249 context.fillText(label, 0, 0); 7250 } 7251 context.restore(); 7252 } 7253 }); 7254 7255 if (scaleLabel.display) { 7256 // Draw the scale label 7257 var scaleLabelX; 7258 var scaleLabelY; 7259 var rotation = 0; 7260 7261 if (isHorizontal) { 7262 scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width 7263 scaleLabelY = options.position === 'bottom' ? me.bottom - (scaleLabelFontSize / 2) : me.top + (scaleLabelFontSize / 2); 7264 } else { 7265 var isLeft = options.position === 'left'; 7266 scaleLabelX = isLeft ? me.left + (scaleLabelFontSize / 2) : me.right - (scaleLabelFontSize / 2); 7267 scaleLabelY = me.top + ((me.bottom - me.top) / 2); 7268 rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; 7269 } 7270 7271 context.save(); 7272 context.translate(scaleLabelX, scaleLabelY); 7273 context.rotate(rotation); 7274 context.textAlign = 'center'; 7275 context.textBaseline = 'middle'; 7276 context.fillStyle = scaleLabelFontColor; // render in correct colour 7277 context.font = scaleLabelFont; 7278 context.fillText(scaleLabel.labelString, 0, 0); 7279 context.restore(); 7280 } 7281 7282 if (gridLines.drawBorder) { 7283 // Draw the line at the edge of the axis 7284 context.lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, 0); 7285 context.strokeStyle = helpers.getValueAtIndexOrDefault(gridLines.color, 0); 7286 var x1 = me.left, 7287 x2 = me.right, 7288 y1 = me.top, 7289 y2 = me.bottom; 7290 7291 var aliasPixel = helpers.aliasPixel(context.lineWidth); 7292 if (isHorizontal) { 7293 y1 = y2 = options.position === 'top' ? me.bottom : me.top; 7294 y1 += aliasPixel; 7295 y2 += aliasPixel; 7296 } else { 7297 x1 = x2 = options.position === 'left' ? me.right : me.left; 7298 x1 += aliasPixel; 7299 x2 += aliasPixel; 7300 } 7301 7302 context.beginPath(); 7303 context.moveTo(x1, y1); 7304 context.lineTo(x2, y2); 7305 context.stroke(); 7306 } 7307 } 7308 }); 7309 }; 7310 7311 },{}],31:[function(require,module,exports){ 7312 "use strict"; 7313 7314 module.exports = function(Chart) { 7315 7316 var helpers = Chart.helpers; 7317 7318 Chart.scaleService = { 7319 // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then 7320 // use the new chart options to grab the correct scale 7321 constructors: {}, 7322 // Use a registration function so that we can move to an ES6 map when we no longer need to support 7323 // old browsers 7324 7325 // Scale config defaults 7326 defaults: {}, 7327 registerScaleType: function(type, scaleConstructor, defaults) { 7328 this.constructors[type] = scaleConstructor; 7329 this.defaults[type] = helpers.clone(defaults); 7330 }, 7331 getScaleConstructor: function(type) { 7332 return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; 7333 }, 7334 getScaleDefaults: function(type) { 7335 // Return the scale defaults merged with the global settings so that we always use the latest ones 7336 return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {}; 7337 }, 7338 updateScaleDefaults: function(type, additions) { 7339 var defaults = this.defaults; 7340 if (defaults.hasOwnProperty(type)) { 7341 defaults[type] = helpers.extend(defaults[type], additions); 7342 } 7343 }, 7344 addScalesToLayout: function(chartInstance) { 7345 // Adds each scale to the chart.boxes array to be sized accordingly 7346 helpers.each(chartInstance.scales, function(scale) { 7347 Chart.layoutService.addBox(chartInstance, scale); 7348 }); 7349 } 7350 }; 7351 }; 7352 },{}],32:[function(require,module,exports){ 7353 "use strict"; 7354 7355 module.exports = function(Chart) { 7356 7357 var helpers = Chart.helpers; 7358 7359 Chart.defaults.global.title = { 7360 display: false, 7361 position: 'top', 7362 fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) 7363 7364 fontStyle: 'bold', 7365 padding: 10, 7366 7367 // actual title 7368 text: '' 7369 }; 7370 7371 var noop = helpers.noop; 7372 Chart.Title = Chart.Element.extend({ 7373 7374 initialize: function(config) { 7375 var me = this; 7376 helpers.extend(me, config); 7377 me.options = helpers.configMerge(Chart.defaults.global.title, config.options); 7378 7379 // Contains hit boxes for each dataset (in dataset order) 7380 me.legendHitBoxes = []; 7381 }, 7382 7383 // These methods are ordered by lifecyle. Utilities then follow. 7384 7385 beforeUpdate: function () { 7386 var chartOpts = this.chart.options; 7387 if (chartOpts && chartOpts.title) { 7388 this.options = helpers.configMerge(Chart.defaults.global.title, chartOpts.title); 7389 } 7390 }, 7391 update: function(maxWidth, maxHeight, margins) { 7392 var me = this; 7393 7394 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 7395 me.beforeUpdate(); 7396 7397 // Absorb the master measurements 7398 me.maxWidth = maxWidth; 7399 me.maxHeight = maxHeight; 7400 me.margins = margins; 7401 7402 // Dimensions 7403 me.beforeSetDimensions(); 7404 me.setDimensions(); 7405 me.afterSetDimensions(); 7406 // Labels 7407 me.beforeBuildLabels(); 7408 me.buildLabels(); 7409 me.afterBuildLabels(); 7410 7411 // Fit 7412 me.beforeFit(); 7413 me.fit(); 7414 me.afterFit(); 7415 // 7416 me.afterUpdate(); 7417 7418 return me.minSize; 7419 7420 }, 7421 afterUpdate: noop, 7422 7423 // 7424 7425 beforeSetDimensions: noop, 7426 setDimensions: function() { 7427 var me = this; 7428 // Set the unconstrained dimension before label rotation 7429 if (me.isHorizontal()) { 7430 // Reset position before calculating rotation 7431 me.width = me.maxWidth; 7432 me.left = 0; 7433 me.right = me.width; 7434 } else { 7435 me.height = me.maxHeight; 7436 7437 // Reset position before calculating rotation 7438 me.top = 0; 7439 me.bottom = me.height; 7440 } 7441 7442 // Reset padding 7443 me.paddingLeft = 0; 7444 me.paddingTop = 0; 7445 me.paddingRight = 0; 7446 me.paddingBottom = 0; 7447 7448 // Reset minSize 7449 me.minSize = { 7450 width: 0, 7451 height: 0 7452 }; 7453 }, 7454 afterSetDimensions: noop, 7455 7456 // 7457 7458 beforeBuildLabels: noop, 7459 buildLabels: noop, 7460 afterBuildLabels: noop, 7461 7462 // 7463 7464 beforeFit: noop, 7465 fit: function() { 7466 7467 var me = this, 7468 ctx = me.ctx, 7469 valueOrDefault = helpers.getValueOrDefault, 7470 opts = me.options, 7471 globalDefaults = Chart.defaults.global, 7472 display = opts.display, 7473 fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize), 7474 minSize = me.minSize; 7475 7476 if (me.isHorizontal()) { 7477 minSize.width = me.maxWidth; // fill all the width 7478 minSize.height = display ? fontSize + (opts.padding * 2) : 0; 7479 } else { 7480 minSize.width = display ? fontSize + (opts.padding * 2) : 0; 7481 minSize.height = me.maxHeight; // fill all the height 7482 } 7483 7484 me.width = minSize.width; 7485 me.height = minSize.height; 7486 7487 }, 7488 afterFit: noop, 7489 7490 // Shared Methods 7491 isHorizontal: function() { 7492 var pos = this.options.position; 7493 return pos === "top" || pos === "bottom"; 7494 }, 7495 7496 // Actualy draw the title block on the canvas 7497 draw: function() { 7498 var me = this, 7499 ctx = me.ctx, 7500 valueOrDefault = helpers.getValueOrDefault, 7501 opts = me.options, 7502 globalDefaults = Chart.defaults.global; 7503 7504 if (opts.display) { 7505 var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize), 7506 fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle), 7507 fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily), 7508 titleFont = helpers.fontString(fontSize, fontStyle, fontFamily), 7509 rotation = 0, 7510 titleX, 7511 titleY, 7512 top = me.top, 7513 left = me.left, 7514 bottom = me.bottom, 7515 right = me.right; 7516 7517 ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour 7518 ctx.font = titleFont; 7519 7520 // Horizontal 7521 if (me.isHorizontal()) { 7522 titleX = left + ((right - left) / 2); // midpoint of the width 7523 titleY = top + ((bottom - top) / 2); // midpoint of the height 7524 } else { 7525 titleX = opts.position === 'left' ? left + (fontSize / 2) : right - (fontSize / 2); 7526 titleY = top + ((bottom - top) / 2); 7527 rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); 7528 } 7529 7530 ctx.save(); 7531 ctx.translate(titleX, titleY); 7532 ctx.rotate(rotation); 7533 ctx.textAlign = 'center'; 7534 ctx.textBaseline = 'middle'; 7535 ctx.fillText(opts.text, 0, 0); 7536 ctx.restore(); 7537 } 7538 } 7539 }); 7540 7541 // Register the title plugin 7542 Chart.plugins.register({ 7543 beforeInit: function(chartInstance) { 7544 var opts = chartInstance.options; 7545 var titleOpts = opts.title; 7546 7547 if (titleOpts) { 7548 chartInstance.titleBlock = new Chart.Title({ 7549 ctx: chartInstance.chart.ctx, 7550 options: titleOpts, 7551 chart: chartInstance 7552 }); 7553 7554 Chart.layoutService.addBox(chartInstance, chartInstance.titleBlock); 7555 } 7556 } 7557 }); 7558 }; 7559 7560 },{}],33:[function(require,module,exports){ 7561 "use strict"; 7562 7563 module.exports = function(Chart) { 7564 7565 var helpers = Chart.helpers; 7566 7567 Chart.defaults.global.tooltips = { 7568 enabled: true, 7569 custom: null, 7570 mode: 'single', 7571 backgroundColor: "rgba(0,0,0,0.8)", 7572 titleFontStyle: "bold", 7573 titleSpacing: 2, 7574 titleMarginBottom: 6, 7575 titleFontColor: "#fff", 7576 titleAlign: "left", 7577 bodySpacing: 2, 7578 bodyFontColor: "#fff", 7579 bodyAlign: "left", 7580 footerFontStyle: "bold", 7581 footerSpacing: 2, 7582 footerMarginTop: 6, 7583 footerFontColor: "#fff", 7584 footerAlign: "left", 7585 yPadding: 6, 7586 xPadding: 6, 7587 yAlign : 'center', 7588 xAlign : 'center', 7589 caretSize: 5, 7590 cornerRadius: 6, 7591 multiKeyBackground: '#fff', 7592 callbacks: { 7593 // Args are: (tooltipItems, data) 7594 beforeTitle: helpers.noop, 7595 title: function(tooltipItems, data) { 7596 // Pick first xLabel for now 7597 var title = ''; 7598 var labels = data.labels; 7599 var labelCount = labels ? labels.length : 0; 7600 7601 if (tooltipItems.length > 0) { 7602 var item = tooltipItems[0]; 7603 7604 if (item.xLabel) { 7605 title = item.xLabel; 7606 } else if (labelCount > 0 && item.index < labelCount) { 7607 title = labels[item.index]; 7608 } 7609 } 7610 7611 return title; 7612 }, 7613 afterTitle: helpers.noop, 7614 7615 // Args are: (tooltipItems, data) 7616 beforeBody: helpers.noop, 7617 7618 // Args are: (tooltipItem, data) 7619 beforeLabel: helpers.noop, 7620 label: function(tooltipItem, data) { 7621 var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; 7622 return datasetLabel + ': ' + tooltipItem.yLabel; 7623 }, 7624 labelColor: function(tooltipItem, chartInstance) { 7625 var meta = chartInstance.getDatasetMeta(tooltipItem.datasetIndex); 7626 var activeElement = meta.data[tooltipItem.index]; 7627 var view = activeElement._view; 7628 return { 7629 borderColor: view.borderColor, 7630 backgroundColor: view.backgroundColor 7631 }; 7632 }, 7633 afterLabel: helpers.noop, 7634 7635 // Args are: (tooltipItems, data) 7636 afterBody: helpers.noop, 7637 7638 // Args are: (tooltipItems, data) 7639 beforeFooter: helpers.noop, 7640 footer: helpers.noop, 7641 afterFooter: helpers.noop 7642 } 7643 }; 7644 7645 // Helper to push or concat based on if the 2nd parameter is an array or not 7646 function pushOrConcat(base, toPush) { 7647 if (toPush) { 7648 if (helpers.isArray(toPush)) { 7649 //base = base.concat(toPush); 7650 Array.prototype.push.apply(base, toPush); 7651 } else { 7652 base.push(toPush); 7653 } 7654 } 7655 7656 return base; 7657 } 7658 7659 function getAveragePosition(elements) { 7660 if (!elements.length) { 7661 return false; 7662 } 7663 7664 var i, len; 7665 var xPositions = []; 7666 var yPositions = []; 7667 7668 for (i = 0, len = elements.length; i < len; ++i) { 7669 var el = elements[i]; 7670 if (el && el.hasValue()){ 7671 var pos = el.tooltipPosition(); 7672 xPositions.push(pos.x); 7673 yPositions.push(pos.y); 7674 } 7675 } 7676 7677 var x = 0, 7678 y = 0; 7679 for (i = 0, len - xPositions.length; i < len; ++i) { 7680 x += xPositions[i]; 7681 y += yPositions[i]; 7682 } 7683 7684 return { 7685 x: Math.round(x / xPositions.length), 7686 y: Math.round(y / xPositions.length) 7687 }; 7688 } 7689 7690 // Private helper to create a tooltip iteam model 7691 // @param element : the chart element (point, arc, bar) to create the tooltip item for 7692 // @return : new tooltip item 7693 function createTooltipItem(element) { 7694 var xScale = element._xScale; 7695 var yScale = element._yScale || element._scale; // handle radar || polarArea charts 7696 var index = element._index, 7697 datasetIndex = element._datasetIndex; 7698 7699 return { 7700 xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', 7701 yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', 7702 index: index, 7703 datasetIndex: datasetIndex 7704 }; 7705 } 7706 7707 Chart.Tooltip = Chart.Element.extend({ 7708 initialize: function() { 7709 var me = this; 7710 var globalDefaults = Chart.defaults.global; 7711 var tooltipOpts = me._options; 7712 var getValueOrDefault = helpers.getValueOrDefault; 7713 7714 helpers.extend(me, { 7715 _model: { 7716 // Positioning 7717 xPadding: tooltipOpts.xPadding, 7718 yPadding: tooltipOpts.yPadding, 7719 xAlign : tooltipOpts.yAlign, 7720 yAlign : tooltipOpts.xAlign, 7721 7722 // Body 7723 bodyFontColor: tooltipOpts.bodyFontColor, 7724 _bodyFontFamily: getValueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), 7725 _bodyFontStyle: getValueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), 7726 _bodyAlign: tooltipOpts.bodyAlign, 7727 bodyFontSize: getValueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), 7728 bodySpacing: tooltipOpts.bodySpacing, 7729 7730 // Title 7731 titleFontColor: tooltipOpts.titleFontColor, 7732 _titleFontFamily: getValueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), 7733 _titleFontStyle: getValueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), 7734 titleFontSize: getValueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), 7735 _titleAlign: tooltipOpts.titleAlign, 7736 titleSpacing: tooltipOpts.titleSpacing, 7737 titleMarginBottom: tooltipOpts.titleMarginBottom, 7738 7739 // Footer 7740 footerFontColor: tooltipOpts.footerFontColor, 7741 _footerFontFamily: getValueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), 7742 _footerFontStyle: getValueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), 7743 footerFontSize: getValueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), 7744 _footerAlign: tooltipOpts.footerAlign, 7745 footerSpacing: tooltipOpts.footerSpacing, 7746 footerMarginTop: tooltipOpts.footerMarginTop, 7747 7748 // Appearance 7749 caretSize: tooltipOpts.caretSize, 7750 cornerRadius: tooltipOpts.cornerRadius, 7751 backgroundColor: tooltipOpts.backgroundColor, 7752 opacity: 0, 7753 legendColorBackground: tooltipOpts.multiKeyBackground 7754 } 7755 }); 7756 }, 7757 7758 // Get the title 7759 // Args are: (tooltipItem, data) 7760 getTitle: function() { 7761 var me = this; 7762 var opts = me._options; 7763 var callbacks = opts.callbacks; 7764 7765 var beforeTitle = callbacks.beforeTitle.apply(me, arguments), 7766 title = callbacks.title.apply(me, arguments), 7767 afterTitle = callbacks.afterTitle.apply(me, arguments); 7768 7769 var lines = []; 7770 lines = pushOrConcat(lines, beforeTitle); 7771 lines = pushOrConcat(lines, title); 7772 lines = pushOrConcat(lines, afterTitle); 7773 7774 return lines; 7775 }, 7776 7777 // Args are: (tooltipItem, data) 7778 getBeforeBody: function() { 7779 var lines = this._options.callbacks.beforeBody.apply(this, arguments); 7780 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; 7781 }, 7782 7783 // Args are: (tooltipItem, data) 7784 getBody: function(tooltipItems, data) { 7785 var me = this; 7786 var callbacks = me._options.callbacks; 7787 var bodyItems = []; 7788 7789 helpers.each(tooltipItems, function(tooltipItem) { 7790 var bodyItem = { 7791 before: [], 7792 lines: [], 7793 after: [] 7794 }; 7795 pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data)); 7796 pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); 7797 pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data)); 7798 7799 bodyItems.push(bodyItem); 7800 }); 7801 7802 return bodyItems; 7803 }, 7804 7805 // Args are: (tooltipItem, data) 7806 getAfterBody: function() { 7807 var lines = this._options.callbacks.afterBody.apply(this, arguments); 7808 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; 7809 }, 7810 7811 // Get the footer and beforeFooter and afterFooter lines 7812 // Args are: (tooltipItem, data) 7813 getFooter: function() { 7814 var me = this; 7815 var callbacks = me._options.callbacks; 7816 7817 var beforeFooter = callbacks.beforeFooter.apply(me, arguments); 7818 var footer = callbacks.footer.apply(me, arguments); 7819 var afterFooter = callbacks.afterFooter.apply(me, arguments); 7820 7821 var lines = []; 7822 lines = pushOrConcat(lines, beforeFooter); 7823 lines = pushOrConcat(lines, footer); 7824 lines = pushOrConcat(lines, afterFooter); 7825 7826 return lines; 7827 }, 7828 7829 update: function(changed) { 7830 var me = this; 7831 var opts = me._options; 7832 var model = me._model; 7833 var active = me._active; 7834 7835 var data = me._data; 7836 var chartInstance = me._chartInstance; 7837 7838 var i, len; 7839 7840 if (active.length) { 7841 model.opacity = 1; 7842 7843 var labelColors = [], 7844 tooltipPosition = getAveragePosition(active); 7845 7846 var tooltipItems = []; 7847 for (i = 0, len = active.length; i < len; ++i) { 7848 tooltipItems.push(createTooltipItem(active[i])); 7849 } 7850 7851 // If the user provided a sorting function, use it to modify the tooltip items 7852 if (opts.itemSort) { 7853 tooltipItems = tooltipItems.sort(opts.itemSort); 7854 } 7855 7856 // If there is more than one item, show color items 7857 if (active.length > 1) { 7858 helpers.each(tooltipItems, function(tooltipItem) { 7859 labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, chartInstance)); 7860 }); 7861 } 7862 7863 // Build the Text Lines 7864 helpers.extend(model, { 7865 title: me.getTitle(tooltipItems, data), 7866 beforeBody: me.getBeforeBody(tooltipItems, data), 7867 body: me.getBody(tooltipItems, data), 7868 afterBody: me.getAfterBody(tooltipItems, data), 7869 footer: me.getFooter(tooltipItems, data), 7870 x: Math.round(tooltipPosition.x), 7871 y: Math.round(tooltipPosition.y), 7872 caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2), 7873 labelColors: labelColors 7874 }); 7875 7876 // We need to determine alignment of 7877 var tooltipSize = me.getTooltipSize(model); 7878 me.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas 7879 7880 helpers.extend(model, me.getBackgroundPoint(model, tooltipSize)); 7881 } else { 7882 me._model.opacity = 0; 7883 } 7884 7885 if (changed && opts.custom) { 7886 opts.custom.call(me, model); 7887 } 7888 7889 return me; 7890 }, 7891 getTooltipSize: function getTooltipSize(vm) { 7892 var ctx = this._chart.ctx; 7893 7894 var size = { 7895 height: vm.yPadding * 2, // Tooltip Padding 7896 width: 0 7897 }; 7898 7899 // Count of all lines in the body 7900 var body = vm.body; 7901 var combinedBodyLength = body.reduce(function(count, bodyItem) { 7902 return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; 7903 }, 0); 7904 combinedBodyLength += vm.beforeBody.length + vm.afterBody.length; 7905 7906 var titleLineCount = vm.title.length; 7907 var footerLineCount = vm.footer.length; 7908 var titleFontSize = vm.titleFontSize, 7909 bodyFontSize = vm.bodyFontSize, 7910 footerFontSize = vm.footerFontSize; 7911 7912 size.height += titleLineCount * titleFontSize; // Title Lines 7913 size.height += (titleLineCount - 1) * vm.titleSpacing; // Title Line Spacing 7914 size.height += titleLineCount ? vm.titleMarginBottom : 0; // Title's bottom Margin 7915 size.height += combinedBodyLength * bodyFontSize; // Body Lines 7916 size.height += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing 7917 size.height += footerLineCount ? vm.footerMarginTop : 0; // Footer Margin 7918 size.height += footerLineCount * (footerFontSize); // Footer Lines 7919 size.height += footerLineCount ? (footerLineCount - 1) * vm.footerSpacing : 0; // Footer Line Spacing 7920 7921 // Title width 7922 var widthPadding = 0; 7923 var maxLineWidth = function(line) { 7924 size.width = Math.max(size.width, ctx.measureText(line).width + widthPadding); 7925 }; 7926 7927 ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); 7928 helpers.each(vm.title, maxLineWidth); 7929 7930 // Body width 7931 ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); 7932 helpers.each(vm.beforeBody.concat(vm.afterBody), maxLineWidth); 7933 7934 // Body lines may include some extra width due to the color box 7935 widthPadding = body.length > 1 ? (bodyFontSize + 2) : 0; 7936 helpers.each(body, function(bodyItem) { 7937 helpers.each(bodyItem.before, maxLineWidth); 7938 helpers.each(bodyItem.lines, maxLineWidth); 7939 helpers.each(bodyItem.after, maxLineWidth); 7940 }); 7941 7942 // Reset back to 0 7943 widthPadding = 0; 7944 7945 // Footer width 7946 ctx.font = helpers.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily); 7947 helpers.each(vm.footer, maxLineWidth); 7948 7949 // Add padding 7950 size.width += 2 * vm.xPadding; 7951 7952 return size; 7953 }, 7954 determineAlignment: function determineAlignment(size) { 7955 var me = this; 7956 var model = me._model; 7957 var chart = me._chart; 7958 var chartArea = me._chartInstance.chartArea; 7959 7960 if (model.y < size.height) { 7961 model.yAlign = 'top'; 7962 } else if (model.y > (chart.height - size.height)) { 7963 model.yAlign = 'bottom'; 7964 } 7965 7966 var lf, rf; // functions to determine left, right alignment 7967 var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart 7968 var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges 7969 var midX = (chartArea.left + chartArea.right) / 2; 7970 var midY = (chartArea.top + chartArea.bottom) / 2; 7971 7972 if (model.yAlign === 'center') { 7973 lf = function(x) { 7974 return x <= midX; 7975 }; 7976 rf = function(x) { 7977 return x > midX; 7978 }; 7979 } else { 7980 lf = function(x) { 7981 return x <= (size.width / 2); 7982 }; 7983 rf = function(x) { 7984 return x >= (chart.width - (size.width / 2)); 7985 }; 7986 } 7987 7988 olf = function(x) { 7989 return x + size.width > chart.width; 7990 }; 7991 orf = function(x) { 7992 return x - size.width < 0; 7993 }; 7994 yf = function(y) { 7995 return y <= midY ? 'top' : 'bottom'; 7996 }; 7997 7998 if (lf(model.x)) { 7999 model.xAlign = 'left'; 8000 8001 // Is tooltip too wide and goes over the right side of the chart.? 8002 if (olf(model.x)) { 8003 model.xAlign = 'center'; 8004 model.yAlign = yf(model.y); 8005 } 8006 } else if (rf(model.x)) { 8007 model.xAlign = 'right'; 8008 8009 // Is tooltip too wide and goes outside left edge of canvas? 8010 if (orf(model.x)) { 8011 model.xAlign = 'center'; 8012 model.yAlign = yf(model.y); 8013 } 8014 } 8015 }, 8016 getBackgroundPoint: function getBackgroundPoint(vm, size) { 8017 // Background Position 8018 var pt = { 8019 x: vm.x, 8020 y: vm.y 8021 }; 8022 8023 var caretSize = vm.caretSize, 8024 caretPadding = vm.caretPadding, 8025 cornerRadius = vm.cornerRadius, 8026 xAlign = vm.xAlign, 8027 yAlign = vm.yAlign, 8028 paddingAndSize = caretSize + caretPadding, 8029 radiusAndPadding = cornerRadius + caretPadding; 8030 8031 if (xAlign === 'right') { 8032 pt.x -= size.width; 8033 } else if (xAlign === 'center') { 8034 pt.x -= (size.width / 2); 8035 } 8036 8037 if (yAlign === 'top') { 8038 pt.y += paddingAndSize; 8039 } else if (yAlign === 'bottom') { 8040 pt.y -= size.height + paddingAndSize; 8041 } else { 8042 pt.y -= (size.height / 2); 8043 } 8044 8045 if (yAlign === 'center') { 8046 if (xAlign === 'left') { 8047 pt.x += paddingAndSize; 8048 } else if (xAlign === 'right') { 8049 pt.x -= paddingAndSize; 8050 } 8051 } else { 8052 if (xAlign === 'left') { 8053 pt.x -= radiusAndPadding; 8054 } else if (xAlign === 'right') { 8055 pt.x += radiusAndPadding; 8056 } 8057 } 8058 8059 return pt; 8060 }, 8061 drawCaret: function drawCaret(tooltipPoint, size, opacity, caretPadding) { 8062 var vm = this._view; 8063 var ctx = this._chart.ctx; 8064 var x1, x2, x3; 8065 var y1, y2, y3; 8066 var caretSize = vm.caretSize; 8067 var cornerRadius = vm.cornerRadius; 8068 var xAlign = vm.xAlign, 8069 yAlign = vm.yAlign; 8070 var ptX = tooltipPoint.x, 8071 ptY = tooltipPoint.y; 8072 var width = size.width, 8073 height = size.height; 8074 8075 if (yAlign === 'center') { 8076 // Left or right side 8077 if (xAlign === 'left') { 8078 x1 = ptX; 8079 x2 = x1 - caretSize; 8080 x3 = x1; 8081 } else { 8082 x1 = ptX + width; 8083 x2 = x1 + caretSize; 8084 x3 = x1; 8085 } 8086 8087 y2 = ptY + (height / 2); 8088 y1 = y2 - caretSize; 8089 y3 = y2 + caretSize; 8090 } else { 8091 if (xAlign === 'left') { 8092 x1 = ptX + cornerRadius; 8093 x2 = x1 + caretSize; 8094 x3 = x2 + caretSize; 8095 } else if (xAlign === 'right') { 8096 x1 = ptX + width - cornerRadius; 8097 x2 = x1 - caretSize; 8098 x3 = x2 - caretSize; 8099 } else { 8100 x2 = ptX + (width / 2); 8101 x1 = x2 - caretSize; 8102 x3 = x2 + caretSize; 8103 } 8104 8105 if (yAlign === 'top') { 8106 y1 = ptY; 8107 y2 = y1 - caretSize; 8108 y3 = y1; 8109 } else { 8110 y1 = ptY + height; 8111 y2 = y1 + caretSize; 8112 y3 = y1; 8113 } 8114 } 8115 8116 var bgColor = helpers.color(vm.backgroundColor); 8117 ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString(); 8118 ctx.beginPath(); 8119 ctx.moveTo(x1, y1); 8120 ctx.lineTo(x2, y2); 8121 ctx.lineTo(x3, y3); 8122 ctx.closePath(); 8123 ctx.fill(); 8124 }, 8125 drawTitle: function drawTitle(pt, vm, ctx, opacity) { 8126 var title = vm.title; 8127 8128 if (title.length) { 8129 ctx.textAlign = vm._titleAlign; 8130 ctx.textBaseline = "top"; 8131 8132 var titleFontSize = vm.titleFontSize, 8133 titleSpacing = vm.titleSpacing; 8134 8135 var titleFontColor = helpers.color(vm.titleFontColor); 8136 ctx.fillStyle = titleFontColor.alpha(opacity * titleFontColor.alpha()).rgbString(); 8137 ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); 8138 8139 var i, len; 8140 for (i = 0, len = title.length; i < len; ++i) { 8141 ctx.fillText(title[i], pt.x, pt.y); 8142 pt.y += titleFontSize + titleSpacing; // Line Height and spacing 8143 8144 if (i + 1 === title.length) { 8145 pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing 8146 } 8147 } 8148 } 8149 }, 8150 drawBody: function drawBody(pt, vm, ctx, opacity) { 8151 var bodyFontSize = vm.bodyFontSize; 8152 var bodySpacing = vm.bodySpacing; 8153 var body = vm.body; 8154 8155 ctx.textAlign = vm._bodyAlign; 8156 ctx.textBaseline = "top"; 8157 8158 var bodyFontColor = helpers.color(vm.bodyFontColor); 8159 var textColor = bodyFontColor.alpha(opacity * bodyFontColor.alpha()).rgbString(); 8160 ctx.fillStyle = textColor; 8161 ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); 8162 8163 // Before Body 8164 var xLinePadding = 0; 8165 var fillLineOfText = function(line) { 8166 ctx.fillText(line, pt.x + xLinePadding, pt.y); 8167 pt.y += bodyFontSize + bodySpacing; 8168 }; 8169 8170 // Before body lines 8171 helpers.each(vm.beforeBody, fillLineOfText); 8172 8173 var drawColorBoxes = body.length > 1; 8174 xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0; 8175 8176 // Draw body lines now 8177 helpers.each(body, function(bodyItem, i) { 8178 helpers.each(bodyItem.before, fillLineOfText); 8179 8180 helpers.each(bodyItem.lines, function(line) { 8181 // Draw Legend-like boxes if needed 8182 if (drawColorBoxes) { 8183 // Fill a white rect so that colours merge nicely if the opacity is < 1 8184 ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString(); 8185 ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); 8186 8187 // Border 8188 ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString(); 8189 ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); 8190 8191 // Inner square 8192 ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString(); 8193 ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); 8194 8195 ctx.fillStyle = textColor; 8196 } 8197 8198 fillLineOfText(line); 8199 }); 8200 8201 helpers.each(bodyItem.after, fillLineOfText); 8202 }); 8203 8204 // Reset back to 0 for after body 8205 xLinePadding = 0; 8206 8207 // After body lines 8208 helpers.each(vm.afterBody, fillLineOfText); 8209 pt.y -= bodySpacing; // Remove last body spacing 8210 }, 8211 drawFooter: function drawFooter(pt, vm, ctx, opacity) { 8212 var footer = vm.footer; 8213 8214 if (footer.length) { 8215 pt.y += vm.footerMarginTop; 8216 8217 ctx.textAlign = vm._footerAlign; 8218 ctx.textBaseline = "top"; 8219 8220 var footerFontColor = helpers.color(vm.footerFontColor); 8221 ctx.fillStyle = footerFontColor.alpha(opacity * footerFontColor.alpha()).rgbString(); 8222 ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); 8223 8224 helpers.each(footer, function(line) { 8225 ctx.fillText(line, pt.x, pt.y); 8226 pt.y += vm.footerFontSize + vm.footerSpacing; 8227 }); 8228 } 8229 }, 8230 draw: function draw() { 8231 var ctx = this._chart.ctx; 8232 var vm = this._view; 8233 8234 if (vm.opacity === 0) { 8235 return; 8236 } 8237 8238 var tooltipSize = this.getTooltipSize(vm); 8239 var pt = { 8240 x: vm.x, 8241 y: vm.y 8242 }; 8243 8244 // IE11/Edge does not like very small opacities, so snap to 0 8245 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; 8246 8247 if (this._options.enabled) { 8248 // Draw Background 8249 var bgColor = helpers.color(vm.backgroundColor); 8250 ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString(); 8251 helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius); 8252 ctx.fill(); 8253 8254 // Draw Caret 8255 this.drawCaret(pt, tooltipSize, opacity, vm.caretPadding); 8256 8257 // Draw Title, Body, and Footer 8258 pt.x += vm.xPadding; 8259 pt.y += vm.yPadding; 8260 8261 // Titles 8262 this.drawTitle(pt, vm, ctx, opacity); 8263 8264 // Body 8265 this.drawBody(pt, vm, ctx, opacity); 8266 8267 // Footer 8268 this.drawFooter(pt, vm, ctx, opacity); 8269 } 8270 } 8271 }); 8272 }; 8273 8274 },{}],34:[function(require,module,exports){ 8275 "use strict"; 8276 8277 module.exports = function(Chart, moment) { 8278 8279 var helpers = Chart.helpers, 8280 globalOpts = Chart.defaults.global; 8281 8282 globalOpts.elements.arc = { 8283 backgroundColor: globalOpts.defaultColor, 8284 borderColor: "#fff", 8285 borderWidth: 2 8286 }; 8287 8288 Chart.elements.Arc = Chart.Element.extend({ 8289 inLabelRange: function(mouseX) { 8290 var vm = this._view; 8291 8292 if (vm) { 8293 return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); 8294 } else { 8295 return false; 8296 } 8297 }, 8298 inRange: function(chartX, chartY) { 8299 var vm = this._view; 8300 8301 if (vm) { 8302 var pointRelativePosition = helpers.getAngleFromPoint(vm, { 8303 x: chartX, 8304 y: chartY 8305 }), 8306 angle = pointRelativePosition.angle, 8307 distance = pointRelativePosition.distance; 8308 8309 //Sanitise angle range 8310 var startAngle = vm.startAngle; 8311 var endAngle = vm.endAngle; 8312 while (endAngle < startAngle) { 8313 endAngle += 2.0 * Math.PI; 8314 } 8315 while (angle > endAngle) { 8316 angle -= 2.0 * Math.PI; 8317 } 8318 while (angle < startAngle) { 8319 angle += 2.0 * Math.PI; 8320 } 8321 8322 //Check if within the range of the open/close angle 8323 var betweenAngles = (angle >= startAngle && angle <= endAngle), 8324 withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); 8325 8326 return (betweenAngles && withinRadius); 8327 } else { 8328 return false; 8329 } 8330 }, 8331 tooltipPosition: function() { 8332 var vm = this._view; 8333 8334 var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), 8335 rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; 8336 return { 8337 x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), 8338 y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) 8339 }; 8340 }, 8341 draw: function() { 8342 8343 var ctx = this._chart.ctx, 8344 vm = this._view, 8345 sA = vm.startAngle, 8346 eA = vm.endAngle; 8347 8348 ctx.beginPath(); 8349 8350 ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); 8351 ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); 8352 8353 ctx.closePath(); 8354 ctx.strokeStyle = vm.borderColor; 8355 ctx.lineWidth = vm.borderWidth; 8356 8357 ctx.fillStyle = vm.backgroundColor; 8358 8359 ctx.fill(); 8360 ctx.lineJoin = 'bevel'; 8361 8362 if (vm.borderWidth) { 8363 ctx.stroke(); 8364 } 8365 } 8366 }); 8367 }; 8368 8369 },{}],35:[function(require,module,exports){ 8370 "use strict"; 8371 8372 module.exports = function(Chart) { 8373 8374 var helpers = Chart.helpers; 8375 var globalDefaults = Chart.defaults.global; 8376 8377 Chart.defaults.global.elements.line = { 8378 tension: 0.4, 8379 backgroundColor: globalDefaults.defaultColor, 8380 borderWidth: 3, 8381 borderColor: globalDefaults.defaultColor, 8382 borderCapStyle: 'butt', 8383 borderDash: [], 8384 borderDashOffset: 0.0, 8385 borderJoinStyle: 'miter', 8386 fill: true // do we fill in the area between the line and its base axis 8387 }; 8388 8389 Chart.elements.Line = Chart.Element.extend({ 8390 lineToNextPoint: function(previousPoint, point, nextPoint, skipHandler, previousSkipHandler) { 8391 var me = this; 8392 var ctx = me._chart.ctx; 8393 var spanGaps = me._view ? me._view.spanGaps : false; 8394 8395 if (point._view.skip && !spanGaps) { 8396 skipHandler.call(me, previousPoint, point, nextPoint); 8397 } else if (previousPoint._view.skip && !spanGaps) { 8398 previousSkipHandler.call(me, previousPoint, point, nextPoint); 8399 } else if (point._view.tension === 0) { 8400 ctx.lineTo(point._view.x, point._view.y); 8401 } else { 8402 // Line between points 8403 ctx.bezierCurveTo( 8404 previousPoint._view.controlPointNextX, 8405 previousPoint._view.controlPointNextY, 8406 point._view.controlPointPreviousX, 8407 point._view.controlPointPreviousY, 8408 point._view.x, 8409 point._view.y 8410 ); 8411 } 8412 }, 8413 8414 draw: function() { 8415 var me = this; 8416 8417 var vm = me._view; 8418 var ctx = me._chart.ctx; 8419 var first = me._children[0]; 8420 var last = me._children[me._children.length - 1]; 8421 8422 function loopBackToStart(drawLineToCenter) { 8423 if (!first._view.skip && !last._view.skip) { 8424 // Draw a bezier line from last to first 8425 ctx.bezierCurveTo( 8426 last._view.controlPointNextX, 8427 last._view.controlPointNextY, 8428 first._view.controlPointPreviousX, 8429 first._view.controlPointPreviousY, 8430 first._view.x, 8431 first._view.y 8432 ); 8433 } else if (drawLineToCenter) { 8434 // Go to center 8435 ctx.lineTo(me._view.scaleZero.x, me._view.scaleZero.y); 8436 } 8437 } 8438 8439 ctx.save(); 8440 8441 // If we had points and want to fill this line, do so. 8442 if (me._children.length > 0 && vm.fill) { 8443 // Draw the background first (so the border is always on top) 8444 ctx.beginPath(); 8445 8446 helpers.each(me._children, function(point, index) { 8447 var previous = helpers.previousItem(me._children, index); 8448 var next = helpers.nextItem(me._children, index); 8449 8450 // First point moves to it's starting position no matter what 8451 if (index === 0) { 8452 if (me._loop) { 8453 ctx.moveTo(vm.scaleZero.x, vm.scaleZero.y); 8454 } else { 8455 ctx.moveTo(point._view.x, vm.scaleZero); 8456 } 8457 8458 if (point._view.skip) { 8459 if (!me._loop) { 8460 ctx.moveTo(next._view.x, me._view.scaleZero); 8461 } 8462 } else { 8463 ctx.lineTo(point._view.x, point._view.y); 8464 } 8465 } else { 8466 me.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) { 8467 if (me._loop) { 8468 // Go to center 8469 ctx.lineTo(me._view.scaleZero.x, me._view.scaleZero.y); 8470 } else { 8471 ctx.lineTo(previousPoint._view.x, me._view.scaleZero); 8472 ctx.moveTo(nextPoint._view.x, me._view.scaleZero); 8473 } 8474 }, function(previousPoint, point) { 8475 // If we skipped the last point, draw a line to ourselves so that the fill is nice 8476 ctx.lineTo(point._view.x, point._view.y); 8477 }); 8478 } 8479 }, me); 8480 8481 // For radial scales, loop back around to the first point 8482 if (me._loop) { 8483 loopBackToStart(true); 8484 } else { 8485 //Round off the line by going to the base of the chart, back to the start, then fill. 8486 ctx.lineTo(me._children[me._children.length - 1]._view.x, vm.scaleZero); 8487 ctx.lineTo(me._children[0]._view.x, vm.scaleZero); 8488 } 8489 8490 ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor; 8491 ctx.closePath(); 8492 ctx.fill(); 8493 } 8494 8495 var globalOptionLineElements = globalDefaults.elements.line; 8496 // Now draw the line between all the points with any borders 8497 ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; 8498 8499 // IE 9 and 10 do not support line dash 8500 if (ctx.setLineDash) { 8501 ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); 8502 } 8503 8504 ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; 8505 ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; 8506 ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; 8507 ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; 8508 ctx.beginPath(); 8509 8510 helpers.each(me._children, function(point, index) { 8511 var previous = helpers.previousItem(me._children, index); 8512 var next = helpers.nextItem(me._children, index); 8513 8514 if (index === 0) { 8515 ctx.moveTo(point._view.x, point._view.y); 8516 } else { 8517 me.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) { 8518 ctx.moveTo(nextPoint._view.x, nextPoint._view.y); 8519 }, function(previousPoint, point) { 8520 // If we skipped the last point, move up to our point preventing a line from being drawn 8521 ctx.moveTo(point._view.x, point._view.y); 8522 }); 8523 } 8524 }, me); 8525 8526 if (me._loop && me._children.length > 0) { 8527 loopBackToStart(); 8528 } 8529 8530 ctx.stroke(); 8531 ctx.restore(); 8532 } 8533 }); 8534 }; 8535 },{}],36:[function(require,module,exports){ 8536 "use strict"; 8537 8538 module.exports = function(Chart) { 8539 8540 var helpers = Chart.helpers, 8541 globalOpts = Chart.defaults.global, 8542 defaultColor = globalOpts.defaultColor; 8543 8544 globalOpts.elements.point = { 8545 radius: 3, 8546 pointStyle: 'circle', 8547 backgroundColor: defaultColor, 8548 borderWidth: 1, 8549 borderColor: defaultColor, 8550 // Hover 8551 hitRadius: 1, 8552 hoverRadius: 4, 8553 hoverBorderWidth: 1 8554 }; 8555 8556 Chart.elements.Point = Chart.Element.extend({ 8557 inRange: function(mouseX, mouseY) { 8558 var vm = this._view; 8559 return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; 8560 }, 8561 inLabelRange: function(mouseX) { 8562 var vm = this._view; 8563 return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; 8564 }, 8565 tooltipPosition: function() { 8566 var vm = this._view; 8567 return { 8568 x: vm.x, 8569 y: vm.y, 8570 padding: vm.radius + vm.borderWidth 8571 }; 8572 }, 8573 draw: function() { 8574 var vm = this._view; 8575 var ctx = this._chart.ctx; 8576 var pointStyle = vm.pointStyle; 8577 var radius = vm.radius; 8578 var x = vm.x; 8579 var y = vm.y; 8580 var type, edgeLength, xOffset, yOffset, height, size; 8581 8582 if (vm.skip) { 8583 return; 8584 } 8585 8586 if (typeof pointStyle === 'object') { 8587 type = pointStyle.toString(); 8588 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { 8589 ctx.drawImage(pointStyle, x - pointStyle.width / 2, y - pointStyle.height / 2); 8590 return; 8591 } 8592 } 8593 8594 if (isNaN(radius) || radius <= 0) { 8595 return; 8596 } 8597 8598 ctx.strokeStyle = vm.borderColor || defaultColor; 8599 ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth); 8600 ctx.fillStyle = vm.backgroundColor || defaultColor; 8601 8602 switch (pointStyle) { 8603 // Default includes circle 8604 default: 8605 ctx.beginPath(); 8606 ctx.arc(x, y, radius, 0, Math.PI * 2); 8607 ctx.closePath(); 8608 ctx.fill(); 8609 break; 8610 case 'triangle': 8611 ctx.beginPath(); 8612 edgeLength = 3 * radius / Math.sqrt(3); 8613 height = edgeLength * Math.sqrt(3) / 2; 8614 ctx.moveTo(x - edgeLength / 2, y + height / 3); 8615 ctx.lineTo(x + edgeLength / 2, y + height / 3); 8616 ctx.lineTo(x, y - 2 * height / 3); 8617 ctx.closePath(); 8618 ctx.fill(); 8619 break; 8620 case 'rect': 8621 size = 1 / Math.SQRT2 * radius; 8622 ctx.fillRect(x - size, y - size, 2 * size, 2 * size); 8623 ctx.strokeRect(x - size, y - size, 2 * size, 2 * size); 8624 break; 8625 case 'rectRot': 8626 size = 1 / Math.SQRT2 * radius; 8627 ctx.beginPath(); 8628 ctx.moveTo(x - size, y); 8629 ctx.lineTo(x, y + size); 8630 ctx.lineTo(x + size, y); 8631 ctx.lineTo(x, y - size); 8632 ctx.closePath(); 8633 ctx.fill(); 8634 break; 8635 case 'cross': 8636 ctx.beginPath(); 8637 ctx.moveTo(x, y + radius); 8638 ctx.lineTo(x, y - radius); 8639 ctx.moveTo(x - radius, y); 8640 ctx.lineTo(x + radius, y); 8641 ctx.closePath(); 8642 break; 8643 case 'crossRot': 8644 ctx.beginPath(); 8645 xOffset = Math.cos(Math.PI / 4) * radius; 8646 yOffset = Math.sin(Math.PI / 4) * radius; 8647 ctx.moveTo(x - xOffset, y - yOffset); 8648 ctx.lineTo(x + xOffset, y + yOffset); 8649 ctx.moveTo(x - xOffset, y + yOffset); 8650 ctx.lineTo(x + xOffset, y - yOffset); 8651 ctx.closePath(); 8652 break; 8653 case 'star': 8654 ctx.beginPath(); 8655 ctx.moveTo(x, y + radius); 8656 ctx.lineTo(x, y - radius); 8657 ctx.moveTo(x - radius, y); 8658 ctx.lineTo(x + radius, y); 8659 xOffset = Math.cos(Math.PI / 4) * radius; 8660 yOffset = Math.sin(Math.PI / 4) * radius; 8661 ctx.moveTo(x - xOffset, y - yOffset); 8662 ctx.lineTo(x + xOffset, y + yOffset); 8663 ctx.moveTo(x - xOffset, y + yOffset); 8664 ctx.lineTo(x + xOffset, y - yOffset); 8665 ctx.closePath(); 8666 break; 8667 case 'line': 8668 ctx.beginPath(); 8669 ctx.moveTo(x - radius, y); 8670 ctx.lineTo(x + radius, y); 8671 ctx.closePath(); 8672 break; 8673 case 'dash': 8674 ctx.beginPath(); 8675 ctx.moveTo(x, y); 8676 ctx.lineTo(x + radius, y); 8677 ctx.closePath(); 8678 break; 8679 } 8680 8681 ctx.stroke(); 8682 } 8683 }); 8684 }; 8685 8686 },{}],37:[function(require,module,exports){ 8687 "use strict"; 8688 8689 module.exports = function(Chart) { 8690 8691 var helpers = Chart.helpers, 8692 globalOpts = Chart.defaults.global; 8693 8694 globalOpts.elements.rectangle = { 8695 backgroundColor: globalOpts.defaultColor, 8696 borderWidth: 0, 8697 borderColor: globalOpts.defaultColor, 8698 borderSkipped: 'bottom' 8699 }; 8700 8701 Chart.elements.Rectangle = Chart.Element.extend({ 8702 draw: function() { 8703 var ctx = this._chart.ctx; 8704 var vm = this._view; 8705 8706 var halfWidth = vm.width / 2, 8707 leftX = vm.x - halfWidth, 8708 rightX = vm.x + halfWidth, 8709 top = vm.base - (vm.base - vm.y), 8710 halfStroke = vm.borderWidth / 2; 8711 8712 // Canvas doesn't allow us to stroke inside the width so we can 8713 // adjust the sizes to fit if we're setting a stroke on the line 8714 if (vm.borderWidth) { 8715 leftX += halfStroke; 8716 rightX -= halfStroke; 8717 top += halfStroke; 8718 } 8719 8720 ctx.beginPath(); 8721 ctx.fillStyle = vm.backgroundColor; 8722 ctx.strokeStyle = vm.borderColor; 8723 ctx.lineWidth = vm.borderWidth; 8724 8725 // Corner points, from bottom-left to bottom-right clockwise 8726 // | 1 2 | 8727 // | 0 3 | 8728 var corners = [ 8729 [leftX, vm.base], 8730 [leftX, top], 8731 [rightX, top], 8732 [rightX, vm.base] 8733 ]; 8734 8735 // Find first (starting) corner with fallback to 'bottom' 8736 var borders = ['bottom', 'left', 'top', 'right']; 8737 var startCorner = borders.indexOf(vm.borderSkipped, 0); 8738 if (startCorner === -1) 8739 startCorner = 0; 8740 8741 function cornerAt(index) { 8742 return corners[(startCorner + index) % 4]; 8743 } 8744 8745 // Draw rectangle from 'startCorner' 8746 ctx.moveTo.apply(ctx, cornerAt(0)); 8747 for (var i = 1; i < 4; i++) 8748 ctx.lineTo.apply(ctx, cornerAt(i)); 8749 8750 ctx.fill(); 8751 if (vm.borderWidth) { 8752 ctx.stroke(); 8753 } 8754 }, 8755 height: function() { 8756 var vm = this._view; 8757 return vm.base - vm.y; 8758 }, 8759 inRange: function(mouseX, mouseY) { 8760 var vm = this._view; 8761 return vm ? 8762 (vm.y < vm.base ? 8763 (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base) : 8764 (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y)) : 8765 false; 8766 }, 8767 inLabelRange: function(mouseX) { 8768 var vm = this._view; 8769 return vm ? (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) : false; 8770 }, 8771 tooltipPosition: function() { 8772 var vm = this._view; 8773 return { 8774 x: vm.x, 8775 y: vm.y 8776 }; 8777 } 8778 }); 8779 8780 }; 8781 },{}],38:[function(require,module,exports){ 8782 "use strict"; 8783 8784 module.exports = function(Chart) { 8785 8786 var helpers = Chart.helpers; 8787 // Default config for a category scale 8788 var defaultConfig = { 8789 position: "bottom" 8790 }; 8791 8792 var DatasetScale = Chart.Scale.extend({ 8793 // Implement this so that 8794 determineDataLimits: function() { 8795 var me = this; 8796 me.minIndex = 0; 8797 me.maxIndex = me.chart.data.labels.length - 1; 8798 var findIndex; 8799 8800 if (me.options.ticks.min !== undefined) { 8801 // user specified min value 8802 findIndex = helpers.indexOf(me.chart.data.labels, me.options.ticks.min); 8803 me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; 8804 } 8805 8806 if (me.options.ticks.max !== undefined) { 8807 // user specified max value 8808 findIndex = helpers.indexOf(me.chart.data.labels, me.options.ticks.max); 8809 me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; 8810 } 8811 8812 me.min = me.chart.data.labels[me.minIndex]; 8813 me.max = me.chart.data.labels[me.maxIndex]; 8814 }, 8815 8816 buildTicks: function(index) { 8817 var me = this; 8818 // If we are viewing some subset of labels, slice the original array 8819 me.ticks = (me.minIndex === 0 && me.maxIndex === me.chart.data.labels.length - 1) ? me.chart.data.labels : me.chart.data.labels.slice(me.minIndex, me.maxIndex + 1); 8820 }, 8821 8822 getLabelForIndex: function(index, datasetIndex) { 8823 return this.ticks[index]; 8824 }, 8825 8826 // Used to get data value locations. Value can either be an index or a numerical value 8827 getPixelForValue: function(value, index, datasetIndex, includeOffset) { 8828 var me = this; 8829 // 1 is added because we need the length but we have the indexes 8830 var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); 8831 8832 if (me.isHorizontal()) { 8833 var innerWidth = me.width - (me.paddingLeft + me.paddingRight); 8834 var valueWidth = innerWidth / offsetAmt; 8835 var widthOffset = (valueWidth * (index - me.minIndex)) + me.paddingLeft; 8836 8837 if (me.options.gridLines.offsetGridLines && includeOffset) { 8838 widthOffset += (valueWidth / 2); 8839 } 8840 8841 return me.left + Math.round(widthOffset); 8842 } else { 8843 var innerHeight = me.height - (me.paddingTop + me.paddingBottom); 8844 var valueHeight = innerHeight / offsetAmt; 8845 var heightOffset = (valueHeight * (index - me.minIndex)) + me.paddingTop; 8846 8847 if (me.options.gridLines.offsetGridLines && includeOffset) { 8848 heightOffset += (valueHeight / 2); 8849 } 8850 8851 return me.top + Math.round(heightOffset); 8852 } 8853 }, 8854 getPixelForTick: function(index, includeOffset) { 8855 return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset); 8856 }, 8857 getValueForPixel: function(pixel) { 8858 var me = this; 8859 var value; 8860 var offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); 8861 var horz = me.isHorizontal(); 8862 var innerDimension = horz ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); 8863 var valueDimension = innerDimension / offsetAmt; 8864 8865 if (me.options.gridLines.offsetGridLines) { 8866 pixel -= (valueDimension / 2); 8867 } 8868 pixel -= horz ? me.paddingLeft : me.paddingTop; 8869 8870 if (pixel <= 0) { 8871 value = 0; 8872 } else { 8873 value = Math.round(pixel / valueDimension); 8874 } 8875 8876 return value; 8877 } 8878 }); 8879 8880 Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig); 8881 8882 }; 8883 },{}],39:[function(require,module,exports){ 8884 "use strict"; 8885 8886 module.exports = function(Chart) { 8887 8888 var helpers = Chart.helpers; 8889 8890 var defaultConfig = { 8891 position: "left", 8892 ticks: { 8893 callback: function(tickValue, index, ticks) { 8894 // If we have lots of ticks, don't use the ones 8895 var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; 8896 8897 // If we have a number like 2.5 as the delta, figure out how many decimal places we need 8898 if (Math.abs(delta) > 1) { 8899 if (tickValue !== Math.floor(tickValue)) { 8900 // not an integer 8901 delta = tickValue - Math.floor(tickValue); 8902 } 8903 } 8904 8905 var logDelta = helpers.log10(Math.abs(delta)); 8906 var tickString = ''; 8907 8908 if (tickValue !== 0) { 8909 var numDecimal = -1 * Math.floor(logDelta); 8910 numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places 8911 tickString = tickValue.toFixed(numDecimal); 8912 } else { 8913 tickString = '0'; // never show decimal places for 0 8914 } 8915 8916 return tickString; 8917 } 8918 } 8919 }; 8920 8921 var LinearScale = Chart.LinearScaleBase.extend({ 8922 determineDataLimits: function() { 8923 var me = this; 8924 var opts = me.options; 8925 var tickOpts = opts.ticks; 8926 var chart = me.chart; 8927 var data = chart.data; 8928 var datasets = data.datasets; 8929 var isHorizontal = me.isHorizontal(); 8930 8931 function IDMatches(meta) { 8932 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; 8933 } 8934 8935 // First Calculate the range 8936 me.min = null; 8937 me.max = null; 8938 8939 if (opts.stacked) { 8940 var valuesPerType = {}; 8941 var hasPositiveValues = false; 8942 var hasNegativeValues = false; 8943 8944 helpers.each(datasets, function(dataset, datasetIndex) { 8945 var meta = chart.getDatasetMeta(datasetIndex); 8946 if (valuesPerType[meta.type] === undefined) { 8947 valuesPerType[meta.type] = { 8948 positiveValues: [], 8949 negativeValues: [] 8950 }; 8951 } 8952 8953 // Store these per type 8954 var positiveValues = valuesPerType[meta.type].positiveValues; 8955 var negativeValues = valuesPerType[meta.type].negativeValues; 8956 8957 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 8958 helpers.each(dataset.data, function(rawValue, index) { 8959 var value = +me.getRightValue(rawValue); 8960 if (isNaN(value) || meta.data[index].hidden) { 8961 return; 8962 } 8963 8964 positiveValues[index] = positiveValues[index] || 0; 8965 negativeValues[index] = negativeValues[index] || 0; 8966 8967 if (opts.relativePoints) { 8968 positiveValues[index] = 100; 8969 } else { 8970 if (value < 0) { 8971 hasNegativeValues = true; 8972 negativeValues[index] += value; 8973 } else { 8974 hasPositiveValues = true; 8975 positiveValues[index] += value; 8976 } 8977 } 8978 }); 8979 } 8980 }); 8981 8982 helpers.each(valuesPerType, function(valuesForType) { 8983 var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); 8984 var minVal = helpers.min(values); 8985 var maxVal = helpers.max(values); 8986 me.min = me.min === null ? minVal : Math.min(me.min, minVal); 8987 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); 8988 }); 8989 8990 } else { 8991 helpers.each(datasets, function(dataset, datasetIndex) { 8992 var meta = chart.getDatasetMeta(datasetIndex); 8993 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 8994 helpers.each(dataset.data, function(rawValue, index) { 8995 var value = +me.getRightValue(rawValue); 8996 if (isNaN(value) || meta.data[index].hidden) { 8997 return; 8998 } 8999 9000 if (me.min === null) { 9001 me.min = value; 9002 } else if (value < me.min) { 9003 me.min = value; 9004 } 9005 9006 if (me.max === null) { 9007 me.max = value; 9008 } else if (value > me.max) { 9009 me.max = value; 9010 } 9011 }); 9012 } 9013 }); 9014 } 9015 9016 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero 9017 this.handleTickRangeOptions(); 9018 }, 9019 getTickLimit: function() { 9020 var maxTicks; 9021 var me = this; 9022 var tickOpts = me.options.ticks; 9023 9024 if (me.isHorizontal()) { 9025 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); 9026 } else { 9027 // The factor of 2 used to scale the font size has been experimentally determined. 9028 var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize); 9029 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); 9030 } 9031 9032 return maxTicks; 9033 }, 9034 // Called after the ticks are built. We need 9035 handleDirectionalChanges: function() { 9036 if (!this.isHorizontal()) { 9037 // We are in a vertical orientation. The top value is the highest. So reverse the array 9038 this.ticks.reverse(); 9039 } 9040 }, 9041 getLabelForIndex: function(index, datasetIndex) { 9042 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); 9043 }, 9044 // Utils 9045 getPixelForValue: function(value, index, datasetIndex, includeOffset) { 9046 // This must be called after fit has been run so that 9047 // this.left, this.top, this.right, and this.bottom have been defined 9048 var me = this; 9049 var paddingLeft = me.paddingLeft; 9050 var paddingBottom = me.paddingBottom; 9051 var start = me.start; 9052 9053 var rightValue = +me.getRightValue(value); 9054 var pixel; 9055 var innerDimension; 9056 var range = me.end - start; 9057 9058 if (me.isHorizontal()) { 9059 innerDimension = me.width - (paddingLeft + me.paddingRight); 9060 pixel = me.left + (innerDimension / range * (rightValue - start)); 9061 return Math.round(pixel + paddingLeft); 9062 } else { 9063 innerDimension = me.height - (me.paddingTop + paddingBottom); 9064 pixel = (me.bottom - paddingBottom) - (innerDimension / range * (rightValue - start)); 9065 return Math.round(pixel); 9066 } 9067 }, 9068 getValueForPixel: function(pixel) { 9069 var me = this; 9070 var isHorizontal = me.isHorizontal(); 9071 var paddingLeft = me.paddingLeft; 9072 var paddingBottom = me.paddingBottom; 9073 var innerDimension = isHorizontal ? me.width - (paddingLeft + me.paddingRight) : me.height - (me.paddingTop + paddingBottom); 9074 var offset = (isHorizontal ? pixel - me.left - paddingLeft : me.bottom - paddingBottom - pixel) / innerDimension; 9075 return me.start + ((me.end - me.start) * offset); 9076 }, 9077 getPixelForTick: function(index, includeOffset) { 9078 return this.getPixelForValue(this.ticksAsNumbers[index], null, null, includeOffset); 9079 } 9080 }); 9081 Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig); 9082 9083 }; 9084 },{}],40:[function(require,module,exports){ 9085 "use strict"; 9086 9087 module.exports = function(Chart) { 9088 9089 var helpers = Chart.helpers, 9090 noop = helpers.noop; 9091 9092 Chart.LinearScaleBase = Chart.Scale.extend({ 9093 handleTickRangeOptions: function() { 9094 var me = this; 9095 var opts = me.options; 9096 var tickOpts = opts.ticks; 9097 9098 // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, 9099 // do nothing since that would make the chart weird. If the user really wants a weird chart 9100 // axis, they can manually override it 9101 if (tickOpts.beginAtZero) { 9102 var minSign = helpers.sign(me.min); 9103 var maxSign = helpers.sign(me.max); 9104 9105 if (minSign < 0 && maxSign < 0) { 9106 // move the top up to 0 9107 me.max = 0; 9108 } else if (minSign > 0 && maxSign > 0) { 9109 // move the botttom down to 0 9110 me.min = 0; 9111 } 9112 } 9113 9114 if (tickOpts.min !== undefined) { 9115 me.min = tickOpts.min; 9116 } else if (tickOpts.suggestedMin !== undefined) { 9117 me.min = Math.min(me.min, tickOpts.suggestedMin); 9118 } 9119 9120 if (tickOpts.max !== undefined) { 9121 me.max = tickOpts.max; 9122 } else if (tickOpts.suggestedMax !== undefined) { 9123 me.max = Math.max(me.max, tickOpts.suggestedMax); 9124 } 9125 9126 if (me.min === me.max) { 9127 me.max++; 9128 9129 if (!tickOpts.beginAtZero) { 9130 me.min--; 9131 } 9132 } 9133 }, 9134 getTickLimit: noop, 9135 handleDirectionalChanges: noop, 9136 9137 buildTicks: function() { 9138 var me = this; 9139 var opts = me.options; 9140 var tickOpts = opts.ticks; 9141 var getValueOrDefault = helpers.getValueOrDefault; 9142 var isHorizontal = me.isHorizontal(); 9143 9144 var ticks = me.ticks = []; 9145 9146 // Figure out what the max number of ticks we can support it is based on the size of 9147 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 9148 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on 9149 // the graph 9150 9151 var maxTicks = me.getTickLimit(); 9152 9153 // Make sure we always have at least 2 ticks 9154 maxTicks = Math.max(2, maxTicks); 9155 9156 // To get a "nice" value for the tick spacing, we will use the appropriately named 9157 // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks 9158 // for details. 9159 9160 var spacing; 9161 var fixedStepSizeSet = (tickOpts.fixedStepSize && tickOpts.fixedStepSize > 0) || (tickOpts.stepSize && tickOpts.stepSize > 0); 9162 if (fixedStepSizeSet) { 9163 spacing = getValueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize); 9164 } else { 9165 var niceRange = helpers.niceNum(me.max - me.min, false); 9166 spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); 9167 } 9168 var niceMin = Math.floor(me.min / spacing) * spacing; 9169 var niceMax = Math.ceil(me.max / spacing) * spacing; 9170 var numSpaces = (niceMax - niceMin) / spacing; 9171 9172 // If very close to our rounded value, use it. 9173 if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { 9174 numSpaces = Math.round(numSpaces); 9175 } else { 9176 numSpaces = Math.ceil(numSpaces); 9177 } 9178 9179 // Put the values into the ticks array 9180 ticks.push(tickOpts.min !== undefined ? tickOpts.min : niceMin); 9181 for (var j = 1; j < numSpaces; ++j) { 9182 ticks.push(niceMin + (j * spacing)); 9183 } 9184 ticks.push(tickOpts.max !== undefined ? tickOpts.max : niceMax); 9185 9186 me.handleDirectionalChanges(); 9187 9188 // At this point, we need to update our max and min given the tick values since we have expanded the 9189 // range of the scale 9190 me.max = helpers.max(ticks); 9191 me.min = helpers.min(ticks); 9192 9193 if (tickOpts.reverse) { 9194 ticks.reverse(); 9195 9196 me.start = me.max; 9197 me.end = me.min; 9198 } else { 9199 me.start = me.min; 9200 me.end = me.max; 9201 } 9202 }, 9203 convertTicksToLabels: function() { 9204 var me = this; 9205 me.ticksAsNumbers = me.ticks.slice(); 9206 me.zeroLineIndex = me.ticks.indexOf(0); 9207 9208 Chart.Scale.prototype.convertTicksToLabels.call(me); 9209 }, 9210 }); 9211 }; 9212 },{}],41:[function(require,module,exports){ 9213 "use strict"; 9214 9215 module.exports = function(Chart) { 9216 9217 var helpers = Chart.helpers; 9218 9219 var defaultConfig = { 9220 position: "left", 9221 9222 // label settings 9223 ticks: { 9224 callback: function(value, index, arr) { 9225 var remain = value / (Math.pow(10, Math.floor(helpers.log10(value)))); 9226 9227 if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) { 9228 return value.toExponential(); 9229 } else { 9230 return ''; 9231 } 9232 } 9233 } 9234 }; 9235 9236 var LogarithmicScale = Chart.Scale.extend({ 9237 determineDataLimits: function() { 9238 var me = this; 9239 var opts = me.options; 9240 var tickOpts = opts.ticks; 9241 var chart = me.chart; 9242 var data = chart.data; 9243 var datasets = data.datasets; 9244 var getValueOrDefault = helpers.getValueOrDefault; 9245 var isHorizontal = me.isHorizontal(); 9246 function IDMatches(meta) { 9247 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; 9248 } 9249 9250 // Calculate Range 9251 me.min = null; 9252 me.max = null; 9253 9254 if (opts.stacked) { 9255 var valuesPerType = {}; 9256 9257 helpers.each(datasets, function(dataset, datasetIndex) { 9258 var meta = chart.getDatasetMeta(datasetIndex); 9259 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 9260 if (valuesPerType[meta.type] === undefined) { 9261 valuesPerType[meta.type] = []; 9262 } 9263 9264 helpers.each(dataset.data, function(rawValue, index) { 9265 var values = valuesPerType[meta.type]; 9266 var value = +me.getRightValue(rawValue); 9267 if (isNaN(value) || meta.data[index].hidden) { 9268 return; 9269 } 9270 9271 values[index] = values[index] || 0; 9272 9273 if (opts.relativePoints) { 9274 values[index] = 100; 9275 } else { 9276 // Don't need to split positive and negative since the log scale can't handle a 0 crossing 9277 values[index] += value; 9278 } 9279 }); 9280 } 9281 }); 9282 9283 helpers.each(valuesPerType, function(valuesForType) { 9284 var minVal = helpers.min(valuesForType); 9285 var maxVal = helpers.max(valuesForType); 9286 me.min = me.min === null ? minVal : Math.min(me.min, minVal); 9287 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); 9288 }); 9289 9290 } else { 9291 helpers.each(datasets, function(dataset, datasetIndex) { 9292 var meta = chart.getDatasetMeta(datasetIndex); 9293 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 9294 helpers.each(dataset.data, function(rawValue, index) { 9295 var value = +me.getRightValue(rawValue); 9296 if (isNaN(value) || meta.data[index].hidden) { 9297 return; 9298 } 9299 9300 if (me.min === null) { 9301 me.min = value; 9302 } else if (value < me.min) { 9303 me.min = value; 9304 } 9305 9306 if (me.max === null) { 9307 me.max = value; 9308 } else if (value > me.max) { 9309 me.max = value; 9310 } 9311 }); 9312 } 9313 }); 9314 } 9315 9316 me.min = getValueOrDefault(tickOpts.min, me.min); 9317 me.max = getValueOrDefault(tickOpts.max, me.max); 9318 9319 if (me.min === me.max) { 9320 if (me.min !== 0 && me.min !== null) { 9321 me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); 9322 me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); 9323 } else { 9324 me.min = 1; 9325 me.max = 10; 9326 } 9327 } 9328 }, 9329 buildTicks: function() { 9330 var me = this; 9331 var opts = me.options; 9332 var tickOpts = opts.ticks; 9333 var getValueOrDefault = helpers.getValueOrDefault; 9334 9335 // Reset the ticks array. Later on, we will draw a grid line at these positions 9336 // The array simply contains the numerical value of the spots where ticks will be 9337 var ticks = me.ticks = []; 9338 9339 // Figure out what the max number of ticks we can support it is based on the size of 9340 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 9341 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on 9342 // the graph 9343 9344 var tickVal = getValueOrDefault(tickOpts.min, Math.pow(10, Math.floor(helpers.log10(me.min)))); 9345 9346 while (tickVal < me.max) { 9347 ticks.push(tickVal); 9348 9349 var exp = Math.floor(helpers.log10(tickVal)); 9350 var significand = Math.floor(tickVal / Math.pow(10, exp)) + 1; 9351 9352 if (significand === 10) { 9353 significand = 1; 9354 ++exp; 9355 } 9356 9357 tickVal = significand * Math.pow(10, exp); 9358 } 9359 9360 var lastTick = getValueOrDefault(tickOpts.max, tickVal); 9361 ticks.push(lastTick); 9362 9363 if (!me.isHorizontal()) { 9364 // We are in a vertical orientation. The top value is the highest. So reverse the array 9365 ticks.reverse(); 9366 } 9367 9368 // At this point, we need to update our max and min given the tick values since we have expanded the 9369 // range of the scale 9370 me.max = helpers.max(ticks); 9371 me.min = helpers.min(ticks); 9372 9373 if (tickOpts.reverse) { 9374 ticks.reverse(); 9375 9376 me.start = me.max; 9377 me.end = me.min; 9378 } else { 9379 me.start = me.min; 9380 me.end = me.max; 9381 } 9382 }, 9383 convertTicksToLabels: function() { 9384 this.tickValues = this.ticks.slice(); 9385 9386 Chart.Scale.prototype.convertTicksToLabels.call(this); 9387 }, 9388 // Get the correct tooltip label 9389 getLabelForIndex: function(index, datasetIndex) { 9390 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); 9391 }, 9392 getPixelForTick: function(index, includeOffset) { 9393 return this.getPixelForValue(this.tickValues[index], null, null, includeOffset); 9394 }, 9395 getPixelForValue: function(value, index, datasetIndex, includeOffset) { 9396 var me = this; 9397 var innerDimension; 9398 var pixel; 9399 9400 var start = me.start; 9401 var newVal = +me.getRightValue(value); 9402 var range = helpers.log10(me.end) - helpers.log10(start); 9403 var paddingTop = me.paddingTop; 9404 var paddingBottom = me.paddingBottom; 9405 var paddingLeft = me.paddingLeft; 9406 9407 if (me.isHorizontal()) { 9408 9409 if (newVal === 0) { 9410 pixel = me.left + paddingLeft; 9411 } else { 9412 innerDimension = me.width - (paddingLeft + me.paddingRight); 9413 pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); 9414 pixel += paddingLeft; 9415 } 9416 } else { 9417 // Bottom - top since pixels increase downard on a screen 9418 if (newVal === 0) { 9419 pixel = me.top + paddingTop; 9420 } else { 9421 innerDimension = me.height - (paddingTop + paddingBottom); 9422 pixel = (me.bottom - paddingBottom) - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); 9423 } 9424 } 9425 9426 return pixel; 9427 }, 9428 getValueForPixel: function(pixel) { 9429 var me = this; 9430 var offset; 9431 var range = helpers.log10(me.end) - helpers.log10(me.start); 9432 var value; 9433 var innerDimension; 9434 9435 if (me.isHorizontal()) { 9436 innerDimension = me.width - (me.paddingLeft + me.paddingRight); 9437 value = me.start * Math.pow(10, (pixel - me.left - me.paddingLeft) * range / innerDimension); 9438 } else { 9439 innerDimension = me.height - (me.paddingTop + me.paddingBottom); 9440 value = Math.pow(10, (me.bottom - me.paddingBottom - pixel) * range / innerDimension) / me.start; 9441 } 9442 9443 return value; 9444 } 9445 }); 9446 Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig); 9447 9448 }; 9449 },{}],42:[function(require,module,exports){ 9450 "use strict"; 9451 9452 module.exports = function(Chart) { 9453 9454 var helpers = Chart.helpers; 9455 var globalDefaults = Chart.defaults.global; 9456 9457 var defaultConfig = { 9458 display: true, 9459 9460 //Boolean - Whether to animate scaling the chart from the centre 9461 animate: true, 9462 lineArc: false, 9463 position: "chartArea", 9464 9465 angleLines: { 9466 display: true, 9467 color: "rgba(0, 0, 0, 0.1)", 9468 lineWidth: 1 9469 }, 9470 9471 // label settings 9472 ticks: { 9473 //Boolean - Show a backdrop to the scale label 9474 showLabelBackdrop: true, 9475 9476 //String - The colour of the label backdrop 9477 backdropColor: "rgba(255,255,255,0.75)", 9478 9479 //Number - The backdrop padding above & below the label in pixels 9480 backdropPaddingY: 2, 9481 9482 //Number - The backdrop padding to the side of the label in pixels 9483 backdropPaddingX: 2 9484 }, 9485 9486 pointLabels: { 9487 //Number - Point label font size in pixels 9488 fontSize: 10, 9489 9490 //Function - Used to convert point labels 9491 callback: function(label) { 9492 return label; 9493 } 9494 } 9495 }; 9496 9497 var LinearRadialScale = Chart.LinearScaleBase.extend({ 9498 getValueCount: function() { 9499 return this.chart.data.labels.length; 9500 }, 9501 setDimensions: function() { 9502 var me = this; 9503 var opts = me.options; 9504 var tickOpts = opts.ticks; 9505 // Set the unconstrained dimension before label rotation 9506 me.width = me.maxWidth; 9507 me.height = me.maxHeight; 9508 me.xCenter = Math.round(me.width / 2); 9509 me.yCenter = Math.round(me.height / 2); 9510 9511 var minSize = helpers.min([me.height, me.width]); 9512 var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); 9513 me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2); 9514 }, 9515 determineDataLimits: function() { 9516 var me = this; 9517 var chart = me.chart; 9518 me.min = null; 9519 me.max = null; 9520 9521 9522 helpers.each(chart.data.datasets, function(dataset, datasetIndex) { 9523 if (chart.isDatasetVisible(datasetIndex)) { 9524 var meta = chart.getDatasetMeta(datasetIndex); 9525 9526 helpers.each(dataset.data, function(rawValue, index) { 9527 var value = +me.getRightValue(rawValue); 9528 if (isNaN(value) || meta.data[index].hidden) { 9529 return; 9530 } 9531 9532 if (me.min === null) { 9533 me.min = value; 9534 } else if (value < me.min) { 9535 me.min = value; 9536 } 9537 9538 if (me.max === null) { 9539 me.max = value; 9540 } else if (value > me.max) { 9541 me.max = value; 9542 } 9543 }); 9544 } 9545 }); 9546 9547 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero 9548 me.handleTickRangeOptions(); 9549 }, 9550 getTickLimit: function() { 9551 var tickOpts = this.options.ticks; 9552 var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); 9553 return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); 9554 }, 9555 convertTicksToLabels: function() { 9556 var me = this; 9557 Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); 9558 9559 // Point labels 9560 me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); 9561 }, 9562 getLabelForIndex: function(index, datasetIndex) { 9563 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); 9564 }, 9565 fit: function() { 9566 /* 9567 * Right, this is really confusing and there is a lot of maths going on here 9568 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 9569 * 9570 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif 9571 * 9572 * Solution: 9573 * 9574 * We assume the radius of the polygon is half the size of the canvas at first 9575 * at each index we check if the text overlaps. 9576 * 9577 * Where it does, we store that angle and that index. 9578 * 9579 * After finding the largest index and angle we calculate how much we need to remove 9580 * from the shape radius to move the point inwards by that x. 9581 * 9582 * We average the left and right distances to get the maximum shape radius that can fit in the box 9583 * along with labels. 9584 * 9585 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion 9586 * on each side, removing that from the size, halving it and adding the left x protrusion width. 9587 * 9588 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels 9589 * and position it in the most space efficient manner 9590 * 9591 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif 9592 */ 9593 9594 var pointLabels = this.options.pointLabels; 9595 var pointLabelFontSize = helpers.getValueOrDefault(pointLabels.fontSize, globalDefaults.defaultFontSize); 9596 var pointLabeFontStyle = helpers.getValueOrDefault(pointLabels.fontStyle, globalDefaults.defaultFontStyle); 9597 var pointLabeFontFamily = helpers.getValueOrDefault(pointLabels.fontFamily, globalDefaults.defaultFontFamily); 9598 var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily); 9599 9600 // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. 9601 // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points 9602 var largestPossibleRadius = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]), 9603 pointPosition, 9604 i, 9605 textWidth, 9606 halfTextWidth, 9607 furthestRight = this.width, 9608 furthestRightIndex, 9609 furthestRightAngle, 9610 furthestLeft = 0, 9611 furthestLeftIndex, 9612 furthestLeftAngle, 9613 xProtrusionLeft, 9614 xProtrusionRight, 9615 radiusReductionRight, 9616 radiusReductionLeft, 9617 maxWidthRadius; 9618 this.ctx.font = pointLabeFont; 9619 9620 for (i = 0; i < this.getValueCount(); i++) { 9621 // 5px to space the text slightly out - similar to what we do in the draw function. 9622 pointPosition = this.getPointPosition(i, largestPossibleRadius); 9623 textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5; 9624 if (i === 0 || i === this.getValueCount() / 2) { 9625 // If we're at index zero, or exactly the middle, we're at exactly the top/bottom 9626 // of the radar chart, so text will be aligned centrally, so we'll half it and compare 9627 // w/left and right text sizes 9628 halfTextWidth = textWidth / 2; 9629 if (pointPosition.x + halfTextWidth > furthestRight) { 9630 furthestRight = pointPosition.x + halfTextWidth; 9631 furthestRightIndex = i; 9632 } 9633 if (pointPosition.x - halfTextWidth < furthestLeft) { 9634 furthestLeft = pointPosition.x - halfTextWidth; 9635 furthestLeftIndex = i; 9636 } 9637 } else if (i < this.getValueCount() / 2) { 9638 // Less than half the values means we'll left align the text 9639 if (pointPosition.x + textWidth > furthestRight) { 9640 furthestRight = pointPosition.x + textWidth; 9641 furthestRightIndex = i; 9642 } 9643 } else if (i > this.getValueCount() / 2) { 9644 // More than half the values means we'll right align the text 9645 if (pointPosition.x - textWidth < furthestLeft) { 9646 furthestLeft = pointPosition.x - textWidth; 9647 furthestLeftIndex = i; 9648 } 9649 } 9650 } 9651 9652 xProtrusionLeft = furthestLeft; 9653 xProtrusionRight = Math.ceil(furthestRight - this.width); 9654 9655 furthestRightAngle = this.getIndexAngle(furthestRightIndex); 9656 furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); 9657 9658 radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); 9659 radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); 9660 9661 // Ensure we actually need to reduce the size of the chart 9662 radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0; 9663 radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; 9664 9665 this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2); 9666 this.setCenterPoint(radiusReductionLeft, radiusReductionRight); 9667 }, 9668 setCenterPoint: function(leftMovement, rightMovement) { 9669 var me = this; 9670 var maxRight = me.width - rightMovement - me.drawingArea, 9671 maxLeft = leftMovement + me.drawingArea; 9672 9673 me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left); 9674 // Always vertically in the centre as the text height doesn't change 9675 me.yCenter = Math.round((me.height / 2) + me.top); 9676 }, 9677 9678 getIndexAngle: function(index) { 9679 var angleMultiplier = (Math.PI * 2) / this.getValueCount(); 9680 // Start from the top instead of right, so remove a quarter of the circle 9681 9682 return index * angleMultiplier - (Math.PI / 2); 9683 }, 9684 getDistanceFromCenterForValue: function(value) { 9685 var me = this; 9686 9687 if (value === null) { 9688 return 0; // null always in center 9689 } 9690 9691 // Take into account half font size + the yPadding of the top value 9692 var scalingFactor = me.drawingArea / (me.max - me.min); 9693 if (me.options.reverse) { 9694 return (me.max - value) * scalingFactor; 9695 } else { 9696 return (value - me.min) * scalingFactor; 9697 } 9698 }, 9699 getPointPosition: function(index, distanceFromCenter) { 9700 var me = this; 9701 var thisAngle = me.getIndexAngle(index); 9702 return { 9703 x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter, 9704 y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter 9705 }; 9706 }, 9707 getPointPositionForValue: function(index, value) { 9708 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); 9709 }, 9710 9711 getBasePosition: function() { 9712 var me = this; 9713 var min = me.min; 9714 var max = me.max; 9715 9716 return me.getPointPositionForValue(0, 9717 me.beginAtZero? 0: 9718 min < 0 && max < 0? max : 9719 min > 0 && max > 0? min : 9720 0); 9721 }, 9722 9723 draw: function() { 9724 var me = this; 9725 var opts = me.options; 9726 var gridLineOpts = opts.gridLines; 9727 var tickOpts = opts.ticks; 9728 var angleLineOpts = opts.angleLines; 9729 var pointLabelOpts = opts.pointLabels; 9730 var getValueOrDefault = helpers.getValueOrDefault; 9731 9732 if (opts.display) { 9733 var ctx = me.ctx; 9734 9735 // Tick Font 9736 var tickFontSize = getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); 9737 var tickFontStyle = getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); 9738 var tickFontFamily = getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); 9739 var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); 9740 9741 helpers.each(me.ticks, function(label, index) { 9742 // Don't draw a centre value (if it is minimum) 9743 if (index > 0 || opts.reverse) { 9744 var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); 9745 var yHeight = me.yCenter - yCenterOffset; 9746 9747 // Draw circular lines around the scale 9748 if (gridLineOpts.display && index !== 0) { 9749 ctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1); 9750 ctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); 9751 9752 if (opts.lineArc) { 9753 // Draw circular arcs between the points 9754 ctx.beginPath(); 9755 ctx.arc(me.xCenter, me.yCenter, yCenterOffset, 0, Math.PI * 2); 9756 ctx.closePath(); 9757 ctx.stroke(); 9758 } else { 9759 // Draw straight lines connecting each index 9760 ctx.beginPath(); 9761 for (var i = 0; i < me.getValueCount(); i++) { 9762 var pointPosition = me.getPointPosition(i, yCenterOffset); 9763 if (i === 0) { 9764 ctx.moveTo(pointPosition.x, pointPosition.y); 9765 } else { 9766 ctx.lineTo(pointPosition.x, pointPosition.y); 9767 } 9768 } 9769 ctx.closePath(); 9770 ctx.stroke(); 9771 } 9772 } 9773 9774 if (tickOpts.display) { 9775 var tickFontColor = getValueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); 9776 ctx.font = tickLabelFont; 9777 9778 if (tickOpts.showLabelBackdrop) { 9779 var labelWidth = ctx.measureText(label).width; 9780 ctx.fillStyle = tickOpts.backdropColor; 9781 ctx.fillRect( 9782 me.xCenter - labelWidth / 2 - tickOpts.backdropPaddingX, 9783 yHeight - tickFontSize / 2 - tickOpts.backdropPaddingY, 9784 labelWidth + tickOpts.backdropPaddingX * 2, 9785 tickFontSize + tickOpts.backdropPaddingY * 2 9786 ); 9787 } 9788 9789 ctx.textAlign = 'center'; 9790 ctx.textBaseline = "middle"; 9791 ctx.fillStyle = tickFontColor; 9792 ctx.fillText(label, me.xCenter, yHeight); 9793 } 9794 } 9795 }); 9796 9797 if (!opts.lineArc) { 9798 ctx.lineWidth = angleLineOpts.lineWidth; 9799 ctx.strokeStyle = angleLineOpts.color; 9800 9801 var outerDistance = me.getDistanceFromCenterForValue(opts.reverse ? me.min : me.max); 9802 9803 // Point Label Font 9804 var pointLabelFontSize = getValueOrDefault(pointLabelOpts.fontSize, globalDefaults.defaultFontSize); 9805 var pointLabeFontStyle = getValueOrDefault(pointLabelOpts.fontStyle, globalDefaults.defaultFontStyle); 9806 var pointLabeFontFamily = getValueOrDefault(pointLabelOpts.fontFamily, globalDefaults.defaultFontFamily); 9807 var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily); 9808 9809 for (var i = me.getValueCount() - 1; i >= 0; i--) { 9810 if (angleLineOpts.display) { 9811 var outerPosition = me.getPointPosition(i, outerDistance); 9812 ctx.beginPath(); 9813 ctx.moveTo(me.xCenter, me.yCenter); 9814 ctx.lineTo(outerPosition.x, outerPosition.y); 9815 ctx.stroke(); 9816 ctx.closePath(); 9817 } 9818 // Extra 3px out for some label spacing 9819 var pointLabelPosition = me.getPointPosition(i, outerDistance + 5); 9820 9821 // Keep this in loop since we may support array properties here 9822 var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor); 9823 ctx.font = pointLabeFont; 9824 ctx.fillStyle = pointLabelFontColor; 9825 9826 var pointLabels = me.pointLabels, 9827 labelsCount = pointLabels.length, 9828 halfLabelsCount = pointLabels.length / 2, 9829 quarterLabelsCount = halfLabelsCount / 2, 9830 upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount), 9831 exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount); 9832 if (i === 0) { 9833 ctx.textAlign = 'center'; 9834 } else if (i === halfLabelsCount) { 9835 ctx.textAlign = 'center'; 9836 } else if (i < halfLabelsCount) { 9837 ctx.textAlign = 'left'; 9838 } else { 9839 ctx.textAlign = 'right'; 9840 } 9841 9842 // Set the correct text baseline based on outer positioning 9843 if (exactQuarter) { 9844 ctx.textBaseline = 'middle'; 9845 } else if (upperHalf) { 9846 ctx.textBaseline = 'bottom'; 9847 } else { 9848 ctx.textBaseline = 'top'; 9849 } 9850 9851 ctx.fillText(pointLabels[i] ? pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y); 9852 } 9853 } 9854 } 9855 } 9856 }); 9857 Chart.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig); 9858 9859 }; 9860 9861 },{}],43:[function(require,module,exports){ 9862 /*global window: false */ 9863 "use strict"; 9864 9865 var moment = require(1); 9866 moment = typeof(moment) === 'function' ? moment : window.moment; 9867 9868 module.exports = function(Chart) { 9869 9870 var helpers = Chart.helpers; 9871 var time = { 9872 units: [{ 9873 name: 'millisecond', 9874 steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] 9875 }, { 9876 name: 'second', 9877 steps: [1, 2, 5, 10, 30] 9878 }, { 9879 name: 'minute', 9880 steps: [1, 2, 5, 10, 30] 9881 }, { 9882 name: 'hour', 9883 steps: [1, 2, 3, 6, 12] 9884 }, { 9885 name: 'day', 9886 steps: [1, 2, 5] 9887 }, { 9888 name: 'week', 9889 maxStep: 4 9890 }, { 9891 name: 'month', 9892 maxStep: 3 9893 }, { 9894 name: 'quarter', 9895 maxStep: 4 9896 }, { 9897 name: 'year', 9898 maxStep: false 9899 }] 9900 }; 9901 9902 var defaultConfig = { 9903 position: "bottom", 9904 9905 time: { 9906 parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment 9907 format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/ 9908 unit: false, // false == automatic or override with week, month, year, etc. 9909 round: false, // none, or override with week, month, year, etc. 9910 displayFormat: false, // DEPRECATED 9911 isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/ 9912 9913 // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ 9914 displayFormats: { 9915 'millisecond': 'h:mm:ss.SSS a', // 11:20:01.123 AM, 9916 'second': 'h:mm:ss a', // 11:20:01 AM 9917 'minute': 'h:mm:ss a', // 11:20:01 AM 9918 'hour': 'MMM D, hA', // Sept 4, 5PM 9919 'day': 'll', // Sep 4 2015 9920 'week': 'll', // Week 46, or maybe "[W]WW - YYYY" ? 9921 'month': 'MMM YYYY', // Sept 2015 9922 'quarter': '[Q]Q - YYYY', // Q3 9923 'year': 'YYYY' // 2015 9924 } 9925 }, 9926 ticks: { 9927 autoSkip: false 9928 } 9929 }; 9930 9931 var TimeScale = Chart.Scale.extend({ 9932 initialize: function() { 9933 if (!moment) { 9934 throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com'); 9935 } 9936 9937 Chart.Scale.prototype.initialize.call(this); 9938 }, 9939 getLabelMoment: function(datasetIndex, index) { 9940 return this.labelMoments[datasetIndex][index]; 9941 }, 9942 getMomentStartOf: function(tick) { 9943 var me = this; 9944 if (me.options.time.unit === 'week' && me.options.time.isoWeekday !== false) { 9945 return tick.clone().startOf('isoWeek').isoWeekday(me.options.time.isoWeekday); 9946 } else { 9947 return tick.clone().startOf(me.tickUnit); 9948 } 9949 }, 9950 determineDataLimits: function() { 9951 var me = this; 9952 me.labelMoments = []; 9953 9954 // Only parse these once. If the dataset does not have data as x,y pairs, we will use 9955 // these 9956 var scaleLabelMoments = []; 9957 if (me.chart.data.labels && me.chart.data.labels.length > 0) { 9958 helpers.each(me.chart.data.labels, function(label, index) { 9959 var labelMoment = me.parseTime(label); 9960 9961 if (labelMoment.isValid()) { 9962 if (me.options.time.round) { 9963 labelMoment.startOf(me.options.time.round); 9964 } 9965 scaleLabelMoments.push(labelMoment); 9966 } 9967 }, me); 9968 9969 me.firstTick = moment.min.call(me, scaleLabelMoments); 9970 me.lastTick = moment.max.call(me, scaleLabelMoments); 9971 } else { 9972 me.firstTick = null; 9973 me.lastTick = null; 9974 } 9975 9976 helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { 9977 var momentsForDataset = []; 9978 var datasetVisible = me.chart.isDatasetVisible(datasetIndex); 9979 9980 if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) { 9981 helpers.each(dataset.data, function(value, index) { 9982 var labelMoment = me.parseTime(me.getRightValue(value)); 9983 9984 if (labelMoment.isValid()) { 9985 if (me.options.time.round) { 9986 labelMoment.startOf(me.options.time.round); 9987 } 9988 momentsForDataset.push(labelMoment); 9989 9990 if (datasetVisible) { 9991 // May have gone outside the scale ranges, make sure we keep the first and last ticks updated 9992 me.firstTick = me.firstTick !== null ? moment.min(me.firstTick, labelMoment) : labelMoment; 9993 me.lastTick = me.lastTick !== null ? moment.max(me.lastTick, labelMoment) : labelMoment; 9994 } 9995 } 9996 }, me); 9997 } else { 9998 // We have no labels. Use the ones from the scale 9999 momentsForDataset = scaleLabelMoments; 10000 } 10001 10002 me.labelMoments.push(momentsForDataset); 10003 }, me); 10004 10005 // Set these after we've done all the data 10006 if (me.options.time.min) { 10007 me.firstTick = me.parseTime(me.options.time.min); 10008 } 10009 10010 if (me.options.time.max) { 10011 me.lastTick = me.parseTime(me.options.time.max); 10012 } 10013 10014 // We will modify these, so clone for later 10015 me.firstTick = (me.firstTick || moment()).clone(); 10016 me.lastTick = (me.lastTick || moment()).clone(); 10017 }, 10018 buildTicks: function(index) { 10019 var me = this; 10020 10021 me.ctx.save(); 10022 var tickFontSize = helpers.getValueOrDefault(me.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); 10023 var tickFontStyle = helpers.getValueOrDefault(me.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle); 10024 var tickFontFamily = helpers.getValueOrDefault(me.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily); 10025 var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); 10026 me.ctx.font = tickLabelFont; 10027 10028 me.ticks = []; 10029 me.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step 10030 me.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc) 10031 10032 // Set unit override if applicable 10033 if (me.options.time.unit) { 10034 me.tickUnit = me.options.time.unit || 'day'; 10035 me.displayFormat = me.options.time.displayFormats[me.tickUnit]; 10036 me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); 10037 me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, 1); 10038 } else { 10039 // Determine the smallest needed unit of the time 10040 var innerWidth = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); 10041 10042 // Crude approximation of what the label length might be 10043 var tempFirstLabel = me.tickFormatFunction(me.firstTick, 0, []); 10044 var tickLabelWidth = me.ctx.measureText(tempFirstLabel).width; 10045 var cosRotation = Math.cos(helpers.toRadians(me.options.ticks.maxRotation)); 10046 var sinRotation = Math.sin(helpers.toRadians(me.options.ticks.maxRotation)); 10047 tickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); 10048 var labelCapacity = innerWidth / (tickLabelWidth); 10049 10050 // Start as small as possible 10051 me.tickUnit = 'millisecond'; 10052 me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); 10053 me.displayFormat = me.options.time.displayFormats[me.tickUnit]; 10054 10055 var unitDefinitionIndex = 0; 10056 var unitDefinition = time.units[unitDefinitionIndex]; 10057 10058 // While we aren't ideal and we don't have units left 10059 while (unitDefinitionIndex < time.units.length) { 10060 // Can we scale this unit. If `false` we can scale infinitely 10061 me.unitScale = 1; 10062 10063 if (helpers.isArray(unitDefinition.steps) && Math.ceil(me.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) { 10064 // Use one of the prefedined steps 10065 for (var idx = 0; idx < unitDefinition.steps.length; ++idx) { 10066 if (unitDefinition.steps[idx] >= Math.ceil(me.scaleSizeInUnits / labelCapacity)) { 10067 me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, unitDefinition.steps[idx]); 10068 break; 10069 } 10070 } 10071 10072 break; 10073 } else if ((unitDefinition.maxStep === false) || (Math.ceil(me.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) { 10074 // We have a max step. Scale this unit 10075 me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, Math.ceil(me.scaleSizeInUnits / labelCapacity)); 10076 break; 10077 } else { 10078 // Move to the next unit up 10079 ++unitDefinitionIndex; 10080 unitDefinition = time.units[unitDefinitionIndex]; 10081 10082 me.tickUnit = unitDefinition.name; 10083 var leadingUnitBuffer = me.firstTick.diff(me.getMomentStartOf(me.firstTick), me.tickUnit, true); 10084 var trailingUnitBuffer = me.getMomentStartOf(me.lastTick.clone().add(1, me.tickUnit)).diff(me.lastTick, me.tickUnit, true); 10085 me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true) + leadingUnitBuffer + trailingUnitBuffer; 10086 me.displayFormat = me.options.time.displayFormats[unitDefinition.name]; 10087 } 10088 } 10089 } 10090 10091 var roundedStart; 10092 10093 // Only round the first tick if we have no hard minimum 10094 if (!me.options.time.min) { 10095 me.firstTick = me.getMomentStartOf(me.firstTick); 10096 roundedStart = me.firstTick; 10097 } else { 10098 roundedStart = me.getMomentStartOf(me.firstTick); 10099 } 10100 10101 // Only round the last tick if we have no hard maximum 10102 if (!me.options.time.max) { 10103 var roundedEnd = me.getMomentStartOf(me.lastTick); 10104 if (roundedEnd.diff(me.lastTick, me.tickUnit, true) !== 0) { 10105 // Do not use end of because we need me to be in the next time unit 10106 me.lastTick = me.getMomentStartOf(me.lastTick.add(1, me.tickUnit)); 10107 } 10108 } 10109 10110 me.smallestLabelSeparation = me.width; 10111 10112 helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { 10113 for (var i = 1; i < me.labelMoments[datasetIndex].length; i++) { 10114 me.smallestLabelSeparation = Math.min(me.smallestLabelSeparation, me.labelMoments[datasetIndex][i].diff(me.labelMoments[datasetIndex][i - 1], me.tickUnit, true)); 10115 } 10116 }, me); 10117 10118 // Tick displayFormat override 10119 if (me.options.time.displayFormat) { 10120 me.displayFormat = me.options.time.displayFormat; 10121 } 10122 10123 // first tick. will have been rounded correctly if options.time.min is not specified 10124 me.ticks.push(me.firstTick.clone()); 10125 10126 // For every unit in between the first and last moment, create a moment and add it to the ticks tick 10127 for (var i = 1; i <= me.scaleSizeInUnits; ++i) { 10128 var newTick = roundedStart.clone().add(i, me.tickUnit); 10129 10130 // Are we greater than the max time 10131 if (me.options.time.max && newTick.diff(me.lastTick, me.tickUnit, true) >= 0) { 10132 break; 10133 } 10134 10135 if (i % me.unitScale === 0) { 10136 me.ticks.push(newTick); 10137 } 10138 } 10139 10140 // Always show the right tick 10141 var diff = me.ticks[me.ticks.length - 1].diff(me.lastTick, me.tickUnit); 10142 if (diff !== 0 || me.scaleSizeInUnits === 0) { 10143 // this is a weird case. If the <max> option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart 10144 // but the last tick was not rounded. 10145 if (me.options.time.max) { 10146 me.ticks.push(me.lastTick.clone()); 10147 me.scaleSizeInUnits = me.lastTick.diff(me.ticks[0], me.tickUnit, true); 10148 } else { 10149 me.ticks.push(me.lastTick.clone()); 10150 me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); 10151 } 10152 } 10153 10154 me.ctx.restore(); 10155 }, 10156 // Get tooltip label 10157 getLabelForIndex: function(index, datasetIndex) { 10158 var me = this; 10159 var label = me.chart.data.labels && index < me.chart.data.labels.length ? me.chart.data.labels[index] : ''; 10160 10161 if (typeof me.chart.data.datasets[datasetIndex].data[0] === 'object') { 10162 label = me.getRightValue(me.chart.data.datasets[datasetIndex].data[index]); 10163 } 10164 10165 // Format nicely 10166 if (me.options.time.tooltipFormat) { 10167 label = me.parseTime(label).format(me.options.time.tooltipFormat); 10168 } 10169 10170 return label; 10171 }, 10172 // Function to format an individual tick mark 10173 tickFormatFunction: function tickFormatFunction(tick, index, ticks) { 10174 var formattedTick = tick.format(this.displayFormat); 10175 var tickOpts = this.options.ticks; 10176 var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback); 10177 10178 if (callback) { 10179 return callback(formattedTick, index, ticks); 10180 } else { 10181 return formattedTick; 10182 } 10183 }, 10184 convertTicksToLabels: function() { 10185 var me = this; 10186 me.tickMoments = me.ticks; 10187 me.ticks = me.ticks.map(me.tickFormatFunction, me); 10188 }, 10189 getPixelForValue: function(value, index, datasetIndex, includeOffset) { 10190 var me = this; 10191 var labelMoment = value && value.isValid && value.isValid() ? value : me.getLabelMoment(datasetIndex, index); 10192 10193 if (labelMoment) { 10194 var offset = labelMoment.diff(me.firstTick, me.tickUnit, true); 10195 10196 var decimal = offset / me.scaleSizeInUnits; 10197 10198 if (me.isHorizontal()) { 10199 var innerWidth = me.width - (me.paddingLeft + me.paddingRight); 10200 var valueWidth = innerWidth / Math.max(me.ticks.length - 1, 1); 10201 var valueOffset = (innerWidth * decimal) + me.paddingLeft; 10202 10203 return me.left + Math.round(valueOffset); 10204 } else { 10205 var innerHeight = me.height - (me.paddingTop + me.paddingBottom); 10206 var valueHeight = innerHeight / Math.max(me.ticks.length - 1, 1); 10207 var heightOffset = (innerHeight * decimal) + me.paddingTop; 10208 10209 return me.top + Math.round(heightOffset); 10210 } 10211 } 10212 }, 10213 getPixelForTick: function(index, includeOffset) { 10214 return this.getPixelForValue(this.tickMoments[index], null, null, includeOffset); 10215 }, 10216 getValueForPixel: function(pixel) { 10217 var me = this; 10218 var innerDimension = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); 10219 var offset = (pixel - (me.isHorizontal() ? me.left + me.paddingLeft : me.top + me.paddingTop)) / innerDimension; 10220 offset *= me.scaleSizeInUnits; 10221 return me.firstTick.clone().add(moment.duration(offset, me.tickUnit).asSeconds(), 'seconds'); 10222 }, 10223 parseTime: function(label) { 10224 var me = this; 10225 if (typeof me.options.time.parser === 'string') { 10226 return moment(label, me.options.time.parser); 10227 } 10228 if (typeof me.options.time.parser === 'function') { 10229 return me.options.time.parser(label); 10230 } 10231 // Date objects 10232 if (typeof label.getMonth === 'function' || typeof label === 'number') { 10233 return moment(label); 10234 } 10235 // Moment support 10236 if (label.isValid && label.isValid()) { 10237 return label; 10238 } 10239 // Custom parsing (return an instance of moment) 10240 if (typeof me.options.time.format !== 'string' && me.options.time.format.call) { 10241 console.warn("options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale"); 10242 return me.options.time.format(label); 10243 } 10244 // Moment format parsing 10245 return moment(label, me.options.time.format); 10246 } 10247 }); 10248 Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig); 10249 10250 }; 10251 10252 },{"1":1}]},{},[7])(7) 10253 });
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |