Chart.js (402654B)
1 /*! 2 * Chart.js 3 * http://chartjs.org/ 4 * Version: 2.7.3 5 * 6 * Copyright 2018 Chart.js Contributors 7 * Released under the MIT license 8 * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md 9 */ 10 (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(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ 11 12 },{}],2:[function(require,module,exports){ 13 /* MIT license */ 14 var colorNames = require(6); 15 16 module.exports = { 17 getRgba: getRgba, 18 getHsla: getHsla, 19 getRgb: getRgb, 20 getHsl: getHsl, 21 getHwb: getHwb, 22 getAlpha: getAlpha, 23 24 hexString: hexString, 25 rgbString: rgbString, 26 rgbaString: rgbaString, 27 percentString: percentString, 28 percentaString: percentaString, 29 hslString: hslString, 30 hslaString: hslaString, 31 hwbString: hwbString, 32 keyword: keyword 33 } 34 35 function getRgba(string) { 36 if (!string) { 37 return; 38 } 39 var abbr = /^#([a-fA-F0-9]{3})$/i, 40 hex = /^#([a-fA-F0-9]{6})$/i, 41 rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, 42 per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, 43 keyword = /(\w+)/; 44 45 var rgb = [0, 0, 0], 46 a = 1, 47 match = string.match(abbr); 48 if (match) { 49 match = match[1]; 50 for (var i = 0; i < rgb.length; i++) { 51 rgb[i] = parseInt(match[i] + match[i], 16); 52 } 53 } 54 else if (match = string.match(hex)) { 55 match = match[1]; 56 for (var i = 0; i < rgb.length; i++) { 57 rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); 58 } 59 } 60 else if (match = string.match(rgba)) { 61 for (var i = 0; i < rgb.length; i++) { 62 rgb[i] = parseInt(match[i + 1]); 63 } 64 a = parseFloat(match[4]); 65 } 66 else if (match = string.match(per)) { 67 for (var i = 0; i < rgb.length; i++) { 68 rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); 69 } 70 a = parseFloat(match[4]); 71 } 72 else if (match = string.match(keyword)) { 73 if (match[1] == "transparent") { 74 return [0, 0, 0, 0]; 75 } 76 rgb = colorNames[match[1]]; 77 if (!rgb) { 78 return; 79 } 80 } 81 82 for (var i = 0; i < rgb.length; i++) { 83 rgb[i] = scale(rgb[i], 0, 255); 84 } 85 if (!a && a != 0) { 86 a = 1; 87 } 88 else { 89 a = scale(a, 0, 1); 90 } 91 rgb[3] = a; 92 return rgb; 93 } 94 95 function getHsla(string) { 96 if (!string) { 97 return; 98 } 99 var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; 100 var match = string.match(hsl); 101 if (match) { 102 var alpha = parseFloat(match[4]); 103 var h = scale(parseInt(match[1]), 0, 360), 104 s = scale(parseFloat(match[2]), 0, 100), 105 l = scale(parseFloat(match[3]), 0, 100), 106 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); 107 return [h, s, l, a]; 108 } 109 } 110 111 function getHwb(string) { 112 if (!string) { 113 return; 114 } 115 var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; 116 var match = string.match(hwb); 117 if (match) { 118 var alpha = parseFloat(match[4]); 119 var h = scale(parseInt(match[1]), 0, 360), 120 w = scale(parseFloat(match[2]), 0, 100), 121 b = scale(parseFloat(match[3]), 0, 100), 122 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); 123 return [h, w, b, a]; 124 } 125 } 126 127 function getRgb(string) { 128 var rgba = getRgba(string); 129 return rgba && rgba.slice(0, 3); 130 } 131 132 function getHsl(string) { 133 var hsla = getHsla(string); 134 return hsla && hsla.slice(0, 3); 135 } 136 137 function getAlpha(string) { 138 var vals = getRgba(string); 139 if (vals) { 140 return vals[3]; 141 } 142 else if (vals = getHsla(string)) { 143 return vals[3]; 144 } 145 else if (vals = getHwb(string)) { 146 return vals[3]; 147 } 148 } 149 150 // generators 151 function hexString(rgb) { 152 return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1]) 153 + hexDouble(rgb[2]); 154 } 155 156 function rgbString(rgba, alpha) { 157 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { 158 return rgbaString(rgba, alpha); 159 } 160 return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; 161 } 162 163 function rgbaString(rgba, alpha) { 164 if (alpha === undefined) { 165 alpha = (rgba[3] !== undefined ? rgba[3] : 1); 166 } 167 return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] 168 + ", " + alpha + ")"; 169 } 170 171 function percentString(rgba, alpha) { 172 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { 173 return percentaString(rgba, alpha); 174 } 175 var r = Math.round(rgba[0]/255 * 100), 176 g = Math.round(rgba[1]/255 * 100), 177 b = Math.round(rgba[2]/255 * 100); 178 179 return "rgb(" + r + "%, " + g + "%, " + b + "%)"; 180 } 181 182 function percentaString(rgba, alpha) { 183 var r = Math.round(rgba[0]/255 * 100), 184 g = Math.round(rgba[1]/255 * 100), 185 b = Math.round(rgba[2]/255 * 100); 186 return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; 187 } 188 189 function hslString(hsla, alpha) { 190 if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { 191 return hslaString(hsla, alpha); 192 } 193 return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; 194 } 195 196 function hslaString(hsla, alpha) { 197 if (alpha === undefined) { 198 alpha = (hsla[3] !== undefined ? hsla[3] : 1); 199 } 200 return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " 201 + alpha + ")"; 202 } 203 204 // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax 205 // (hwb have alpha optional & 1 is default value) 206 function hwbString(hwb, alpha) { 207 if (alpha === undefined) { 208 alpha = (hwb[3] !== undefined ? hwb[3] : 1); 209 } 210 return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" 211 + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; 212 } 213 214 function keyword(rgb) { 215 return reverseNames[rgb.slice(0, 3)]; 216 } 217 218 // helpers 219 function scale(num, min, max) { 220 return Math.min(Math.max(min, num), max); 221 } 222 223 function hexDouble(num) { 224 var str = num.toString(16).toUpperCase(); 225 return (str.length < 2) ? "0" + str : str; 226 } 227 228 229 //create a list of reverse color names 230 var reverseNames = {}; 231 for (var name in colorNames) { 232 reverseNames[colorNames[name]] = name; 233 } 234 235 },{"6":6}],3:[function(require,module,exports){ 236 /* MIT license */ 237 var convert = require(5); 238 var string = require(2); 239 240 var Color = function (obj) { 241 if (obj instanceof Color) { 242 return obj; 243 } 244 if (!(this instanceof Color)) { 245 return new Color(obj); 246 } 247 248 this.valid = false; 249 this.values = { 250 rgb: [0, 0, 0], 251 hsl: [0, 0, 0], 252 hsv: [0, 0, 0], 253 hwb: [0, 0, 0], 254 cmyk: [0, 0, 0, 0], 255 alpha: 1 256 }; 257 258 // parse Color() argument 259 var vals; 260 if (typeof obj === 'string') { 261 vals = string.getRgba(obj); 262 if (vals) { 263 this.setValues('rgb', vals); 264 } else if (vals = string.getHsla(obj)) { 265 this.setValues('hsl', vals); 266 } else if (vals = string.getHwb(obj)) { 267 this.setValues('hwb', vals); 268 } 269 } else if (typeof obj === 'object') { 270 vals = obj; 271 if (vals.r !== undefined || vals.red !== undefined) { 272 this.setValues('rgb', vals); 273 } else if (vals.l !== undefined || vals.lightness !== undefined) { 274 this.setValues('hsl', vals); 275 } else if (vals.v !== undefined || vals.value !== undefined) { 276 this.setValues('hsv', vals); 277 } else if (vals.w !== undefined || vals.whiteness !== undefined) { 278 this.setValues('hwb', vals); 279 } else if (vals.c !== undefined || vals.cyan !== undefined) { 280 this.setValues('cmyk', vals); 281 } 282 } 283 }; 284 285 Color.prototype = { 286 isValid: function () { 287 return this.valid; 288 }, 289 rgb: function () { 290 return this.setSpace('rgb', arguments); 291 }, 292 hsl: function () { 293 return this.setSpace('hsl', arguments); 294 }, 295 hsv: function () { 296 return this.setSpace('hsv', arguments); 297 }, 298 hwb: function () { 299 return this.setSpace('hwb', arguments); 300 }, 301 cmyk: function () { 302 return this.setSpace('cmyk', arguments); 303 }, 304 305 rgbArray: function () { 306 return this.values.rgb; 307 }, 308 hslArray: function () { 309 return this.values.hsl; 310 }, 311 hsvArray: function () { 312 return this.values.hsv; 313 }, 314 hwbArray: function () { 315 var values = this.values; 316 if (values.alpha !== 1) { 317 return values.hwb.concat([values.alpha]); 318 } 319 return values.hwb; 320 }, 321 cmykArray: function () { 322 return this.values.cmyk; 323 }, 324 rgbaArray: function () { 325 var values = this.values; 326 return values.rgb.concat([values.alpha]); 327 }, 328 hslaArray: function () { 329 var values = this.values; 330 return values.hsl.concat([values.alpha]); 331 }, 332 alpha: function (val) { 333 if (val === undefined) { 334 return this.values.alpha; 335 } 336 this.setValues('alpha', val); 337 return this; 338 }, 339 340 red: function (val) { 341 return this.setChannel('rgb', 0, val); 342 }, 343 green: function (val) { 344 return this.setChannel('rgb', 1, val); 345 }, 346 blue: function (val) { 347 return this.setChannel('rgb', 2, val); 348 }, 349 hue: function (val) { 350 if (val) { 351 val %= 360; 352 val = val < 0 ? 360 + val : val; 353 } 354 return this.setChannel('hsl', 0, val); 355 }, 356 saturation: function (val) { 357 return this.setChannel('hsl', 1, val); 358 }, 359 lightness: function (val) { 360 return this.setChannel('hsl', 2, val); 361 }, 362 saturationv: function (val) { 363 return this.setChannel('hsv', 1, val); 364 }, 365 whiteness: function (val) { 366 return this.setChannel('hwb', 1, val); 367 }, 368 blackness: function (val) { 369 return this.setChannel('hwb', 2, val); 370 }, 371 value: function (val) { 372 return this.setChannel('hsv', 2, val); 373 }, 374 cyan: function (val) { 375 return this.setChannel('cmyk', 0, val); 376 }, 377 magenta: function (val) { 378 return this.setChannel('cmyk', 1, val); 379 }, 380 yellow: function (val) { 381 return this.setChannel('cmyk', 2, val); 382 }, 383 black: function (val) { 384 return this.setChannel('cmyk', 3, val); 385 }, 386 387 hexString: function () { 388 return string.hexString(this.values.rgb); 389 }, 390 rgbString: function () { 391 return string.rgbString(this.values.rgb, this.values.alpha); 392 }, 393 rgbaString: function () { 394 return string.rgbaString(this.values.rgb, this.values.alpha); 395 }, 396 percentString: function () { 397 return string.percentString(this.values.rgb, this.values.alpha); 398 }, 399 hslString: function () { 400 return string.hslString(this.values.hsl, this.values.alpha); 401 }, 402 hslaString: function () { 403 return string.hslaString(this.values.hsl, this.values.alpha); 404 }, 405 hwbString: function () { 406 return string.hwbString(this.values.hwb, this.values.alpha); 407 }, 408 keyword: function () { 409 return string.keyword(this.values.rgb, this.values.alpha); 410 }, 411 412 rgbNumber: function () { 413 var rgb = this.values.rgb; 414 return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; 415 }, 416 417 luminosity: function () { 418 // http://www.w3.org/TR/WCAG20/#relativeluminancedef 419 var rgb = this.values.rgb; 420 var lum = []; 421 for (var i = 0; i < rgb.length; i++) { 422 var chan = rgb[i] / 255; 423 lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); 424 } 425 return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; 426 }, 427 428 contrast: function (color2) { 429 // http://www.w3.org/TR/WCAG20/#contrast-ratiodef 430 var lum1 = this.luminosity(); 431 var lum2 = color2.luminosity(); 432 if (lum1 > lum2) { 433 return (lum1 + 0.05) / (lum2 + 0.05); 434 } 435 return (lum2 + 0.05) / (lum1 + 0.05); 436 }, 437 438 level: function (color2) { 439 var contrastRatio = this.contrast(color2); 440 if (contrastRatio >= 7.1) { 441 return 'AAA'; 442 } 443 444 return (contrastRatio >= 4.5) ? 'AA' : ''; 445 }, 446 447 dark: function () { 448 // YIQ equation from http://24ways.org/2010/calculating-color-contrast 449 var rgb = this.values.rgb; 450 var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; 451 return yiq < 128; 452 }, 453 454 light: function () { 455 return !this.dark(); 456 }, 457 458 negate: function () { 459 var rgb = []; 460 for (var i = 0; i < 3; i++) { 461 rgb[i] = 255 - this.values.rgb[i]; 462 } 463 this.setValues('rgb', rgb); 464 return this; 465 }, 466 467 lighten: function (ratio) { 468 var hsl = this.values.hsl; 469 hsl[2] += hsl[2] * ratio; 470 this.setValues('hsl', hsl); 471 return this; 472 }, 473 474 darken: function (ratio) { 475 var hsl = this.values.hsl; 476 hsl[2] -= hsl[2] * ratio; 477 this.setValues('hsl', hsl); 478 return this; 479 }, 480 481 saturate: function (ratio) { 482 var hsl = this.values.hsl; 483 hsl[1] += hsl[1] * ratio; 484 this.setValues('hsl', hsl); 485 return this; 486 }, 487 488 desaturate: function (ratio) { 489 var hsl = this.values.hsl; 490 hsl[1] -= hsl[1] * ratio; 491 this.setValues('hsl', hsl); 492 return this; 493 }, 494 495 whiten: function (ratio) { 496 var hwb = this.values.hwb; 497 hwb[1] += hwb[1] * ratio; 498 this.setValues('hwb', hwb); 499 return this; 500 }, 501 502 blacken: function (ratio) { 503 var hwb = this.values.hwb; 504 hwb[2] += hwb[2] * ratio; 505 this.setValues('hwb', hwb); 506 return this; 507 }, 508 509 greyscale: function () { 510 var rgb = this.values.rgb; 511 // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale 512 var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; 513 this.setValues('rgb', [val, val, val]); 514 return this; 515 }, 516 517 clearer: function (ratio) { 518 var alpha = this.values.alpha; 519 this.setValues('alpha', alpha - (alpha * ratio)); 520 return this; 521 }, 522 523 opaquer: function (ratio) { 524 var alpha = this.values.alpha; 525 this.setValues('alpha', alpha + (alpha * ratio)); 526 return this; 527 }, 528 529 rotate: function (degrees) { 530 var hsl = this.values.hsl; 531 var hue = (hsl[0] + degrees) % 360; 532 hsl[0] = hue < 0 ? 360 + hue : hue; 533 this.setValues('hsl', hsl); 534 return this; 535 }, 536 537 /** 538 * Ported from sass implementation in C 539 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 540 */ 541 mix: function (mixinColor, weight) { 542 var color1 = this; 543 var color2 = mixinColor; 544 var p = weight === undefined ? 0.5 : weight; 545 546 var w = 2 * p - 1; 547 var a = color1.alpha() - color2.alpha(); 548 549 var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; 550 var w2 = 1 - w1; 551 552 return this 553 .rgb( 554 w1 * color1.red() + w2 * color2.red(), 555 w1 * color1.green() + w2 * color2.green(), 556 w1 * color1.blue() + w2 * color2.blue() 557 ) 558 .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); 559 }, 560 561 toJSON: function () { 562 return this.rgb(); 563 }, 564 565 clone: function () { 566 // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, 567 // making the final build way to big to embed in Chart.js. So let's do it manually, 568 // assuming that values to clone are 1 dimension arrays containing only numbers, 569 // except 'alpha' which is a number. 570 var result = new Color(); 571 var source = this.values; 572 var target = result.values; 573 var value, type; 574 575 for (var prop in source) { 576 if (source.hasOwnProperty(prop)) { 577 value = source[prop]; 578 type = ({}).toString.call(value); 579 if (type === '[object Array]') { 580 target[prop] = value.slice(0); 581 } else if (type === '[object Number]') { 582 target[prop] = value; 583 } else { 584 console.error('unexpected color value:', value); 585 } 586 } 587 } 588 589 return result; 590 } 591 }; 592 593 Color.prototype.spaces = { 594 rgb: ['red', 'green', 'blue'], 595 hsl: ['hue', 'saturation', 'lightness'], 596 hsv: ['hue', 'saturation', 'value'], 597 hwb: ['hue', 'whiteness', 'blackness'], 598 cmyk: ['cyan', 'magenta', 'yellow', 'black'] 599 }; 600 601 Color.prototype.maxes = { 602 rgb: [255, 255, 255], 603 hsl: [360, 100, 100], 604 hsv: [360, 100, 100], 605 hwb: [360, 100, 100], 606 cmyk: [100, 100, 100, 100] 607 }; 608 609 Color.prototype.getValues = function (space) { 610 var values = this.values; 611 var vals = {}; 612 613 for (var i = 0; i < space.length; i++) { 614 vals[space.charAt(i)] = values[space][i]; 615 } 616 617 if (values.alpha !== 1) { 618 vals.a = values.alpha; 619 } 620 621 // {r: 255, g: 255, b: 255, a: 0.4} 622 return vals; 623 }; 624 625 Color.prototype.setValues = function (space, vals) { 626 var values = this.values; 627 var spaces = this.spaces; 628 var maxes = this.maxes; 629 var alpha = 1; 630 var i; 631 632 this.valid = true; 633 634 if (space === 'alpha') { 635 alpha = vals; 636 } else if (vals.length) { 637 // [10, 10, 10] 638 values[space] = vals.slice(0, space.length); 639 alpha = vals[space.length]; 640 } else if (vals[space.charAt(0)] !== undefined) { 641 // {r: 10, g: 10, b: 10} 642 for (i = 0; i < space.length; i++) { 643 values[space][i] = vals[space.charAt(i)]; 644 } 645 646 alpha = vals.a; 647 } else if (vals[spaces[space][0]] !== undefined) { 648 // {red: 10, green: 10, blue: 10} 649 var chans = spaces[space]; 650 651 for (i = 0; i < space.length; i++) { 652 values[space][i] = vals[chans[i]]; 653 } 654 655 alpha = vals.alpha; 656 } 657 658 values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); 659 660 if (space === 'alpha') { 661 return false; 662 } 663 664 var capped; 665 666 // cap values of the space prior converting all values 667 for (i = 0; i < space.length; i++) { 668 capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); 669 values[space][i] = Math.round(capped); 670 } 671 672 // convert to all the other color spaces 673 for (var sname in spaces) { 674 if (sname !== space) { 675 values[sname] = convert[space][sname](values[space]); 676 } 677 } 678 679 return true; 680 }; 681 682 Color.prototype.setSpace = function (space, args) { 683 var vals = args[0]; 684 685 if (vals === undefined) { 686 // color.rgb() 687 return this.getValues(space); 688 } 689 690 // color.rgb(10, 10, 10) 691 if (typeof vals === 'number') { 692 vals = Array.prototype.slice.call(args); 693 } 694 695 this.setValues(space, vals); 696 return this; 697 }; 698 699 Color.prototype.setChannel = function (space, index, val) { 700 var svalues = this.values[space]; 701 if (val === undefined) { 702 // color.red() 703 return svalues[index]; 704 } else if (val === svalues[index]) { 705 // color.red(color.red()) 706 return this; 707 } 708 709 // color.red(100) 710 svalues[index] = val; 711 this.setValues(space, svalues); 712 713 return this; 714 }; 715 716 if (typeof window !== 'undefined') { 717 window.Color = Color; 718 } 719 720 module.exports = Color; 721 722 },{"2":2,"5":5}],4:[function(require,module,exports){ 723 /* MIT license */ 724 725 module.exports = { 726 rgb2hsl: rgb2hsl, 727 rgb2hsv: rgb2hsv, 728 rgb2hwb: rgb2hwb, 729 rgb2cmyk: rgb2cmyk, 730 rgb2keyword: rgb2keyword, 731 rgb2xyz: rgb2xyz, 732 rgb2lab: rgb2lab, 733 rgb2lch: rgb2lch, 734 735 hsl2rgb: hsl2rgb, 736 hsl2hsv: hsl2hsv, 737 hsl2hwb: hsl2hwb, 738 hsl2cmyk: hsl2cmyk, 739 hsl2keyword: hsl2keyword, 740 741 hsv2rgb: hsv2rgb, 742 hsv2hsl: hsv2hsl, 743 hsv2hwb: hsv2hwb, 744 hsv2cmyk: hsv2cmyk, 745 hsv2keyword: hsv2keyword, 746 747 hwb2rgb: hwb2rgb, 748 hwb2hsl: hwb2hsl, 749 hwb2hsv: hwb2hsv, 750 hwb2cmyk: hwb2cmyk, 751 hwb2keyword: hwb2keyword, 752 753 cmyk2rgb: cmyk2rgb, 754 cmyk2hsl: cmyk2hsl, 755 cmyk2hsv: cmyk2hsv, 756 cmyk2hwb: cmyk2hwb, 757 cmyk2keyword: cmyk2keyword, 758 759 keyword2rgb: keyword2rgb, 760 keyword2hsl: keyword2hsl, 761 keyword2hsv: keyword2hsv, 762 keyword2hwb: keyword2hwb, 763 keyword2cmyk: keyword2cmyk, 764 keyword2lab: keyword2lab, 765 keyword2xyz: keyword2xyz, 766 767 xyz2rgb: xyz2rgb, 768 xyz2lab: xyz2lab, 769 xyz2lch: xyz2lch, 770 771 lab2xyz: lab2xyz, 772 lab2rgb: lab2rgb, 773 lab2lch: lab2lch, 774 775 lch2lab: lch2lab, 776 lch2xyz: lch2xyz, 777 lch2rgb: lch2rgb 778 } 779 780 781 function rgb2hsl(rgb) { 782 var r = rgb[0]/255, 783 g = rgb[1]/255, 784 b = rgb[2]/255, 785 min = Math.min(r, g, b), 786 max = Math.max(r, g, b), 787 delta = max - min, 788 h, s, l; 789 790 if (max == min) 791 h = 0; 792 else if (r == max) 793 h = (g - b) / delta; 794 else if (g == max) 795 h = 2 + (b - r) / delta; 796 else if (b == max) 797 h = 4 + (r - g)/ delta; 798 799 h = Math.min(h * 60, 360); 800 801 if (h < 0) 802 h += 360; 803 804 l = (min + max) / 2; 805 806 if (max == min) 807 s = 0; 808 else if (l <= 0.5) 809 s = delta / (max + min); 810 else 811 s = delta / (2 - max - min); 812 813 return [h, s * 100, l * 100]; 814 } 815 816 function rgb2hsv(rgb) { 817 var r = rgb[0], 818 g = rgb[1], 819 b = rgb[2], 820 min = Math.min(r, g, b), 821 max = Math.max(r, g, b), 822 delta = max - min, 823 h, s, v; 824 825 if (max == 0) 826 s = 0; 827 else 828 s = (delta/max * 1000)/10; 829 830 if (max == min) 831 h = 0; 832 else if (r == max) 833 h = (g - b) / delta; 834 else if (g == max) 835 h = 2 + (b - r) / delta; 836 else if (b == max) 837 h = 4 + (r - g) / delta; 838 839 h = Math.min(h * 60, 360); 840 841 if (h < 0) 842 h += 360; 843 844 v = ((max / 255) * 1000) / 10; 845 846 return [h, s, v]; 847 } 848 849 function rgb2hwb(rgb) { 850 var r = rgb[0], 851 g = rgb[1], 852 b = rgb[2], 853 h = rgb2hsl(rgb)[0], 854 w = 1/255 * Math.min(r, Math.min(g, b)), 855 b = 1 - 1/255 * Math.max(r, Math.max(g, b)); 856 857 return [h, w * 100, b * 100]; 858 } 859 860 function rgb2cmyk(rgb) { 861 var r = rgb[0] / 255, 862 g = rgb[1] / 255, 863 b = rgb[2] / 255, 864 c, m, y, k; 865 866 k = Math.min(1 - r, 1 - g, 1 - b); 867 c = (1 - r - k) / (1 - k) || 0; 868 m = (1 - g - k) / (1 - k) || 0; 869 y = (1 - b - k) / (1 - k) || 0; 870 return [c * 100, m * 100, y * 100, k * 100]; 871 } 872 873 function rgb2keyword(rgb) { 874 return reverseKeywords[JSON.stringify(rgb)]; 875 } 876 877 function rgb2xyz(rgb) { 878 var r = rgb[0] / 255, 879 g = rgb[1] / 255, 880 b = rgb[2] / 255; 881 882 // assume sRGB 883 r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); 884 g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); 885 b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); 886 887 var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); 888 var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); 889 var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); 890 891 return [x * 100, y *100, z * 100]; 892 } 893 894 function rgb2lab(rgb) { 895 var xyz = rgb2xyz(rgb), 896 x = xyz[0], 897 y = xyz[1], 898 z = xyz[2], 899 l, a, b; 900 901 x /= 95.047; 902 y /= 100; 903 z /= 108.883; 904 905 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); 906 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); 907 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); 908 909 l = (116 * y) - 16; 910 a = 500 * (x - y); 911 b = 200 * (y - z); 912 913 return [l, a, b]; 914 } 915 916 function rgb2lch(args) { 917 return lab2lch(rgb2lab(args)); 918 } 919 920 function hsl2rgb(hsl) { 921 var h = hsl[0] / 360, 922 s = hsl[1] / 100, 923 l = hsl[2] / 100, 924 t1, t2, t3, rgb, val; 925 926 if (s == 0) { 927 val = l * 255; 928 return [val, val, val]; 929 } 930 931 if (l < 0.5) 932 t2 = l * (1 + s); 933 else 934 t2 = l + s - l * s; 935 t1 = 2 * l - t2; 936 937 rgb = [0, 0, 0]; 938 for (var i = 0; i < 3; i++) { 939 t3 = h + 1 / 3 * - (i - 1); 940 t3 < 0 && t3++; 941 t3 > 1 && t3--; 942 943 if (6 * t3 < 1) 944 val = t1 + (t2 - t1) * 6 * t3; 945 else if (2 * t3 < 1) 946 val = t2; 947 else if (3 * t3 < 2) 948 val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; 949 else 950 val = t1; 951 952 rgb[i] = val * 255; 953 } 954 955 return rgb; 956 } 957 958 function hsl2hsv(hsl) { 959 var h = hsl[0], 960 s = hsl[1] / 100, 961 l = hsl[2] / 100, 962 sv, v; 963 964 if(l === 0) { 965 // no need to do calc on black 966 // also avoids divide by 0 error 967 return [0, 0, 0]; 968 } 969 970 l *= 2; 971 s *= (l <= 1) ? l : 2 - l; 972 v = (l + s) / 2; 973 sv = (2 * s) / (l + s); 974 return [h, sv * 100, v * 100]; 975 } 976 977 function hsl2hwb(args) { 978 return rgb2hwb(hsl2rgb(args)); 979 } 980 981 function hsl2cmyk(args) { 982 return rgb2cmyk(hsl2rgb(args)); 983 } 984 985 function hsl2keyword(args) { 986 return rgb2keyword(hsl2rgb(args)); 987 } 988 989 990 function hsv2rgb(hsv) { 991 var h = hsv[0] / 60, 992 s = hsv[1] / 100, 993 v = hsv[2] / 100, 994 hi = Math.floor(h) % 6; 995 996 var f = h - Math.floor(h), 997 p = 255 * v * (1 - s), 998 q = 255 * v * (1 - (s * f)), 999 t = 255 * v * (1 - (s * (1 - f))), 1000 v = 255 * v; 1001 1002 switch(hi) { 1003 case 0: 1004 return [v, t, p]; 1005 case 1: 1006 return [q, v, p]; 1007 case 2: 1008 return [p, v, t]; 1009 case 3: 1010 return [p, q, v]; 1011 case 4: 1012 return [t, p, v]; 1013 case 5: 1014 return [v, p, q]; 1015 } 1016 } 1017 1018 function hsv2hsl(hsv) { 1019 var h = hsv[0], 1020 s = hsv[1] / 100, 1021 v = hsv[2] / 100, 1022 sl, l; 1023 1024 l = (2 - s) * v; 1025 sl = s * v; 1026 sl /= (l <= 1) ? l : 2 - l; 1027 sl = sl || 0; 1028 l /= 2; 1029 return [h, sl * 100, l * 100]; 1030 } 1031 1032 function hsv2hwb(args) { 1033 return rgb2hwb(hsv2rgb(args)) 1034 } 1035 1036 function hsv2cmyk(args) { 1037 return rgb2cmyk(hsv2rgb(args)); 1038 } 1039 1040 function hsv2keyword(args) { 1041 return rgb2keyword(hsv2rgb(args)); 1042 } 1043 1044 // http://dev.w3.org/csswg/css-color/#hwb-to-rgb 1045 function hwb2rgb(hwb) { 1046 var h = hwb[0] / 360, 1047 wh = hwb[1] / 100, 1048 bl = hwb[2] / 100, 1049 ratio = wh + bl, 1050 i, v, f, n; 1051 1052 // wh + bl cant be > 1 1053 if (ratio > 1) { 1054 wh /= ratio; 1055 bl /= ratio; 1056 } 1057 1058 i = Math.floor(6 * h); 1059 v = 1 - bl; 1060 f = 6 * h - i; 1061 if ((i & 0x01) != 0) { 1062 f = 1 - f; 1063 } 1064 n = wh + f * (v - wh); // linear interpolation 1065 1066 switch (i) { 1067 default: 1068 case 6: 1069 case 0: r = v; g = n; b = wh; break; 1070 case 1: r = n; g = v; b = wh; break; 1071 case 2: r = wh; g = v; b = n; break; 1072 case 3: r = wh; g = n; b = v; break; 1073 case 4: r = n; g = wh; b = v; break; 1074 case 5: r = v; g = wh; b = n; break; 1075 } 1076 1077 return [r * 255, g * 255, b * 255]; 1078 } 1079 1080 function hwb2hsl(args) { 1081 return rgb2hsl(hwb2rgb(args)); 1082 } 1083 1084 function hwb2hsv(args) { 1085 return rgb2hsv(hwb2rgb(args)); 1086 } 1087 1088 function hwb2cmyk(args) { 1089 return rgb2cmyk(hwb2rgb(args)); 1090 } 1091 1092 function hwb2keyword(args) { 1093 return rgb2keyword(hwb2rgb(args)); 1094 } 1095 1096 function cmyk2rgb(cmyk) { 1097 var c = cmyk[0] / 100, 1098 m = cmyk[1] / 100, 1099 y = cmyk[2] / 100, 1100 k = cmyk[3] / 100, 1101 r, g, b; 1102 1103 r = 1 - Math.min(1, c * (1 - k) + k); 1104 g = 1 - Math.min(1, m * (1 - k) + k); 1105 b = 1 - Math.min(1, y * (1 - k) + k); 1106 return [r * 255, g * 255, b * 255]; 1107 } 1108 1109 function cmyk2hsl(args) { 1110 return rgb2hsl(cmyk2rgb(args)); 1111 } 1112 1113 function cmyk2hsv(args) { 1114 return rgb2hsv(cmyk2rgb(args)); 1115 } 1116 1117 function cmyk2hwb(args) { 1118 return rgb2hwb(cmyk2rgb(args)); 1119 } 1120 1121 function cmyk2keyword(args) { 1122 return rgb2keyword(cmyk2rgb(args)); 1123 } 1124 1125 1126 function xyz2rgb(xyz) { 1127 var x = xyz[0] / 100, 1128 y = xyz[1] / 100, 1129 z = xyz[2] / 100, 1130 r, g, b; 1131 1132 r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); 1133 g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); 1134 b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); 1135 1136 // assume sRGB 1137 r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) 1138 : r = (r * 12.92); 1139 1140 g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) 1141 : g = (g * 12.92); 1142 1143 b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) 1144 : b = (b * 12.92); 1145 1146 r = Math.min(Math.max(0, r), 1); 1147 g = Math.min(Math.max(0, g), 1); 1148 b = Math.min(Math.max(0, b), 1); 1149 1150 return [r * 255, g * 255, b * 255]; 1151 } 1152 1153 function xyz2lab(xyz) { 1154 var x = xyz[0], 1155 y = xyz[1], 1156 z = xyz[2], 1157 l, a, b; 1158 1159 x /= 95.047; 1160 y /= 100; 1161 z /= 108.883; 1162 1163 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); 1164 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); 1165 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); 1166 1167 l = (116 * y) - 16; 1168 a = 500 * (x - y); 1169 b = 200 * (y - z); 1170 1171 return [l, a, b]; 1172 } 1173 1174 function xyz2lch(args) { 1175 return lab2lch(xyz2lab(args)); 1176 } 1177 1178 function lab2xyz(lab) { 1179 var l = lab[0], 1180 a = lab[1], 1181 b = lab[2], 1182 x, y, z, y2; 1183 1184 if (l <= 8) { 1185 y = (l * 100) / 903.3; 1186 y2 = (7.787 * (y / 100)) + (16 / 116); 1187 } else { 1188 y = 100 * Math.pow((l + 16) / 116, 3); 1189 y2 = Math.pow(y / 100, 1/3); 1190 } 1191 1192 x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3); 1193 1194 z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3); 1195 1196 return [x, y, z]; 1197 } 1198 1199 function lab2lch(lab) { 1200 var l = lab[0], 1201 a = lab[1], 1202 b = lab[2], 1203 hr, h, c; 1204 1205 hr = Math.atan2(b, a); 1206 h = hr * 360 / 2 / Math.PI; 1207 if (h < 0) { 1208 h += 360; 1209 } 1210 c = Math.sqrt(a * a + b * b); 1211 return [l, c, h]; 1212 } 1213 1214 function lab2rgb(args) { 1215 return xyz2rgb(lab2xyz(args)); 1216 } 1217 1218 function lch2lab(lch) { 1219 var l = lch[0], 1220 c = lch[1], 1221 h = lch[2], 1222 a, b, hr; 1223 1224 hr = h / 360 * 2 * Math.PI; 1225 a = c * Math.cos(hr); 1226 b = c * Math.sin(hr); 1227 return [l, a, b]; 1228 } 1229 1230 function lch2xyz(args) { 1231 return lab2xyz(lch2lab(args)); 1232 } 1233 1234 function lch2rgb(args) { 1235 return lab2rgb(lch2lab(args)); 1236 } 1237 1238 function keyword2rgb(keyword) { 1239 return cssKeywords[keyword]; 1240 } 1241 1242 function keyword2hsl(args) { 1243 return rgb2hsl(keyword2rgb(args)); 1244 } 1245 1246 function keyword2hsv(args) { 1247 return rgb2hsv(keyword2rgb(args)); 1248 } 1249 1250 function keyword2hwb(args) { 1251 return rgb2hwb(keyword2rgb(args)); 1252 } 1253 1254 function keyword2cmyk(args) { 1255 return rgb2cmyk(keyword2rgb(args)); 1256 } 1257 1258 function keyword2lab(args) { 1259 return rgb2lab(keyword2rgb(args)); 1260 } 1261 1262 function keyword2xyz(args) { 1263 return rgb2xyz(keyword2rgb(args)); 1264 } 1265 1266 var cssKeywords = { 1267 aliceblue: [240,248,255], 1268 antiquewhite: [250,235,215], 1269 aqua: [0,255,255], 1270 aquamarine: [127,255,212], 1271 azure: [240,255,255], 1272 beige: [245,245,220], 1273 bisque: [255,228,196], 1274 black: [0,0,0], 1275 blanchedalmond: [255,235,205], 1276 blue: [0,0,255], 1277 blueviolet: [138,43,226], 1278 brown: [165,42,42], 1279 burlywood: [222,184,135], 1280 cadetblue: [95,158,160], 1281 chartreuse: [127,255,0], 1282 chocolate: [210,105,30], 1283 coral: [255,127,80], 1284 cornflowerblue: [100,149,237], 1285 cornsilk: [255,248,220], 1286 crimson: [220,20,60], 1287 cyan: [0,255,255], 1288 darkblue: [0,0,139], 1289 darkcyan: [0,139,139], 1290 darkgoldenrod: [184,134,11], 1291 darkgray: [169,169,169], 1292 darkgreen: [0,100,0], 1293 darkgrey: [169,169,169], 1294 darkkhaki: [189,183,107], 1295 darkmagenta: [139,0,139], 1296 darkolivegreen: [85,107,47], 1297 darkorange: [255,140,0], 1298 darkorchid: [153,50,204], 1299 darkred: [139,0,0], 1300 darksalmon: [233,150,122], 1301 darkseagreen: [143,188,143], 1302 darkslateblue: [72,61,139], 1303 darkslategray: [47,79,79], 1304 darkslategrey: [47,79,79], 1305 darkturquoise: [0,206,209], 1306 darkviolet: [148,0,211], 1307 deeppink: [255,20,147], 1308 deepskyblue: [0,191,255], 1309 dimgray: [105,105,105], 1310 dimgrey: [105,105,105], 1311 dodgerblue: [30,144,255], 1312 firebrick: [178,34,34], 1313 floralwhite: [255,250,240], 1314 forestgreen: [34,139,34], 1315 fuchsia: [255,0,255], 1316 gainsboro: [220,220,220], 1317 ghostwhite: [248,248,255], 1318 gold: [255,215,0], 1319 goldenrod: [218,165,32], 1320 gray: [128,128,128], 1321 green: [0,128,0], 1322 greenyellow: [173,255,47], 1323 grey: [128,128,128], 1324 honeydew: [240,255,240], 1325 hotpink: [255,105,180], 1326 indianred: [205,92,92], 1327 indigo: [75,0,130], 1328 ivory: [255,255,240], 1329 khaki: [240,230,140], 1330 lavender: [230,230,250], 1331 lavenderblush: [255,240,245], 1332 lawngreen: [124,252,0], 1333 lemonchiffon: [255,250,205], 1334 lightblue: [173,216,230], 1335 lightcoral: [240,128,128], 1336 lightcyan: [224,255,255], 1337 lightgoldenrodyellow: [250,250,210], 1338 lightgray: [211,211,211], 1339 lightgreen: [144,238,144], 1340 lightgrey: [211,211,211], 1341 lightpink: [255,182,193], 1342 lightsalmon: [255,160,122], 1343 lightseagreen: [32,178,170], 1344 lightskyblue: [135,206,250], 1345 lightslategray: [119,136,153], 1346 lightslategrey: [119,136,153], 1347 lightsteelblue: [176,196,222], 1348 lightyellow: [255,255,224], 1349 lime: [0,255,0], 1350 limegreen: [50,205,50], 1351 linen: [250,240,230], 1352 magenta: [255,0,255], 1353 maroon: [128,0,0], 1354 mediumaquamarine: [102,205,170], 1355 mediumblue: [0,0,205], 1356 mediumorchid: [186,85,211], 1357 mediumpurple: [147,112,219], 1358 mediumseagreen: [60,179,113], 1359 mediumslateblue: [123,104,238], 1360 mediumspringgreen: [0,250,154], 1361 mediumturquoise: [72,209,204], 1362 mediumvioletred: [199,21,133], 1363 midnightblue: [25,25,112], 1364 mintcream: [245,255,250], 1365 mistyrose: [255,228,225], 1366 moccasin: [255,228,181], 1367 navajowhite: [255,222,173], 1368 navy: [0,0,128], 1369 oldlace: [253,245,230], 1370 olive: [128,128,0], 1371 olivedrab: [107,142,35], 1372 orange: [255,165,0], 1373 orangered: [255,69,0], 1374 orchid: [218,112,214], 1375 palegoldenrod: [238,232,170], 1376 palegreen: [152,251,152], 1377 paleturquoise: [175,238,238], 1378 palevioletred: [219,112,147], 1379 papayawhip: [255,239,213], 1380 peachpuff: [255,218,185], 1381 peru: [205,133,63], 1382 pink: [255,192,203], 1383 plum: [221,160,221], 1384 powderblue: [176,224,230], 1385 purple: [128,0,128], 1386 rebeccapurple: [102, 51, 153], 1387 red: [255,0,0], 1388 rosybrown: [188,143,143], 1389 royalblue: [65,105,225], 1390 saddlebrown: [139,69,19], 1391 salmon: [250,128,114], 1392 sandybrown: [244,164,96], 1393 seagreen: [46,139,87], 1394 seashell: [255,245,238], 1395 sienna: [160,82,45], 1396 silver: [192,192,192], 1397 skyblue: [135,206,235], 1398 slateblue: [106,90,205], 1399 slategray: [112,128,144], 1400 slategrey: [112,128,144], 1401 snow: [255,250,250], 1402 springgreen: [0,255,127], 1403 steelblue: [70,130,180], 1404 tan: [210,180,140], 1405 teal: [0,128,128], 1406 thistle: [216,191,216], 1407 tomato: [255,99,71], 1408 turquoise: [64,224,208], 1409 violet: [238,130,238], 1410 wheat: [245,222,179], 1411 white: [255,255,255], 1412 whitesmoke: [245,245,245], 1413 yellow: [255,255,0], 1414 yellowgreen: [154,205,50] 1415 }; 1416 1417 var reverseKeywords = {}; 1418 for (var key in cssKeywords) { 1419 reverseKeywords[JSON.stringify(cssKeywords[key])] = key; 1420 } 1421 1422 },{}],5:[function(require,module,exports){ 1423 var conversions = require(4); 1424 1425 var convert = function() { 1426 return new Converter(); 1427 } 1428 1429 for (var func in conversions) { 1430 // export Raw versions 1431 convert[func + "Raw"] = (function(func) { 1432 // accept array or plain args 1433 return function(arg) { 1434 if (typeof arg == "number") 1435 arg = Array.prototype.slice.call(arguments); 1436 return conversions[func](arg); 1437 } 1438 })(func); 1439 1440 var pair = /(\w+)2(\w+)/.exec(func), 1441 from = pair[1], 1442 to = pair[2]; 1443 1444 // export rgb2hsl and ["rgb"]["hsl"] 1445 convert[from] = convert[from] || {}; 1446 1447 convert[from][to] = convert[func] = (function(func) { 1448 return function(arg) { 1449 if (typeof arg == "number") 1450 arg = Array.prototype.slice.call(arguments); 1451 1452 var val = conversions[func](arg); 1453 if (typeof val == "string" || val === undefined) 1454 return val; // keyword 1455 1456 for (var i = 0; i < val.length; i++) 1457 val[i] = Math.round(val[i]); 1458 return val; 1459 } 1460 })(func); 1461 } 1462 1463 1464 /* Converter does lazy conversion and caching */ 1465 var Converter = function() { 1466 this.convs = {}; 1467 }; 1468 1469 /* Either get the values for a space or 1470 set the values for a space, depending on args */ 1471 Converter.prototype.routeSpace = function(space, args) { 1472 var values = args[0]; 1473 if (values === undefined) { 1474 // color.rgb() 1475 return this.getValues(space); 1476 } 1477 // color.rgb(10, 10, 10) 1478 if (typeof values == "number") { 1479 values = Array.prototype.slice.call(args); 1480 } 1481 1482 return this.setValues(space, values); 1483 }; 1484 1485 /* Set the values for a space, invalidating cache */ 1486 Converter.prototype.setValues = function(space, values) { 1487 this.space = space; 1488 this.convs = {}; 1489 this.convs[space] = values; 1490 return this; 1491 }; 1492 1493 /* Get the values for a space. If there's already 1494 a conversion for the space, fetch it, otherwise 1495 compute it */ 1496 Converter.prototype.getValues = function(space) { 1497 var vals = this.convs[space]; 1498 if (!vals) { 1499 var fspace = this.space, 1500 from = this.convs[fspace]; 1501 vals = convert[fspace][space](from); 1502 1503 this.convs[space] = vals; 1504 } 1505 return vals; 1506 }; 1507 1508 ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) { 1509 Converter.prototype[space] = function(vals) { 1510 return this.routeSpace(space, arguments); 1511 } 1512 }); 1513 1514 module.exports = convert; 1515 },{"4":4}],6:[function(require,module,exports){ 1516 'use strict' 1517 1518 module.exports = { 1519 "aliceblue": [240, 248, 255], 1520 "antiquewhite": [250, 235, 215], 1521 "aqua": [0, 255, 255], 1522 "aquamarine": [127, 255, 212], 1523 "azure": [240, 255, 255], 1524 "beige": [245, 245, 220], 1525 "bisque": [255, 228, 196], 1526 "black": [0, 0, 0], 1527 "blanchedalmond": [255, 235, 205], 1528 "blue": [0, 0, 255], 1529 "blueviolet": [138, 43, 226], 1530 "brown": [165, 42, 42], 1531 "burlywood": [222, 184, 135], 1532 "cadetblue": [95, 158, 160], 1533 "chartreuse": [127, 255, 0], 1534 "chocolate": [210, 105, 30], 1535 "coral": [255, 127, 80], 1536 "cornflowerblue": [100, 149, 237], 1537 "cornsilk": [255, 248, 220], 1538 "crimson": [220, 20, 60], 1539 "cyan": [0, 255, 255], 1540 "darkblue": [0, 0, 139], 1541 "darkcyan": [0, 139, 139], 1542 "darkgoldenrod": [184, 134, 11], 1543 "darkgray": [169, 169, 169], 1544 "darkgreen": [0, 100, 0], 1545 "darkgrey": [169, 169, 169], 1546 "darkkhaki": [189, 183, 107], 1547 "darkmagenta": [139, 0, 139], 1548 "darkolivegreen": [85, 107, 47], 1549 "darkorange": [255, 140, 0], 1550 "darkorchid": [153, 50, 204], 1551 "darkred": [139, 0, 0], 1552 "darksalmon": [233, 150, 122], 1553 "darkseagreen": [143, 188, 143], 1554 "darkslateblue": [72, 61, 139], 1555 "darkslategray": [47, 79, 79], 1556 "darkslategrey": [47, 79, 79], 1557 "darkturquoise": [0, 206, 209], 1558 "darkviolet": [148, 0, 211], 1559 "deeppink": [255, 20, 147], 1560 "deepskyblue": [0, 191, 255], 1561 "dimgray": [105, 105, 105], 1562 "dimgrey": [105, 105, 105], 1563 "dodgerblue": [30, 144, 255], 1564 "firebrick": [178, 34, 34], 1565 "floralwhite": [255, 250, 240], 1566 "forestgreen": [34, 139, 34], 1567 "fuchsia": [255, 0, 255], 1568 "gainsboro": [220, 220, 220], 1569 "ghostwhite": [248, 248, 255], 1570 "gold": [255, 215, 0], 1571 "goldenrod": [218, 165, 32], 1572 "gray": [128, 128, 128], 1573 "green": [0, 128, 0], 1574 "greenyellow": [173, 255, 47], 1575 "grey": [128, 128, 128], 1576 "honeydew": [240, 255, 240], 1577 "hotpink": [255, 105, 180], 1578 "indianred": [205, 92, 92], 1579 "indigo": [75, 0, 130], 1580 "ivory": [255, 255, 240], 1581 "khaki": [240, 230, 140], 1582 "lavender": [230, 230, 250], 1583 "lavenderblush": [255, 240, 245], 1584 "lawngreen": [124, 252, 0], 1585 "lemonchiffon": [255, 250, 205], 1586 "lightblue": [173, 216, 230], 1587 "lightcoral": [240, 128, 128], 1588 "lightcyan": [224, 255, 255], 1589 "lightgoldenrodyellow": [250, 250, 210], 1590 "lightgray": [211, 211, 211], 1591 "lightgreen": [144, 238, 144], 1592 "lightgrey": [211, 211, 211], 1593 "lightpink": [255, 182, 193], 1594 "lightsalmon": [255, 160, 122], 1595 "lightseagreen": [32, 178, 170], 1596 "lightskyblue": [135, 206, 250], 1597 "lightslategray": [119, 136, 153], 1598 "lightslategrey": [119, 136, 153], 1599 "lightsteelblue": [176, 196, 222], 1600 "lightyellow": [255, 255, 224], 1601 "lime": [0, 255, 0], 1602 "limegreen": [50, 205, 50], 1603 "linen": [250, 240, 230], 1604 "magenta": [255, 0, 255], 1605 "maroon": [128, 0, 0], 1606 "mediumaquamarine": [102, 205, 170], 1607 "mediumblue": [0, 0, 205], 1608 "mediumorchid": [186, 85, 211], 1609 "mediumpurple": [147, 112, 219], 1610 "mediumseagreen": [60, 179, 113], 1611 "mediumslateblue": [123, 104, 238], 1612 "mediumspringgreen": [0, 250, 154], 1613 "mediumturquoise": [72, 209, 204], 1614 "mediumvioletred": [199, 21, 133], 1615 "midnightblue": [25, 25, 112], 1616 "mintcream": [245, 255, 250], 1617 "mistyrose": [255, 228, 225], 1618 "moccasin": [255, 228, 181], 1619 "navajowhite": [255, 222, 173], 1620 "navy": [0, 0, 128], 1621 "oldlace": [253, 245, 230], 1622 "olive": [128, 128, 0], 1623 "olivedrab": [107, 142, 35], 1624 "orange": [255, 165, 0], 1625 "orangered": [255, 69, 0], 1626 "orchid": [218, 112, 214], 1627 "palegoldenrod": [238, 232, 170], 1628 "palegreen": [152, 251, 152], 1629 "paleturquoise": [175, 238, 238], 1630 "palevioletred": [219, 112, 147], 1631 "papayawhip": [255, 239, 213], 1632 "peachpuff": [255, 218, 185], 1633 "peru": [205, 133, 63], 1634 "pink": [255, 192, 203], 1635 "plum": [221, 160, 221], 1636 "powderblue": [176, 224, 230], 1637 "purple": [128, 0, 128], 1638 "rebeccapurple": [102, 51, 153], 1639 "red": [255, 0, 0], 1640 "rosybrown": [188, 143, 143], 1641 "royalblue": [65, 105, 225], 1642 "saddlebrown": [139, 69, 19], 1643 "salmon": [250, 128, 114], 1644 "sandybrown": [244, 164, 96], 1645 "seagreen": [46, 139, 87], 1646 "seashell": [255, 245, 238], 1647 "sienna": [160, 82, 45], 1648 "silver": [192, 192, 192], 1649 "skyblue": [135, 206, 235], 1650 "slateblue": [106, 90, 205], 1651 "slategray": [112, 128, 144], 1652 "slategrey": [112, 128, 144], 1653 "snow": [255, 250, 250], 1654 "springgreen": [0, 255, 127], 1655 "steelblue": [70, 130, 180], 1656 "tan": [210, 180, 140], 1657 "teal": [0, 128, 128], 1658 "thistle": [216, 191, 216], 1659 "tomato": [255, 99, 71], 1660 "turquoise": [64, 224, 208], 1661 "violet": [238, 130, 238], 1662 "wheat": [245, 222, 179], 1663 "white": [255, 255, 255], 1664 "whitesmoke": [245, 245, 245], 1665 "yellow": [255, 255, 0], 1666 "yellowgreen": [154, 205, 50] 1667 }; 1668 1669 },{}],7:[function(require,module,exports){ 1670 /** 1671 * @namespace Chart 1672 */ 1673 var Chart = require(30)(); 1674 1675 Chart.helpers = require(46); 1676 1677 // @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! 1678 require(28)(Chart); 1679 1680 Chart.Animation = require(22); 1681 Chart.animationService = require(23); 1682 Chart.defaults = require(26); 1683 Chart.Element = require(27); 1684 Chart.elements = require(41); 1685 Chart.Interaction = require(29); 1686 Chart.layouts = require(31); 1687 Chart.platform = require(49); 1688 Chart.plugins = require(32); 1689 Chart.Scale = require(33); 1690 Chart.scaleService = require(34); 1691 Chart.Ticks = require(35); 1692 Chart.Tooltip = require(36); 1693 1694 require(24)(Chart); 1695 require(25)(Chart); 1696 1697 require(56)(Chart); 1698 require(54)(Chart); 1699 require(55)(Chart); 1700 require(57)(Chart); 1701 require(58)(Chart); 1702 require(59)(Chart); 1703 1704 // Controllers must be loaded after elements 1705 // See Chart.core.datasetController.dataElementType 1706 require(15)(Chart); 1707 require(16)(Chart); 1708 require(17)(Chart); 1709 require(18)(Chart); 1710 require(19)(Chart); 1711 require(20)(Chart); 1712 require(21)(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 // Loading built-in plugins 1723 var plugins = require(50); 1724 for (var k in plugins) { 1725 if (plugins.hasOwnProperty(k)) { 1726 Chart.plugins.register(plugins[k]); 1727 } 1728 } 1729 1730 Chart.platform.initialize(); 1731 1732 module.exports = Chart; 1733 if (typeof window !== 'undefined') { 1734 window.Chart = Chart; 1735 } 1736 1737 // DEPRECATIONS 1738 1739 /** 1740 * Provided for backward compatibility, not available anymore 1741 * @namespace Chart.Legend 1742 * @deprecated since version 2.1.5 1743 * @todo remove at version 3 1744 * @private 1745 */ 1746 Chart.Legend = plugins.legend._element; 1747 1748 /** 1749 * Provided for backward compatibility, not available anymore 1750 * @namespace Chart.Title 1751 * @deprecated since version 2.1.5 1752 * @todo remove at version 3 1753 * @private 1754 */ 1755 Chart.Title = plugins.title._element; 1756 1757 /** 1758 * Provided for backward compatibility, use Chart.plugins instead 1759 * @namespace Chart.pluginService 1760 * @deprecated since version 2.1.5 1761 * @todo remove at version 3 1762 * @private 1763 */ 1764 Chart.pluginService = Chart.plugins; 1765 1766 /** 1767 * Provided for backward compatibility, inheriting from Chart.PlugingBase has no 1768 * effect, instead simply create/register plugins via plain JavaScript objects. 1769 * @interface Chart.PluginBase 1770 * @deprecated since version 2.5.0 1771 * @todo remove at version 3 1772 * @private 1773 */ 1774 Chart.PluginBase = Chart.Element.extend({}); 1775 1776 /** 1777 * Provided for backward compatibility, use Chart.helpers.canvas instead. 1778 * @namespace Chart.canvasHelpers 1779 * @deprecated since version 2.6.0 1780 * @todo remove at version 3 1781 * @private 1782 */ 1783 Chart.canvasHelpers = Chart.helpers.canvas; 1784 1785 /** 1786 * Provided for backward compatibility, use Chart.layouts instead. 1787 * @namespace Chart.layoutService 1788 * @deprecated since version 2.8.0 1789 * @todo remove at version 3 1790 * @private 1791 */ 1792 Chart.layoutService = Chart.layouts; 1793 1794 },{"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,"41":41,"46":46,"49":49,"50":50,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"8":8,"9":9}],8:[function(require,module,exports){ 1795 'use strict'; 1796 1797 module.exports = function(Chart) { 1798 1799 Chart.Bar = function(context, config) { 1800 config.type = 'bar'; 1801 1802 return new Chart(context, config); 1803 }; 1804 1805 }; 1806 1807 },{}],9:[function(require,module,exports){ 1808 'use strict'; 1809 1810 module.exports = function(Chart) { 1811 1812 Chart.Bubble = function(context, config) { 1813 config.type = 'bubble'; 1814 return new Chart(context, config); 1815 }; 1816 1817 }; 1818 1819 },{}],10:[function(require,module,exports){ 1820 'use strict'; 1821 1822 module.exports = function(Chart) { 1823 1824 Chart.Doughnut = function(context, config) { 1825 config.type = 'doughnut'; 1826 1827 return new Chart(context, config); 1828 }; 1829 1830 }; 1831 1832 },{}],11:[function(require,module,exports){ 1833 'use strict'; 1834 1835 module.exports = function(Chart) { 1836 1837 Chart.Line = function(context, config) { 1838 config.type = 'line'; 1839 1840 return new Chart(context, config); 1841 }; 1842 1843 }; 1844 1845 },{}],12:[function(require,module,exports){ 1846 'use strict'; 1847 1848 module.exports = function(Chart) { 1849 1850 Chart.PolarArea = function(context, config) { 1851 config.type = 'polarArea'; 1852 1853 return new Chart(context, config); 1854 }; 1855 1856 }; 1857 1858 },{}],13:[function(require,module,exports){ 1859 'use strict'; 1860 1861 module.exports = function(Chart) { 1862 1863 Chart.Radar = function(context, config) { 1864 config.type = 'radar'; 1865 1866 return new Chart(context, config); 1867 }; 1868 1869 }; 1870 1871 },{}],14:[function(require,module,exports){ 1872 'use strict'; 1873 1874 module.exports = function(Chart) { 1875 Chart.Scatter = function(context, config) { 1876 config.type = 'scatter'; 1877 return new Chart(context, config); 1878 }; 1879 }; 1880 1881 },{}],15:[function(require,module,exports){ 1882 'use strict'; 1883 1884 var defaults = require(26); 1885 var elements = require(41); 1886 var helpers = require(46); 1887 1888 defaults._set('bar', { 1889 hover: { 1890 mode: 'label' 1891 }, 1892 1893 scales: { 1894 xAxes: [{ 1895 type: 'category', 1896 1897 // Specific to Bar Controller 1898 categoryPercentage: 0.8, 1899 barPercentage: 0.9, 1900 1901 // offset settings 1902 offset: true, 1903 1904 // grid line settings 1905 gridLines: { 1906 offsetGridLines: true 1907 } 1908 }], 1909 1910 yAxes: [{ 1911 type: 'linear' 1912 }] 1913 } 1914 }); 1915 1916 defaults._set('horizontalBar', { 1917 hover: { 1918 mode: 'index', 1919 axis: 'y' 1920 }, 1921 1922 scales: { 1923 xAxes: [{ 1924 type: 'linear', 1925 position: 'bottom' 1926 }], 1927 1928 yAxes: [{ 1929 position: 'left', 1930 type: 'category', 1931 1932 // Specific to Horizontal Bar Controller 1933 categoryPercentage: 0.8, 1934 barPercentage: 0.9, 1935 1936 // offset settings 1937 offset: true, 1938 1939 // grid line settings 1940 gridLines: { 1941 offsetGridLines: true 1942 } 1943 }] 1944 }, 1945 1946 elements: { 1947 rectangle: { 1948 borderSkipped: 'left' 1949 } 1950 }, 1951 1952 tooltips: { 1953 callbacks: { 1954 title: function(item, data) { 1955 // Pick first xLabel for now 1956 var title = ''; 1957 1958 if (item.length > 0) { 1959 if (item[0].yLabel) { 1960 title = item[0].yLabel; 1961 } else if (data.labels.length > 0 && item[0].index < data.labels.length) { 1962 title = data.labels[item[0].index]; 1963 } 1964 } 1965 1966 return title; 1967 }, 1968 1969 label: function(item, data) { 1970 var datasetLabel = data.datasets[item.datasetIndex].label || ''; 1971 return datasetLabel + ': ' + item.xLabel; 1972 } 1973 }, 1974 mode: 'index', 1975 axis: 'y' 1976 } 1977 }); 1978 1979 /** 1980 * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. 1981 * @private 1982 */ 1983 function computeMinSampleSize(scale, pixels) { 1984 var min = scale.isHorizontal() ? scale.width : scale.height; 1985 var ticks = scale.getTicks(); 1986 var prev, curr, i, ilen; 1987 1988 for (i = 1, ilen = pixels.length; i < ilen; ++i) { 1989 min = Math.min(min, pixels[i] - pixels[i - 1]); 1990 } 1991 1992 for (i = 0, ilen = ticks.length; i < ilen; ++i) { 1993 curr = scale.getPixelForTick(i); 1994 min = i > 0 ? Math.min(min, curr - prev) : min; 1995 prev = curr; 1996 } 1997 1998 return min; 1999 } 2000 2001 /** 2002 * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, 2003 * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This 2004 * mode currently always generates bars equally sized (until we introduce scriptable options?). 2005 * @private 2006 */ 2007 function computeFitCategoryTraits(index, ruler, options) { 2008 var thickness = options.barThickness; 2009 var count = ruler.stackCount; 2010 var curr = ruler.pixels[index]; 2011 var size, ratio; 2012 2013 if (helpers.isNullOrUndef(thickness)) { 2014 size = ruler.min * options.categoryPercentage; 2015 ratio = options.barPercentage; 2016 } else { 2017 // When bar thickness is enforced, category and bar percentages are ignored. 2018 // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') 2019 // and deprecate barPercentage since this value is ignored when thickness is absolute. 2020 size = thickness * count; 2021 ratio = 1; 2022 } 2023 2024 return { 2025 chunk: size / count, 2026 ratio: ratio, 2027 start: curr - (size / 2) 2028 }; 2029 } 2030 2031 /** 2032 * Computes an "optimal" category that globally arranges bars side by side (no gap when 2033 * percentage options are 1), based on the previous and following categories. This mode 2034 * generates bars with different widths when data are not evenly spaced. 2035 * @private 2036 */ 2037 function computeFlexCategoryTraits(index, ruler, options) { 2038 var pixels = ruler.pixels; 2039 var curr = pixels[index]; 2040 var prev = index > 0 ? pixels[index - 1] : null; 2041 var next = index < pixels.length - 1 ? pixels[index + 1] : null; 2042 var percent = options.categoryPercentage; 2043 var start, size; 2044 2045 if (prev === null) { 2046 // first data: its size is double based on the next point or, 2047 // if it's also the last data, we use the scale end extremity. 2048 prev = curr - (next === null ? ruler.end - curr : next - curr); 2049 } 2050 2051 if (next === null) { 2052 // last data: its size is also double based on the previous point. 2053 next = curr + curr - prev; 2054 } 2055 2056 start = curr - ((curr - prev) / 2) * percent; 2057 size = ((next - prev) / 2) * percent; 2058 2059 return { 2060 chunk: size / ruler.stackCount, 2061 ratio: options.barPercentage, 2062 start: start 2063 }; 2064 } 2065 2066 module.exports = function(Chart) { 2067 2068 Chart.controllers.bar = Chart.DatasetController.extend({ 2069 2070 dataElementType: elements.Rectangle, 2071 2072 initialize: function() { 2073 var me = this; 2074 var meta; 2075 2076 Chart.DatasetController.prototype.initialize.apply(me, arguments); 2077 2078 meta = me.getMeta(); 2079 meta.stack = me.getDataset().stack; 2080 meta.bar = true; 2081 }, 2082 2083 update: function(reset) { 2084 var me = this; 2085 var rects = me.getMeta().data; 2086 var i, ilen; 2087 2088 me._ruler = me.getRuler(); 2089 2090 for (i = 0, ilen = rects.length; i < ilen; ++i) { 2091 me.updateElement(rects[i], i, reset); 2092 } 2093 }, 2094 2095 updateElement: function(rectangle, index, reset) { 2096 var me = this; 2097 var chart = me.chart; 2098 var meta = me.getMeta(); 2099 var dataset = me.getDataset(); 2100 var custom = rectangle.custom || {}; 2101 var rectangleOptions = chart.options.elements.rectangle; 2102 2103 rectangle._xScale = me.getScaleForId(meta.xAxisID); 2104 rectangle._yScale = me.getScaleForId(meta.yAxisID); 2105 rectangle._datasetIndex = me.index; 2106 rectangle._index = index; 2107 2108 rectangle._model = { 2109 datasetLabel: dataset.label, 2110 label: chart.data.labels[index], 2111 borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped, 2112 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor), 2113 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor), 2114 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth) 2115 }; 2116 2117 me.updateElementGeometry(rectangle, index, reset); 2118 2119 rectangle.pivot(); 2120 }, 2121 2122 /** 2123 * @private 2124 */ 2125 updateElementGeometry: function(rectangle, index, reset) { 2126 var me = this; 2127 var model = rectangle._model; 2128 var vscale = me.getValueScale(); 2129 var base = vscale.getBasePixel(); 2130 var horizontal = vscale.isHorizontal(); 2131 var ruler = me._ruler || me.getRuler(); 2132 var vpixels = me.calculateBarValuePixels(me.index, index); 2133 var ipixels = me.calculateBarIndexPixels(me.index, index, ruler); 2134 2135 model.horizontal = horizontal; 2136 model.base = reset ? base : vpixels.base; 2137 model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; 2138 model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; 2139 model.height = horizontal ? ipixels.size : undefined; 2140 model.width = horizontal ? undefined : ipixels.size; 2141 }, 2142 2143 /** 2144 * @private 2145 */ 2146 getValueScaleId: function() { 2147 return this.getMeta().yAxisID; 2148 }, 2149 2150 /** 2151 * @private 2152 */ 2153 getIndexScaleId: function() { 2154 return this.getMeta().xAxisID; 2155 }, 2156 2157 /** 2158 * @private 2159 */ 2160 getValueScale: function() { 2161 return this.getScaleForId(this.getValueScaleId()); 2162 }, 2163 2164 /** 2165 * @private 2166 */ 2167 getIndexScale: function() { 2168 return this.getScaleForId(this.getIndexScaleId()); 2169 }, 2170 2171 /** 2172 * Returns the stacks based on groups and bar visibility. 2173 * @param {Number} [last] - The dataset index 2174 * @returns {Array} The stack list 2175 * @private 2176 */ 2177 _getStacks: function(last) { 2178 var me = this; 2179 var chart = me.chart; 2180 var scale = me.getIndexScale(); 2181 var stacked = scale.options.stacked; 2182 var ilen = last === undefined ? chart.data.datasets.length : last + 1; 2183 var stacks = []; 2184 var i, meta; 2185 2186 for (i = 0; i < ilen; ++i) { 2187 meta = chart.getDatasetMeta(i); 2188 if (meta.bar && chart.isDatasetVisible(i) && 2189 (stacked === false || 2190 (stacked === true && stacks.indexOf(meta.stack) === -1) || 2191 (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) { 2192 stacks.push(meta.stack); 2193 } 2194 } 2195 2196 return stacks; 2197 }, 2198 2199 /** 2200 * Returns the effective number of stacks based on groups and bar visibility. 2201 * @private 2202 */ 2203 getStackCount: function() { 2204 return this._getStacks().length; 2205 }, 2206 2207 /** 2208 * Returns the stack index for the given dataset based on groups and bar visibility. 2209 * @param {Number} [datasetIndex] - The dataset index 2210 * @param {String} [name] - The stack name to find 2211 * @returns {Number} The stack index 2212 * @private 2213 */ 2214 getStackIndex: function(datasetIndex, name) { 2215 var stacks = this._getStacks(datasetIndex); 2216 var index = (name !== undefined) 2217 ? stacks.indexOf(name) 2218 : -1; // indexOf returns -1 if element is not present 2219 2220 return (index === -1) 2221 ? stacks.length - 1 2222 : index; 2223 }, 2224 2225 /** 2226 * @private 2227 */ 2228 getRuler: function() { 2229 var me = this; 2230 var scale = me.getIndexScale(); 2231 var stackCount = me.getStackCount(); 2232 var datasetIndex = me.index; 2233 var isHorizontal = scale.isHorizontal(); 2234 var start = isHorizontal ? scale.left : scale.top; 2235 var end = start + (isHorizontal ? scale.width : scale.height); 2236 var pixels = []; 2237 var i, ilen, min; 2238 2239 for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { 2240 pixels.push(scale.getPixelForValue(null, i, datasetIndex)); 2241 } 2242 2243 min = helpers.isNullOrUndef(scale.options.barThickness) 2244 ? computeMinSampleSize(scale, pixels) 2245 : -1; 2246 2247 return { 2248 min: min, 2249 pixels: pixels, 2250 start: start, 2251 end: end, 2252 stackCount: stackCount, 2253 scale: scale 2254 }; 2255 }, 2256 2257 /** 2258 * Note: pixel values are not clamped to the scale area. 2259 * @private 2260 */ 2261 calculateBarValuePixels: function(datasetIndex, index) { 2262 var me = this; 2263 var chart = me.chart; 2264 var meta = me.getMeta(); 2265 var scale = me.getValueScale(); 2266 var datasets = chart.data.datasets; 2267 var value = scale.getRightValue(datasets[datasetIndex].data[index]); 2268 var stacked = scale.options.stacked; 2269 var stack = meta.stack; 2270 var start = 0; 2271 var i, imeta, ivalue, base, head, size; 2272 2273 if (stacked || (stacked === undefined && stack !== undefined)) { 2274 for (i = 0; i < datasetIndex; ++i) { 2275 imeta = chart.getDatasetMeta(i); 2276 2277 if (imeta.bar && 2278 imeta.stack === stack && 2279 imeta.controller.getValueScaleId() === scale.id && 2280 chart.isDatasetVisible(i)) { 2281 2282 ivalue = scale.getRightValue(datasets[i].data[index]); 2283 if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) { 2284 start += ivalue; 2285 } 2286 } 2287 } 2288 } 2289 2290 base = scale.getPixelForValue(start); 2291 head = scale.getPixelForValue(start + value); 2292 size = (head - base) / 2; 2293 2294 return { 2295 size: size, 2296 base: base, 2297 head: head, 2298 center: head + size / 2 2299 }; 2300 }, 2301 2302 /** 2303 * @private 2304 */ 2305 calculateBarIndexPixels: function(datasetIndex, index, ruler) { 2306 var me = this; 2307 var options = ruler.scale.options; 2308 var range = options.barThickness === 'flex' 2309 ? computeFlexCategoryTraits(index, ruler, options) 2310 : computeFitCategoryTraits(index, ruler, options); 2311 2312 var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); 2313 var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); 2314 var size = Math.min( 2315 helpers.valueOrDefault(options.maxBarThickness, Infinity), 2316 range.chunk * range.ratio); 2317 2318 return { 2319 base: center - size / 2, 2320 head: center + size / 2, 2321 center: center, 2322 size: size 2323 }; 2324 }, 2325 2326 draw: function() { 2327 var me = this; 2328 var chart = me.chart; 2329 var scale = me.getValueScale(); 2330 var rects = me.getMeta().data; 2331 var dataset = me.getDataset(); 2332 var ilen = rects.length; 2333 var i = 0; 2334 2335 helpers.canvas.clipArea(chart.ctx, chart.chartArea); 2336 2337 for (; i < ilen; ++i) { 2338 if (!isNaN(scale.getRightValue(dataset.data[i]))) { 2339 rects[i].draw(); 2340 } 2341 } 2342 2343 helpers.canvas.unclipArea(chart.ctx); 2344 }, 2345 }); 2346 2347 Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ 2348 /** 2349 * @private 2350 */ 2351 getValueScaleId: function() { 2352 return this.getMeta().xAxisID; 2353 }, 2354 2355 /** 2356 * @private 2357 */ 2358 getIndexScaleId: function() { 2359 return this.getMeta().yAxisID; 2360 } 2361 }); 2362 }; 2363 2364 },{"26":26,"41":41,"46":46}],16:[function(require,module,exports){ 2365 'use strict'; 2366 2367 var defaults = require(26); 2368 var elements = require(41); 2369 var helpers = require(46); 2370 2371 defaults._set('bubble', { 2372 hover: { 2373 mode: 'single' 2374 }, 2375 2376 scales: { 2377 xAxes: [{ 2378 type: 'linear', // bubble should probably use a linear scale by default 2379 position: 'bottom', 2380 id: 'x-axis-0' // need an ID so datasets can reference the scale 2381 }], 2382 yAxes: [{ 2383 type: 'linear', 2384 position: 'left', 2385 id: 'y-axis-0' 2386 }] 2387 }, 2388 2389 tooltips: { 2390 callbacks: { 2391 title: function() { 2392 // Title doesn't make sense for scatter since we format the data as a point 2393 return ''; 2394 }, 2395 label: function(item, data) { 2396 var datasetLabel = data.datasets[item.datasetIndex].label || ''; 2397 var dataPoint = data.datasets[item.datasetIndex].data[item.index]; 2398 return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; 2399 } 2400 } 2401 } 2402 }); 2403 2404 2405 module.exports = function(Chart) { 2406 2407 Chart.controllers.bubble = Chart.DatasetController.extend({ 2408 /** 2409 * @protected 2410 */ 2411 dataElementType: elements.Point, 2412 2413 /** 2414 * @protected 2415 */ 2416 update: function(reset) { 2417 var me = this; 2418 var meta = me.getMeta(); 2419 var points = meta.data; 2420 2421 // Update Points 2422 helpers.each(points, function(point, index) { 2423 me.updateElement(point, index, reset); 2424 }); 2425 }, 2426 2427 /** 2428 * @protected 2429 */ 2430 updateElement: function(point, index, reset) { 2431 var me = this; 2432 var meta = me.getMeta(); 2433 var custom = point.custom || {}; 2434 var xScale = me.getScaleForId(meta.xAxisID); 2435 var yScale = me.getScaleForId(meta.yAxisID); 2436 var options = me._resolveElementOptions(point, index); 2437 var data = me.getDataset().data[index]; 2438 var dsIndex = me.index; 2439 2440 var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); 2441 var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); 2442 2443 point._xScale = xScale; 2444 point._yScale = yScale; 2445 point._options = options; 2446 point._datasetIndex = dsIndex; 2447 point._index = index; 2448 point._model = { 2449 backgroundColor: options.backgroundColor, 2450 borderColor: options.borderColor, 2451 borderWidth: options.borderWidth, 2452 hitRadius: options.hitRadius, 2453 pointStyle: options.pointStyle, 2454 rotation: options.rotation, 2455 radius: reset ? 0 : options.radius, 2456 skip: custom.skip || isNaN(x) || isNaN(y), 2457 x: x, 2458 y: y, 2459 }; 2460 2461 point.pivot(); 2462 }, 2463 2464 /** 2465 * @protected 2466 */ 2467 setHoverStyle: function(point) { 2468 var model = point._model; 2469 var options = point._options; 2470 point.$previousStyle = { 2471 backgroundColor: model.backgroundColor, 2472 borderColor: model.borderColor, 2473 borderWidth: model.borderWidth, 2474 radius: model.radius 2475 }; 2476 model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor)); 2477 model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor)); 2478 model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth); 2479 model.radius = options.radius + options.hoverRadius; 2480 }, 2481 2482 /** 2483 * @private 2484 */ 2485 _resolveElementOptions: function(point, index) { 2486 var me = this; 2487 var chart = me.chart; 2488 var datasets = chart.data.datasets; 2489 var dataset = datasets[me.index]; 2490 var custom = point.custom || {}; 2491 var options = chart.options.elements.point; 2492 var resolve = helpers.options.resolve; 2493 var data = dataset.data[index]; 2494 var values = {}; 2495 var i, ilen, key; 2496 2497 // Scriptable options 2498 var context = { 2499 chart: chart, 2500 dataIndex: index, 2501 dataset: dataset, 2502 datasetIndex: me.index 2503 }; 2504 2505 var keys = [ 2506 'backgroundColor', 2507 'borderColor', 2508 'borderWidth', 2509 'hoverBackgroundColor', 2510 'hoverBorderColor', 2511 'hoverBorderWidth', 2512 'hoverRadius', 2513 'hitRadius', 2514 'pointStyle', 2515 'rotation' 2516 ]; 2517 2518 for (i = 0, ilen = keys.length; i < ilen; ++i) { 2519 key = keys[i]; 2520 values[key] = resolve([ 2521 custom[key], 2522 dataset[key], 2523 options[key] 2524 ], context, index); 2525 } 2526 2527 // Custom radius resolution 2528 values.radius = resolve([ 2529 custom.radius, 2530 data ? data.r : undefined, 2531 dataset.radius, 2532 options.radius 2533 ], context, index); 2534 return values; 2535 } 2536 }); 2537 }; 2538 2539 },{"26":26,"41":41,"46":46}],17:[function(require,module,exports){ 2540 'use strict'; 2541 2542 var defaults = require(26); 2543 var elements = require(41); 2544 var helpers = require(46); 2545 2546 defaults._set('doughnut', { 2547 animation: { 2548 // Boolean - Whether we animate the rotation of the Doughnut 2549 animateRotate: true, 2550 // Boolean - Whether we animate scaling the Doughnut from the centre 2551 animateScale: false 2552 }, 2553 hover: { 2554 mode: 'single' 2555 }, 2556 legendCallback: function(chart) { 2557 var text = []; 2558 text.push('<ul class="' + chart.id + '-legend">'); 2559 2560 var data = chart.data; 2561 var datasets = data.datasets; 2562 var labels = data.labels; 2563 2564 if (datasets.length) { 2565 for (var i = 0; i < datasets[0].data.length; ++i) { 2566 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); 2567 if (labels[i]) { 2568 text.push(labels[i]); 2569 } 2570 text.push('</li>'); 2571 } 2572 } 2573 2574 text.push('</ul>'); 2575 return text.join(''); 2576 }, 2577 legend: { 2578 labels: { 2579 generateLabels: function(chart) { 2580 var data = chart.data; 2581 if (data.labels.length && data.datasets.length) { 2582 return data.labels.map(function(label, i) { 2583 var meta = chart.getDatasetMeta(0); 2584 var ds = data.datasets[0]; 2585 var arc = meta.data[i]; 2586 var custom = arc && arc.custom || {}; 2587 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; 2588 var arcOpts = chart.options.elements.arc; 2589 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); 2590 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); 2591 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); 2592 2593 return { 2594 text: label, 2595 fillStyle: fill, 2596 strokeStyle: stroke, 2597 lineWidth: bw, 2598 hidden: isNaN(ds.data[i]) || meta.data[i].hidden, 2599 2600 // Extra data used for toggling the correct item 2601 index: i 2602 }; 2603 }); 2604 } 2605 return []; 2606 } 2607 }, 2608 2609 onClick: function(e, legendItem) { 2610 var index = legendItem.index; 2611 var chart = this.chart; 2612 var i, ilen, meta; 2613 2614 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { 2615 meta = chart.getDatasetMeta(i); 2616 // toggle visibility of index if exists 2617 if (meta.data[index]) { 2618 meta.data[index].hidden = !meta.data[index].hidden; 2619 } 2620 } 2621 2622 chart.update(); 2623 } 2624 }, 2625 2626 // The percentage of the chart that we cut out of the middle. 2627 cutoutPercentage: 50, 2628 2629 // The rotation of the chart, where the first data arc begins. 2630 rotation: Math.PI * -0.5, 2631 2632 // The total circumference of the chart. 2633 circumference: Math.PI * 2.0, 2634 2635 // Need to override these to give a nice default 2636 tooltips: { 2637 callbacks: { 2638 title: function() { 2639 return ''; 2640 }, 2641 label: function(tooltipItem, data) { 2642 var dataLabel = data.labels[tooltipItem.index]; 2643 var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; 2644 2645 if (helpers.isArray(dataLabel)) { 2646 // show value on first line of multiline label 2647 // need to clone because we are changing the value 2648 dataLabel = dataLabel.slice(); 2649 dataLabel[0] += value; 2650 } else { 2651 dataLabel += value; 2652 } 2653 2654 return dataLabel; 2655 } 2656 } 2657 } 2658 }); 2659 2660 defaults._set('pie', helpers.clone(defaults.doughnut)); 2661 defaults._set('pie', { 2662 cutoutPercentage: 0 2663 }); 2664 2665 module.exports = function(Chart) { 2666 2667 Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ 2668 2669 dataElementType: elements.Arc, 2670 2671 linkScales: helpers.noop, 2672 2673 // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly 2674 getRingIndex: function(datasetIndex) { 2675 var ringIndex = 0; 2676 2677 for (var j = 0; j < datasetIndex; ++j) { 2678 if (this.chart.isDatasetVisible(j)) { 2679 ++ringIndex; 2680 } 2681 } 2682 2683 return ringIndex; 2684 }, 2685 2686 update: function(reset) { 2687 var me = this; 2688 var chart = me.chart; 2689 var chartArea = chart.chartArea; 2690 var opts = chart.options; 2691 var arcOpts = opts.elements.arc; 2692 var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth; 2693 var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth; 2694 var minSize = Math.min(availableWidth, availableHeight); 2695 var offset = {x: 0, y: 0}; 2696 var meta = me.getMeta(); 2697 var cutoutPercentage = opts.cutoutPercentage; 2698 var circumference = opts.circumference; 2699 2700 // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc 2701 if (circumference < Math.PI * 2.0) { 2702 var startAngle = opts.rotation % (Math.PI * 2.0); 2703 startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); 2704 var endAngle = startAngle + circumference; 2705 var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; 2706 var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; 2707 var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); 2708 var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); 2709 var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); 2710 var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); 2711 var cutout = cutoutPercentage / 100.0; 2712 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))}; 2713 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))}; 2714 var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; 2715 minSize = Math.min(availableWidth / size.width, availableHeight / size.height); 2716 offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; 2717 } 2718 2719 chart.borderWidth = me.getMaxBorderWidth(meta.data); 2720 chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); 2721 chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); 2722 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); 2723 chart.offsetX = offset.x * chart.outerRadius; 2724 chart.offsetY = offset.y * chart.outerRadius; 2725 2726 meta.total = me.calculateTotal(); 2727 2728 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); 2729 me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0); 2730 2731 helpers.each(meta.data, function(arc, index) { 2732 me.updateElement(arc, index, reset); 2733 }); 2734 }, 2735 2736 updateElement: function(arc, index, reset) { 2737 var me = this; 2738 var chart = me.chart; 2739 var chartArea = chart.chartArea; 2740 var opts = chart.options; 2741 var animationOpts = opts.animation; 2742 var centerX = (chartArea.left + chartArea.right) / 2; 2743 var centerY = (chartArea.top + chartArea.bottom) / 2; 2744 var startAngle = opts.rotation; // non reset case handled later 2745 var endAngle = opts.rotation; // non reset case handled later 2746 var dataset = me.getDataset(); 2747 var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)); 2748 var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; 2749 var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; 2750 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; 2751 2752 helpers.extend(arc, { 2753 // Utility 2754 _datasetIndex: me.index, 2755 _index: index, 2756 2757 // Desired view properties 2758 _model: { 2759 x: centerX + chart.offsetX, 2760 y: centerY + chart.offsetY, 2761 startAngle: startAngle, 2762 endAngle: endAngle, 2763 circumference: circumference, 2764 outerRadius: outerRadius, 2765 innerRadius: innerRadius, 2766 label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) 2767 } 2768 }); 2769 2770 var model = arc._model; 2771 2772 // Resets the visual styles 2773 var custom = arc.custom || {}; 2774 var valueOrDefault = helpers.valueAtIndexOrDefault; 2775 var elementOpts = this.chart.options.elements.arc; 2776 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); 2777 model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); 2778 model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); 2779 2780 // Set correct angles if not resetting 2781 if (!reset || !animationOpts.animateRotate) { 2782 if (index === 0) { 2783 model.startAngle = opts.rotation; 2784 } else { 2785 model.startAngle = me.getMeta().data[index - 1]._model.endAngle; 2786 } 2787 2788 model.endAngle = model.startAngle + model.circumference; 2789 } 2790 2791 arc.pivot(); 2792 }, 2793 2794 calculateTotal: function() { 2795 var dataset = this.getDataset(); 2796 var meta = this.getMeta(); 2797 var total = 0; 2798 var value; 2799 2800 helpers.each(meta.data, function(element, index) { 2801 value = dataset.data[index]; 2802 if (!isNaN(value) && !element.hidden) { 2803 total += Math.abs(value); 2804 } 2805 }); 2806 2807 /* if (total === 0) { 2808 total = NaN; 2809 }*/ 2810 2811 return total; 2812 }, 2813 2814 calculateCircumference: function(value) { 2815 var total = this.getMeta().total; 2816 if (total > 0 && !isNaN(value)) { 2817 return (Math.PI * 2.0) * (Math.abs(value) / total); 2818 } 2819 return 0; 2820 }, 2821 2822 // gets the max border or hover width to properly scale pie charts 2823 getMaxBorderWidth: function(arcs) { 2824 var max = 0; 2825 var index = this.index; 2826 var length = arcs.length; 2827 var borderWidth; 2828 var hoverWidth; 2829 2830 for (var i = 0; i < length; i++) { 2831 borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0; 2832 hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; 2833 2834 max = borderWidth > max ? borderWidth : max; 2835 max = hoverWidth > max ? hoverWidth : max; 2836 } 2837 return max; 2838 } 2839 }); 2840 }; 2841 2842 },{"26":26,"41":41,"46":46}],18:[function(require,module,exports){ 2843 'use strict'; 2844 2845 var defaults = require(26); 2846 var elements = require(41); 2847 var helpers = require(46); 2848 2849 defaults._set('line', { 2850 showLines: true, 2851 spanGaps: false, 2852 2853 hover: { 2854 mode: 'label' 2855 }, 2856 2857 scales: { 2858 xAxes: [{ 2859 type: 'category', 2860 id: 'x-axis-0' 2861 }], 2862 yAxes: [{ 2863 type: 'linear', 2864 id: 'y-axis-0' 2865 }] 2866 } 2867 }); 2868 2869 module.exports = function(Chart) { 2870 2871 function lineEnabled(dataset, options) { 2872 return helpers.valueOrDefault(dataset.showLine, options.showLines); 2873 } 2874 2875 Chart.controllers.line = Chart.DatasetController.extend({ 2876 2877 datasetElementType: elements.Line, 2878 2879 dataElementType: elements.Point, 2880 2881 update: function(reset) { 2882 var me = this; 2883 var meta = me.getMeta(); 2884 var line = meta.dataset; 2885 var points = meta.data || []; 2886 var options = me.chart.options; 2887 var lineElementOptions = options.elements.line; 2888 var scale = me.getScaleForId(meta.yAxisID); 2889 var i, ilen, custom; 2890 var dataset = me.getDataset(); 2891 var showLine = lineEnabled(dataset, options); 2892 2893 // Update Line 2894 if (showLine) { 2895 custom = line.custom || {}; 2896 2897 // Compatibility: If the properties are defined with only the old name, use those values 2898 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { 2899 dataset.lineTension = dataset.tension; 2900 } 2901 2902 // Utility 2903 line._scale = scale; 2904 line._datasetIndex = me.index; 2905 // Data 2906 line._children = points; 2907 // Model 2908 line._model = { 2909 // Appearance 2910 // The default behavior of lines is to break at null values, according 2911 // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 2912 // This option gives lines the ability to span gaps 2913 spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps, 2914 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), 2915 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), 2916 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), 2917 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), 2918 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), 2919 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), 2920 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), 2921 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), 2922 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), 2923 steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped), 2924 cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode), 2925 }; 2926 2927 line.pivot(); 2928 } 2929 2930 // Update Points 2931 for (i = 0, ilen = points.length; i < ilen; ++i) { 2932 me.updateElement(points[i], i, reset); 2933 } 2934 2935 if (showLine && line._model.tension !== 0) { 2936 me.updateBezierControlPoints(); 2937 } 2938 2939 // Now pivot the point for animation 2940 for (i = 0, ilen = points.length; i < ilen; ++i) { 2941 points[i].pivot(); 2942 } 2943 }, 2944 2945 getPointBackgroundColor: function(point, index) { 2946 var backgroundColor = this.chart.options.elements.point.backgroundColor; 2947 var dataset = this.getDataset(); 2948 var custom = point.custom || {}; 2949 2950 if (custom.backgroundColor) { 2951 backgroundColor = custom.backgroundColor; 2952 } else if (dataset.pointBackgroundColor) { 2953 backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); 2954 } else if (dataset.backgroundColor) { 2955 backgroundColor = dataset.backgroundColor; 2956 } 2957 2958 return backgroundColor; 2959 }, 2960 2961 getPointBorderColor: function(point, index) { 2962 var borderColor = this.chart.options.elements.point.borderColor; 2963 var dataset = this.getDataset(); 2964 var custom = point.custom || {}; 2965 2966 if (custom.borderColor) { 2967 borderColor = custom.borderColor; 2968 } else if (dataset.pointBorderColor) { 2969 borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); 2970 } else if (dataset.borderColor) { 2971 borderColor = dataset.borderColor; 2972 } 2973 2974 return borderColor; 2975 }, 2976 2977 getPointBorderWidth: function(point, index) { 2978 var borderWidth = this.chart.options.elements.point.borderWidth; 2979 var dataset = this.getDataset(); 2980 var custom = point.custom || {}; 2981 2982 if (!isNaN(custom.borderWidth)) { 2983 borderWidth = custom.borderWidth; 2984 } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) { 2985 borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); 2986 } else if (!isNaN(dataset.borderWidth)) { 2987 borderWidth = dataset.borderWidth; 2988 } 2989 2990 return borderWidth; 2991 }, 2992 2993 getPointRotation: function(point, index) { 2994 var pointRotation = this.chart.options.elements.point.rotation; 2995 var dataset = this.getDataset(); 2996 var custom = point.custom || {}; 2997 2998 if (!isNaN(custom.rotation)) { 2999 pointRotation = custom.rotation; 3000 } else if (!isNaN(dataset.pointRotation) || helpers.isArray(dataset.pointRotation)) { 3001 pointRotation = helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointRotation); 3002 } 3003 return pointRotation; 3004 }, 3005 3006 updateElement: function(point, index, reset) { 3007 var me = this; 3008 var meta = me.getMeta(); 3009 var custom = point.custom || {}; 3010 var dataset = me.getDataset(); 3011 var datasetIndex = me.index; 3012 var value = dataset.data[index]; 3013 var yScale = me.getScaleForId(meta.yAxisID); 3014 var xScale = me.getScaleForId(meta.xAxisID); 3015 var pointOptions = me.chart.options.elements.point; 3016 var x, y; 3017 3018 // Compatibility: If the properties are defined with only the old name, use those values 3019 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { 3020 dataset.pointRadius = dataset.radius; 3021 } 3022 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { 3023 dataset.pointHitRadius = dataset.hitRadius; 3024 } 3025 3026 x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); 3027 y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); 3028 3029 // Utility 3030 point._xScale = xScale; 3031 point._yScale = yScale; 3032 point._datasetIndex = datasetIndex; 3033 point._index = index; 3034 3035 // Desired view properties 3036 point._model = { 3037 x: x, 3038 y: y, 3039 skip: custom.skip || isNaN(x) || isNaN(y), 3040 // Appearance 3041 radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), 3042 pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), 3043 rotation: me.getPointRotation(point, index), 3044 backgroundColor: me.getPointBackgroundColor(point, index), 3045 borderColor: me.getPointBorderColor(point, index), 3046 borderWidth: me.getPointBorderWidth(point, index), 3047 tension: meta.dataset._model ? meta.dataset._model.tension : 0, 3048 steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false, 3049 // Tooltip 3050 hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) 3051 }; 3052 }, 3053 3054 calculatePointY: function(value, index, datasetIndex) { 3055 var me = this; 3056 var chart = me.chart; 3057 var meta = me.getMeta(); 3058 var yScale = me.getScaleForId(meta.yAxisID); 3059 var sumPos = 0; 3060 var sumNeg = 0; 3061 var i, ds, dsMeta; 3062 3063 if (yScale.options.stacked) { 3064 for (i = 0; i < datasetIndex; i++) { 3065 ds = chart.data.datasets[i]; 3066 dsMeta = chart.getDatasetMeta(i); 3067 if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { 3068 var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); 3069 if (stackedRightValue < 0) { 3070 sumNeg += stackedRightValue || 0; 3071 } else { 3072 sumPos += stackedRightValue || 0; 3073 } 3074 } 3075 } 3076 3077 var rightValue = Number(yScale.getRightValue(value)); 3078 if (rightValue < 0) { 3079 return yScale.getPixelForValue(sumNeg + rightValue); 3080 } 3081 return yScale.getPixelForValue(sumPos + rightValue); 3082 } 3083 3084 return yScale.getPixelForValue(value); 3085 }, 3086 3087 updateBezierControlPoints: function() { 3088 var me = this; 3089 var meta = me.getMeta(); 3090 var area = me.chart.chartArea; 3091 var points = (meta.data || []); 3092 var i, ilen, point, model, controlPoints; 3093 3094 // Only consider points that are drawn in case the spanGaps option is used 3095 if (meta.dataset._model.spanGaps) { 3096 points = points.filter(function(pt) { 3097 return !pt._model.skip; 3098 }); 3099 } 3100 3101 function capControlPoint(pt, min, max) { 3102 return Math.max(Math.min(pt, max), min); 3103 } 3104 3105 if (meta.dataset._model.cubicInterpolationMode === 'monotone') { 3106 helpers.splineCurveMonotone(points); 3107 } else { 3108 for (i = 0, ilen = points.length; i < ilen; ++i) { 3109 point = points[i]; 3110 model = point._model; 3111 controlPoints = helpers.splineCurve( 3112 helpers.previousItem(points, i)._model, 3113 model, 3114 helpers.nextItem(points, i)._model, 3115 meta.dataset._model.tension 3116 ); 3117 model.controlPointPreviousX = controlPoints.previous.x; 3118 model.controlPointPreviousY = controlPoints.previous.y; 3119 model.controlPointNextX = controlPoints.next.x; 3120 model.controlPointNextY = controlPoints.next.y; 3121 } 3122 } 3123 3124 if (me.chart.options.elements.line.capBezierPoints) { 3125 for (i = 0, ilen = points.length; i < ilen; ++i) { 3126 model = points[i]._model; 3127 model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); 3128 model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); 3129 model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); 3130 model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); 3131 } 3132 } 3133 }, 3134 3135 draw: function() { 3136 var me = this; 3137 var chart = me.chart; 3138 var meta = me.getMeta(); 3139 var points = meta.data || []; 3140 var area = chart.chartArea; 3141 var ilen = points.length; 3142 var halfBorderWidth; 3143 var i = 0; 3144 3145 if (lineEnabled(me.getDataset(), chart.options)) { 3146 halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2; 3147 3148 helpers.canvas.clipArea(chart.ctx, { 3149 left: area.left, 3150 right: area.right, 3151 top: area.top - halfBorderWidth, 3152 bottom: area.bottom + halfBorderWidth 3153 }); 3154 3155 meta.dataset.draw(); 3156 3157 helpers.canvas.unclipArea(chart.ctx); 3158 } 3159 3160 // Draw the points 3161 for (; i < ilen; ++i) { 3162 points[i].draw(area); 3163 } 3164 }, 3165 3166 setHoverStyle: function(element) { 3167 // Point 3168 var dataset = this.chart.data.datasets[element._datasetIndex]; 3169 var index = element._index; 3170 var custom = element.custom || {}; 3171 var model = element._model; 3172 3173 element.$previousStyle = { 3174 backgroundColor: model.backgroundColor, 3175 borderColor: model.borderColor, 3176 borderWidth: model.borderWidth, 3177 radius: model.radius 3178 }; 3179 3180 model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); 3181 model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); 3182 model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); 3183 model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); 3184 }, 3185 }); 3186 }; 3187 3188 },{"26":26,"41":41,"46":46}],19:[function(require,module,exports){ 3189 'use strict'; 3190 3191 var defaults = require(26); 3192 var elements = require(41); 3193 var helpers = require(46); 3194 3195 defaults._set('polarArea', { 3196 scale: { 3197 type: 'radialLinear', 3198 angleLines: { 3199 display: false 3200 }, 3201 gridLines: { 3202 circular: true 3203 }, 3204 pointLabels: { 3205 display: false 3206 }, 3207 ticks: { 3208 beginAtZero: true 3209 } 3210 }, 3211 3212 // Boolean - Whether to animate the rotation of the chart 3213 animation: { 3214 animateRotate: true, 3215 animateScale: true 3216 }, 3217 3218 startAngle: -0.5 * Math.PI, 3219 legendCallback: function(chart) { 3220 var text = []; 3221 text.push('<ul class="' + chart.id + '-legend">'); 3222 3223 var data = chart.data; 3224 var datasets = data.datasets; 3225 var labels = data.labels; 3226 3227 if (datasets.length) { 3228 for (var i = 0; i < datasets[0].data.length; ++i) { 3229 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); 3230 if (labels[i]) { 3231 text.push(labels[i]); 3232 } 3233 text.push('</li>'); 3234 } 3235 } 3236 3237 text.push('</ul>'); 3238 return text.join(''); 3239 }, 3240 legend: { 3241 labels: { 3242 generateLabels: function(chart) { 3243 var data = chart.data; 3244 if (data.labels.length && data.datasets.length) { 3245 return data.labels.map(function(label, i) { 3246 var meta = chart.getDatasetMeta(0); 3247 var ds = data.datasets[0]; 3248 var arc = meta.data[i]; 3249 var custom = arc.custom || {}; 3250 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; 3251 var arcOpts = chart.options.elements.arc; 3252 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); 3253 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); 3254 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); 3255 3256 return { 3257 text: label, 3258 fillStyle: fill, 3259 strokeStyle: stroke, 3260 lineWidth: bw, 3261 hidden: isNaN(ds.data[i]) || meta.data[i].hidden, 3262 3263 // Extra data used for toggling the correct item 3264 index: i 3265 }; 3266 }); 3267 } 3268 return []; 3269 } 3270 }, 3271 3272 onClick: function(e, legendItem) { 3273 var index = legendItem.index; 3274 var chart = this.chart; 3275 var i, ilen, meta; 3276 3277 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { 3278 meta = chart.getDatasetMeta(i); 3279 meta.data[index].hidden = !meta.data[index].hidden; 3280 } 3281 3282 chart.update(); 3283 } 3284 }, 3285 3286 // Need to override these to give a nice default 3287 tooltips: { 3288 callbacks: { 3289 title: function() { 3290 return ''; 3291 }, 3292 label: function(item, data) { 3293 return data.labels[item.index] + ': ' + item.yLabel; 3294 } 3295 } 3296 } 3297 }); 3298 3299 module.exports = function(Chart) { 3300 3301 Chart.controllers.polarArea = Chart.DatasetController.extend({ 3302 3303 dataElementType: elements.Arc, 3304 3305 linkScales: helpers.noop, 3306 3307 update: function(reset) { 3308 var me = this; 3309 var dataset = me.getDataset(); 3310 var meta = me.getMeta(); 3311 var start = me.chart.options.startAngle || 0; 3312 var starts = me._starts = []; 3313 var angles = me._angles = []; 3314 var i, ilen, angle; 3315 3316 me._updateRadius(); 3317 3318 meta.count = me.countVisibleElements(); 3319 3320 for (i = 0, ilen = dataset.data.length; i < ilen; i++) { 3321 starts[i] = start; 3322 angle = me._computeAngle(i); 3323 angles[i] = angle; 3324 start += angle; 3325 } 3326 3327 helpers.each(meta.data, function(arc, index) { 3328 me.updateElement(arc, index, reset); 3329 }); 3330 }, 3331 3332 /** 3333 * @private 3334 */ 3335 _updateRadius: function() { 3336 var me = this; 3337 var chart = me.chart; 3338 var chartArea = chart.chartArea; 3339 var opts = chart.options; 3340 var arcOpts = opts.elements.arc; 3341 var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); 3342 3343 chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0); 3344 chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); 3345 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); 3346 3347 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); 3348 me.innerRadius = me.outerRadius - chart.radiusLength; 3349 }, 3350 3351 updateElement: function(arc, index, reset) { 3352 var me = this; 3353 var chart = me.chart; 3354 var dataset = me.getDataset(); 3355 var opts = chart.options; 3356 var animationOpts = opts.animation; 3357 var scale = chart.scale; 3358 var labels = chart.data.labels; 3359 3360 var centerX = scale.xCenter; 3361 var centerY = scale.yCenter; 3362 3363 // var negHalfPI = -0.5 * Math.PI; 3364 var datasetStartAngle = opts.startAngle; 3365 var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); 3366 var startAngle = me._starts[index]; 3367 var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); 3368 3369 var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); 3370 3371 helpers.extend(arc, { 3372 // Utility 3373 _datasetIndex: me.index, 3374 _index: index, 3375 _scale: scale, 3376 3377 // Desired view properties 3378 _model: { 3379 x: centerX, 3380 y: centerY, 3381 innerRadius: 0, 3382 outerRadius: reset ? resetRadius : distance, 3383 startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, 3384 endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, 3385 label: helpers.valueAtIndexOrDefault(labels, index, labels[index]) 3386 } 3387 }); 3388 3389 // Apply border and fill style 3390 var elementOpts = this.chart.options.elements.arc; 3391 var custom = arc.custom || {}; 3392 var valueOrDefault = helpers.valueAtIndexOrDefault; 3393 var model = arc._model; 3394 3395 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); 3396 model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); 3397 model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); 3398 3399 arc.pivot(); 3400 }, 3401 3402 countVisibleElements: function() { 3403 var dataset = this.getDataset(); 3404 var meta = this.getMeta(); 3405 var count = 0; 3406 3407 helpers.each(meta.data, function(element, index) { 3408 if (!isNaN(dataset.data[index]) && !element.hidden) { 3409 count++; 3410 } 3411 }); 3412 3413 return count; 3414 }, 3415 3416 /** 3417 * @private 3418 */ 3419 _computeAngle: function(index) { 3420 var me = this; 3421 var count = this.getMeta().count; 3422 var dataset = me.getDataset(); 3423 var meta = me.getMeta(); 3424 3425 if (isNaN(dataset.data[index]) || meta.data[index].hidden) { 3426 return 0; 3427 } 3428 3429 // Scriptable options 3430 var context = { 3431 chart: me.chart, 3432 dataIndex: index, 3433 dataset: dataset, 3434 datasetIndex: me.index 3435 }; 3436 3437 return helpers.options.resolve([ 3438 me.chart.options.elements.arc.angle, 3439 (2 * Math.PI) / count 3440 ], context, index); 3441 } 3442 }); 3443 }; 3444 3445 },{"26":26,"41":41,"46":46}],20:[function(require,module,exports){ 3446 'use strict'; 3447 3448 var defaults = require(26); 3449 var elements = require(41); 3450 var helpers = require(46); 3451 3452 defaults._set('radar', { 3453 scale: { 3454 type: 'radialLinear' 3455 }, 3456 elements: { 3457 line: { 3458 tension: 0 // no bezier in radar 3459 } 3460 } 3461 }); 3462 3463 module.exports = function(Chart) { 3464 3465 Chart.controllers.radar = Chart.DatasetController.extend({ 3466 3467 datasetElementType: elements.Line, 3468 3469 dataElementType: elements.Point, 3470 3471 linkScales: helpers.noop, 3472 3473 update: function(reset) { 3474 var me = this; 3475 var meta = me.getMeta(); 3476 var line = meta.dataset; 3477 var points = meta.data; 3478 var custom = line.custom || {}; 3479 var dataset = me.getDataset(); 3480 var lineElementOptions = me.chart.options.elements.line; 3481 var scale = me.chart.scale; 3482 3483 // Compatibility: If the properties are defined with only the old name, use those values 3484 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { 3485 dataset.lineTension = dataset.tension; 3486 } 3487 3488 helpers.extend(meta.dataset, { 3489 // Utility 3490 _datasetIndex: me.index, 3491 _scale: scale, 3492 // Data 3493 _children: points, 3494 _loop: true, 3495 // Model 3496 _model: { 3497 // Appearance 3498 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), 3499 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), 3500 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), 3501 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), 3502 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), 3503 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), 3504 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), 3505 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), 3506 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), 3507 } 3508 }); 3509 3510 meta.dataset.pivot(); 3511 3512 // Update Points 3513 helpers.each(points, function(point, index) { 3514 me.updateElement(point, index, reset); 3515 }, me); 3516 3517 // Update bezier control points 3518 me.updateBezierControlPoints(); 3519 }, 3520 updateElement: function(point, index, reset) { 3521 var me = this; 3522 var custom = point.custom || {}; 3523 var dataset = me.getDataset(); 3524 var scale = me.chart.scale; 3525 var pointElementOptions = me.chart.options.elements.point; 3526 var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); 3527 3528 // Compatibility: If the properties are defined with only the old name, use those values 3529 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { 3530 dataset.pointRadius = dataset.radius; 3531 } 3532 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { 3533 dataset.pointHitRadius = dataset.hitRadius; 3534 } 3535 3536 helpers.extend(point, { 3537 // Utility 3538 _datasetIndex: me.index, 3539 _index: index, 3540 _scale: scale, 3541 3542 // Desired view properties 3543 _model: { 3544 x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales 3545 y: reset ? scale.yCenter : pointPosition.y, 3546 3547 // Appearance 3548 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension), 3549 radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), 3550 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), 3551 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), 3552 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), 3553 pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), 3554 rotation: custom.rotation ? custom.rotation : helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointElementOptions.rotation), 3555 3556 // Tooltip 3557 hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius) 3558 } 3559 }); 3560 3561 point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); 3562 }, 3563 updateBezierControlPoints: function() { 3564 var chartArea = this.chart.chartArea; 3565 var meta = this.getMeta(); 3566 3567 helpers.each(meta.data, function(point, index) { 3568 var model = point._model; 3569 var controlPoints = helpers.splineCurve( 3570 helpers.previousItem(meta.data, index, true)._model, 3571 model, 3572 helpers.nextItem(meta.data, index, true)._model, 3573 model.tension 3574 ); 3575 3576 // Prevent the bezier going outside of the bounds of the graph 3577 model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); 3578 model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); 3579 3580 model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); 3581 model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); 3582 3583 // Now pivot the point for animation 3584 point.pivot(); 3585 }); 3586 }, 3587 3588 setHoverStyle: function(point) { 3589 // Point 3590 var dataset = this.chart.data.datasets[point._datasetIndex]; 3591 var custom = point.custom || {}; 3592 var index = point._index; 3593 var model = point._model; 3594 3595 point.$previousStyle = { 3596 backgroundColor: model.backgroundColor, 3597 borderColor: model.borderColor, 3598 borderWidth: model.borderWidth, 3599 radius: model.radius 3600 }; 3601 3602 model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); 3603 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); 3604 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); 3605 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); 3606 }, 3607 }); 3608 }; 3609 3610 },{"26":26,"41":41,"46":46}],21:[function(require,module,exports){ 3611 'use strict'; 3612 3613 var defaults = require(26); 3614 3615 defaults._set('scatter', { 3616 hover: { 3617 mode: 'single' 3618 }, 3619 3620 scales: { 3621 xAxes: [{ 3622 id: 'x-axis-1', // need an ID so datasets can reference the scale 3623 type: 'linear', // scatter should not use a category axis 3624 position: 'bottom' 3625 }], 3626 yAxes: [{ 3627 id: 'y-axis-1', 3628 type: 'linear', 3629 position: 'left' 3630 }] 3631 }, 3632 3633 showLines: false, 3634 3635 tooltips: { 3636 callbacks: { 3637 title: function() { 3638 return ''; // doesn't make sense for scatter since data are formatted as a point 3639 }, 3640 label: function(item) { 3641 return '(' + item.xLabel + ', ' + item.yLabel + ')'; 3642 } 3643 } 3644 } 3645 }); 3646 3647 module.exports = function(Chart) { 3648 3649 // Scatter charts use line controllers 3650 Chart.controllers.scatter = Chart.controllers.line; 3651 3652 }; 3653 3654 },{"26":26}],22:[function(require,module,exports){ 3655 'use strict'; 3656 3657 var Element = require(27); 3658 3659 var exports = module.exports = Element.extend({ 3660 chart: null, // the animation associated chart instance 3661 currentStep: 0, // the current animation step 3662 numSteps: 60, // default number of steps 3663 easing: '', // the easing to use for this animation 3664 render: null, // render function used by the animation service 3665 3666 onAnimationProgress: null, // user specified callback to fire on each step of the animation 3667 onAnimationComplete: null, // user specified callback to fire when the animation finishes 3668 }); 3669 3670 // DEPRECATIONS 3671 3672 /** 3673 * Provided for backward compatibility, use Chart.Animation instead 3674 * @prop Chart.Animation#animationObject 3675 * @deprecated since version 2.6.0 3676 * @todo remove at version 3 3677 */ 3678 Object.defineProperty(exports.prototype, 'animationObject', { 3679 get: function() { 3680 return this; 3681 } 3682 }); 3683 3684 /** 3685 * Provided for backward compatibility, use Chart.Animation#chart instead 3686 * @prop Chart.Animation#chartInstance 3687 * @deprecated since version 2.6.0 3688 * @todo remove at version 3 3689 */ 3690 Object.defineProperty(exports.prototype, 'chartInstance', { 3691 get: function() { 3692 return this.chart; 3693 }, 3694 set: function(value) { 3695 this.chart = value; 3696 } 3697 }); 3698 3699 },{"27":27}],23:[function(require,module,exports){ 3700 /* global window: false */ 3701 'use strict'; 3702 3703 var defaults = require(26); 3704 var helpers = require(46); 3705 3706 defaults._set('global', { 3707 animation: { 3708 duration: 1000, 3709 easing: 'easeOutQuart', 3710 onProgress: helpers.noop, 3711 onComplete: helpers.noop 3712 } 3713 }); 3714 3715 module.exports = { 3716 frameDuration: 17, 3717 animations: [], 3718 dropFrames: 0, 3719 request: null, 3720 3721 /** 3722 * @param {Chart} chart - The chart to animate. 3723 * @param {Chart.Animation} animation - The animation that we will animate. 3724 * @param {Number} duration - The animation duration in ms. 3725 * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions 3726 */ 3727 addAnimation: function(chart, animation, duration, lazy) { 3728 var animations = this.animations; 3729 var i, ilen; 3730 3731 animation.chart = chart; 3732 3733 if (!lazy) { 3734 chart.animating = true; 3735 } 3736 3737 for (i = 0, ilen = animations.length; i < ilen; ++i) { 3738 if (animations[i].chart === chart) { 3739 animations[i] = animation; 3740 return; 3741 } 3742 } 3743 3744 animations.push(animation); 3745 3746 // If there are no animations queued, manually kickstart a digest, for lack of a better word 3747 if (animations.length === 1) { 3748 this.requestAnimationFrame(); 3749 } 3750 }, 3751 3752 cancelAnimation: function(chart) { 3753 var index = helpers.findIndex(this.animations, function(animation) { 3754 return animation.chart === chart; 3755 }); 3756 3757 if (index !== -1) { 3758 this.animations.splice(index, 1); 3759 chart.animating = false; 3760 } 3761 }, 3762 3763 requestAnimationFrame: function() { 3764 var me = this; 3765 if (me.request === null) { 3766 // Skip animation frame requests until the active one is executed. 3767 // This can happen when processing mouse events, e.g. 'mousemove' 3768 // and 'mouseout' events will trigger multiple renders. 3769 me.request = helpers.requestAnimFrame.call(window, function() { 3770 me.request = null; 3771 me.startDigest(); 3772 }); 3773 } 3774 }, 3775 3776 /** 3777 * @private 3778 */ 3779 startDigest: function() { 3780 var me = this; 3781 var startTime = Date.now(); 3782 var framesToDrop = 0; 3783 3784 if (me.dropFrames > 1) { 3785 framesToDrop = Math.floor(me.dropFrames); 3786 me.dropFrames = me.dropFrames % 1; 3787 } 3788 3789 me.advance(1 + framesToDrop); 3790 3791 var endTime = Date.now(); 3792 3793 me.dropFrames += (endTime - startTime) / me.frameDuration; 3794 3795 // Do we have more stuff to animate? 3796 if (me.animations.length > 0) { 3797 me.requestAnimationFrame(); 3798 } 3799 }, 3800 3801 /** 3802 * @private 3803 */ 3804 advance: function(count) { 3805 var animations = this.animations; 3806 var animation, chart; 3807 var i = 0; 3808 3809 while (i < animations.length) { 3810 animation = animations[i]; 3811 chart = animation.chart; 3812 3813 animation.currentStep = (animation.currentStep || 0) + count; 3814 animation.currentStep = Math.min(animation.currentStep, animation.numSteps); 3815 3816 helpers.callback(animation.render, [chart, animation], chart); 3817 helpers.callback(animation.onAnimationProgress, [animation], chart); 3818 3819 if (animation.currentStep >= animation.numSteps) { 3820 helpers.callback(animation.onAnimationComplete, [animation], chart); 3821 chart.animating = false; 3822 animations.splice(i, 1); 3823 } else { 3824 ++i; 3825 } 3826 } 3827 } 3828 }; 3829 3830 },{"26":26,"46":46}],24:[function(require,module,exports){ 3831 'use strict'; 3832 3833 var Animation = require(22); 3834 var animations = require(23); 3835 var defaults = require(26); 3836 var helpers = require(46); 3837 var Interaction = require(29); 3838 var layouts = require(31); 3839 var platform = require(49); 3840 var plugins = require(32); 3841 var scaleService = require(34); 3842 var Tooltip = require(36); 3843 3844 module.exports = function(Chart) { 3845 3846 // Create a dictionary of chart types, to allow for extension of existing types 3847 Chart.types = {}; 3848 3849 // Store a reference to each instance - allowing us to globally resize chart instances on window resize. 3850 // Destroy method on the chart will remove the instance of the chart from this reference. 3851 Chart.instances = {}; 3852 3853 // Controllers available for dataset visualization eg. bar, line, slice, etc. 3854 Chart.controllers = {}; 3855 3856 /** 3857 * Initializes the given config with global and chart default values. 3858 */ 3859 function initConfig(config) { 3860 config = config || {}; 3861 3862 // Do NOT use configMerge() for the data object because this method merges arrays 3863 // and so would change references to labels and datasets, preventing data updates. 3864 var data = config.data = config.data || {}; 3865 data.datasets = data.datasets || []; 3866 data.labels = data.labels || []; 3867 3868 config.options = helpers.configMerge( 3869 defaults.global, 3870 defaults[config.type], 3871 config.options || {}); 3872 3873 return config; 3874 } 3875 3876 /** 3877 * Updates the config of the chart 3878 * @param chart {Chart} chart to update the options for 3879 */ 3880 function updateConfig(chart) { 3881 var newOptions = chart.options; 3882 3883 helpers.each(chart.scales, function(scale) { 3884 layouts.removeBox(chart, scale); 3885 }); 3886 3887 newOptions = helpers.configMerge( 3888 Chart.defaults.global, 3889 Chart.defaults[chart.config.type], 3890 newOptions); 3891 3892 chart.options = chart.config.options = newOptions; 3893 chart.ensureScalesHaveIDs(); 3894 chart.buildOrUpdateScales(); 3895 // Tooltip 3896 chart.tooltip._options = newOptions.tooltips; 3897 chart.tooltip.initialize(); 3898 } 3899 3900 function positionIsHorizontal(position) { 3901 return position === 'top' || position === 'bottom'; 3902 } 3903 3904 helpers.extend(Chart.prototype, /** @lends Chart */ { 3905 /** 3906 * @private 3907 */ 3908 construct: function(item, config) { 3909 var me = this; 3910 3911 config = initConfig(config); 3912 3913 var context = platform.acquireContext(item, config); 3914 var canvas = context && context.canvas; 3915 var height = canvas && canvas.height; 3916 var width = canvas && canvas.width; 3917 3918 me.id = helpers.uid(); 3919 me.ctx = context; 3920 me.canvas = canvas; 3921 me.config = config; 3922 me.width = width; 3923 me.height = height; 3924 me.aspectRatio = height ? width / height : null; 3925 me.options = config.options; 3926 me._bufferedRender = false; 3927 3928 /** 3929 * Provided for backward compatibility, Chart and Chart.Controller have been merged, 3930 * the "instance" still need to be defined since it might be called from plugins. 3931 * @prop Chart#chart 3932 * @deprecated since version 2.6.0 3933 * @todo remove at version 3 3934 * @private 3935 */ 3936 me.chart = me; 3937 me.controller = me; // chart.chart.controller #inception 3938 3939 // Add the chart instance to the global namespace 3940 Chart.instances[me.id] = me; 3941 3942 // Define alias to the config data: `chart.data === chart.config.data` 3943 Object.defineProperty(me, 'data', { 3944 get: function() { 3945 return me.config.data; 3946 }, 3947 set: function(value) { 3948 me.config.data = value; 3949 } 3950 }); 3951 3952 if (!context || !canvas) { 3953 // The given item is not a compatible context2d element, let's return before finalizing 3954 // the chart initialization but after setting basic chart / controller properties that 3955 // can help to figure out that the chart is not valid (e.g chart.canvas !== null); 3956 // https://github.com/chartjs/Chart.js/issues/2807 3957 console.error("Failed to create chart: can't acquire context from the given item"); 3958 return; 3959 } 3960 3961 me.initialize(); 3962 me.update(); 3963 }, 3964 3965 /** 3966 * @private 3967 */ 3968 initialize: function() { 3969 var me = this; 3970 3971 // Before init plugin notification 3972 plugins.notify(me, 'beforeInit'); 3973 3974 helpers.retinaScale(me, me.options.devicePixelRatio); 3975 3976 me.bindEvents(); 3977 3978 if (me.options.responsive) { 3979 // Initial resize before chart draws (must be silent to preserve initial animations). 3980 me.resize(true); 3981 } 3982 3983 // Make sure scales have IDs and are built before we build any controllers. 3984 me.ensureScalesHaveIDs(); 3985 me.buildOrUpdateScales(); 3986 me.initToolTip(); 3987 3988 // After init plugin notification 3989 plugins.notify(me, 'afterInit'); 3990 3991 return me; 3992 }, 3993 3994 clear: function() { 3995 helpers.canvas.clear(this); 3996 return this; 3997 }, 3998 3999 stop: function() { 4000 // Stops any current animation loop occurring 4001 animations.cancelAnimation(this); 4002 return this; 4003 }, 4004 4005 resize: function(silent) { 4006 var me = this; 4007 var options = me.options; 4008 var canvas = me.canvas; 4009 var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; 4010 4011 // the canvas render width and height will be casted to integers so make sure that 4012 // the canvas display style uses the same integer values to avoid blurring effect. 4013 4014 // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed 4015 var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas))); 4016 var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas))); 4017 4018 if (me.width === newWidth && me.height === newHeight) { 4019 return; 4020 } 4021 4022 canvas.width = me.width = newWidth; 4023 canvas.height = me.height = newHeight; 4024 canvas.style.width = newWidth + 'px'; 4025 canvas.style.height = newHeight + 'px'; 4026 4027 helpers.retinaScale(me, options.devicePixelRatio); 4028 4029 if (!silent) { 4030 // Notify any plugins about the resize 4031 var newSize = {width: newWidth, height: newHeight}; 4032 plugins.notify(me, 'resize', [newSize]); 4033 4034 // Notify of resize 4035 if (me.options.onResize) { 4036 me.options.onResize(me, newSize); 4037 } 4038 4039 me.stop(); 4040 me.update({ 4041 duration: me.options.responsiveAnimationDuration 4042 }); 4043 } 4044 }, 4045 4046 ensureScalesHaveIDs: function() { 4047 var options = this.options; 4048 var scalesOptions = options.scales || {}; 4049 var scaleOptions = options.scale; 4050 4051 helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) { 4052 xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); 4053 }); 4054 4055 helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) { 4056 yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); 4057 }); 4058 4059 if (scaleOptions) { 4060 scaleOptions.id = scaleOptions.id || 'scale'; 4061 } 4062 }, 4063 4064 /** 4065 * Builds a map of scale ID to scale object for future lookup. 4066 */ 4067 buildOrUpdateScales: function() { 4068 var me = this; 4069 var options = me.options; 4070 var scales = me.scales || {}; 4071 var items = []; 4072 var updated = Object.keys(scales).reduce(function(obj, id) { 4073 obj[id] = false; 4074 return obj; 4075 }, {}); 4076 4077 if (options.scales) { 4078 items = items.concat( 4079 (options.scales.xAxes || []).map(function(xAxisOptions) { 4080 return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'}; 4081 }), 4082 (options.scales.yAxes || []).map(function(yAxisOptions) { 4083 return {options: yAxisOptions, dtype: 'linear', dposition: 'left'}; 4084 }) 4085 ); 4086 } 4087 4088 if (options.scale) { 4089 items.push({ 4090 options: options.scale, 4091 dtype: 'radialLinear', 4092 isDefault: true, 4093 dposition: 'chartArea' 4094 }); 4095 } 4096 4097 helpers.each(items, function(item) { 4098 var scaleOptions = item.options; 4099 var id = scaleOptions.id; 4100 var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype); 4101 4102 if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { 4103 scaleOptions.position = item.dposition; 4104 } 4105 4106 updated[id] = true; 4107 var scale = null; 4108 if (id in scales && scales[id].type === scaleType) { 4109 scale = scales[id]; 4110 scale.options = scaleOptions; 4111 scale.ctx = me.ctx; 4112 scale.chart = me; 4113 } else { 4114 var scaleClass = scaleService.getScaleConstructor(scaleType); 4115 if (!scaleClass) { 4116 return; 4117 } 4118 scale = new scaleClass({ 4119 id: id, 4120 type: scaleType, 4121 options: scaleOptions, 4122 ctx: me.ctx, 4123 chart: me 4124 }); 4125 scales[scale.id] = scale; 4126 } 4127 4128 scale.mergeTicksOptions(); 4129 4130 // TODO(SB): I think we should be able to remove this custom case (options.scale) 4131 // and consider it as a regular scale part of the "scales"" map only! This would 4132 // make the logic easier and remove some useless? custom code. 4133 if (item.isDefault) { 4134 me.scale = scale; 4135 } 4136 }); 4137 // clear up discarded scales 4138 helpers.each(updated, function(hasUpdated, id) { 4139 if (!hasUpdated) { 4140 delete scales[id]; 4141 } 4142 }); 4143 4144 me.scales = scales; 4145 4146 scaleService.addScalesToLayout(this); 4147 }, 4148 4149 buildOrUpdateControllers: function() { 4150 var me = this; 4151 var types = []; 4152 var newControllers = []; 4153 4154 helpers.each(me.data.datasets, function(dataset, datasetIndex) { 4155 var meta = me.getDatasetMeta(datasetIndex); 4156 var type = dataset.type || me.config.type; 4157 4158 if (meta.type && meta.type !== type) { 4159 me.destroyDatasetMeta(datasetIndex); 4160 meta = me.getDatasetMeta(datasetIndex); 4161 } 4162 meta.type = type; 4163 4164 types.push(meta.type); 4165 4166 if (meta.controller) { 4167 meta.controller.updateIndex(datasetIndex); 4168 meta.controller.linkScales(); 4169 } else { 4170 var ControllerClass = Chart.controllers[meta.type]; 4171 if (ControllerClass === undefined) { 4172 throw new Error('"' + meta.type + '" is not a chart type.'); 4173 } 4174 4175 meta.controller = new ControllerClass(me, datasetIndex); 4176 newControllers.push(meta.controller); 4177 } 4178 }, me); 4179 4180 return newControllers; 4181 }, 4182 4183 /** 4184 * Reset the elements of all datasets 4185 * @private 4186 */ 4187 resetElements: function() { 4188 var me = this; 4189 helpers.each(me.data.datasets, function(dataset, datasetIndex) { 4190 me.getDatasetMeta(datasetIndex).controller.reset(); 4191 }, me); 4192 }, 4193 4194 /** 4195 * Resets the chart back to it's state before the initial animation 4196 */ 4197 reset: function() { 4198 this.resetElements(); 4199 this.tooltip.initialize(); 4200 }, 4201 4202 update: function(config) { 4203 var me = this; 4204 4205 if (!config || typeof config !== 'object') { 4206 // backwards compatibility 4207 config = { 4208 duration: config, 4209 lazy: arguments[1] 4210 }; 4211 } 4212 4213 updateConfig(me); 4214 4215 // plugins options references might have change, let's invalidate the cache 4216 // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 4217 plugins._invalidate(me); 4218 4219 if (plugins.notify(me, 'beforeUpdate') === false) { 4220 return; 4221 } 4222 4223 // In case the entire data object changed 4224 me.tooltip._data = me.data; 4225 4226 // Make sure dataset controllers are updated and new controllers are reset 4227 var newControllers = me.buildOrUpdateControllers(); 4228 4229 // Make sure all dataset controllers have correct meta data counts 4230 helpers.each(me.data.datasets, function(dataset, datasetIndex) { 4231 me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); 4232 }, me); 4233 4234 me.updateLayout(); 4235 4236 // Can only reset the new controllers after the scales have been updated 4237 if (me.options.animation && me.options.animation.duration) { 4238 helpers.each(newControllers, function(controller) { 4239 controller.reset(); 4240 }); 4241 } 4242 4243 me.updateDatasets(); 4244 4245 // Need to reset tooltip in case it is displayed with elements that are removed 4246 // after update. 4247 me.tooltip.initialize(); 4248 4249 // Last active contains items that were previously in the tooltip. 4250 // When we reset the tooltip, we need to clear it 4251 me.lastActive = []; 4252 4253 // Do this before render so that any plugins that need final scale updates can use it 4254 plugins.notify(me, 'afterUpdate'); 4255 4256 if (me._bufferedRender) { 4257 me._bufferedRequest = { 4258 duration: config.duration, 4259 easing: config.easing, 4260 lazy: config.lazy 4261 }; 4262 } else { 4263 me.render(config); 4264 } 4265 }, 4266 4267 /** 4268 * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` 4269 * hook, in which case, plugins will not be called on `afterLayout`. 4270 * @private 4271 */ 4272 updateLayout: function() { 4273 var me = this; 4274 4275 if (plugins.notify(me, 'beforeLayout') === false) { 4276 return; 4277 } 4278 4279 layouts.update(this, this.width, this.height); 4280 4281 /** 4282 * Provided for backward compatibility, use `afterLayout` instead. 4283 * @method IPlugin#afterScaleUpdate 4284 * @deprecated since version 2.5.0 4285 * @todo remove at version 3 4286 * @private 4287 */ 4288 plugins.notify(me, 'afterScaleUpdate'); 4289 plugins.notify(me, 'afterLayout'); 4290 }, 4291 4292 /** 4293 * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` 4294 * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. 4295 * @private 4296 */ 4297 updateDatasets: function() { 4298 var me = this; 4299 4300 if (plugins.notify(me, 'beforeDatasetsUpdate') === false) { 4301 return; 4302 } 4303 4304 for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { 4305 me.updateDataset(i); 4306 } 4307 4308 plugins.notify(me, 'afterDatasetsUpdate'); 4309 }, 4310 4311 /** 4312 * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` 4313 * hook, in which case, plugins will not be called on `afterDatasetUpdate`. 4314 * @private 4315 */ 4316 updateDataset: function(index) { 4317 var me = this; 4318 var meta = me.getDatasetMeta(index); 4319 var args = { 4320 meta: meta, 4321 index: index 4322 }; 4323 4324 if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { 4325 return; 4326 } 4327 4328 meta.controller.update(); 4329 4330 plugins.notify(me, 'afterDatasetUpdate', [args]); 4331 }, 4332 4333 render: function(config) { 4334 var me = this; 4335 4336 if (!config || typeof config !== 'object') { 4337 // backwards compatibility 4338 config = { 4339 duration: config, 4340 lazy: arguments[1] 4341 }; 4342 } 4343 4344 var duration = config.duration; 4345 var lazy = config.lazy; 4346 4347 if (plugins.notify(me, 'beforeRender') === false) { 4348 return; 4349 } 4350 4351 var animationOptions = me.options.animation; 4352 var onComplete = function(animation) { 4353 plugins.notify(me, 'afterRender'); 4354 helpers.callback(animationOptions && animationOptions.onComplete, [animation], me); 4355 }; 4356 4357 if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) { 4358 var animation = new Animation({ 4359 numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps 4360 easing: config.easing || animationOptions.easing, 4361 4362 render: function(chart, animationObject) { 4363 var easingFunction = helpers.easing.effects[animationObject.easing]; 4364 var currentStep = animationObject.currentStep; 4365 var stepDecimal = currentStep / animationObject.numSteps; 4366 4367 chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); 4368 }, 4369 4370 onAnimationProgress: animationOptions.onProgress, 4371 onAnimationComplete: onComplete 4372 }); 4373 4374 animations.addAnimation(me, animation, duration, lazy); 4375 } else { 4376 me.draw(); 4377 4378 // See https://github.com/chartjs/Chart.js/issues/3781 4379 onComplete(new Animation({numSteps: 0, chart: me})); 4380 } 4381 4382 return me; 4383 }, 4384 4385 draw: function(easingValue) { 4386 var me = this; 4387 4388 me.clear(); 4389 4390 if (helpers.isNullOrUndef(easingValue)) { 4391 easingValue = 1; 4392 } 4393 4394 me.transition(easingValue); 4395 4396 if (me.width <= 0 || me.height <= 0) { 4397 return; 4398 } 4399 4400 if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) { 4401 return; 4402 } 4403 4404 // Draw all the scales 4405 helpers.each(me.boxes, function(box) { 4406 box.draw(me.chartArea); 4407 }, me); 4408 4409 if (me.scale) { 4410 me.scale.draw(); 4411 } 4412 4413 me.drawDatasets(easingValue); 4414 me._drawTooltip(easingValue); 4415 4416 plugins.notify(me, 'afterDraw', [easingValue]); 4417 }, 4418 4419 /** 4420 * @private 4421 */ 4422 transition: function(easingValue) { 4423 var me = this; 4424 4425 for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { 4426 if (me.isDatasetVisible(i)) { 4427 me.getDatasetMeta(i).controller.transition(easingValue); 4428 } 4429 } 4430 4431 me.tooltip.transition(easingValue); 4432 }, 4433 4434 /** 4435 * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` 4436 * hook, in which case, plugins will not be called on `afterDatasetsDraw`. 4437 * @private 4438 */ 4439 drawDatasets: function(easingValue) { 4440 var me = this; 4441 4442 if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { 4443 return; 4444 } 4445 4446 // Draw datasets reversed to support proper line stacking 4447 for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) { 4448 if (me.isDatasetVisible(i)) { 4449 me.drawDataset(i, easingValue); 4450 } 4451 } 4452 4453 plugins.notify(me, 'afterDatasetsDraw', [easingValue]); 4454 }, 4455 4456 /** 4457 * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` 4458 * hook, in which case, plugins will not be called on `afterDatasetDraw`. 4459 * @private 4460 */ 4461 drawDataset: function(index, easingValue) { 4462 var me = this; 4463 var meta = me.getDatasetMeta(index); 4464 var args = { 4465 meta: meta, 4466 index: index, 4467 easingValue: easingValue 4468 }; 4469 4470 if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { 4471 return; 4472 } 4473 4474 meta.controller.draw(easingValue); 4475 4476 plugins.notify(me, 'afterDatasetDraw', [args]); 4477 }, 4478 4479 /** 4480 * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` 4481 * hook, in which case, plugins will not be called on `afterTooltipDraw`. 4482 * @private 4483 */ 4484 _drawTooltip: function(easingValue) { 4485 var me = this; 4486 var tooltip = me.tooltip; 4487 var args = { 4488 tooltip: tooltip, 4489 easingValue: easingValue 4490 }; 4491 4492 if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { 4493 return; 4494 } 4495 4496 tooltip.draw(); 4497 4498 plugins.notify(me, 'afterTooltipDraw', [args]); 4499 }, 4500 4501 // Get the single element that was clicked on 4502 // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw 4503 getElementAtEvent: function(e) { 4504 return Interaction.modes.single(this, e); 4505 }, 4506 4507 getElementsAtEvent: function(e) { 4508 return Interaction.modes.label(this, e, {intersect: true}); 4509 }, 4510 4511 getElementsAtXAxis: function(e) { 4512 return Interaction.modes['x-axis'](this, e, {intersect: true}); 4513 }, 4514 4515 getElementsAtEventForMode: function(e, mode, options) { 4516 var method = Interaction.modes[mode]; 4517 if (typeof method === 'function') { 4518 return method(this, e, options); 4519 } 4520 4521 return []; 4522 }, 4523 4524 getDatasetAtEvent: function(e) { 4525 return Interaction.modes.dataset(this, e, {intersect: true}); 4526 }, 4527 4528 getDatasetMeta: function(datasetIndex) { 4529 var me = this; 4530 var dataset = me.data.datasets[datasetIndex]; 4531 if (!dataset._meta) { 4532 dataset._meta = {}; 4533 } 4534 4535 var meta = dataset._meta[me.id]; 4536 if (!meta) { 4537 meta = dataset._meta[me.id] = { 4538 type: null, 4539 data: [], 4540 dataset: null, 4541 controller: null, 4542 hidden: null, // See isDatasetVisible() comment 4543 xAxisID: null, 4544 yAxisID: null 4545 }; 4546 } 4547 4548 return meta; 4549 }, 4550 4551 getVisibleDatasetCount: function() { 4552 var count = 0; 4553 for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { 4554 if (this.isDatasetVisible(i)) { 4555 count++; 4556 } 4557 } 4558 return count; 4559 }, 4560 4561 isDatasetVisible: function(datasetIndex) { 4562 var meta = this.getDatasetMeta(datasetIndex); 4563 4564 // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, 4565 // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. 4566 return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; 4567 }, 4568 4569 generateLegend: function() { 4570 return this.options.legendCallback(this); 4571 }, 4572 4573 /** 4574 * @private 4575 */ 4576 destroyDatasetMeta: function(datasetIndex) { 4577 var id = this.id; 4578 var dataset = this.data.datasets[datasetIndex]; 4579 var meta = dataset._meta && dataset._meta[id]; 4580 4581 if (meta) { 4582 meta.controller.destroy(); 4583 delete dataset._meta[id]; 4584 } 4585 }, 4586 4587 destroy: function() { 4588 var me = this; 4589 var canvas = me.canvas; 4590 var i, ilen; 4591 4592 me.stop(); 4593 4594 // dataset controllers need to cleanup associated data 4595 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { 4596 me.destroyDatasetMeta(i); 4597 } 4598 4599 if (canvas) { 4600 me.unbindEvents(); 4601 helpers.canvas.clear(me); 4602 platform.releaseContext(me.ctx); 4603 me.canvas = null; 4604 me.ctx = null; 4605 } 4606 4607 plugins.notify(me, 'destroy'); 4608 4609 delete Chart.instances[me.id]; 4610 }, 4611 4612 toBase64Image: function() { 4613 return this.canvas.toDataURL.apply(this.canvas, arguments); 4614 }, 4615 4616 initToolTip: function() { 4617 var me = this; 4618 me.tooltip = new Tooltip({ 4619 _chart: me, 4620 _chartInstance: me, // deprecated, backward compatibility 4621 _data: me.data, 4622 _options: me.options.tooltips 4623 }, me); 4624 }, 4625 4626 /** 4627 * @private 4628 */ 4629 bindEvents: function() { 4630 var me = this; 4631 var listeners = me._listeners = {}; 4632 var listener = function() { 4633 me.eventHandler.apply(me, arguments); 4634 }; 4635 4636 helpers.each(me.options.events, function(type) { 4637 platform.addEventListener(me, type, listener); 4638 listeners[type] = listener; 4639 }); 4640 4641 // Elements used to detect size change should not be injected for non responsive charts. 4642 // See https://github.com/chartjs/Chart.js/issues/2210 4643 if (me.options.responsive) { 4644 listener = function() { 4645 me.resize(); 4646 }; 4647 4648 platform.addEventListener(me, 'resize', listener); 4649 listeners.resize = listener; 4650 } 4651 }, 4652 4653 /** 4654 * @private 4655 */ 4656 unbindEvents: function() { 4657 var me = this; 4658 var listeners = me._listeners; 4659 if (!listeners) { 4660 return; 4661 } 4662 4663 delete me._listeners; 4664 helpers.each(listeners, function(listener, type) { 4665 platform.removeEventListener(me, type, listener); 4666 }); 4667 }, 4668 4669 updateHoverStyle: function(elements, mode, enabled) { 4670 var method = enabled ? 'setHoverStyle' : 'removeHoverStyle'; 4671 var element, i, ilen; 4672 4673 for (i = 0, ilen = elements.length; i < ilen; ++i) { 4674 element = elements[i]; 4675 if (element) { 4676 this.getDatasetMeta(element._datasetIndex).controller[method](element); 4677 } 4678 } 4679 }, 4680 4681 /** 4682 * @private 4683 */ 4684 eventHandler: function(e) { 4685 var me = this; 4686 var tooltip = me.tooltip; 4687 4688 if (plugins.notify(me, 'beforeEvent', [e]) === false) { 4689 return; 4690 } 4691 4692 // Buffer any update calls so that renders do not occur 4693 me._bufferedRender = true; 4694 me._bufferedRequest = null; 4695 4696 var changed = me.handleEvent(e); 4697 // for smooth tooltip animations issue #4989 4698 // the tooltip should be the source of change 4699 // Animation check workaround: 4700 // tooltip._start will be null when tooltip isn't animating 4701 if (tooltip) { 4702 changed = tooltip._start 4703 ? tooltip.handleEvent(e) 4704 : changed | tooltip.handleEvent(e); 4705 } 4706 4707 plugins.notify(me, 'afterEvent', [e]); 4708 4709 var bufferedRequest = me._bufferedRequest; 4710 if (bufferedRequest) { 4711 // If we have an update that was triggered, we need to do a normal render 4712 me.render(bufferedRequest); 4713 } else if (changed && !me.animating) { 4714 // If entering, leaving, or changing elements, animate the change via pivot 4715 me.stop(); 4716 4717 // We only need to render at this point. Updating will cause scales to be 4718 // recomputed generating flicker & using more memory than necessary. 4719 me.render({ 4720 duration: me.options.hover.animationDuration, 4721 lazy: true 4722 }); 4723 } 4724 4725 me._bufferedRender = false; 4726 me._bufferedRequest = null; 4727 4728 return me; 4729 }, 4730 4731 /** 4732 * Handle an event 4733 * @private 4734 * @param {IEvent} event the event to handle 4735 * @return {Boolean} true if the chart needs to re-render 4736 */ 4737 handleEvent: function(e) { 4738 var me = this; 4739 var options = me.options || {}; 4740 var hoverOptions = options.hover; 4741 var changed = false; 4742 4743 me.lastActive = me.lastActive || []; 4744 4745 // Find Active Elements for hover and tooltips 4746 if (e.type === 'mouseout') { 4747 me.active = []; 4748 } else { 4749 me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); 4750 } 4751 4752 // Invoke onHover hook 4753 // Need to call with native event here to not break backwards compatibility 4754 helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); 4755 4756 if (e.type === 'mouseup' || e.type === 'click') { 4757 if (options.onClick) { 4758 // Use e.native here for backwards compatibility 4759 options.onClick.call(me, e.native, me.active); 4760 } 4761 } 4762 4763 // Remove styling for last active (even if it may still be active) 4764 if (me.lastActive.length) { 4765 me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); 4766 } 4767 4768 // Built in hover styling 4769 if (me.active.length && hoverOptions.mode) { 4770 me.updateHoverStyle(me.active, hoverOptions.mode, true); 4771 } 4772 4773 changed = !helpers.arrayEquals(me.active, me.lastActive); 4774 4775 // Remember Last Actives 4776 me.lastActive = me.active; 4777 4778 return changed; 4779 } 4780 }); 4781 4782 /** 4783 * Provided for backward compatibility, use Chart instead. 4784 * @class Chart.Controller 4785 * @deprecated since version 2.6.0 4786 * @todo remove at version 3 4787 * @private 4788 */ 4789 Chart.Controller = Chart; 4790 }; 4791 4792 },{"22":22,"23":23,"26":26,"29":29,"31":31,"32":32,"34":34,"36":36,"46":46,"49":49}],25:[function(require,module,exports){ 4793 'use strict'; 4794 4795 var helpers = require(46); 4796 4797 module.exports = function(Chart) { 4798 4799 var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; 4800 4801 /** 4802 * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', 4803 * 'unshift') and notify the listener AFTER the array has been altered. Listeners are 4804 * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. 4805 */ 4806 function listenArrayEvents(array, listener) { 4807 if (array._chartjs) { 4808 array._chartjs.listeners.push(listener); 4809 return; 4810 } 4811 4812 Object.defineProperty(array, '_chartjs', { 4813 configurable: true, 4814 enumerable: false, 4815 value: { 4816 listeners: [listener] 4817 } 4818 }); 4819 4820 arrayEvents.forEach(function(key) { 4821 var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); 4822 var base = array[key]; 4823 4824 Object.defineProperty(array, key, { 4825 configurable: true, 4826 enumerable: false, 4827 value: function() { 4828 var args = Array.prototype.slice.call(arguments); 4829 var res = base.apply(this, args); 4830 4831 helpers.each(array._chartjs.listeners, function(object) { 4832 if (typeof object[method] === 'function') { 4833 object[method].apply(object, args); 4834 } 4835 }); 4836 4837 return res; 4838 } 4839 }); 4840 }); 4841 } 4842 4843 /** 4844 * Removes the given array event listener and cleanup extra attached properties (such as 4845 * the _chartjs stub and overridden methods) if array doesn't have any more listeners. 4846 */ 4847 function unlistenArrayEvents(array, listener) { 4848 var stub = array._chartjs; 4849 if (!stub) { 4850 return; 4851 } 4852 4853 var listeners = stub.listeners; 4854 var index = listeners.indexOf(listener); 4855 if (index !== -1) { 4856 listeners.splice(index, 1); 4857 } 4858 4859 if (listeners.length > 0) { 4860 return; 4861 } 4862 4863 arrayEvents.forEach(function(key) { 4864 delete array[key]; 4865 }); 4866 4867 delete array._chartjs; 4868 } 4869 4870 // Base class for all dataset controllers (line, bar, etc) 4871 Chart.DatasetController = function(chart, datasetIndex) { 4872 this.initialize(chart, datasetIndex); 4873 }; 4874 4875 helpers.extend(Chart.DatasetController.prototype, { 4876 4877 /** 4878 * Element type used to generate a meta dataset (e.g. Chart.element.Line). 4879 * @type {Chart.core.element} 4880 */ 4881 datasetElementType: null, 4882 4883 /** 4884 * Element type used to generate a meta data (e.g. Chart.element.Point). 4885 * @type {Chart.core.element} 4886 */ 4887 dataElementType: null, 4888 4889 initialize: function(chart, datasetIndex) { 4890 var me = this; 4891 me.chart = chart; 4892 me.index = datasetIndex; 4893 me.linkScales(); 4894 me.addElements(); 4895 }, 4896 4897 updateIndex: function(datasetIndex) { 4898 this.index = datasetIndex; 4899 }, 4900 4901 linkScales: function() { 4902 var me = this; 4903 var meta = me.getMeta(); 4904 var dataset = me.getDataset(); 4905 4906 if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) { 4907 meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; 4908 } 4909 if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) { 4910 meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; 4911 } 4912 }, 4913 4914 getDataset: function() { 4915 return this.chart.data.datasets[this.index]; 4916 }, 4917 4918 getMeta: function() { 4919 return this.chart.getDatasetMeta(this.index); 4920 }, 4921 4922 getScaleForId: function(scaleID) { 4923 return this.chart.scales[scaleID]; 4924 }, 4925 4926 reset: function() { 4927 this.update(true); 4928 }, 4929 4930 /** 4931 * @private 4932 */ 4933 destroy: function() { 4934 if (this._data) { 4935 unlistenArrayEvents(this._data, this); 4936 } 4937 }, 4938 4939 createMetaDataset: function() { 4940 var me = this; 4941 var type = me.datasetElementType; 4942 return type && new type({ 4943 _chart: me.chart, 4944 _datasetIndex: me.index 4945 }); 4946 }, 4947 4948 createMetaData: function(index) { 4949 var me = this; 4950 var type = me.dataElementType; 4951 return type && new type({ 4952 _chart: me.chart, 4953 _datasetIndex: me.index, 4954 _index: index 4955 }); 4956 }, 4957 4958 addElements: function() { 4959 var me = this; 4960 var meta = me.getMeta(); 4961 var data = me.getDataset().data || []; 4962 var metaData = meta.data; 4963 var i, ilen; 4964 4965 for (i = 0, ilen = data.length; i < ilen; ++i) { 4966 metaData[i] = metaData[i] || me.createMetaData(i); 4967 } 4968 4969 meta.dataset = meta.dataset || me.createMetaDataset(); 4970 }, 4971 4972 addElementAndReset: function(index) { 4973 var element = this.createMetaData(index); 4974 this.getMeta().data.splice(index, 0, element); 4975 this.updateElement(element, index, true); 4976 }, 4977 4978 buildOrUpdateElements: function() { 4979 var me = this; 4980 var dataset = me.getDataset(); 4981 var data = dataset.data || (dataset.data = []); 4982 4983 // In order to correctly handle data addition/deletion animation (an thus simulate 4984 // real-time charts), we need to monitor these data modifications and synchronize 4985 // the internal meta data accordingly. 4986 if (me._data !== data) { 4987 if (me._data) { 4988 // This case happens when the user replaced the data array instance. 4989 unlistenArrayEvents(me._data, me); 4990 } 4991 4992 listenArrayEvents(data, me); 4993 me._data = data; 4994 } 4995 4996 // Re-sync meta data in case the user replaced the data array or if we missed 4997 // any updates and so make sure that we handle number of datapoints changing. 4998 me.resyncElements(); 4999 }, 5000 5001 update: helpers.noop, 5002 5003 transition: function(easingValue) { 5004 var meta = this.getMeta(); 5005 var elements = meta.data || []; 5006 var ilen = elements.length; 5007 var i = 0; 5008 5009 for (; i < ilen; ++i) { 5010 elements[i].transition(easingValue); 5011 } 5012 5013 if (meta.dataset) { 5014 meta.dataset.transition(easingValue); 5015 } 5016 }, 5017 5018 draw: function() { 5019 var meta = this.getMeta(); 5020 var elements = meta.data || []; 5021 var ilen = elements.length; 5022 var i = 0; 5023 5024 if (meta.dataset) { 5025 meta.dataset.draw(); 5026 } 5027 5028 for (; i < ilen; ++i) { 5029 elements[i].draw(); 5030 } 5031 }, 5032 5033 removeHoverStyle: function(element) { 5034 helpers.merge(element._model, element.$previousStyle || {}); 5035 delete element.$previousStyle; 5036 }, 5037 5038 setHoverStyle: function(element) { 5039 var dataset = this.chart.data.datasets[element._datasetIndex]; 5040 var index = element._index; 5041 var custom = element.custom || {}; 5042 var valueOrDefault = helpers.valueAtIndexOrDefault; 5043 var getHoverColor = helpers.getHoverColor; 5044 var model = element._model; 5045 5046 element.$previousStyle = { 5047 backgroundColor: model.backgroundColor, 5048 borderColor: model.borderColor, 5049 borderWidth: model.borderWidth 5050 }; 5051 5052 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); 5053 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); 5054 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); 5055 }, 5056 5057 /** 5058 * @private 5059 */ 5060 resyncElements: function() { 5061 var me = this; 5062 var meta = me.getMeta(); 5063 var data = me.getDataset().data; 5064 var numMeta = meta.data.length; 5065 var numData = data.length; 5066 5067 if (numData < numMeta) { 5068 meta.data.splice(numData, numMeta - numData); 5069 } else if (numData > numMeta) { 5070 me.insertElements(numMeta, numData - numMeta); 5071 } 5072 }, 5073 5074 /** 5075 * @private 5076 */ 5077 insertElements: function(start, count) { 5078 for (var i = 0; i < count; ++i) { 5079 this.addElementAndReset(start + i); 5080 } 5081 }, 5082 5083 /** 5084 * @private 5085 */ 5086 onDataPush: function() { 5087 this.insertElements(this.getDataset().data.length - 1, arguments.length); 5088 }, 5089 5090 /** 5091 * @private 5092 */ 5093 onDataPop: function() { 5094 this.getMeta().data.pop(); 5095 }, 5096 5097 /** 5098 * @private 5099 */ 5100 onDataShift: function() { 5101 this.getMeta().data.shift(); 5102 }, 5103 5104 /** 5105 * @private 5106 */ 5107 onDataSplice: function(start, count) { 5108 this.getMeta().data.splice(start, count); 5109 this.insertElements(start, arguments.length - 2); 5110 }, 5111 5112 /** 5113 * @private 5114 */ 5115 onDataUnshift: function() { 5116 this.insertElements(0, arguments.length); 5117 } 5118 }); 5119 5120 Chart.DatasetController.extend = helpers.inherits; 5121 }; 5122 5123 },{"46":46}],26:[function(require,module,exports){ 5124 'use strict'; 5125 5126 var helpers = require(46); 5127 5128 module.exports = { 5129 /** 5130 * @private 5131 */ 5132 _set: function(scope, values) { 5133 return helpers.merge(this[scope] || (this[scope] = {}), values); 5134 } 5135 }; 5136 5137 },{"46":46}],27:[function(require,module,exports){ 5138 'use strict'; 5139 5140 var color = require(3); 5141 var helpers = require(46); 5142 5143 function interpolate(start, view, model, ease) { 5144 var keys = Object.keys(model); 5145 var i, ilen, key, actual, origin, target, type, c0, c1; 5146 5147 for (i = 0, ilen = keys.length; i < ilen; ++i) { 5148 key = keys[i]; 5149 5150 target = model[key]; 5151 5152 // if a value is added to the model after pivot() has been called, the view 5153 // doesn't contain it, so let's initialize the view to the target value. 5154 if (!view.hasOwnProperty(key)) { 5155 view[key] = target; 5156 } 5157 5158 actual = view[key]; 5159 5160 if (actual === target || key[0] === '_') { 5161 continue; 5162 } 5163 5164 if (!start.hasOwnProperty(key)) { 5165 start[key] = actual; 5166 } 5167 5168 origin = start[key]; 5169 5170 type = typeof target; 5171 5172 if (type === typeof origin) { 5173 if (type === 'string') { 5174 c0 = color(origin); 5175 if (c0.valid) { 5176 c1 = color(target); 5177 if (c1.valid) { 5178 view[key] = c1.mix(c0, ease).rgbString(); 5179 continue; 5180 } 5181 } 5182 } else if (type === 'number' && isFinite(origin) && isFinite(target)) { 5183 view[key] = origin + (target - origin) * ease; 5184 continue; 5185 } 5186 } 5187 5188 view[key] = target; 5189 } 5190 } 5191 5192 var Element = function(configuration) { 5193 helpers.extend(this, configuration); 5194 this.initialize.apply(this, arguments); 5195 }; 5196 5197 helpers.extend(Element.prototype, { 5198 5199 initialize: function() { 5200 this.hidden = false; 5201 }, 5202 5203 pivot: function() { 5204 var me = this; 5205 if (!me._view) { 5206 me._view = helpers.clone(me._model); 5207 } 5208 me._start = {}; 5209 return me; 5210 }, 5211 5212 transition: function(ease) { 5213 var me = this; 5214 var model = me._model; 5215 var start = me._start; 5216 var view = me._view; 5217 5218 // No animation -> No Transition 5219 if (!model || ease === 1) { 5220 me._view = model; 5221 me._start = null; 5222 return me; 5223 } 5224 5225 if (!view) { 5226 view = me._view = {}; 5227 } 5228 5229 if (!start) { 5230 start = me._start = {}; 5231 } 5232 5233 interpolate(start, view, model, ease); 5234 5235 return me; 5236 }, 5237 5238 tooltipPosition: function() { 5239 return { 5240 x: this._model.x, 5241 y: this._model.y 5242 }; 5243 }, 5244 5245 hasValue: function() { 5246 return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); 5247 } 5248 }); 5249 5250 Element.extend = helpers.inherits; 5251 5252 module.exports = Element; 5253 5254 },{"3":3,"46":46}],28:[function(require,module,exports){ 5255 /* global window: false */ 5256 /* global document: false */ 5257 'use strict'; 5258 5259 var color = require(3); 5260 var defaults = require(26); 5261 var helpers = require(46); 5262 var scaleService = require(34); 5263 5264 module.exports = function() { 5265 5266 // -- Basic js utility methods 5267 5268 helpers.configMerge = function(/* objects ... */) { 5269 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { 5270 merger: function(key, target, source, options) { 5271 var tval = target[key] || {}; 5272 var sval = source[key]; 5273 5274 if (key === 'scales') { 5275 // scale config merging is complex. Add our own function here for that 5276 target[key] = helpers.scaleMerge(tval, sval); 5277 } else if (key === 'scale') { 5278 // used in polar area & radar charts since there is only one scale 5279 target[key] = helpers.merge(tval, [scaleService.getScaleDefaults(sval.type), sval]); 5280 } else { 5281 helpers._merger(key, target, source, options); 5282 } 5283 } 5284 }); 5285 }; 5286 5287 helpers.scaleMerge = function(/* objects ... */) { 5288 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { 5289 merger: function(key, target, source, options) { 5290 if (key === 'xAxes' || key === 'yAxes') { 5291 var slen = source[key].length; 5292 var i, type, scale; 5293 5294 if (!target[key]) { 5295 target[key] = []; 5296 } 5297 5298 for (i = 0; i < slen; ++i) { 5299 scale = source[key][i]; 5300 type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear'); 5301 5302 if (i >= target[key].length) { 5303 target[key].push({}); 5304 } 5305 5306 if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { 5307 // new/untyped scale or type changed: let's apply the new defaults 5308 // then merge source scale to correctly overwrite the defaults. 5309 helpers.merge(target[key][i], [scaleService.getScaleDefaults(type), scale]); 5310 } else { 5311 // scales type are the same 5312 helpers.merge(target[key][i], scale); 5313 } 5314 } 5315 } else { 5316 helpers._merger(key, target, source, options); 5317 } 5318 } 5319 }); 5320 }; 5321 5322 helpers.where = function(collection, filterCallback) { 5323 if (helpers.isArray(collection) && Array.prototype.filter) { 5324 return collection.filter(filterCallback); 5325 } 5326 var filtered = []; 5327 5328 helpers.each(collection, function(item) { 5329 if (filterCallback(item)) { 5330 filtered.push(item); 5331 } 5332 }); 5333 5334 return filtered; 5335 }; 5336 helpers.findIndex = Array.prototype.findIndex ? 5337 function(array, callback, scope) { 5338 return array.findIndex(callback, scope); 5339 } : 5340 function(array, callback, scope) { 5341 scope = scope === undefined ? array : scope; 5342 for (var i = 0, ilen = array.length; i < ilen; ++i) { 5343 if (callback.call(scope, array[i], i, array)) { 5344 return i; 5345 } 5346 } 5347 return -1; 5348 }; 5349 helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { 5350 // Default to start of the array 5351 if (helpers.isNullOrUndef(startIndex)) { 5352 startIndex = -1; 5353 } 5354 for (var i = startIndex + 1; i < arrayToSearch.length; i++) { 5355 var currentItem = arrayToSearch[i]; 5356 if (filterCallback(currentItem)) { 5357 return currentItem; 5358 } 5359 } 5360 }; 5361 helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { 5362 // Default to end of the array 5363 if (helpers.isNullOrUndef(startIndex)) { 5364 startIndex = arrayToSearch.length; 5365 } 5366 for (var i = startIndex - 1; i >= 0; i--) { 5367 var currentItem = arrayToSearch[i]; 5368 if (filterCallback(currentItem)) { 5369 return currentItem; 5370 } 5371 } 5372 }; 5373 5374 // -- Math methods 5375 helpers.isNumber = function(n) { 5376 return !isNaN(parseFloat(n)) && isFinite(n); 5377 }; 5378 helpers.almostEquals = function(x, y, epsilon) { 5379 return Math.abs(x - y) < epsilon; 5380 }; 5381 helpers.almostWhole = function(x, epsilon) { 5382 var rounded = Math.round(x); 5383 return (((rounded - epsilon) < x) && ((rounded + epsilon) > x)); 5384 }; 5385 helpers.max = function(array) { 5386 return array.reduce(function(max, value) { 5387 if (!isNaN(value)) { 5388 return Math.max(max, value); 5389 } 5390 return max; 5391 }, Number.NEGATIVE_INFINITY); 5392 }; 5393 helpers.min = function(array) { 5394 return array.reduce(function(min, value) { 5395 if (!isNaN(value)) { 5396 return Math.min(min, value); 5397 } 5398 return min; 5399 }, Number.POSITIVE_INFINITY); 5400 }; 5401 helpers.sign = Math.sign ? 5402 function(x) { 5403 return Math.sign(x); 5404 } : 5405 function(x) { 5406 x = +x; // convert to a number 5407 if (x === 0 || isNaN(x)) { 5408 return x; 5409 } 5410 return x > 0 ? 1 : -1; 5411 }; 5412 helpers.log10 = Math.log10 ? 5413 function(x) { 5414 return Math.log10(x); 5415 } : 5416 function(x) { 5417 var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. 5418 // Check for whole powers of 10, 5419 // which due to floating point rounding error should be corrected. 5420 var powerOf10 = Math.round(exponent); 5421 var isPowerOf10 = x === Math.pow(10, powerOf10); 5422 5423 return isPowerOf10 ? powerOf10 : exponent; 5424 }; 5425 helpers.toRadians = function(degrees) { 5426 return degrees * (Math.PI / 180); 5427 }; 5428 helpers.toDegrees = function(radians) { 5429 return radians * (180 / Math.PI); 5430 }; 5431 // Gets the angle from vertical upright to the point about a centre. 5432 helpers.getAngleFromPoint = function(centrePoint, anglePoint) { 5433 var distanceFromXCenter = anglePoint.x - centrePoint.x; 5434 var distanceFromYCenter = anglePoint.y - centrePoint.y; 5435 var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); 5436 5437 var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); 5438 5439 if (angle < (-0.5 * Math.PI)) { 5440 angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] 5441 } 5442 5443 return { 5444 angle: angle, 5445 distance: radialDistanceFromCenter 5446 }; 5447 }; 5448 helpers.distanceBetweenPoints = function(pt1, pt2) { 5449 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); 5450 }; 5451 helpers.aliasPixel = function(pixelWidth) { 5452 return (pixelWidth % 2 === 0) ? 0 : 0.5; 5453 }; 5454 helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { 5455 // Props to Rob Spencer at scaled innovation for his post on splining between points 5456 // http://scaledinnovation.com/analytics/splines/aboutSplines.html 5457 5458 // This function must also respect "skipped" points 5459 5460 var previous = firstPoint.skip ? middlePoint : firstPoint; 5461 var current = middlePoint; 5462 var next = afterPoint.skip ? middlePoint : afterPoint; 5463 5464 var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); 5465 var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); 5466 5467 var s01 = d01 / (d01 + d12); 5468 var s12 = d12 / (d01 + d12); 5469 5470 // If all points are the same, s01 & s02 will be inf 5471 s01 = isNaN(s01) ? 0 : s01; 5472 s12 = isNaN(s12) ? 0 : s12; 5473 5474 var fa = t * s01; // scaling factor for triangle Ta 5475 var fb = t * s12; 5476 5477 return { 5478 previous: { 5479 x: current.x - fa * (next.x - previous.x), 5480 y: current.y - fa * (next.y - previous.y) 5481 }, 5482 next: { 5483 x: current.x + fb * (next.x - previous.x), 5484 y: current.y + fb * (next.y - previous.y) 5485 } 5486 }; 5487 }; 5488 helpers.EPSILON = Number.EPSILON || 1e-14; 5489 helpers.splineCurveMonotone = function(points) { 5490 // This function calculates Bézier control points in a similar way than |splineCurve|, 5491 // but preserves monotonicity of the provided data and ensures no local extremums are added 5492 // between the dataset discrete points due to the interpolation. 5493 // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation 5494 5495 var pointsWithTangents = (points || []).map(function(point) { 5496 return { 5497 model: point._model, 5498 deltaK: 0, 5499 mK: 0 5500 }; 5501 }); 5502 5503 // Calculate slopes (deltaK) and initialize tangents (mK) 5504 var pointsLen = pointsWithTangents.length; 5505 var i, pointBefore, pointCurrent, pointAfter; 5506 for (i = 0; i < pointsLen; ++i) { 5507 pointCurrent = pointsWithTangents[i]; 5508 if (pointCurrent.model.skip) { 5509 continue; 5510 } 5511 5512 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; 5513 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; 5514 if (pointAfter && !pointAfter.model.skip) { 5515 var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); 5516 5517 // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 5518 pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; 5519 } 5520 5521 if (!pointBefore || pointBefore.model.skip) { 5522 pointCurrent.mK = pointCurrent.deltaK; 5523 } else if (!pointAfter || pointAfter.model.skip) { 5524 pointCurrent.mK = pointBefore.deltaK; 5525 } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { 5526 pointCurrent.mK = 0; 5527 } else { 5528 pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; 5529 } 5530 } 5531 5532 // Adjust tangents to ensure monotonic properties 5533 var alphaK, betaK, tauK, squaredMagnitude; 5534 for (i = 0; i < pointsLen - 1; ++i) { 5535 pointCurrent = pointsWithTangents[i]; 5536 pointAfter = pointsWithTangents[i + 1]; 5537 if (pointCurrent.model.skip || pointAfter.model.skip) { 5538 continue; 5539 } 5540 5541 if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { 5542 pointCurrent.mK = pointAfter.mK = 0; 5543 continue; 5544 } 5545 5546 alphaK = pointCurrent.mK / pointCurrent.deltaK; 5547 betaK = pointAfter.mK / pointCurrent.deltaK; 5548 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); 5549 if (squaredMagnitude <= 9) { 5550 continue; 5551 } 5552 5553 tauK = 3 / Math.sqrt(squaredMagnitude); 5554 pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; 5555 pointAfter.mK = betaK * tauK * pointCurrent.deltaK; 5556 } 5557 5558 // Compute control points 5559 var deltaX; 5560 for (i = 0; i < pointsLen; ++i) { 5561 pointCurrent = pointsWithTangents[i]; 5562 if (pointCurrent.model.skip) { 5563 continue; 5564 } 5565 5566 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; 5567 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; 5568 if (pointBefore && !pointBefore.model.skip) { 5569 deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; 5570 pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; 5571 pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; 5572 } 5573 if (pointAfter && !pointAfter.model.skip) { 5574 deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; 5575 pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; 5576 pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; 5577 } 5578 } 5579 }; 5580 helpers.nextItem = function(collection, index, loop) { 5581 if (loop) { 5582 return index >= collection.length - 1 ? collection[0] : collection[index + 1]; 5583 } 5584 return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; 5585 }; 5586 helpers.previousItem = function(collection, index, loop) { 5587 if (loop) { 5588 return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; 5589 } 5590 return index <= 0 ? collection[0] : collection[index - 1]; 5591 }; 5592 // Implementation of the nice number algorithm used in determining where axis labels will go 5593 helpers.niceNum = function(range, round) { 5594 var exponent = Math.floor(helpers.log10(range)); 5595 var fraction = range / Math.pow(10, exponent); 5596 var niceFraction; 5597 5598 if (round) { 5599 if (fraction < 1.5) { 5600 niceFraction = 1; 5601 } else if (fraction < 3) { 5602 niceFraction = 2; 5603 } else if (fraction < 7) { 5604 niceFraction = 5; 5605 } else { 5606 niceFraction = 10; 5607 } 5608 } else if (fraction <= 1.0) { 5609 niceFraction = 1; 5610 } else if (fraction <= 2) { 5611 niceFraction = 2; 5612 } else if (fraction <= 5) { 5613 niceFraction = 5; 5614 } else { 5615 niceFraction = 10; 5616 } 5617 5618 return niceFraction * Math.pow(10, exponent); 5619 }; 5620 // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ 5621 helpers.requestAnimFrame = (function() { 5622 if (typeof window === 'undefined') { 5623 return function(callback) { 5624 callback(); 5625 }; 5626 } 5627 return window.requestAnimationFrame || 5628 window.webkitRequestAnimationFrame || 5629 window.mozRequestAnimationFrame || 5630 window.oRequestAnimationFrame || 5631 window.msRequestAnimationFrame || 5632 function(callback) { 5633 return window.setTimeout(callback, 1000 / 60); 5634 }; 5635 }()); 5636 // -- DOM methods 5637 helpers.getRelativePosition = function(evt, chart) { 5638 var mouseX, mouseY; 5639 var e = evt.originalEvent || evt; 5640 var canvas = evt.target || evt.srcElement; 5641 var boundingRect = canvas.getBoundingClientRect(); 5642 5643 var touches = e.touches; 5644 if (touches && touches.length > 0) { 5645 mouseX = touches[0].clientX; 5646 mouseY = touches[0].clientY; 5647 5648 } else { 5649 mouseX = e.clientX; 5650 mouseY = e.clientY; 5651 } 5652 5653 // Scale mouse coordinates into canvas coordinates 5654 // by following the pattern laid out by 'jerryj' in the comments of 5655 // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ 5656 var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left')); 5657 var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top')); 5658 var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right')); 5659 var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom')); 5660 var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; 5661 var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; 5662 5663 // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However 5664 // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here 5665 mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); 5666 mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); 5667 5668 return { 5669 x: mouseX, 5670 y: mouseY 5671 }; 5672 5673 }; 5674 5675 // Private helper function to convert max-width/max-height values that may be percentages into a number 5676 function parseMaxStyle(styleValue, node, parentProperty) { 5677 var valueInPixels; 5678 if (typeof styleValue === 'string') { 5679 valueInPixels = parseInt(styleValue, 10); 5680 5681 if (styleValue.indexOf('%') !== -1) { 5682 // percentage * size in dimension 5683 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; 5684 } 5685 } else { 5686 valueInPixels = styleValue; 5687 } 5688 5689 return valueInPixels; 5690 } 5691 5692 /** 5693 * Returns if the given value contains an effective constraint. 5694 * @private 5695 */ 5696 function isConstrainedValue(value) { 5697 return value !== undefined && value !== null && value !== 'none'; 5698 } 5699 5700 // Private helper to get a constraint dimension 5701 // @param domNode : the node to check the constraint on 5702 // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight) 5703 // @param percentageProperty : property of parent to use when calculating width as a percentage 5704 // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser 5705 function getConstraintDimension(domNode, maxStyle, percentageProperty) { 5706 var view = document.defaultView; 5707 var parentNode = helpers._getParentNode(domNode); 5708 var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; 5709 var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; 5710 var hasCNode = isConstrainedValue(constrainedNode); 5711 var hasCContainer = isConstrainedValue(constrainedContainer); 5712 var infinity = Number.POSITIVE_INFINITY; 5713 5714 if (hasCNode || hasCContainer) { 5715 return Math.min( 5716 hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, 5717 hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); 5718 } 5719 5720 return 'none'; 5721 } 5722 // returns Number or undefined if no constraint 5723 helpers.getConstraintWidth = function(domNode) { 5724 return getConstraintDimension(domNode, 'max-width', 'clientWidth'); 5725 }; 5726 // returns Number or undefined if no constraint 5727 helpers.getConstraintHeight = function(domNode) { 5728 return getConstraintDimension(domNode, 'max-height', 'clientHeight'); 5729 }; 5730 /** 5731 * @private 5732 */ 5733 helpers._calculatePadding = function(container, padding, parentDimension) { 5734 padding = helpers.getStyle(container, padding); 5735 5736 return padding.indexOf('%') > -1 ? parentDimension / parseInt(padding, 10) : parseInt(padding, 10); 5737 }; 5738 /** 5739 * @private 5740 */ 5741 helpers._getParentNode = function(domNode) { 5742 var parent = domNode.parentNode; 5743 if (parent && parent.host) { 5744 parent = parent.host; 5745 } 5746 return parent; 5747 }; 5748 helpers.getMaximumWidth = function(domNode) { 5749 var container = helpers._getParentNode(domNode); 5750 if (!container) { 5751 return domNode.clientWidth; 5752 } 5753 5754 var clientWidth = container.clientWidth; 5755 var paddingLeft = helpers._calculatePadding(container, 'padding-left', clientWidth); 5756 var paddingRight = helpers._calculatePadding(container, 'padding-right', clientWidth); 5757 5758 var w = clientWidth - paddingLeft - paddingRight; 5759 var cw = helpers.getConstraintWidth(domNode); 5760 return isNaN(cw) ? w : Math.min(w, cw); 5761 }; 5762 helpers.getMaximumHeight = function(domNode) { 5763 var container = helpers._getParentNode(domNode); 5764 if (!container) { 5765 return domNode.clientHeight; 5766 } 5767 5768 var clientHeight = container.clientHeight; 5769 var paddingTop = helpers._calculatePadding(container, 'padding-top', clientHeight); 5770 var paddingBottom = helpers._calculatePadding(container, 'padding-bottom', clientHeight); 5771 5772 var h = clientHeight - paddingTop - paddingBottom; 5773 var ch = helpers.getConstraintHeight(domNode); 5774 return isNaN(ch) ? h : Math.min(h, ch); 5775 }; 5776 helpers.getStyle = function(el, property) { 5777 return el.currentStyle ? 5778 el.currentStyle[property] : 5779 document.defaultView.getComputedStyle(el, null).getPropertyValue(property); 5780 }; 5781 helpers.retinaScale = function(chart, forceRatio) { 5782 var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1; 5783 if (pixelRatio === 1) { 5784 return; 5785 } 5786 5787 var canvas = chart.canvas; 5788 var height = chart.height; 5789 var width = chart.width; 5790 5791 canvas.height = height * pixelRatio; 5792 canvas.width = width * pixelRatio; 5793 chart.ctx.scale(pixelRatio, pixelRatio); 5794 5795 // If no style has been set on the canvas, the render size is used as display size, 5796 // making the chart visually bigger, so let's enforce it to the "correct" values. 5797 // See https://github.com/chartjs/Chart.js/issues/3575 5798 if (!canvas.style.height && !canvas.style.width) { 5799 canvas.style.height = height + 'px'; 5800 canvas.style.width = width + 'px'; 5801 } 5802 }; 5803 // -- Canvas methods 5804 helpers.fontString = function(pixelSize, fontStyle, fontFamily) { 5805 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; 5806 }; 5807 helpers.longestText = function(ctx, font, arrayOfThings, cache) { 5808 cache = cache || {}; 5809 var data = cache.data = cache.data || {}; 5810 var gc = cache.garbageCollect = cache.garbageCollect || []; 5811 5812 if (cache.font !== font) { 5813 data = cache.data = {}; 5814 gc = cache.garbageCollect = []; 5815 cache.font = font; 5816 } 5817 5818 ctx.font = font; 5819 var longest = 0; 5820 helpers.each(arrayOfThings, function(thing) { 5821 // Undefined strings and arrays should not be measured 5822 if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) { 5823 longest = helpers.measureText(ctx, data, gc, longest, thing); 5824 } else if (helpers.isArray(thing)) { 5825 // if it is an array lets measure each element 5826 // to do maybe simplify this function a bit so we can do this more recursively? 5827 helpers.each(thing, function(nestedThing) { 5828 // Undefined strings and arrays should not be measured 5829 if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) { 5830 longest = helpers.measureText(ctx, data, gc, longest, nestedThing); 5831 } 5832 }); 5833 } 5834 }); 5835 5836 var gcLen = gc.length / 2; 5837 if (gcLen > arrayOfThings.length) { 5838 for (var i = 0; i < gcLen; i++) { 5839 delete data[gc[i]]; 5840 } 5841 gc.splice(0, gcLen); 5842 } 5843 return longest; 5844 }; 5845 helpers.measureText = function(ctx, data, gc, longest, string) { 5846 var textWidth = data[string]; 5847 if (!textWidth) { 5848 textWidth = data[string] = ctx.measureText(string).width; 5849 gc.push(string); 5850 } 5851 if (textWidth > longest) { 5852 longest = textWidth; 5853 } 5854 return longest; 5855 }; 5856 helpers.numberOfLabelLines = function(arrayOfThings) { 5857 var numberOfLines = 1; 5858 helpers.each(arrayOfThings, function(thing) { 5859 if (helpers.isArray(thing)) { 5860 if (thing.length > numberOfLines) { 5861 numberOfLines = thing.length; 5862 } 5863 } 5864 }); 5865 return numberOfLines; 5866 }; 5867 5868 helpers.color = !color ? 5869 function(value) { 5870 console.error('Color.js not found!'); 5871 return value; 5872 } : 5873 function(value) { 5874 /* global CanvasGradient */ 5875 if (value instanceof CanvasGradient) { 5876 value = defaults.global.defaultColor; 5877 } 5878 5879 return color(value); 5880 }; 5881 5882 helpers.getHoverColor = function(colorValue) { 5883 /* global CanvasPattern */ 5884 return (colorValue instanceof CanvasPattern) ? 5885 colorValue : 5886 helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString(); 5887 }; 5888 }; 5889 5890 },{"26":26,"3":3,"34":34,"46":46}],29:[function(require,module,exports){ 5891 'use strict'; 5892 5893 var helpers = require(46); 5894 5895 /** 5896 * Helper function to get relative position for an event 5897 * @param {Event|IEvent} event - The event to get the position for 5898 * @param {Chart} chart - The chart 5899 * @returns {Point} the event position 5900 */ 5901 function getRelativePosition(e, chart) { 5902 if (e.native) { 5903 return { 5904 x: e.x, 5905 y: e.y 5906 }; 5907 } 5908 5909 return helpers.getRelativePosition(e, chart); 5910 } 5911 5912 /** 5913 * Helper function to traverse all of the visible elements in the chart 5914 * @param chart {chart} the chart 5915 * @param handler {Function} the callback to execute for each visible item 5916 */ 5917 function parseVisibleItems(chart, handler) { 5918 var datasets = chart.data.datasets; 5919 var meta, i, j, ilen, jlen; 5920 5921 for (i = 0, ilen = datasets.length; i < ilen; ++i) { 5922 if (!chart.isDatasetVisible(i)) { 5923 continue; 5924 } 5925 5926 meta = chart.getDatasetMeta(i); 5927 for (j = 0, jlen = meta.data.length; j < jlen; ++j) { 5928 var element = meta.data[j]; 5929 if (!element._view.skip) { 5930 handler(element); 5931 } 5932 } 5933 } 5934 } 5935 5936 /** 5937 * Helper function to get the items that intersect the event position 5938 * @param items {ChartElement[]} elements to filter 5939 * @param position {Point} the point to be nearest to 5940 * @return {ChartElement[]} the nearest items 5941 */ 5942 function getIntersectItems(chart, position) { 5943 var elements = []; 5944 5945 parseVisibleItems(chart, function(element) { 5946 if (element.inRange(position.x, position.y)) { 5947 elements.push(element); 5948 } 5949 }); 5950 5951 return elements; 5952 } 5953 5954 /** 5955 * Helper function to get the items nearest to the event position considering all visible items in teh chart 5956 * @param chart {Chart} the chart to look at elements from 5957 * @param position {Point} the point to be nearest to 5958 * @param intersect {Boolean} if true, only consider items that intersect the position 5959 * @param distanceMetric {Function} function to provide the distance between points 5960 * @return {ChartElement[]} the nearest items 5961 */ 5962 function getNearestItems(chart, position, intersect, distanceMetric) { 5963 var minDistance = Number.POSITIVE_INFINITY; 5964 var nearestItems = []; 5965 5966 parseVisibleItems(chart, function(element) { 5967 if (intersect && !element.inRange(position.x, position.y)) { 5968 return; 5969 } 5970 5971 var center = element.getCenterPoint(); 5972 var distance = distanceMetric(position, center); 5973 5974 if (distance < minDistance) { 5975 nearestItems = [element]; 5976 minDistance = distance; 5977 } else if (distance === minDistance) { 5978 // Can have multiple items at the same distance in which case we sort by size 5979 nearestItems.push(element); 5980 } 5981 }); 5982 5983 return nearestItems; 5984 } 5985 5986 /** 5987 * Get a distance metric function for two points based on the 5988 * axis mode setting 5989 * @param {String} axis the axis mode. x|y|xy 5990 */ 5991 function getDistanceMetricForAxis(axis) { 5992 var useX = axis.indexOf('x') !== -1; 5993 var useY = axis.indexOf('y') !== -1; 5994 5995 return function(pt1, pt2) { 5996 var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; 5997 var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; 5998 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); 5999 }; 6000 } 6001 6002 function indexMode(chart, e, options) { 6003 var position = getRelativePosition(e, chart); 6004 // Default axis for index mode is 'x' to match old behaviour 6005 options.axis = options.axis || 'x'; 6006 var distanceMetric = getDistanceMetricForAxis(options.axis); 6007 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); 6008 var elements = []; 6009 6010 if (!items.length) { 6011 return []; 6012 } 6013 6014 chart.data.datasets.forEach(function(dataset, datasetIndex) { 6015 if (chart.isDatasetVisible(datasetIndex)) { 6016 var meta = chart.getDatasetMeta(datasetIndex); 6017 var element = meta.data[items[0]._index]; 6018 6019 // don't count items that are skipped (null data) 6020 if (element && !element._view.skip) { 6021 elements.push(element); 6022 } 6023 } 6024 }); 6025 6026 return elements; 6027 } 6028 6029 /** 6030 * @interface IInteractionOptions 6031 */ 6032 /** 6033 * If true, only consider items that intersect the point 6034 * @name IInterfaceOptions#boolean 6035 * @type Boolean 6036 */ 6037 6038 /** 6039 * Contains interaction related functions 6040 * @namespace Chart.Interaction 6041 */ 6042 module.exports = { 6043 // Helper function for different modes 6044 modes: { 6045 single: function(chart, e) { 6046 var position = getRelativePosition(e, chart); 6047 var elements = []; 6048 6049 parseVisibleItems(chart, function(element) { 6050 if (element.inRange(position.x, position.y)) { 6051 elements.push(element); 6052 return elements; 6053 } 6054 }); 6055 6056 return elements.slice(0, 1); 6057 }, 6058 6059 /** 6060 * @function Chart.Interaction.modes.label 6061 * @deprecated since version 2.4.0 6062 * @todo remove at version 3 6063 * @private 6064 */ 6065 label: indexMode, 6066 6067 /** 6068 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something 6069 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item 6070 * @function Chart.Interaction.modes.index 6071 * @since v2.4.0 6072 * @param chart {chart} the chart we are returning items from 6073 * @param e {Event} the event we are find things at 6074 * @param options {IInteractionOptions} options to use during interaction 6075 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6076 */ 6077 index: indexMode, 6078 6079 /** 6080 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something 6081 * If the options.intersect is false, we find the nearest item and return the items in that dataset 6082 * @function Chart.Interaction.modes.dataset 6083 * @param chart {chart} the chart we are returning items from 6084 * @param e {Event} the event we are find things at 6085 * @param options {IInteractionOptions} options to use during interaction 6086 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6087 */ 6088 dataset: function(chart, e, options) { 6089 var position = getRelativePosition(e, chart); 6090 options.axis = options.axis || 'xy'; 6091 var distanceMetric = getDistanceMetricForAxis(options.axis); 6092 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); 6093 6094 if (items.length > 0) { 6095 items = chart.getDatasetMeta(items[0]._datasetIndex).data; 6096 } 6097 6098 return items; 6099 }, 6100 6101 /** 6102 * @function Chart.Interaction.modes.x-axis 6103 * @deprecated since version 2.4.0. Use index mode and intersect == true 6104 * @todo remove at version 3 6105 * @private 6106 */ 6107 'x-axis': function(chart, e) { 6108 return indexMode(chart, e, {intersect: false}); 6109 }, 6110 6111 /** 6112 * Point mode returns all elements that hit test based on the event position 6113 * of the event 6114 * @function Chart.Interaction.modes.intersect 6115 * @param chart {chart} the chart we are returning items from 6116 * @param e {Event} the event we are find things at 6117 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6118 */ 6119 point: function(chart, e) { 6120 var position = getRelativePosition(e, chart); 6121 return getIntersectItems(chart, position); 6122 }, 6123 6124 /** 6125 * nearest mode returns the element closest to the point 6126 * @function Chart.Interaction.modes.intersect 6127 * @param chart {chart} the chart we are returning items from 6128 * @param e {Event} the event we are find things at 6129 * @param options {IInteractionOptions} options to use 6130 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6131 */ 6132 nearest: function(chart, e, options) { 6133 var position = getRelativePosition(e, chart); 6134 options.axis = options.axis || 'xy'; 6135 var distanceMetric = getDistanceMetricForAxis(options.axis); 6136 var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric); 6137 6138 // We have multiple items at the same distance from the event. Now sort by smallest 6139 if (nearestItems.length > 1) { 6140 nearestItems.sort(function(a, b) { 6141 var sizeA = a.getArea(); 6142 var sizeB = b.getArea(); 6143 var ret = sizeA - sizeB; 6144 6145 if (ret === 0) { 6146 // if equal sort by dataset index 6147 ret = a._datasetIndex - b._datasetIndex; 6148 } 6149 6150 return ret; 6151 }); 6152 } 6153 6154 // Return only 1 item 6155 return nearestItems.slice(0, 1); 6156 }, 6157 6158 /** 6159 * x mode returns the elements that hit-test at the current x coordinate 6160 * @function Chart.Interaction.modes.x 6161 * @param chart {chart} the chart we are returning items from 6162 * @param e {Event} the event we are find things at 6163 * @param options {IInteractionOptions} options to use 6164 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6165 */ 6166 x: function(chart, e, options) { 6167 var position = getRelativePosition(e, chart); 6168 var items = []; 6169 var intersectsItem = false; 6170 6171 parseVisibleItems(chart, function(element) { 6172 if (element.inXRange(position.x)) { 6173 items.push(element); 6174 } 6175 6176 if (element.inRange(position.x, position.y)) { 6177 intersectsItem = true; 6178 } 6179 }); 6180 6181 // If we want to trigger on an intersect and we don't have any items 6182 // that intersect the position, return nothing 6183 if (options.intersect && !intersectsItem) { 6184 items = []; 6185 } 6186 return items; 6187 }, 6188 6189 /** 6190 * y mode returns the elements that hit-test at the current y coordinate 6191 * @function Chart.Interaction.modes.y 6192 * @param chart {chart} the chart we are returning items from 6193 * @param e {Event} the event we are find things at 6194 * @param options {IInteractionOptions} options to use 6195 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6196 */ 6197 y: function(chart, e, options) { 6198 var position = getRelativePosition(e, chart); 6199 var items = []; 6200 var intersectsItem = false; 6201 6202 parseVisibleItems(chart, function(element) { 6203 if (element.inYRange(position.y)) { 6204 items.push(element); 6205 } 6206 6207 if (element.inRange(position.x, position.y)) { 6208 intersectsItem = true; 6209 } 6210 }); 6211 6212 // If we want to trigger on an intersect and we don't have any items 6213 // that intersect the position, return nothing 6214 if (options.intersect && !intersectsItem) { 6215 items = []; 6216 } 6217 return items; 6218 } 6219 } 6220 }; 6221 6222 },{"46":46}],30:[function(require,module,exports){ 6223 'use strict'; 6224 6225 var defaults = require(26); 6226 6227 defaults._set('global', { 6228 responsive: true, 6229 responsiveAnimationDuration: 0, 6230 maintainAspectRatio: true, 6231 events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], 6232 hover: { 6233 onHover: null, 6234 mode: 'nearest', 6235 intersect: true, 6236 animationDuration: 400 6237 }, 6238 onClick: null, 6239 defaultColor: 'rgba(0,0,0,0.1)', 6240 defaultFontColor: '#666', 6241 defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", 6242 defaultFontSize: 12, 6243 defaultFontStyle: 'normal', 6244 showLines: true, 6245 6246 // Element defaults defined in element extensions 6247 elements: {}, 6248 6249 // Layout options such as padding 6250 layout: { 6251 padding: { 6252 top: 0, 6253 right: 0, 6254 bottom: 0, 6255 left: 0 6256 } 6257 } 6258 }); 6259 6260 module.exports = function() { 6261 6262 // Occupy the global variable of Chart, and create a simple base class 6263 var Chart = function(item, config) { 6264 this.construct(item, config); 6265 return this; 6266 }; 6267 6268 Chart.Chart = Chart; 6269 6270 return Chart; 6271 }; 6272 6273 },{"26":26}],31:[function(require,module,exports){ 6274 'use strict'; 6275 6276 var helpers = require(46); 6277 6278 function filterByPosition(array, position) { 6279 return helpers.where(array, function(v) { 6280 return v.position === position; 6281 }); 6282 } 6283 6284 function sortByWeight(array, reverse) { 6285 array.forEach(function(v, i) { 6286 v._tmpIndex_ = i; 6287 return v; 6288 }); 6289 array.sort(function(a, b) { 6290 var v0 = reverse ? b : a; 6291 var v1 = reverse ? a : b; 6292 return v0.weight === v1.weight ? 6293 v0._tmpIndex_ - v1._tmpIndex_ : 6294 v0.weight - v1.weight; 6295 }); 6296 array.forEach(function(v) { 6297 delete v._tmpIndex_; 6298 }); 6299 } 6300 6301 /** 6302 * @interface ILayoutItem 6303 * @prop {String} position - The position of the item in the chart layout. Possible values are 6304 * 'left', 'top', 'right', 'bottom', and 'chartArea' 6305 * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area 6306 * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down 6307 * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) 6308 * @prop {Function} update - Takes two parameters: width and height. Returns size of item 6309 * @prop {Function} getPadding - Returns an object with padding on the edges 6310 * @prop {Number} width - Width of item. Must be valid after update() 6311 * @prop {Number} height - Height of item. Must be valid after update() 6312 * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update 6313 * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update 6314 * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update 6315 * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update 6316 */ 6317 6318 // The layout service is very self explanatory. It's responsible for the layout within a chart. 6319 // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need 6320 // It is this service's responsibility of carrying out that layout. 6321 module.exports = { 6322 defaults: {}, 6323 6324 /** 6325 * Register a box to a chart. 6326 * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. 6327 * @param {Chart} chart - the chart to use 6328 * @param {ILayoutItem} item - the item to add to be layed out 6329 */ 6330 addBox: function(chart, item) { 6331 if (!chart.boxes) { 6332 chart.boxes = []; 6333 } 6334 6335 // initialize item with default values 6336 item.fullWidth = item.fullWidth || false; 6337 item.position = item.position || 'top'; 6338 item.weight = item.weight || 0; 6339 6340 chart.boxes.push(item); 6341 }, 6342 6343 /** 6344 * Remove a layoutItem from a chart 6345 * @param {Chart} chart - the chart to remove the box from 6346 * @param {Object} layoutItem - the item to remove from the layout 6347 */ 6348 removeBox: function(chart, layoutItem) { 6349 var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; 6350 if (index !== -1) { 6351 chart.boxes.splice(index, 1); 6352 } 6353 }, 6354 6355 /** 6356 * Sets (or updates) options on the given `item`. 6357 * @param {Chart} chart - the chart in which the item lives (or will be added to) 6358 * @param {Object} item - the item to configure with the given options 6359 * @param {Object} options - the new item options. 6360 */ 6361 configure: function(chart, item, options) { 6362 var props = ['fullWidth', 'position', 'weight']; 6363 var ilen = props.length; 6364 var i = 0; 6365 var prop; 6366 6367 for (; i < ilen; ++i) { 6368 prop = props[i]; 6369 if (options.hasOwnProperty(prop)) { 6370 item[prop] = options[prop]; 6371 } 6372 } 6373 }, 6374 6375 /** 6376 * Fits boxes of the given chart into the given size by having each box measure itself 6377 * then running a fitting algorithm 6378 * @param {Chart} chart - the chart 6379 * @param {Number} width - the width to fit into 6380 * @param {Number} height - the height to fit into 6381 */ 6382 update: function(chart, width, height) { 6383 if (!chart) { 6384 return; 6385 } 6386 6387 var layoutOptions = chart.options.layout || {}; 6388 var padding = helpers.options.toPadding(layoutOptions.padding); 6389 var leftPadding = padding.left; 6390 var rightPadding = padding.right; 6391 var topPadding = padding.top; 6392 var bottomPadding = padding.bottom; 6393 6394 var leftBoxes = filterByPosition(chart.boxes, 'left'); 6395 var rightBoxes = filterByPosition(chart.boxes, 'right'); 6396 var topBoxes = filterByPosition(chart.boxes, 'top'); 6397 var bottomBoxes = filterByPosition(chart.boxes, 'bottom'); 6398 var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea'); 6399 6400 // Sort boxes by weight. A higher weight is further away from the chart area 6401 sortByWeight(leftBoxes, true); 6402 sortByWeight(rightBoxes, false); 6403 sortByWeight(topBoxes, true); 6404 sortByWeight(bottomBoxes, false); 6405 6406 // Essentially we now have any number of boxes on each of the 4 sides. 6407 // Our canvas looks like the following. 6408 // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and 6409 // B1 is the bottom axis 6410 // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays 6411 // These locations are single-box locations only, when trying to register a chartArea location that is already taken, 6412 // an error will be thrown. 6413 // 6414 // |----------------------------------------------------| 6415 // | T1 (Full Width) | 6416 // |----------------------------------------------------| 6417 // | | | T2 | | 6418 // | |----|-------------------------------------|----| 6419 // | | | C1 | | C2 | | 6420 // | | |----| |----| | 6421 // | | | | | 6422 // | L1 | L2 | ChartArea (C0) | R1 | 6423 // | | | | | 6424 // | | |----| |----| | 6425 // | | | C3 | | C4 | | 6426 // | |----|-------------------------------------|----| 6427 // | | | B1 | | 6428 // |----------------------------------------------------| 6429 // | B2 (Full Width) | 6430 // |----------------------------------------------------| 6431 // 6432 // What we do to find the best sizing, we do the following 6433 // 1. Determine the minimum size of the chart area. 6434 // 2. Split the remaining width equally between each vertical axis 6435 // 3. Split the remaining height equally between each horizontal axis 6436 // 4. Give each layout the maximum size it can be. The layout will return it's minimum size 6437 // 5. Adjust the sizes of each axis based on it's minimum reported size. 6438 // 6. Refit each axis 6439 // 7. Position each axis in the final location 6440 // 8. Tell the chart the final location of the chart area 6441 // 9. Tell any axes that overlay the chart area the positions of the chart area 6442 6443 // Step 1 6444 var chartWidth = width - leftPadding - rightPadding; 6445 var chartHeight = height - topPadding - bottomPadding; 6446 var chartAreaWidth = chartWidth / 2; // min 50% 6447 var chartAreaHeight = chartHeight / 2; // min 50% 6448 6449 // Step 2 6450 var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); 6451 6452 // Step 3 6453 var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); 6454 6455 // Step 4 6456 var maxChartAreaWidth = chartWidth; 6457 var maxChartAreaHeight = chartHeight; 6458 var minBoxSizes = []; 6459 6460 function getMinimumBoxSize(box) { 6461 var minSize; 6462 var isHorizontal = box.isHorizontal(); 6463 6464 if (isHorizontal) { 6465 minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); 6466 maxChartAreaHeight -= minSize.height; 6467 } else { 6468 minSize = box.update(verticalBoxWidth, maxChartAreaHeight); 6469 maxChartAreaWidth -= minSize.width; 6470 } 6471 6472 minBoxSizes.push({ 6473 horizontal: isHorizontal, 6474 minSize: minSize, 6475 box: box, 6476 }); 6477 } 6478 6479 helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); 6480 6481 // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478) 6482 var maxHorizontalLeftPadding = 0; 6483 var maxHorizontalRightPadding = 0; 6484 var maxVerticalTopPadding = 0; 6485 var maxVerticalBottomPadding = 0; 6486 6487 helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) { 6488 if (horizontalBox.getPadding) { 6489 var boxPadding = horizontalBox.getPadding(); 6490 maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left); 6491 maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right); 6492 } 6493 }); 6494 6495 helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) { 6496 if (verticalBox.getPadding) { 6497 var boxPadding = verticalBox.getPadding(); 6498 maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top); 6499 maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom); 6500 } 6501 }); 6502 6503 // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could 6504 // be if the axes are drawn at their minimum sizes. 6505 // Steps 5 & 6 6506 var totalLeftBoxesWidth = leftPadding; 6507 var totalRightBoxesWidth = rightPadding; 6508 var totalTopBoxesHeight = topPadding; 6509 var totalBottomBoxesHeight = bottomPadding; 6510 6511 // Function to fit a box 6512 function fitBox(box) { 6513 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) { 6514 return minBox.box === box; 6515 }); 6516 6517 if (minBoxSize) { 6518 if (box.isHorizontal()) { 6519 var scaleMargin = { 6520 left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding), 6521 right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding), 6522 top: 0, 6523 bottom: 0 6524 }; 6525 6526 // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends 6527 // on the margin. Sometimes they need to increase in size slightly 6528 box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); 6529 } else { 6530 box.update(minBoxSize.minSize.width, maxChartAreaHeight); 6531 } 6532 } 6533 } 6534 6535 // Update, and calculate the left and right margins for the horizontal boxes 6536 helpers.each(leftBoxes.concat(rightBoxes), fitBox); 6537 6538 helpers.each(leftBoxes, function(box) { 6539 totalLeftBoxesWidth += box.width; 6540 }); 6541 6542 helpers.each(rightBoxes, function(box) { 6543 totalRightBoxesWidth += box.width; 6544 }); 6545 6546 // Set the Left and Right margins for the horizontal boxes 6547 helpers.each(topBoxes.concat(bottomBoxes), fitBox); 6548 6549 // Figure out how much margin is on the top and bottom of the vertical boxes 6550 helpers.each(topBoxes, function(box) { 6551 totalTopBoxesHeight += box.height; 6552 }); 6553 6554 helpers.each(bottomBoxes, function(box) { 6555 totalBottomBoxesHeight += box.height; 6556 }); 6557 6558 function finalFitVerticalBox(box) { 6559 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) { 6560 return minSize.box === box; 6561 }); 6562 6563 var scaleMargin = { 6564 left: 0, 6565 right: 0, 6566 top: totalTopBoxesHeight, 6567 bottom: totalBottomBoxesHeight 6568 }; 6569 6570 if (minBoxSize) { 6571 box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); 6572 } 6573 } 6574 6575 // Let the left layout know the final margin 6576 helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); 6577 6578 // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) 6579 totalLeftBoxesWidth = leftPadding; 6580 totalRightBoxesWidth = rightPadding; 6581 totalTopBoxesHeight = topPadding; 6582 totalBottomBoxesHeight = bottomPadding; 6583 6584 helpers.each(leftBoxes, function(box) { 6585 totalLeftBoxesWidth += box.width; 6586 }); 6587 6588 helpers.each(rightBoxes, function(box) { 6589 totalRightBoxesWidth += box.width; 6590 }); 6591 6592 helpers.each(topBoxes, function(box) { 6593 totalTopBoxesHeight += box.height; 6594 }); 6595 helpers.each(bottomBoxes, function(box) { 6596 totalBottomBoxesHeight += box.height; 6597 }); 6598 6599 // We may be adding some padding to account for rotated x axis labels 6600 var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0); 6601 totalLeftBoxesWidth += leftPaddingAddition; 6602 totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0); 6603 6604 var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0); 6605 totalTopBoxesHeight += topPaddingAddition; 6606 totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0); 6607 6608 // Figure out if our chart area changed. This would occur if the dataset layout label rotation 6609 // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do 6610 // without calling `fit` again 6611 var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; 6612 var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; 6613 6614 if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { 6615 helpers.each(leftBoxes, function(box) { 6616 box.height = newMaxChartAreaHeight; 6617 }); 6618 6619 helpers.each(rightBoxes, function(box) { 6620 box.height = newMaxChartAreaHeight; 6621 }); 6622 6623 helpers.each(topBoxes, function(box) { 6624 if (!box.fullWidth) { 6625 box.width = newMaxChartAreaWidth; 6626 } 6627 }); 6628 6629 helpers.each(bottomBoxes, function(box) { 6630 if (!box.fullWidth) { 6631 box.width = newMaxChartAreaWidth; 6632 } 6633 }); 6634 6635 maxChartAreaHeight = newMaxChartAreaHeight; 6636 maxChartAreaWidth = newMaxChartAreaWidth; 6637 } 6638 6639 // Step 7 - Position the boxes 6640 var left = leftPadding + leftPaddingAddition; 6641 var top = topPadding + topPaddingAddition; 6642 6643 function placeBox(box) { 6644 if (box.isHorizontal()) { 6645 box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth; 6646 box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth; 6647 box.top = top; 6648 box.bottom = top + box.height; 6649 6650 // Move to next point 6651 top = box.bottom; 6652 6653 } else { 6654 6655 box.left = left; 6656 box.right = left + box.width; 6657 box.top = totalTopBoxesHeight; 6658 box.bottom = totalTopBoxesHeight + maxChartAreaHeight; 6659 6660 // Move to next point 6661 left = box.right; 6662 } 6663 } 6664 6665 helpers.each(leftBoxes.concat(topBoxes), placeBox); 6666 6667 // Account for chart width and height 6668 left += maxChartAreaWidth; 6669 top += maxChartAreaHeight; 6670 6671 helpers.each(rightBoxes, placeBox); 6672 helpers.each(bottomBoxes, placeBox); 6673 6674 // Step 8 6675 chart.chartArea = { 6676 left: totalLeftBoxesWidth, 6677 top: totalTopBoxesHeight, 6678 right: totalLeftBoxesWidth + maxChartAreaWidth, 6679 bottom: totalTopBoxesHeight + maxChartAreaHeight 6680 }; 6681 6682 // Step 9 6683 helpers.each(chartAreaBoxes, function(box) { 6684 box.left = chart.chartArea.left; 6685 box.top = chart.chartArea.top; 6686 box.right = chart.chartArea.right; 6687 box.bottom = chart.chartArea.bottom; 6688 6689 box.update(maxChartAreaWidth, maxChartAreaHeight); 6690 }); 6691 } 6692 }; 6693 6694 },{"46":46}],32:[function(require,module,exports){ 6695 'use strict'; 6696 6697 var defaults = require(26); 6698 var helpers = require(46); 6699 6700 defaults._set('global', { 6701 plugins: {} 6702 }); 6703 6704 /** 6705 * The plugin service singleton 6706 * @namespace Chart.plugins 6707 * @since 2.1.0 6708 */ 6709 module.exports = { 6710 /** 6711 * Globally registered plugins. 6712 * @private 6713 */ 6714 _plugins: [], 6715 6716 /** 6717 * This identifier is used to invalidate the descriptors cache attached to each chart 6718 * when a global plugin is registered or unregistered. In this case, the cache ID is 6719 * incremented and descriptors are regenerated during following API calls. 6720 * @private 6721 */ 6722 _cacheId: 0, 6723 6724 /** 6725 * Registers the given plugin(s) if not already registered. 6726 * @param {Array|Object} plugins plugin instance(s). 6727 */ 6728 register: function(plugins) { 6729 var p = this._plugins; 6730 ([]).concat(plugins).forEach(function(plugin) { 6731 if (p.indexOf(plugin) === -1) { 6732 p.push(plugin); 6733 } 6734 }); 6735 6736 this._cacheId++; 6737 }, 6738 6739 /** 6740 * Unregisters the given plugin(s) only if registered. 6741 * @param {Array|Object} plugins plugin instance(s). 6742 */ 6743 unregister: function(plugins) { 6744 var p = this._plugins; 6745 ([]).concat(plugins).forEach(function(plugin) { 6746 var idx = p.indexOf(plugin); 6747 if (idx !== -1) { 6748 p.splice(idx, 1); 6749 } 6750 }); 6751 6752 this._cacheId++; 6753 }, 6754 6755 /** 6756 * Remove all registered plugins. 6757 * @since 2.1.5 6758 */ 6759 clear: function() { 6760 this._plugins = []; 6761 this._cacheId++; 6762 }, 6763 6764 /** 6765 * Returns the number of registered plugins? 6766 * @returns {Number} 6767 * @since 2.1.5 6768 */ 6769 count: function() { 6770 return this._plugins.length; 6771 }, 6772 6773 /** 6774 * Returns all registered plugin instances. 6775 * @returns {Array} array of plugin objects. 6776 * @since 2.1.5 6777 */ 6778 getAll: function() { 6779 return this._plugins; 6780 }, 6781 6782 /** 6783 * Calls enabled plugins for `chart` on the specified hook and with the given args. 6784 * This method immediately returns as soon as a plugin explicitly returns false. The 6785 * returned value can be used, for instance, to interrupt the current action. 6786 * @param {Object} chart - The chart instance for which plugins should be called. 6787 * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). 6788 * @param {Array} [args] - Extra arguments to apply to the hook call. 6789 * @returns {Boolean} false if any of the plugins return false, else returns true. 6790 */ 6791 notify: function(chart, hook, args) { 6792 var descriptors = this.descriptors(chart); 6793 var ilen = descriptors.length; 6794 var i, descriptor, plugin, params, method; 6795 6796 for (i = 0; i < ilen; ++i) { 6797 descriptor = descriptors[i]; 6798 plugin = descriptor.plugin; 6799 method = plugin[hook]; 6800 if (typeof method === 'function') { 6801 params = [chart].concat(args || []); 6802 params.push(descriptor.options); 6803 if (method.apply(plugin, params) === false) { 6804 return false; 6805 } 6806 } 6807 } 6808 6809 return true; 6810 }, 6811 6812 /** 6813 * Returns descriptors of enabled plugins for the given chart. 6814 * @returns {Array} [{ plugin, options }] 6815 * @private 6816 */ 6817 descriptors: function(chart) { 6818 var cache = chart.$plugins || (chart.$plugins = {}); 6819 if (cache.id === this._cacheId) { 6820 return cache.descriptors; 6821 } 6822 6823 var plugins = []; 6824 var descriptors = []; 6825 var config = (chart && chart.config) || {}; 6826 var options = (config.options && config.options.plugins) || {}; 6827 6828 this._plugins.concat(config.plugins || []).forEach(function(plugin) { 6829 var idx = plugins.indexOf(plugin); 6830 if (idx !== -1) { 6831 return; 6832 } 6833 6834 var id = plugin.id; 6835 var opts = options[id]; 6836 if (opts === false) { 6837 return; 6838 } 6839 6840 if (opts === true) { 6841 opts = helpers.clone(defaults.global.plugins[id]); 6842 } 6843 6844 plugins.push(plugin); 6845 descriptors.push({ 6846 plugin: plugin, 6847 options: opts || {} 6848 }); 6849 }); 6850 6851 cache.descriptors = descriptors; 6852 cache.id = this._cacheId; 6853 return descriptors; 6854 }, 6855 6856 /** 6857 * Invalidates cache for the given chart: descriptors hold a reference on plugin option, 6858 * but in some cases, this reference can be changed by the user when updating options. 6859 * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 6860 * @private 6861 */ 6862 _invalidate: function(chart) { 6863 delete chart.$plugins; 6864 } 6865 }; 6866 6867 /** 6868 * Plugin extension hooks. 6869 * @interface IPlugin 6870 * @since 2.1.0 6871 */ 6872 /** 6873 * @method IPlugin#beforeInit 6874 * @desc Called before initializing `chart`. 6875 * @param {Chart.Controller} chart - The chart instance. 6876 * @param {Object} options - The plugin options. 6877 */ 6878 /** 6879 * @method IPlugin#afterInit 6880 * @desc Called after `chart` has been initialized and before the first update. 6881 * @param {Chart.Controller} chart - The chart instance. 6882 * @param {Object} options - The plugin options. 6883 */ 6884 /** 6885 * @method IPlugin#beforeUpdate 6886 * @desc Called before updating `chart`. If any plugin returns `false`, the update 6887 * is cancelled (and thus subsequent render(s)) until another `update` is triggered. 6888 * @param {Chart.Controller} chart - The chart instance. 6889 * @param {Object} options - The plugin options. 6890 * @returns {Boolean} `false` to cancel the chart update. 6891 */ 6892 /** 6893 * @method IPlugin#afterUpdate 6894 * @desc Called after `chart` has been updated and before rendering. Note that this 6895 * hook will not be called if the chart update has been previously cancelled. 6896 * @param {Chart.Controller} chart - The chart instance. 6897 * @param {Object} options - The plugin options. 6898 */ 6899 /** 6900 * @method IPlugin#beforeDatasetsUpdate 6901 * @desc Called before updating the `chart` datasets. If any plugin returns `false`, 6902 * the datasets update is cancelled until another `update` is triggered. 6903 * @param {Chart.Controller} chart - The chart instance. 6904 * @param {Object} options - The plugin options. 6905 * @returns {Boolean} false to cancel the datasets update. 6906 * @since version 2.1.5 6907 */ 6908 /** 6909 * @method IPlugin#afterDatasetsUpdate 6910 * @desc Called after the `chart` datasets have been updated. Note that this hook 6911 * will not be called if the datasets update has been previously cancelled. 6912 * @param {Chart.Controller} chart - The chart instance. 6913 * @param {Object} options - The plugin options. 6914 * @since version 2.1.5 6915 */ 6916 /** 6917 * @method IPlugin#beforeDatasetUpdate 6918 * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin 6919 * returns `false`, the datasets update is cancelled until another `update` is triggered. 6920 * @param {Chart} chart - The chart instance. 6921 * @param {Object} args - The call arguments. 6922 * @param {Number} args.index - The dataset index. 6923 * @param {Object} args.meta - The dataset metadata. 6924 * @param {Object} options - The plugin options. 6925 * @returns {Boolean} `false` to cancel the chart datasets drawing. 6926 */ 6927 /** 6928 * @method IPlugin#afterDatasetUpdate 6929 * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note 6930 * that this hook will not be called if the datasets update has been previously cancelled. 6931 * @param {Chart} chart - The chart instance. 6932 * @param {Object} args - The call arguments. 6933 * @param {Number} args.index - The dataset index. 6934 * @param {Object} args.meta - The dataset metadata. 6935 * @param {Object} options - The plugin options. 6936 */ 6937 /** 6938 * @method IPlugin#beforeLayout 6939 * @desc Called before laying out `chart`. If any plugin returns `false`, 6940 * the layout update is cancelled until another `update` is triggered. 6941 * @param {Chart.Controller} chart - The chart instance. 6942 * @param {Object} options - The plugin options. 6943 * @returns {Boolean} `false` to cancel the chart layout. 6944 */ 6945 /** 6946 * @method IPlugin#afterLayout 6947 * @desc Called after the `chart` has been layed out. Note that this hook will not 6948 * be called if the layout update has been previously cancelled. 6949 * @param {Chart.Controller} chart - The chart instance. 6950 * @param {Object} options - The plugin options. 6951 */ 6952 /** 6953 * @method IPlugin#beforeRender 6954 * @desc Called before rendering `chart`. If any plugin returns `false`, 6955 * the rendering is cancelled until another `render` is triggered. 6956 * @param {Chart.Controller} chart - The chart instance. 6957 * @param {Object} options - The plugin options. 6958 * @returns {Boolean} `false` to cancel the chart rendering. 6959 */ 6960 /** 6961 * @method IPlugin#afterRender 6962 * @desc Called after the `chart` has been fully rendered (and animation completed). Note 6963 * that this hook will not be called if the rendering has been previously cancelled. 6964 * @param {Chart.Controller} chart - The chart instance. 6965 * @param {Object} options - The plugin options. 6966 */ 6967 /** 6968 * @method IPlugin#beforeDraw 6969 * @desc Called before drawing `chart` at every animation frame specified by the given 6970 * easing value. If any plugin returns `false`, the frame drawing is cancelled until 6971 * another `render` is triggered. 6972 * @param {Chart.Controller} chart - The chart instance. 6973 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. 6974 * @param {Object} options - The plugin options. 6975 * @returns {Boolean} `false` to cancel the chart drawing. 6976 */ 6977 /** 6978 * @method IPlugin#afterDraw 6979 * @desc Called after the `chart` has been drawn for the specific easing value. Note 6980 * that this hook will not be called if the drawing has been previously cancelled. 6981 * @param {Chart.Controller} chart - The chart instance. 6982 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. 6983 * @param {Object} options - The plugin options. 6984 */ 6985 /** 6986 * @method IPlugin#beforeDatasetsDraw 6987 * @desc Called before drawing the `chart` datasets. If any plugin returns `false`, 6988 * the datasets drawing is cancelled until another `render` is triggered. 6989 * @param {Chart.Controller} chart - The chart instance. 6990 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. 6991 * @param {Object} options - The plugin options. 6992 * @returns {Boolean} `false` to cancel the chart datasets drawing. 6993 */ 6994 /** 6995 * @method IPlugin#afterDatasetsDraw 6996 * @desc Called after the `chart` datasets have been drawn. Note that this hook 6997 * will not be called if the datasets drawing has been previously cancelled. 6998 * @param {Chart.Controller} chart - The chart instance. 6999 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. 7000 * @param {Object} options - The plugin options. 7001 */ 7002 /** 7003 * @method IPlugin#beforeDatasetDraw 7004 * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets 7005 * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing 7006 * is cancelled until another `render` is triggered. 7007 * @param {Chart} chart - The chart instance. 7008 * @param {Object} args - The call arguments. 7009 * @param {Number} args.index - The dataset index. 7010 * @param {Object} args.meta - The dataset metadata. 7011 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. 7012 * @param {Object} options - The plugin options. 7013 * @returns {Boolean} `false` to cancel the chart datasets drawing. 7014 */ 7015 /** 7016 * @method IPlugin#afterDatasetDraw 7017 * @desc Called after the `chart` datasets at the given `args.index` have been drawn 7018 * (datasets are drawn in the reverse order). Note that this hook will not be called 7019 * if the datasets drawing has been previously cancelled. 7020 * @param {Chart} chart - The chart instance. 7021 * @param {Object} args - The call arguments. 7022 * @param {Number} args.index - The dataset index. 7023 * @param {Object} args.meta - The dataset metadata. 7024 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. 7025 * @param {Object} options - The plugin options. 7026 */ 7027 /** 7028 * @method IPlugin#beforeTooltipDraw 7029 * @desc Called before drawing the `tooltip`. If any plugin returns `false`, 7030 * the tooltip drawing is cancelled until another `render` is triggered. 7031 * @param {Chart} chart - The chart instance. 7032 * @param {Object} args - The call arguments. 7033 * @param {Object} args.tooltip - The tooltip. 7034 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. 7035 * @param {Object} options - The plugin options. 7036 * @returns {Boolean} `false` to cancel the chart tooltip drawing. 7037 */ 7038 /** 7039 * @method IPlugin#afterTooltipDraw 7040 * @desc Called after drawing the `tooltip`. Note that this hook will not 7041 * be called if the tooltip drawing has been previously cancelled. 7042 * @param {Chart} chart - The chart instance. 7043 * @param {Object} args - The call arguments. 7044 * @param {Object} args.tooltip - The tooltip. 7045 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. 7046 * @param {Object} options - The plugin options. 7047 */ 7048 /** 7049 * @method IPlugin#beforeEvent 7050 * @desc Called before processing the specified `event`. If any plugin returns `false`, 7051 * the event will be discarded. 7052 * @param {Chart.Controller} chart - The chart instance. 7053 * @param {IEvent} event - The event object. 7054 * @param {Object} options - The plugin options. 7055 */ 7056 /** 7057 * @method IPlugin#afterEvent 7058 * @desc Called after the `event` has been consumed. Note that this hook 7059 * will not be called if the `event` has been previously discarded. 7060 * @param {Chart.Controller} chart - The chart instance. 7061 * @param {IEvent} event - The event object. 7062 * @param {Object} options - The plugin options. 7063 */ 7064 /** 7065 * @method IPlugin#resize 7066 * @desc Called after the chart as been resized. 7067 * @param {Chart.Controller} chart - The chart instance. 7068 * @param {Number} size - The new canvas display size (eq. canvas.style width & height). 7069 * @param {Object} options - The plugin options. 7070 */ 7071 /** 7072 * @method IPlugin#destroy 7073 * @desc Called after the chart as been destroyed. 7074 * @param {Chart.Controller} chart - The chart instance. 7075 * @param {Object} options - The plugin options. 7076 */ 7077 7078 },{"26":26,"46":46}],33:[function(require,module,exports){ 7079 'use strict'; 7080 7081 var defaults = require(26); 7082 var Element = require(27); 7083 var helpers = require(46); 7084 var Ticks = require(35); 7085 7086 defaults._set('scale', { 7087 display: true, 7088 position: 'left', 7089 offset: false, 7090 7091 // grid line settings 7092 gridLines: { 7093 display: true, 7094 color: 'rgba(0, 0, 0, 0.1)', 7095 lineWidth: 1, 7096 drawBorder: true, 7097 drawOnChartArea: true, 7098 drawTicks: true, 7099 tickMarkLength: 10, 7100 zeroLineWidth: 1, 7101 zeroLineColor: 'rgba(0,0,0,0.25)', 7102 zeroLineBorderDash: [], 7103 zeroLineBorderDashOffset: 0.0, 7104 offsetGridLines: false, 7105 borderDash: [], 7106 borderDashOffset: 0.0 7107 }, 7108 7109 // scale label 7110 scaleLabel: { 7111 // display property 7112 display: false, 7113 7114 // actual label 7115 labelString: '', 7116 7117 // line height 7118 lineHeight: 1.2, 7119 7120 // top/bottom padding 7121 padding: { 7122 top: 4, 7123 bottom: 4 7124 } 7125 }, 7126 7127 // label settings 7128 ticks: { 7129 beginAtZero: false, 7130 minRotation: 0, 7131 maxRotation: 50, 7132 mirror: false, 7133 padding: 0, 7134 reverse: false, 7135 display: true, 7136 autoSkip: true, 7137 autoSkipPadding: 0, 7138 labelOffset: 0, 7139 // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. 7140 callback: Ticks.formatters.values, 7141 minor: {}, 7142 major: {} 7143 } 7144 }); 7145 7146 function labelsFromTicks(ticks) { 7147 var labels = []; 7148 var i, ilen; 7149 7150 for (i = 0, ilen = ticks.length; i < ilen; ++i) { 7151 labels.push(ticks[i].label); 7152 } 7153 7154 return labels; 7155 } 7156 7157 function getLineValue(scale, index, offsetGridLines) { 7158 var lineValue = scale.getPixelForTick(index); 7159 7160 if (offsetGridLines) { 7161 if (index === 0) { 7162 lineValue -= (scale.getPixelForTick(1) - lineValue) / 2; 7163 } else { 7164 lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2; 7165 } 7166 } 7167 return lineValue; 7168 } 7169 7170 function computeTextSize(context, tick, font) { 7171 return helpers.isArray(tick) ? 7172 helpers.longestText(context, font, tick) : 7173 context.measureText(tick).width; 7174 } 7175 7176 function parseFontOptions(options) { 7177 var valueOrDefault = helpers.valueOrDefault; 7178 var globalDefaults = defaults.global; 7179 var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); 7180 var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle); 7181 var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily); 7182 7183 return { 7184 size: size, 7185 style: style, 7186 family: family, 7187 font: helpers.fontString(size, style, family) 7188 }; 7189 } 7190 7191 function parseLineHeight(options) { 7192 return helpers.options.toLineHeight( 7193 helpers.valueOrDefault(options.lineHeight, 1.2), 7194 helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize)); 7195 } 7196 7197 module.exports = Element.extend({ 7198 /** 7199 * Get the padding needed for the scale 7200 * @method getPadding 7201 * @private 7202 * @returns {Padding} the necessary padding 7203 */ 7204 getPadding: function() { 7205 var me = this; 7206 return { 7207 left: me.paddingLeft || 0, 7208 top: me.paddingTop || 0, 7209 right: me.paddingRight || 0, 7210 bottom: me.paddingBottom || 0 7211 }; 7212 }, 7213 7214 /** 7215 * Returns the scale tick objects ({label, major}) 7216 * @since 2.7 7217 */ 7218 getTicks: function() { 7219 return this._ticks; 7220 }, 7221 7222 // These methods are ordered by lifecyle. Utilities then follow. 7223 // Any function defined here is inherited by all scale types. 7224 // Any function can be extended by the scale type 7225 7226 mergeTicksOptions: function() { 7227 var ticks = this.options.ticks; 7228 if (ticks.minor === false) { 7229 ticks.minor = { 7230 display: false 7231 }; 7232 } 7233 if (ticks.major === false) { 7234 ticks.major = { 7235 display: false 7236 }; 7237 } 7238 for (var key in ticks) { 7239 if (key !== 'major' && key !== 'minor') { 7240 if (typeof ticks.minor[key] === 'undefined') { 7241 ticks.minor[key] = ticks[key]; 7242 } 7243 if (typeof ticks.major[key] === 'undefined') { 7244 ticks.major[key] = ticks[key]; 7245 } 7246 } 7247 } 7248 }, 7249 beforeUpdate: function() { 7250 helpers.callback(this.options.beforeUpdate, [this]); 7251 }, 7252 7253 update: function(maxWidth, maxHeight, margins) { 7254 var me = this; 7255 var i, ilen, labels, label, ticks, tick; 7256 7257 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 7258 me.beforeUpdate(); 7259 7260 // Absorb the master measurements 7261 me.maxWidth = maxWidth; 7262 me.maxHeight = maxHeight; 7263 me.margins = helpers.extend({ 7264 left: 0, 7265 right: 0, 7266 top: 0, 7267 bottom: 0 7268 }, margins); 7269 me.longestTextCache = me.longestTextCache || {}; 7270 7271 // Dimensions 7272 me.beforeSetDimensions(); 7273 me.setDimensions(); 7274 me.afterSetDimensions(); 7275 7276 // Data min/max 7277 me.beforeDataLimits(); 7278 me.determineDataLimits(); 7279 me.afterDataLimits(); 7280 7281 // Ticks - `this.ticks` is now DEPRECATED! 7282 // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member 7283 // and must not be accessed directly from outside this class. `this.ticks` being 7284 // around for long time and not marked as private, we can't change its structure 7285 // without unexpected breaking changes. If you need to access the scale ticks, 7286 // use scale.getTicks() instead. 7287 7288 me.beforeBuildTicks(); 7289 7290 // New implementations should return an array of objects but for BACKWARD COMPAT, 7291 // we still support no return (`this.ticks` internally set by calling this method). 7292 ticks = me.buildTicks() || []; 7293 7294 me.afterBuildTicks(); 7295 7296 me.beforeTickToLabelConversion(); 7297 7298 // New implementations should return the formatted tick labels but for BACKWARD 7299 // COMPAT, we still support no return (`this.ticks` internally changed by calling 7300 // this method and supposed to contain only string values). 7301 labels = me.convertTicksToLabels(ticks) || me.ticks; 7302 7303 me.afterTickToLabelConversion(); 7304 7305 me.ticks = labels; // BACKWARD COMPATIBILITY 7306 7307 // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change! 7308 7309 // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) 7310 for (i = 0, ilen = labels.length; i < ilen; ++i) { 7311 label = labels[i]; 7312 tick = ticks[i]; 7313 if (!tick) { 7314 ticks.push(tick = { 7315 label: label, 7316 major: false 7317 }); 7318 } else { 7319 tick.label = label; 7320 } 7321 } 7322 7323 me._ticks = ticks; 7324 7325 // Tick Rotation 7326 me.beforeCalculateTickRotation(); 7327 me.calculateTickRotation(); 7328 me.afterCalculateTickRotation(); 7329 // Fit 7330 me.beforeFit(); 7331 me.fit(); 7332 me.afterFit(); 7333 // 7334 me.afterUpdate(); 7335 7336 return me.minSize; 7337 7338 }, 7339 afterUpdate: function() { 7340 helpers.callback(this.options.afterUpdate, [this]); 7341 }, 7342 7343 // 7344 7345 beforeSetDimensions: function() { 7346 helpers.callback(this.options.beforeSetDimensions, [this]); 7347 }, 7348 setDimensions: function() { 7349 var me = this; 7350 // Set the unconstrained dimension before label rotation 7351 if (me.isHorizontal()) { 7352 // Reset position before calculating rotation 7353 me.width = me.maxWidth; 7354 me.left = 0; 7355 me.right = me.width; 7356 } else { 7357 me.height = me.maxHeight; 7358 7359 // Reset position before calculating rotation 7360 me.top = 0; 7361 me.bottom = me.height; 7362 } 7363 7364 // Reset padding 7365 me.paddingLeft = 0; 7366 me.paddingTop = 0; 7367 me.paddingRight = 0; 7368 me.paddingBottom = 0; 7369 }, 7370 afterSetDimensions: function() { 7371 helpers.callback(this.options.afterSetDimensions, [this]); 7372 }, 7373 7374 // Data limits 7375 beforeDataLimits: function() { 7376 helpers.callback(this.options.beforeDataLimits, [this]); 7377 }, 7378 determineDataLimits: helpers.noop, 7379 afterDataLimits: function() { 7380 helpers.callback(this.options.afterDataLimits, [this]); 7381 }, 7382 7383 // 7384 beforeBuildTicks: function() { 7385 helpers.callback(this.options.beforeBuildTicks, [this]); 7386 }, 7387 buildTicks: helpers.noop, 7388 afterBuildTicks: function() { 7389 helpers.callback(this.options.afterBuildTicks, [this]); 7390 }, 7391 7392 beforeTickToLabelConversion: function() { 7393 helpers.callback(this.options.beforeTickToLabelConversion, [this]); 7394 }, 7395 convertTicksToLabels: function() { 7396 var me = this; 7397 // Convert ticks to strings 7398 var tickOpts = me.options.ticks; 7399 me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); 7400 }, 7401 afterTickToLabelConversion: function() { 7402 helpers.callback(this.options.afterTickToLabelConversion, [this]); 7403 }, 7404 7405 // 7406 7407 beforeCalculateTickRotation: function() { 7408 helpers.callback(this.options.beforeCalculateTickRotation, [this]); 7409 }, 7410 calculateTickRotation: function() { 7411 var me = this; 7412 var context = me.ctx; 7413 var tickOpts = me.options.ticks; 7414 var labels = labelsFromTicks(me._ticks); 7415 7416 // Get the width of each grid by calculating the difference 7417 // between x offsets between 0 and 1. 7418 var tickFont = parseFontOptions(tickOpts); 7419 context.font = tickFont.font; 7420 7421 var labelRotation = tickOpts.minRotation || 0; 7422 7423 if (labels.length && me.options.display && me.isHorizontal()) { 7424 var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache); 7425 var labelWidth = originalLabelWidth; 7426 var cosRotation, sinRotation; 7427 7428 // Allow 3 pixels x2 padding either side for label readability 7429 var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; 7430 7431 // Max label rotation can be set or default to 90 - also act as a loop counter 7432 while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) { 7433 var angleRadians = helpers.toRadians(labelRotation); 7434 cosRotation = Math.cos(angleRadians); 7435 sinRotation = Math.sin(angleRadians); 7436 7437 if (sinRotation * originalLabelWidth > me.maxHeight) { 7438 // go back one step 7439 labelRotation--; 7440 break; 7441 } 7442 7443 labelRotation++; 7444 labelWidth = cosRotation * originalLabelWidth; 7445 } 7446 } 7447 7448 me.labelRotation = labelRotation; 7449 }, 7450 afterCalculateTickRotation: function() { 7451 helpers.callback(this.options.afterCalculateTickRotation, [this]); 7452 }, 7453 7454 // 7455 7456 beforeFit: function() { 7457 helpers.callback(this.options.beforeFit, [this]); 7458 }, 7459 fit: function() { 7460 var me = this; 7461 // Reset 7462 var minSize = me.minSize = { 7463 width: 0, 7464 height: 0 7465 }; 7466 7467 var labels = labelsFromTicks(me._ticks); 7468 7469 var opts = me.options; 7470 var tickOpts = opts.ticks; 7471 var scaleLabelOpts = opts.scaleLabel; 7472 var gridLineOpts = opts.gridLines; 7473 var display = opts.display; 7474 var isHorizontal = me.isHorizontal(); 7475 7476 var tickFont = parseFontOptions(tickOpts); 7477 var tickMarkLength = opts.gridLines.tickMarkLength; 7478 7479 // Width 7480 if (isHorizontal) { 7481 // subtract the margins to line up with the chartArea if we are a full width scale 7482 minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; 7483 } else { 7484 minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0; 7485 } 7486 7487 // height 7488 if (isHorizontal) { 7489 minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0; 7490 } else { 7491 minSize.height = me.maxHeight; // fill all the height 7492 } 7493 7494 // Are we showing a title for the scale? 7495 if (scaleLabelOpts.display && display) { 7496 var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts); 7497 var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding); 7498 var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height; 7499 7500 if (isHorizontal) { 7501 minSize.height += deltaHeight; 7502 } else { 7503 minSize.width += deltaHeight; 7504 } 7505 } 7506 7507 // Don't bother fitting the ticks if we are not showing them 7508 if (tickOpts.display && display) { 7509 var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache); 7510 var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels); 7511 var lineSpace = tickFont.size * 0.5; 7512 var tickPadding = me.options.ticks.padding; 7513 7514 if (isHorizontal) { 7515 // A horizontal axis is more constrained by the height. 7516 me.longestLabelWidth = largestTextWidth; 7517 7518 var angleRadians = helpers.toRadians(me.labelRotation); 7519 var cosRotation = Math.cos(angleRadians); 7520 var sinRotation = Math.sin(angleRadians); 7521 7522 // TODO - improve this calculation 7523 var labelHeight = (sinRotation * largestTextWidth) 7524 + (tickFont.size * tallestLabelHeightInLines) 7525 + (lineSpace * (tallestLabelHeightInLines - 1)) 7526 + lineSpace; // padding 7527 7528 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); 7529 7530 me.ctx.font = tickFont.font; 7531 var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font); 7532 var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font); 7533 7534 // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned 7535 // which means that the right padding is dominated by the font height 7536 if (me.labelRotation !== 0) { 7537 me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges 7538 me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3; 7539 } else { 7540 me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges 7541 me.paddingRight = lastLabelWidth / 2 + 3; 7542 } 7543 } else { 7544 // A vertical axis is more constrained by the width. Labels are the 7545 // dominant factor here, so get that length first and account for padding 7546 if (tickOpts.mirror) { 7547 largestTextWidth = 0; 7548 } else { 7549 // use lineSpace for consistency with horizontal axis 7550 // tickPadding is not implemented for horizontal 7551 largestTextWidth += tickPadding + lineSpace; 7552 } 7553 7554 minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth); 7555 7556 me.paddingTop = tickFont.size / 2; 7557 me.paddingBottom = tickFont.size / 2; 7558 } 7559 } 7560 7561 me.handleMargins(); 7562 7563 me.width = minSize.width; 7564 me.height = minSize.height; 7565 }, 7566 7567 /** 7568 * Handle margins and padding interactions 7569 * @private 7570 */ 7571 handleMargins: function() { 7572 var me = this; 7573 if (me.margins) { 7574 me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); 7575 me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); 7576 me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); 7577 me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); 7578 } 7579 }, 7580 7581 afterFit: function() { 7582 helpers.callback(this.options.afterFit, [this]); 7583 }, 7584 7585 // Shared Methods 7586 isHorizontal: function() { 7587 return this.options.position === 'top' || this.options.position === 'bottom'; 7588 }, 7589 isFullWidth: function() { 7590 return (this.options.fullWidth); 7591 }, 7592 7593 // 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 7594 getRightValue: function(rawValue) { 7595 // Null and undefined values first 7596 if (helpers.isNullOrUndef(rawValue)) { 7597 return NaN; 7598 } 7599 // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values 7600 if (typeof rawValue === 'number' && !isFinite(rawValue)) { 7601 return NaN; 7602 } 7603 // If it is in fact an object, dive in one more level 7604 if (rawValue) { 7605 if (this.isHorizontal()) { 7606 if (rawValue.x !== undefined) { 7607 return this.getRightValue(rawValue.x); 7608 } 7609 } else if (rawValue.y !== undefined) { 7610 return this.getRightValue(rawValue.y); 7611 } 7612 } 7613 7614 // Value is good, return it 7615 return rawValue; 7616 }, 7617 7618 /** 7619 * Used to get the value to display in the tooltip for the data at the given index 7620 * @param index 7621 * @param datasetIndex 7622 */ 7623 getLabelForIndex: helpers.noop, 7624 7625 /** 7626 * Returns the location of the given data point. Value can either be an index or a numerical value 7627 * The coordinate (0, 0) is at the upper-left corner of the canvas 7628 * @param value 7629 * @param index 7630 * @param datasetIndex 7631 */ 7632 getPixelForValue: helpers.noop, 7633 7634 /** 7635 * Used to get the data value from a given pixel. This is the inverse of getPixelForValue 7636 * The coordinate (0, 0) is at the upper-left corner of the canvas 7637 * @param pixel 7638 */ 7639 getValueForPixel: helpers.noop, 7640 7641 /** 7642 * Returns the location of the tick at the given index 7643 * The coordinate (0, 0) is at the upper-left corner of the canvas 7644 */ 7645 getPixelForTick: function(index) { 7646 var me = this; 7647 var offset = me.options.offset; 7648 if (me.isHorizontal()) { 7649 var innerWidth = me.width - (me.paddingLeft + me.paddingRight); 7650 var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1); 7651 var pixel = (tickWidth * index) + me.paddingLeft; 7652 7653 if (offset) { 7654 pixel += tickWidth / 2; 7655 } 7656 7657 var finalVal = me.left + Math.round(pixel); 7658 finalVal += me.isFullWidth() ? me.margins.left : 0; 7659 return finalVal; 7660 } 7661 var innerHeight = me.height - (me.paddingTop + me.paddingBottom); 7662 return me.top + (index * (innerHeight / (me._ticks.length - 1))); 7663 }, 7664 7665 /** 7666 * Utility for getting the pixel location of a percentage of scale 7667 * The coordinate (0, 0) is at the upper-left corner of the canvas 7668 */ 7669 getPixelForDecimal: function(decimal) { 7670 var me = this; 7671 if (me.isHorizontal()) { 7672 var innerWidth = me.width - (me.paddingLeft + me.paddingRight); 7673 var valueOffset = (innerWidth * decimal) + me.paddingLeft; 7674 7675 var finalVal = me.left + Math.round(valueOffset); 7676 finalVal += me.isFullWidth() ? me.margins.left : 0; 7677 return finalVal; 7678 } 7679 return me.top + (decimal * me.height); 7680 }, 7681 7682 /** 7683 * Returns the pixel for the minimum chart value 7684 * The coordinate (0, 0) is at the upper-left corner of the canvas 7685 */ 7686 getBasePixel: function() { 7687 return this.getPixelForValue(this.getBaseValue()); 7688 }, 7689 7690 getBaseValue: function() { 7691 var me = this; 7692 var min = me.min; 7693 var max = me.max; 7694 7695 return me.beginAtZero ? 0 : 7696 min < 0 && max < 0 ? max : 7697 min > 0 && max > 0 ? min : 7698 0; 7699 }, 7700 7701 /** 7702 * Returns a subset of ticks to be plotted to avoid overlapping labels. 7703 * @private 7704 */ 7705 _autoSkip: function(ticks) { 7706 var skipRatio; 7707 var me = this; 7708 var isHorizontal = me.isHorizontal(); 7709 var optionTicks = me.options.ticks.minor; 7710 var tickCount = ticks.length; 7711 var labelRotationRadians = helpers.toRadians(me.labelRotation); 7712 var cosRotation = Math.cos(labelRotationRadians); 7713 var longestRotatedLabel = me.longestLabelWidth * cosRotation; 7714 var result = []; 7715 var i, tick, shouldSkip; 7716 7717 // figure out the maximum number of gridlines to show 7718 var maxTicks; 7719 if (optionTicks.maxTicksLimit) { 7720 maxTicks = optionTicks.maxTicksLimit; 7721 } 7722 7723 if (isHorizontal) { 7724 skipRatio = false; 7725 7726 if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) { 7727 skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight))); 7728 } 7729 7730 // if they defined a max number of optionTicks, 7731 // increase skipRatio until that number is met 7732 if (maxTicks && tickCount > maxTicks) { 7733 skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks)); 7734 } 7735 } 7736 7737 for (i = 0; i < tickCount; i++) { 7738 tick = ticks[i]; 7739 7740 // Since we always show the last tick,we need may need to hide the last shown one before 7741 shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount); 7742 if (shouldSkip && i !== tickCount - 1) { 7743 // leave tick in place but make sure it's not displayed (#4635) 7744 delete tick.label; 7745 } 7746 result.push(tick); 7747 } 7748 return result; 7749 }, 7750 7751 // Actually draw the scale on the canvas 7752 // @param {rectangle} chartArea : the area of the chart to draw full grid lines on 7753 draw: function(chartArea) { 7754 var me = this; 7755 var options = me.options; 7756 if (!options.display) { 7757 return; 7758 } 7759 7760 var context = me.ctx; 7761 var globalDefaults = defaults.global; 7762 var optionTicks = options.ticks.minor; 7763 var optionMajorTicks = options.ticks.major || optionTicks; 7764 var gridLines = options.gridLines; 7765 var scaleLabel = options.scaleLabel; 7766 7767 var isRotated = me.labelRotation !== 0; 7768 var isHorizontal = me.isHorizontal(); 7769 7770 var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks(); 7771 var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); 7772 var tickFont = parseFontOptions(optionTicks); 7773 var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor); 7774 var majorTickFont = parseFontOptions(optionMajorTicks); 7775 7776 var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; 7777 7778 var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); 7779 var scaleLabelFont = parseFontOptions(scaleLabel); 7780 var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding); 7781 var labelRotationRadians = helpers.toRadians(me.labelRotation); 7782 7783 var itemsToDraw = []; 7784 7785 var axisWidth = me.options.gridLines.lineWidth; 7786 var xTickStart = options.position === 'right' ? me.left : me.right - axisWidth - tl; 7787 var xTickEnd = options.position === 'right' ? me.left + tl : me.right; 7788 var yTickStart = options.position === 'bottom' ? me.top + axisWidth : me.bottom - tl - axisWidth; 7789 var yTickEnd = options.position === 'bottom' ? me.top + axisWidth + tl : me.bottom + axisWidth; 7790 7791 helpers.each(ticks, function(tick, index) { 7792 // autoskipper skipped this tick (#4635) 7793 if (helpers.isNullOrUndef(tick.label)) { 7794 return; 7795 } 7796 7797 var label = tick.label; 7798 var lineWidth, lineColor, borderDash, borderDashOffset; 7799 if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) { 7800 // Draw the first index specially 7801 lineWidth = gridLines.zeroLineWidth; 7802 lineColor = gridLines.zeroLineColor; 7803 borderDash = gridLines.zeroLineBorderDash; 7804 borderDashOffset = gridLines.zeroLineBorderDashOffset; 7805 } else { 7806 lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index); 7807 lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index); 7808 borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash); 7809 borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset); 7810 } 7811 7812 // Common properties 7813 var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY; 7814 var textAlign = 'middle'; 7815 var textBaseline = 'middle'; 7816 var tickPadding = optionTicks.padding; 7817 7818 if (isHorizontal) { 7819 var labelYOffset = tl + tickPadding; 7820 7821 if (options.position === 'bottom') { 7822 // bottom 7823 textBaseline = !isRotated ? 'top' : 'middle'; 7824 textAlign = !isRotated ? 'center' : 'right'; 7825 labelY = me.top + labelYOffset; 7826 } else { 7827 // top 7828 textBaseline = !isRotated ? 'bottom' : 'middle'; 7829 textAlign = !isRotated ? 'center' : 'left'; 7830 labelY = me.bottom - labelYOffset; 7831 } 7832 7833 var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); 7834 if (xLineValue < me.left) { 7835 lineColor = 'rgba(0,0,0,0)'; 7836 } 7837 xLineValue += helpers.aliasPixel(lineWidth); 7838 7839 labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option) 7840 7841 tx1 = tx2 = x1 = x2 = xLineValue; 7842 ty1 = yTickStart; 7843 ty2 = yTickEnd; 7844 y1 = chartArea.top; 7845 y2 = chartArea.bottom + axisWidth; 7846 } else { 7847 var isLeft = options.position === 'left'; 7848 var labelXOffset; 7849 7850 if (optionTicks.mirror) { 7851 textAlign = isLeft ? 'left' : 'right'; 7852 labelXOffset = tickPadding; 7853 } else { 7854 textAlign = isLeft ? 'right' : 'left'; 7855 labelXOffset = tl + tickPadding; 7856 } 7857 7858 labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset; 7859 7860 var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); 7861 if (yLineValue < me.top) { 7862 lineColor = 'rgba(0,0,0,0)'; 7863 } 7864 yLineValue += helpers.aliasPixel(lineWidth); 7865 7866 labelY = me.getPixelForTick(index) + optionTicks.labelOffset; 7867 7868 tx1 = xTickStart; 7869 tx2 = xTickEnd; 7870 x1 = chartArea.left; 7871 x2 = chartArea.right + axisWidth; 7872 ty1 = ty2 = y1 = y2 = yLineValue; 7873 } 7874 7875 itemsToDraw.push({ 7876 tx1: tx1, 7877 ty1: ty1, 7878 tx2: tx2, 7879 ty2: ty2, 7880 x1: x1, 7881 y1: y1, 7882 x2: x2, 7883 y2: y2, 7884 labelX: labelX, 7885 labelY: labelY, 7886 glWidth: lineWidth, 7887 glColor: lineColor, 7888 glBorderDash: borderDash, 7889 glBorderDashOffset: borderDashOffset, 7890 rotation: -1 * labelRotationRadians, 7891 label: label, 7892 major: tick.major, 7893 textBaseline: textBaseline, 7894 textAlign: textAlign 7895 }); 7896 }); 7897 7898 // Draw all of the tick labels, tick marks, and grid lines at the correct places 7899 helpers.each(itemsToDraw, function(itemToDraw) { 7900 if (gridLines.display) { 7901 context.save(); 7902 context.lineWidth = itemToDraw.glWidth; 7903 context.strokeStyle = itemToDraw.glColor; 7904 if (context.setLineDash) { 7905 context.setLineDash(itemToDraw.glBorderDash); 7906 context.lineDashOffset = itemToDraw.glBorderDashOffset; 7907 } 7908 7909 context.beginPath(); 7910 7911 if (gridLines.drawTicks) { 7912 context.moveTo(itemToDraw.tx1, itemToDraw.ty1); 7913 context.lineTo(itemToDraw.tx2, itemToDraw.ty2); 7914 } 7915 7916 if (gridLines.drawOnChartArea) { 7917 context.moveTo(itemToDraw.x1, itemToDraw.y1); 7918 context.lineTo(itemToDraw.x2, itemToDraw.y2); 7919 } 7920 7921 context.stroke(); 7922 context.restore(); 7923 } 7924 7925 if (optionTicks.display) { 7926 // Make sure we draw text in the correct color and font 7927 context.save(); 7928 context.translate(itemToDraw.labelX, itemToDraw.labelY); 7929 context.rotate(itemToDraw.rotation); 7930 context.font = itemToDraw.major ? majorTickFont.font : tickFont.font; 7931 context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor; 7932 context.textBaseline = itemToDraw.textBaseline; 7933 context.textAlign = itemToDraw.textAlign; 7934 7935 var label = itemToDraw.label; 7936 if (helpers.isArray(label)) { 7937 var lineCount = label.length; 7938 var lineHeight = tickFont.size * 1.5; 7939 var y = me.isHorizontal() ? 0 : -lineHeight * (lineCount - 1) / 2; 7940 7941 for (var i = 0; i < lineCount; ++i) { 7942 // We just make sure the multiline element is a string here.. 7943 context.fillText('' + label[i], 0, y); 7944 // apply same lineSpacing as calculated @ L#320 7945 y += lineHeight; 7946 } 7947 } else { 7948 context.fillText(label, 0, 0); 7949 } 7950 context.restore(); 7951 } 7952 }); 7953 7954 if (scaleLabel.display) { 7955 // Draw the scale label 7956 var scaleLabelX; 7957 var scaleLabelY; 7958 var rotation = 0; 7959 var halfLineHeight = parseLineHeight(scaleLabel) / 2; 7960 7961 if (isHorizontal) { 7962 scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width 7963 scaleLabelY = options.position === 'bottom' 7964 ? me.bottom - halfLineHeight - scaleLabelPadding.bottom 7965 : me.top + halfLineHeight + scaleLabelPadding.top; 7966 } else { 7967 var isLeft = options.position === 'left'; 7968 scaleLabelX = isLeft 7969 ? me.left + halfLineHeight + scaleLabelPadding.top 7970 : me.right - halfLineHeight - scaleLabelPadding.top; 7971 scaleLabelY = me.top + ((me.bottom - me.top) / 2); 7972 rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; 7973 } 7974 7975 context.save(); 7976 context.translate(scaleLabelX, scaleLabelY); 7977 context.rotate(rotation); 7978 context.textAlign = 'center'; 7979 context.textBaseline = 'middle'; 7980 context.fillStyle = scaleLabelFontColor; // render in correct colour 7981 context.font = scaleLabelFont.font; 7982 context.fillText(scaleLabel.labelString, 0, 0); 7983 context.restore(); 7984 } 7985 7986 if (gridLines.drawBorder) { 7987 // Draw the line at the edge of the axis 7988 context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0); 7989 context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0); 7990 var x1 = me.left; 7991 var x2 = me.right + axisWidth; 7992 var y1 = me.top; 7993 var y2 = me.bottom + axisWidth; 7994 7995 var aliasPixel = helpers.aliasPixel(context.lineWidth); 7996 if (isHorizontal) { 7997 y1 = y2 = options.position === 'top' ? me.bottom : me.top; 7998 y1 += aliasPixel; 7999 y2 += aliasPixel; 8000 } else { 8001 x1 = x2 = options.position === 'left' ? me.right : me.left; 8002 x1 += aliasPixel; 8003 x2 += aliasPixel; 8004 } 8005 8006 context.beginPath(); 8007 context.moveTo(x1, y1); 8008 context.lineTo(x2, y2); 8009 context.stroke(); 8010 } 8011 } 8012 }); 8013 8014 },{"26":26,"27":27,"35":35,"46":46}],34:[function(require,module,exports){ 8015 'use strict'; 8016 8017 var defaults = require(26); 8018 var helpers = require(46); 8019 var layouts = require(31); 8020 8021 module.exports = { 8022 // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then 8023 // use the new chart options to grab the correct scale 8024 constructors: {}, 8025 // Use a registration function so that we can move to an ES6 map when we no longer need to support 8026 // old browsers 8027 8028 // Scale config defaults 8029 defaults: {}, 8030 registerScaleType: function(type, scaleConstructor, scaleDefaults) { 8031 this.constructors[type] = scaleConstructor; 8032 this.defaults[type] = helpers.clone(scaleDefaults); 8033 }, 8034 getScaleConstructor: function(type) { 8035 return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; 8036 }, 8037 getScaleDefaults: function(type) { 8038 // Return the scale defaults merged with the global settings so that we always use the latest ones 8039 return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {}; 8040 }, 8041 updateScaleDefaults: function(type, additions) { 8042 var me = this; 8043 if (me.defaults.hasOwnProperty(type)) { 8044 me.defaults[type] = helpers.extend(me.defaults[type], additions); 8045 } 8046 }, 8047 addScalesToLayout: function(chart) { 8048 // Adds each scale to the chart.boxes array to be sized accordingly 8049 helpers.each(chart.scales, function(scale) { 8050 // Set ILayoutItem parameters for backwards compatibility 8051 scale.fullWidth = scale.options.fullWidth; 8052 scale.position = scale.options.position; 8053 scale.weight = scale.options.weight; 8054 layouts.addBox(chart, scale); 8055 }); 8056 } 8057 }; 8058 8059 },{"26":26,"31":31,"46":46}],35:[function(require,module,exports){ 8060 'use strict'; 8061 8062 var helpers = require(46); 8063 8064 /** 8065 * Namespace to hold static tick generation functions 8066 * @namespace Chart.Ticks 8067 */ 8068 module.exports = { 8069 /** 8070 * Namespace to hold formatters for different types of ticks 8071 * @namespace Chart.Ticks.formatters 8072 */ 8073 formatters: { 8074 /** 8075 * Formatter for value labels 8076 * @method Chart.Ticks.formatters.values 8077 * @param value the value to display 8078 * @return {String|Array} the label to display 8079 */ 8080 values: function(value) { 8081 return helpers.isArray(value) ? value : '' + value; 8082 }, 8083 8084 /** 8085 * Formatter for linear numeric ticks 8086 * @method Chart.Ticks.formatters.linear 8087 * @param tickValue {Number} the value to be formatted 8088 * @param index {Number} the position of the tickValue parameter in the ticks array 8089 * @param ticks {Array<Number>} the list of ticks being converted 8090 * @return {String} string representation of the tickValue parameter 8091 */ 8092 linear: function(tickValue, index, ticks) { 8093 // If we have lots of ticks, don't use the ones 8094 var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; 8095 8096 // If we have a number like 2.5 as the delta, figure out how many decimal places we need 8097 if (Math.abs(delta) > 1) { 8098 if (tickValue !== Math.floor(tickValue)) { 8099 // not an integer 8100 delta = tickValue - Math.floor(tickValue); 8101 } 8102 } 8103 8104 var logDelta = helpers.log10(Math.abs(delta)); 8105 var tickString = ''; 8106 8107 if (tickValue !== 0) { 8108 var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1])); 8109 if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation 8110 var logTick = helpers.log10(Math.abs(tickValue)); 8111 tickString = tickValue.toExponential(Math.floor(logTick) - Math.floor(logDelta)); 8112 } else { 8113 var numDecimal = -1 * Math.floor(logDelta); 8114 numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places 8115 tickString = tickValue.toFixed(numDecimal); 8116 } 8117 } else { 8118 tickString = '0'; // never show decimal places for 0 8119 } 8120 8121 return tickString; 8122 }, 8123 8124 logarithmic: function(tickValue, index, ticks) { 8125 var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue)))); 8126 8127 if (tickValue === 0) { 8128 return '0'; 8129 } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { 8130 return tickValue.toExponential(); 8131 } 8132 return ''; 8133 } 8134 } 8135 }; 8136 8137 },{"46":46}],36:[function(require,module,exports){ 8138 'use strict'; 8139 8140 var defaults = require(26); 8141 var Element = require(27); 8142 var helpers = require(46); 8143 8144 defaults._set('global', { 8145 tooltips: { 8146 enabled: true, 8147 custom: null, 8148 mode: 'nearest', 8149 position: 'average', 8150 intersect: true, 8151 backgroundColor: 'rgba(0,0,0,0.8)', 8152 titleFontStyle: 'bold', 8153 titleSpacing: 2, 8154 titleMarginBottom: 6, 8155 titleFontColor: '#fff', 8156 titleAlign: 'left', 8157 bodySpacing: 2, 8158 bodyFontColor: '#fff', 8159 bodyAlign: 'left', 8160 footerFontStyle: 'bold', 8161 footerSpacing: 2, 8162 footerMarginTop: 6, 8163 footerFontColor: '#fff', 8164 footerAlign: 'left', 8165 yPadding: 6, 8166 xPadding: 6, 8167 caretPadding: 2, 8168 caretSize: 5, 8169 cornerRadius: 6, 8170 multiKeyBackground: '#fff', 8171 displayColors: true, 8172 borderColor: 'rgba(0,0,0,0)', 8173 borderWidth: 0, 8174 callbacks: { 8175 // Args are: (tooltipItems, data) 8176 beforeTitle: helpers.noop, 8177 title: function(tooltipItems, data) { 8178 // Pick first xLabel for now 8179 var title = ''; 8180 var labels = data.labels; 8181 var labelCount = labels ? labels.length : 0; 8182 8183 if (tooltipItems.length > 0) { 8184 var item = tooltipItems[0]; 8185 8186 if (item.xLabel) { 8187 title = item.xLabel; 8188 } else if (labelCount > 0 && item.index < labelCount) { 8189 title = labels[item.index]; 8190 } 8191 } 8192 8193 return title; 8194 }, 8195 afterTitle: helpers.noop, 8196 8197 // Args are: (tooltipItems, data) 8198 beforeBody: helpers.noop, 8199 8200 // Args are: (tooltipItem, data) 8201 beforeLabel: helpers.noop, 8202 label: function(tooltipItem, data) { 8203 var label = data.datasets[tooltipItem.datasetIndex].label || ''; 8204 8205 if (label) { 8206 label += ': '; 8207 } 8208 label += tooltipItem.yLabel; 8209 return label; 8210 }, 8211 labelColor: function(tooltipItem, chart) { 8212 var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); 8213 var activeElement = meta.data[tooltipItem.index]; 8214 var view = activeElement._view; 8215 return { 8216 borderColor: view.borderColor, 8217 backgroundColor: view.backgroundColor 8218 }; 8219 }, 8220 labelTextColor: function() { 8221 return this._options.bodyFontColor; 8222 }, 8223 afterLabel: helpers.noop, 8224 8225 // Args are: (tooltipItems, data) 8226 afterBody: helpers.noop, 8227 8228 // Args are: (tooltipItems, data) 8229 beforeFooter: helpers.noop, 8230 footer: helpers.noop, 8231 afterFooter: helpers.noop 8232 } 8233 } 8234 }); 8235 8236 var positioners = { 8237 /** 8238 * Average mode places the tooltip at the average position of the elements shown 8239 * @function Chart.Tooltip.positioners.average 8240 * @param elements {ChartElement[]} the elements being displayed in the tooltip 8241 * @returns {Point} tooltip position 8242 */ 8243 average: function(elements) { 8244 if (!elements.length) { 8245 return false; 8246 } 8247 8248 var i, len; 8249 var x = 0; 8250 var y = 0; 8251 var count = 0; 8252 8253 for (i = 0, len = elements.length; i < len; ++i) { 8254 var el = elements[i]; 8255 if (el && el.hasValue()) { 8256 var pos = el.tooltipPosition(); 8257 x += pos.x; 8258 y += pos.y; 8259 ++count; 8260 } 8261 } 8262 8263 return { 8264 x: Math.round(x / count), 8265 y: Math.round(y / count) 8266 }; 8267 }, 8268 8269 /** 8270 * Gets the tooltip position nearest of the item nearest to the event position 8271 * @function Chart.Tooltip.positioners.nearest 8272 * @param elements {Chart.Element[]} the tooltip elements 8273 * @param eventPosition {Point} the position of the event in canvas coordinates 8274 * @returns {Point} the tooltip position 8275 */ 8276 nearest: function(elements, eventPosition) { 8277 var x = eventPosition.x; 8278 var y = eventPosition.y; 8279 var minDistance = Number.POSITIVE_INFINITY; 8280 var i, len, nearestElement; 8281 8282 for (i = 0, len = elements.length; i < len; ++i) { 8283 var el = elements[i]; 8284 if (el && el.hasValue()) { 8285 var center = el.getCenterPoint(); 8286 var d = helpers.distanceBetweenPoints(eventPosition, center); 8287 8288 if (d < minDistance) { 8289 minDistance = d; 8290 nearestElement = el; 8291 } 8292 } 8293 } 8294 8295 if (nearestElement) { 8296 var tp = nearestElement.tooltipPosition(); 8297 x = tp.x; 8298 y = tp.y; 8299 } 8300 8301 return { 8302 x: x, 8303 y: y 8304 }; 8305 } 8306 }; 8307 8308 /** 8309 * Helper method to merge the opacity into a color 8310 */ 8311 function mergeOpacity(colorString, opacity) { 8312 var color = helpers.color(colorString); 8313 return color.alpha(opacity * color.alpha()).rgbaString(); 8314 } 8315 8316 // Helper to push or concat based on if the 2nd parameter is an array or not 8317 function pushOrConcat(base, toPush) { 8318 if (toPush) { 8319 if (helpers.isArray(toPush)) { 8320 // base = base.concat(toPush); 8321 Array.prototype.push.apply(base, toPush); 8322 } else { 8323 base.push(toPush); 8324 } 8325 } 8326 8327 return base; 8328 } 8329 8330 /** 8331 * Returns array of strings split by newline 8332 * @param {String} value - The value to split by newline. 8333 * @returns {Array} value if newline present - Returned from String split() method 8334 * @function 8335 */ 8336 function splitNewlines(str) { 8337 if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { 8338 return str.split('\n'); 8339 } 8340 return str; 8341 } 8342 8343 8344 // Private helper to create a tooltip item model 8345 // @param element : the chart element (point, arc, bar) to create the tooltip item for 8346 // @return : new tooltip item 8347 function createTooltipItem(element) { 8348 var xScale = element._xScale; 8349 var yScale = element._yScale || element._scale; // handle radar || polarArea charts 8350 var index = element._index; 8351 var datasetIndex = element._datasetIndex; 8352 8353 return { 8354 xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', 8355 yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', 8356 index: index, 8357 datasetIndex: datasetIndex, 8358 x: element._model.x, 8359 y: element._model.y 8360 }; 8361 } 8362 8363 /** 8364 * Helper to get the reset model for the tooltip 8365 * @param tooltipOpts {Object} the tooltip options 8366 */ 8367 function getBaseModel(tooltipOpts) { 8368 var globalDefaults = defaults.global; 8369 var valueOrDefault = helpers.valueOrDefault; 8370 8371 return { 8372 // Positioning 8373 xPadding: tooltipOpts.xPadding, 8374 yPadding: tooltipOpts.yPadding, 8375 xAlign: tooltipOpts.xAlign, 8376 yAlign: tooltipOpts.yAlign, 8377 8378 // Body 8379 bodyFontColor: tooltipOpts.bodyFontColor, 8380 _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), 8381 _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), 8382 _bodyAlign: tooltipOpts.bodyAlign, 8383 bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), 8384 bodySpacing: tooltipOpts.bodySpacing, 8385 8386 // Title 8387 titleFontColor: tooltipOpts.titleFontColor, 8388 _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), 8389 _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), 8390 titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), 8391 _titleAlign: tooltipOpts.titleAlign, 8392 titleSpacing: tooltipOpts.titleSpacing, 8393 titleMarginBottom: tooltipOpts.titleMarginBottom, 8394 8395 // Footer 8396 footerFontColor: tooltipOpts.footerFontColor, 8397 _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), 8398 _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), 8399 footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), 8400 _footerAlign: tooltipOpts.footerAlign, 8401 footerSpacing: tooltipOpts.footerSpacing, 8402 footerMarginTop: tooltipOpts.footerMarginTop, 8403 8404 // Appearance 8405 caretSize: tooltipOpts.caretSize, 8406 cornerRadius: tooltipOpts.cornerRadius, 8407 backgroundColor: tooltipOpts.backgroundColor, 8408 opacity: 0, 8409 legendColorBackground: tooltipOpts.multiKeyBackground, 8410 displayColors: tooltipOpts.displayColors, 8411 borderColor: tooltipOpts.borderColor, 8412 borderWidth: tooltipOpts.borderWidth 8413 }; 8414 } 8415 8416 /** 8417 * Get the size of the tooltip 8418 */ 8419 function getTooltipSize(tooltip, model) { 8420 var ctx = tooltip._chart.ctx; 8421 8422 var height = model.yPadding * 2; // Tooltip Padding 8423 var width = 0; 8424 8425 // Count of all lines in the body 8426 var body = model.body; 8427 var combinedBodyLength = body.reduce(function(count, bodyItem) { 8428 return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; 8429 }, 0); 8430 combinedBodyLength += model.beforeBody.length + model.afterBody.length; 8431 8432 var titleLineCount = model.title.length; 8433 var footerLineCount = model.footer.length; 8434 var titleFontSize = model.titleFontSize; 8435 var bodyFontSize = model.bodyFontSize; 8436 var footerFontSize = model.footerFontSize; 8437 8438 height += titleLineCount * titleFontSize; // Title Lines 8439 height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing 8440 height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin 8441 height += combinedBodyLength * bodyFontSize; // Body Lines 8442 height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing 8443 height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin 8444 height += footerLineCount * (footerFontSize); // Footer Lines 8445 height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing 8446 8447 // Title width 8448 var widthPadding = 0; 8449 var maxLineWidth = function(line) { 8450 width = Math.max(width, ctx.measureText(line).width + widthPadding); 8451 }; 8452 8453 ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); 8454 helpers.each(model.title, maxLineWidth); 8455 8456 // Body width 8457 ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); 8458 helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth); 8459 8460 // Body lines may include some extra width due to the color box 8461 widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; 8462 helpers.each(body, function(bodyItem) { 8463 helpers.each(bodyItem.before, maxLineWidth); 8464 helpers.each(bodyItem.lines, maxLineWidth); 8465 helpers.each(bodyItem.after, maxLineWidth); 8466 }); 8467 8468 // Reset back to 0 8469 widthPadding = 0; 8470 8471 // Footer width 8472 ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); 8473 helpers.each(model.footer, maxLineWidth); 8474 8475 // Add padding 8476 width += 2 * model.xPadding; 8477 8478 return { 8479 width: width, 8480 height: height 8481 }; 8482 } 8483 8484 /** 8485 * Helper to get the alignment of a tooltip given the size 8486 */ 8487 function determineAlignment(tooltip, size) { 8488 var model = tooltip._model; 8489 var chart = tooltip._chart; 8490 var chartArea = tooltip._chart.chartArea; 8491 var xAlign = 'center'; 8492 var yAlign = 'center'; 8493 8494 if (model.y < size.height) { 8495 yAlign = 'top'; 8496 } else if (model.y > (chart.height - size.height)) { 8497 yAlign = 'bottom'; 8498 } 8499 8500 var lf, rf; // functions to determine left, right alignment 8501 var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart 8502 var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges 8503 var midX = (chartArea.left + chartArea.right) / 2; 8504 var midY = (chartArea.top + chartArea.bottom) / 2; 8505 8506 if (yAlign === 'center') { 8507 lf = function(x) { 8508 return x <= midX; 8509 }; 8510 rf = function(x) { 8511 return x > midX; 8512 }; 8513 } else { 8514 lf = function(x) { 8515 return x <= (size.width / 2); 8516 }; 8517 rf = function(x) { 8518 return x >= (chart.width - (size.width / 2)); 8519 }; 8520 } 8521 8522 olf = function(x) { 8523 return x + size.width + model.caretSize + model.caretPadding > chart.width; 8524 }; 8525 orf = function(x) { 8526 return x - size.width - model.caretSize - model.caretPadding < 0; 8527 }; 8528 yf = function(y) { 8529 return y <= midY ? 'top' : 'bottom'; 8530 }; 8531 8532 if (lf(model.x)) { 8533 xAlign = 'left'; 8534 8535 // Is tooltip too wide and goes over the right side of the chart.? 8536 if (olf(model.x)) { 8537 xAlign = 'center'; 8538 yAlign = yf(model.y); 8539 } 8540 } else if (rf(model.x)) { 8541 xAlign = 'right'; 8542 8543 // Is tooltip too wide and goes outside left edge of canvas? 8544 if (orf(model.x)) { 8545 xAlign = 'center'; 8546 yAlign = yf(model.y); 8547 } 8548 } 8549 8550 var opts = tooltip._options; 8551 return { 8552 xAlign: opts.xAlign ? opts.xAlign : xAlign, 8553 yAlign: opts.yAlign ? opts.yAlign : yAlign 8554 }; 8555 } 8556 8557 /** 8558 * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment 8559 */ 8560 function getBackgroundPoint(vm, size, alignment, chart) { 8561 // Background Position 8562 var x = vm.x; 8563 var y = vm.y; 8564 8565 var caretSize = vm.caretSize; 8566 var caretPadding = vm.caretPadding; 8567 var cornerRadius = vm.cornerRadius; 8568 var xAlign = alignment.xAlign; 8569 var yAlign = alignment.yAlign; 8570 var paddingAndSize = caretSize + caretPadding; 8571 var radiusAndPadding = cornerRadius + caretPadding; 8572 8573 if (xAlign === 'right') { 8574 x -= size.width; 8575 } else if (xAlign === 'center') { 8576 x -= (size.width / 2); 8577 if (x + size.width > chart.width) { 8578 x = chart.width - size.width; 8579 } 8580 if (x < 0) { 8581 x = 0; 8582 } 8583 } 8584 8585 if (yAlign === 'top') { 8586 y += paddingAndSize; 8587 } else if (yAlign === 'bottom') { 8588 y -= size.height + paddingAndSize; 8589 } else { 8590 y -= (size.height / 2); 8591 } 8592 8593 if (yAlign === 'center') { 8594 if (xAlign === 'left') { 8595 x += paddingAndSize; 8596 } else if (xAlign === 'right') { 8597 x -= paddingAndSize; 8598 } 8599 } else if (xAlign === 'left') { 8600 x -= radiusAndPadding; 8601 } else if (xAlign === 'right') { 8602 x += radiusAndPadding; 8603 } 8604 8605 return { 8606 x: x, 8607 y: y 8608 }; 8609 } 8610 8611 /** 8612 * Helper to build before and after body lines 8613 */ 8614 function getBeforeAfterBodyLines(callback) { 8615 return pushOrConcat([], splitNewlines(callback)); 8616 } 8617 8618 var exports = module.exports = Element.extend({ 8619 initialize: function() { 8620 this._model = getBaseModel(this._options); 8621 this._lastActive = []; 8622 }, 8623 8624 // Get the title 8625 // Args are: (tooltipItem, data) 8626 getTitle: function() { 8627 var me = this; 8628 var opts = me._options; 8629 var callbacks = opts.callbacks; 8630 8631 var beforeTitle = callbacks.beforeTitle.apply(me, arguments); 8632 var title = callbacks.title.apply(me, arguments); 8633 var afterTitle = callbacks.afterTitle.apply(me, arguments); 8634 8635 var lines = []; 8636 lines = pushOrConcat(lines, splitNewlines(beforeTitle)); 8637 lines = pushOrConcat(lines, splitNewlines(title)); 8638 lines = pushOrConcat(lines, splitNewlines(afterTitle)); 8639 8640 return lines; 8641 }, 8642 8643 // Args are: (tooltipItem, data) 8644 getBeforeBody: function() { 8645 return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments)); 8646 }, 8647 8648 // Args are: (tooltipItem, data) 8649 getBody: function(tooltipItems, data) { 8650 var me = this; 8651 var callbacks = me._options.callbacks; 8652 var bodyItems = []; 8653 8654 helpers.each(tooltipItems, function(tooltipItem) { 8655 var bodyItem = { 8656 before: [], 8657 lines: [], 8658 after: [] 8659 }; 8660 pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data))); 8661 pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); 8662 pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data))); 8663 8664 bodyItems.push(bodyItem); 8665 }); 8666 8667 return bodyItems; 8668 }, 8669 8670 // Args are: (tooltipItem, data) 8671 getAfterBody: function() { 8672 return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments)); 8673 }, 8674 8675 // Get the footer and beforeFooter and afterFooter lines 8676 // Args are: (tooltipItem, data) 8677 getFooter: function() { 8678 var me = this; 8679 var callbacks = me._options.callbacks; 8680 8681 var beforeFooter = callbacks.beforeFooter.apply(me, arguments); 8682 var footer = callbacks.footer.apply(me, arguments); 8683 var afterFooter = callbacks.afterFooter.apply(me, arguments); 8684 8685 var lines = []; 8686 lines = pushOrConcat(lines, splitNewlines(beforeFooter)); 8687 lines = pushOrConcat(lines, splitNewlines(footer)); 8688 lines = pushOrConcat(lines, splitNewlines(afterFooter)); 8689 8690 return lines; 8691 }, 8692 8693 update: function(changed) { 8694 var me = this; 8695 var opts = me._options; 8696 8697 // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition 8698 // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time 8699 // which breaks any animations. 8700 var existingModel = me._model; 8701 var model = me._model = getBaseModel(opts); 8702 var active = me._active; 8703 8704 var data = me._data; 8705 8706 // In the case where active.length === 0 we need to keep these at existing values for good animations 8707 var alignment = { 8708 xAlign: existingModel.xAlign, 8709 yAlign: existingModel.yAlign 8710 }; 8711 var backgroundPoint = { 8712 x: existingModel.x, 8713 y: existingModel.y 8714 }; 8715 var tooltipSize = { 8716 width: existingModel.width, 8717 height: existingModel.height 8718 }; 8719 var tooltipPosition = { 8720 x: existingModel.caretX, 8721 y: existingModel.caretY 8722 }; 8723 8724 var i, len; 8725 8726 if (active.length) { 8727 model.opacity = 1; 8728 8729 var labelColors = []; 8730 var labelTextColors = []; 8731 tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition); 8732 8733 var tooltipItems = []; 8734 for (i = 0, len = active.length; i < len; ++i) { 8735 tooltipItems.push(createTooltipItem(active[i])); 8736 } 8737 8738 // If the user provided a filter function, use it to modify the tooltip items 8739 if (opts.filter) { 8740 tooltipItems = tooltipItems.filter(function(a) { 8741 return opts.filter(a, data); 8742 }); 8743 } 8744 8745 // If the user provided a sorting function, use it to modify the tooltip items 8746 if (opts.itemSort) { 8747 tooltipItems = tooltipItems.sort(function(a, b) { 8748 return opts.itemSort(a, b, data); 8749 }); 8750 } 8751 8752 // Determine colors for boxes 8753 helpers.each(tooltipItems, function(tooltipItem) { 8754 labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); 8755 labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); 8756 }); 8757 8758 8759 // Build the Text Lines 8760 model.title = me.getTitle(tooltipItems, data); 8761 model.beforeBody = me.getBeforeBody(tooltipItems, data); 8762 model.body = me.getBody(tooltipItems, data); 8763 model.afterBody = me.getAfterBody(tooltipItems, data); 8764 model.footer = me.getFooter(tooltipItems, data); 8765 8766 // Initial positioning and colors 8767 model.x = Math.round(tooltipPosition.x); 8768 model.y = Math.round(tooltipPosition.y); 8769 model.caretPadding = opts.caretPadding; 8770 model.labelColors = labelColors; 8771 model.labelTextColors = labelTextColors; 8772 8773 // data points 8774 model.dataPoints = tooltipItems; 8775 8776 // We need to determine alignment of the tooltip 8777 tooltipSize = getTooltipSize(this, model); 8778 alignment = determineAlignment(this, tooltipSize); 8779 // Final Size and Position 8780 backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart); 8781 } else { 8782 model.opacity = 0; 8783 } 8784 8785 model.xAlign = alignment.xAlign; 8786 model.yAlign = alignment.yAlign; 8787 model.x = backgroundPoint.x; 8788 model.y = backgroundPoint.y; 8789 model.width = tooltipSize.width; 8790 model.height = tooltipSize.height; 8791 8792 // Point where the caret on the tooltip points to 8793 model.caretX = tooltipPosition.x; 8794 model.caretY = tooltipPosition.y; 8795 8796 me._model = model; 8797 8798 if (changed && opts.custom) { 8799 opts.custom.call(me, model); 8800 } 8801 8802 return me; 8803 }, 8804 8805 drawCaret: function(tooltipPoint, size) { 8806 var ctx = this._chart.ctx; 8807 var vm = this._view; 8808 var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); 8809 8810 ctx.lineTo(caretPosition.x1, caretPosition.y1); 8811 ctx.lineTo(caretPosition.x2, caretPosition.y2); 8812 ctx.lineTo(caretPosition.x3, caretPosition.y3); 8813 }, 8814 getCaretPosition: function(tooltipPoint, size, vm) { 8815 var x1, x2, x3, y1, y2, y3; 8816 var caretSize = vm.caretSize; 8817 var cornerRadius = vm.cornerRadius; 8818 var xAlign = vm.xAlign; 8819 var yAlign = vm.yAlign; 8820 var ptX = tooltipPoint.x; 8821 var ptY = tooltipPoint.y; 8822 var width = size.width; 8823 var height = size.height; 8824 8825 if (yAlign === 'center') { 8826 y2 = ptY + (height / 2); 8827 8828 if (xAlign === 'left') { 8829 x1 = ptX; 8830 x2 = x1 - caretSize; 8831 x3 = x1; 8832 8833 y1 = y2 + caretSize; 8834 y3 = y2 - caretSize; 8835 } else { 8836 x1 = ptX + width; 8837 x2 = x1 + caretSize; 8838 x3 = x1; 8839 8840 y1 = y2 - caretSize; 8841 y3 = y2 + caretSize; 8842 } 8843 } else { 8844 if (xAlign === 'left') { 8845 x2 = ptX + cornerRadius + (caretSize); 8846 x1 = x2 - caretSize; 8847 x3 = x2 + caretSize; 8848 } else if (xAlign === 'right') { 8849 x2 = ptX + width - cornerRadius - caretSize; 8850 x1 = x2 - caretSize; 8851 x3 = x2 + caretSize; 8852 } else { 8853 x2 = vm.caretX; 8854 x1 = x2 - caretSize; 8855 x3 = x2 + caretSize; 8856 } 8857 if (yAlign === 'top') { 8858 y1 = ptY; 8859 y2 = y1 - caretSize; 8860 y3 = y1; 8861 } else { 8862 y1 = ptY + height; 8863 y2 = y1 + caretSize; 8864 y3 = y1; 8865 // invert drawing order 8866 var tmp = x3; 8867 x3 = x1; 8868 x1 = tmp; 8869 } 8870 } 8871 return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; 8872 }, 8873 8874 drawTitle: function(pt, vm, ctx, opacity) { 8875 var title = vm.title; 8876 8877 if (title.length) { 8878 ctx.textAlign = vm._titleAlign; 8879 ctx.textBaseline = 'top'; 8880 8881 var titleFontSize = vm.titleFontSize; 8882 var titleSpacing = vm.titleSpacing; 8883 8884 ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity); 8885 ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); 8886 8887 var i, len; 8888 for (i = 0, len = title.length; i < len; ++i) { 8889 ctx.fillText(title[i], pt.x, pt.y); 8890 pt.y += titleFontSize + titleSpacing; // Line Height and spacing 8891 8892 if (i + 1 === title.length) { 8893 pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing 8894 } 8895 } 8896 } 8897 }, 8898 8899 drawBody: function(pt, vm, ctx, opacity) { 8900 var bodyFontSize = vm.bodyFontSize; 8901 var bodySpacing = vm.bodySpacing; 8902 var body = vm.body; 8903 8904 ctx.textAlign = vm._bodyAlign; 8905 ctx.textBaseline = 'top'; 8906 ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); 8907 8908 // Before Body 8909 var xLinePadding = 0; 8910 var fillLineOfText = function(line) { 8911 ctx.fillText(line, pt.x + xLinePadding, pt.y); 8912 pt.y += bodyFontSize + bodySpacing; 8913 }; 8914 8915 // Before body lines 8916 ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity); 8917 helpers.each(vm.beforeBody, fillLineOfText); 8918 8919 var drawColorBoxes = vm.displayColors; 8920 xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0; 8921 8922 // Draw body lines now 8923 helpers.each(body, function(bodyItem, i) { 8924 var textColor = mergeOpacity(vm.labelTextColors[i], opacity); 8925 ctx.fillStyle = textColor; 8926 helpers.each(bodyItem.before, fillLineOfText); 8927 8928 helpers.each(bodyItem.lines, function(line) { 8929 // Draw Legend-like boxes if needed 8930 if (drawColorBoxes) { 8931 // Fill a white rect so that colours merge nicely if the opacity is < 1 8932 ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity); 8933 ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); 8934 8935 // Border 8936 ctx.lineWidth = 1; 8937 ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity); 8938 ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); 8939 8940 // Inner square 8941 ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity); 8942 ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); 8943 ctx.fillStyle = textColor; 8944 } 8945 8946 fillLineOfText(line); 8947 }); 8948 8949 helpers.each(bodyItem.after, fillLineOfText); 8950 }); 8951 8952 // Reset back to 0 for after body 8953 xLinePadding = 0; 8954 8955 // After body lines 8956 helpers.each(vm.afterBody, fillLineOfText); 8957 pt.y -= bodySpacing; // Remove last body spacing 8958 }, 8959 8960 drawFooter: function(pt, vm, ctx, opacity) { 8961 var footer = vm.footer; 8962 8963 if (footer.length) { 8964 pt.y += vm.footerMarginTop; 8965 8966 ctx.textAlign = vm._footerAlign; 8967 ctx.textBaseline = 'top'; 8968 8969 ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity); 8970 ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); 8971 8972 helpers.each(footer, function(line) { 8973 ctx.fillText(line, pt.x, pt.y); 8974 pt.y += vm.footerFontSize + vm.footerSpacing; 8975 }); 8976 } 8977 }, 8978 8979 drawBackground: function(pt, vm, ctx, tooltipSize, opacity) { 8980 ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity); 8981 ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity); 8982 ctx.lineWidth = vm.borderWidth; 8983 var xAlign = vm.xAlign; 8984 var yAlign = vm.yAlign; 8985 var x = pt.x; 8986 var y = pt.y; 8987 var width = tooltipSize.width; 8988 var height = tooltipSize.height; 8989 var radius = vm.cornerRadius; 8990 8991 ctx.beginPath(); 8992 ctx.moveTo(x + radius, y); 8993 if (yAlign === 'top') { 8994 this.drawCaret(pt, tooltipSize); 8995 } 8996 ctx.lineTo(x + width - radius, y); 8997 ctx.quadraticCurveTo(x + width, y, x + width, y + radius); 8998 if (yAlign === 'center' && xAlign === 'right') { 8999 this.drawCaret(pt, tooltipSize); 9000 } 9001 ctx.lineTo(x + width, y + height - radius); 9002 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); 9003 if (yAlign === 'bottom') { 9004 this.drawCaret(pt, tooltipSize); 9005 } 9006 ctx.lineTo(x + radius, y + height); 9007 ctx.quadraticCurveTo(x, y + height, x, y + height - radius); 9008 if (yAlign === 'center' && xAlign === 'left') { 9009 this.drawCaret(pt, tooltipSize); 9010 } 9011 ctx.lineTo(x, y + radius); 9012 ctx.quadraticCurveTo(x, y, x + radius, y); 9013 ctx.closePath(); 9014 9015 ctx.fill(); 9016 9017 if (vm.borderWidth > 0) { 9018 ctx.stroke(); 9019 } 9020 }, 9021 9022 draw: function() { 9023 var ctx = this._chart.ctx; 9024 var vm = this._view; 9025 9026 if (vm.opacity === 0) { 9027 return; 9028 } 9029 9030 var tooltipSize = { 9031 width: vm.width, 9032 height: vm.height 9033 }; 9034 var pt = { 9035 x: vm.x, 9036 y: vm.y 9037 }; 9038 9039 // IE11/Edge does not like very small opacities, so snap to 0 9040 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; 9041 9042 // Truthy/falsey value for empty tooltip 9043 var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; 9044 9045 if (this._options.enabled && hasTooltipContent) { 9046 // Draw Background 9047 this.drawBackground(pt, vm, ctx, tooltipSize, opacity); 9048 9049 // Draw Title, Body, and Footer 9050 pt.x += vm.xPadding; 9051 pt.y += vm.yPadding; 9052 9053 // Titles 9054 this.drawTitle(pt, vm, ctx, opacity); 9055 9056 // Body 9057 this.drawBody(pt, vm, ctx, opacity); 9058 9059 // Footer 9060 this.drawFooter(pt, vm, ctx, opacity); 9061 } 9062 }, 9063 9064 /** 9065 * Handle an event 9066 * @private 9067 * @param {IEvent} event - The event to handle 9068 * @returns {Boolean} true if the tooltip changed 9069 */ 9070 handleEvent: function(e) { 9071 var me = this; 9072 var options = me._options; 9073 var changed = false; 9074 9075 me._lastActive = me._lastActive || []; 9076 9077 // Find Active Elements for tooltips 9078 if (e.type === 'mouseout') { 9079 me._active = []; 9080 } else { 9081 me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); 9082 } 9083 9084 // Remember Last Actives 9085 changed = !helpers.arrayEquals(me._active, me._lastActive); 9086 9087 // Only handle target event on tooltip change 9088 if (changed) { 9089 me._lastActive = me._active; 9090 9091 if (options.enabled || options.custom) { 9092 me._eventPosition = { 9093 x: e.x, 9094 y: e.y 9095 }; 9096 9097 me.update(true); 9098 me.pivot(); 9099 } 9100 } 9101 9102 return changed; 9103 } 9104 }); 9105 9106 /** 9107 * @namespace Chart.Tooltip.positioners 9108 */ 9109 exports.positioners = positioners; 9110 9111 9112 },{"26":26,"27":27,"46":46}],37:[function(require,module,exports){ 9113 'use strict'; 9114 9115 var defaults = require(26); 9116 var Element = require(27); 9117 var helpers = require(46); 9118 9119 defaults._set('global', { 9120 elements: { 9121 arc: { 9122 backgroundColor: defaults.global.defaultColor, 9123 borderColor: '#fff', 9124 borderWidth: 2 9125 } 9126 } 9127 }); 9128 9129 module.exports = Element.extend({ 9130 inLabelRange: function(mouseX) { 9131 var vm = this._view; 9132 9133 if (vm) { 9134 return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); 9135 } 9136 return false; 9137 }, 9138 9139 inRange: function(chartX, chartY) { 9140 var vm = this._view; 9141 9142 if (vm) { 9143 var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY}); 9144 var angle = pointRelativePosition.angle; 9145 var distance = pointRelativePosition.distance; 9146 9147 // Sanitise angle range 9148 var startAngle = vm.startAngle; 9149 var endAngle = vm.endAngle; 9150 while (endAngle < startAngle) { 9151 endAngle += 2.0 * Math.PI; 9152 } 9153 while (angle > endAngle) { 9154 angle -= 2.0 * Math.PI; 9155 } 9156 while (angle < startAngle) { 9157 angle += 2.0 * Math.PI; 9158 } 9159 9160 // Check if within the range of the open/close angle 9161 var betweenAngles = (angle >= startAngle && angle <= endAngle); 9162 var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); 9163 9164 return (betweenAngles && withinRadius); 9165 } 9166 return false; 9167 }, 9168 9169 getCenterPoint: function() { 9170 var vm = this._view; 9171 var halfAngle = (vm.startAngle + vm.endAngle) / 2; 9172 var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; 9173 return { 9174 x: vm.x + Math.cos(halfAngle) * halfRadius, 9175 y: vm.y + Math.sin(halfAngle) * halfRadius 9176 }; 9177 }, 9178 9179 getArea: function() { 9180 var vm = this._view; 9181 return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); 9182 }, 9183 9184 tooltipPosition: function() { 9185 var vm = this._view; 9186 var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); 9187 var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; 9188 9189 return { 9190 x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), 9191 y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) 9192 }; 9193 }, 9194 9195 draw: function() { 9196 var ctx = this._chart.ctx; 9197 var vm = this._view; 9198 var sA = vm.startAngle; 9199 var eA = vm.endAngle; 9200 9201 ctx.beginPath(); 9202 9203 ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); 9204 ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); 9205 9206 ctx.closePath(); 9207 ctx.strokeStyle = vm.borderColor; 9208 ctx.lineWidth = vm.borderWidth; 9209 9210 ctx.fillStyle = vm.backgroundColor; 9211 9212 ctx.fill(); 9213 ctx.lineJoin = 'bevel'; 9214 9215 if (vm.borderWidth) { 9216 ctx.stroke(); 9217 } 9218 } 9219 }); 9220 9221 },{"26":26,"27":27,"46":46}],38:[function(require,module,exports){ 9222 'use strict'; 9223 9224 var defaults = require(26); 9225 var Element = require(27); 9226 var helpers = require(46); 9227 9228 var globalDefaults = defaults.global; 9229 9230 defaults._set('global', { 9231 elements: { 9232 line: { 9233 tension: 0.4, 9234 backgroundColor: globalDefaults.defaultColor, 9235 borderWidth: 3, 9236 borderColor: globalDefaults.defaultColor, 9237 borderCapStyle: 'butt', 9238 borderDash: [], 9239 borderDashOffset: 0.0, 9240 borderJoinStyle: 'miter', 9241 capBezierPoints: true, 9242 fill: true, // do we fill in the area between the line and its base axis 9243 } 9244 } 9245 }); 9246 9247 module.exports = Element.extend({ 9248 draw: function() { 9249 var me = this; 9250 var vm = me._view; 9251 var ctx = me._chart.ctx; 9252 var spanGaps = vm.spanGaps; 9253 var points = me._children.slice(); // clone array 9254 var globalOptionLineElements = globalDefaults.elements.line; 9255 var lastDrawnIndex = -1; 9256 var index, current, previous, currentVM; 9257 9258 // If we are looping, adding the first point again 9259 if (me._loop && points.length) { 9260 points.push(points[0]); 9261 } 9262 9263 ctx.save(); 9264 9265 // Stroke Line Options 9266 ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; 9267 9268 // IE 9 and 10 do not support line dash 9269 if (ctx.setLineDash) { 9270 ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); 9271 } 9272 9273 ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; 9274 ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; 9275 ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; 9276 ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; 9277 9278 // Stroke Line 9279 ctx.beginPath(); 9280 lastDrawnIndex = -1; 9281 9282 for (index = 0; index < points.length; ++index) { 9283 current = points[index]; 9284 previous = helpers.previousItem(points, index); 9285 currentVM = current._view; 9286 9287 // First point moves to it's starting position no matter what 9288 if (index === 0) { 9289 if (!currentVM.skip) { 9290 ctx.moveTo(currentVM.x, currentVM.y); 9291 lastDrawnIndex = index; 9292 } 9293 } else { 9294 previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; 9295 9296 if (!currentVM.skip) { 9297 if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { 9298 // There was a gap and this is the first point after the gap 9299 ctx.moveTo(currentVM.x, currentVM.y); 9300 } else { 9301 // Line to next point 9302 helpers.canvas.lineTo(ctx, previous._view, current._view); 9303 } 9304 lastDrawnIndex = index; 9305 } 9306 } 9307 } 9308 9309 ctx.stroke(); 9310 ctx.restore(); 9311 } 9312 }); 9313 9314 },{"26":26,"27":27,"46":46}],39:[function(require,module,exports){ 9315 'use strict'; 9316 9317 var defaults = require(26); 9318 var Element = require(27); 9319 var helpers = require(46); 9320 9321 var defaultColor = defaults.global.defaultColor; 9322 9323 defaults._set('global', { 9324 elements: { 9325 point: { 9326 radius: 3, 9327 pointStyle: 'circle', 9328 backgroundColor: defaultColor, 9329 borderColor: defaultColor, 9330 borderWidth: 1, 9331 // Hover 9332 hitRadius: 1, 9333 hoverRadius: 4, 9334 hoverBorderWidth: 1 9335 } 9336 } 9337 }); 9338 9339 function xRange(mouseX) { 9340 var vm = this._view; 9341 return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; 9342 } 9343 9344 function yRange(mouseY) { 9345 var vm = this._view; 9346 return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; 9347 } 9348 9349 module.exports = Element.extend({ 9350 inRange: function(mouseX, mouseY) { 9351 var vm = this._view; 9352 return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; 9353 }, 9354 9355 inLabelRange: xRange, 9356 inXRange: xRange, 9357 inYRange: yRange, 9358 9359 getCenterPoint: function() { 9360 var vm = this._view; 9361 return { 9362 x: vm.x, 9363 y: vm.y 9364 }; 9365 }, 9366 9367 getArea: function() { 9368 return Math.PI * Math.pow(this._view.radius, 2); 9369 }, 9370 9371 tooltipPosition: function() { 9372 var vm = this._view; 9373 return { 9374 x: vm.x, 9375 y: vm.y, 9376 padding: vm.radius + vm.borderWidth 9377 }; 9378 }, 9379 9380 draw: function(chartArea) { 9381 var vm = this._view; 9382 var model = this._model; 9383 var ctx = this._chart.ctx; 9384 var pointStyle = vm.pointStyle; 9385 var rotation = vm.rotation; 9386 var radius = vm.radius; 9387 var x = vm.x; 9388 var y = vm.y; 9389 var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.) 9390 9391 if (vm.skip) { 9392 return; 9393 } 9394 9395 // Clipping for Points. 9396 if (chartArea === undefined || (model.x >= chartArea.left && chartArea.right * errMargin >= model.x && model.y >= chartArea.top && chartArea.bottom * errMargin >= model.y)) { 9397 ctx.strokeStyle = vm.borderColor || defaultColor; 9398 ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); 9399 ctx.fillStyle = vm.backgroundColor || defaultColor; 9400 helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); 9401 } 9402 } 9403 }); 9404 9405 },{"26":26,"27":27,"46":46}],40:[function(require,module,exports){ 9406 'use strict'; 9407 9408 var defaults = require(26); 9409 var Element = require(27); 9410 9411 defaults._set('global', { 9412 elements: { 9413 rectangle: { 9414 backgroundColor: defaults.global.defaultColor, 9415 borderColor: defaults.global.defaultColor, 9416 borderSkipped: 'bottom', 9417 borderWidth: 0 9418 } 9419 } 9420 }); 9421 9422 function isVertical(bar) { 9423 return bar._view.width !== undefined; 9424 } 9425 9426 /** 9427 * Helper function to get the bounds of the bar regardless of the orientation 9428 * @param bar {Chart.Element.Rectangle} the bar 9429 * @return {Bounds} bounds of the bar 9430 * @private 9431 */ 9432 function getBarBounds(bar) { 9433 var vm = bar._view; 9434 var x1, x2, y1, y2; 9435 9436 if (isVertical(bar)) { 9437 // vertical 9438 var halfWidth = vm.width / 2; 9439 x1 = vm.x - halfWidth; 9440 x2 = vm.x + halfWidth; 9441 y1 = Math.min(vm.y, vm.base); 9442 y2 = Math.max(vm.y, vm.base); 9443 } else { 9444 // horizontal bar 9445 var halfHeight = vm.height / 2; 9446 x1 = Math.min(vm.x, vm.base); 9447 x2 = Math.max(vm.x, vm.base); 9448 y1 = vm.y - halfHeight; 9449 y2 = vm.y + halfHeight; 9450 } 9451 9452 return { 9453 left: x1, 9454 top: y1, 9455 right: x2, 9456 bottom: y2 9457 }; 9458 } 9459 9460 module.exports = Element.extend({ 9461 draw: function() { 9462 var ctx = this._chart.ctx; 9463 var vm = this._view; 9464 var left, right, top, bottom, signX, signY, borderSkipped; 9465 var borderWidth = vm.borderWidth; 9466 9467 if (!vm.horizontal) { 9468 // bar 9469 left = vm.x - vm.width / 2; 9470 right = vm.x + vm.width / 2; 9471 top = vm.y; 9472 bottom = vm.base; 9473 signX = 1; 9474 signY = bottom > top ? 1 : -1; 9475 borderSkipped = vm.borderSkipped || 'bottom'; 9476 } else { 9477 // horizontal bar 9478 left = vm.base; 9479 right = vm.x; 9480 top = vm.y - vm.height / 2; 9481 bottom = vm.y + vm.height / 2; 9482 signX = right > left ? 1 : -1; 9483 signY = 1; 9484 borderSkipped = vm.borderSkipped || 'left'; 9485 } 9486 9487 // Canvas doesn't allow us to stroke inside the width so we can 9488 // adjust the sizes to fit if we're setting a stroke on the line 9489 if (borderWidth) { 9490 // borderWidth shold be less than bar width and bar height. 9491 var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); 9492 borderWidth = borderWidth > barSize ? barSize : borderWidth; 9493 var halfStroke = borderWidth / 2; 9494 // Adjust borderWidth when bar top position is near vm.base(zero). 9495 var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0); 9496 var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0); 9497 var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0); 9498 var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0); 9499 // not become a vertical line? 9500 if (borderLeft !== borderRight) { 9501 top = borderTop; 9502 bottom = borderBottom; 9503 } 9504 // not become a horizontal line? 9505 if (borderTop !== borderBottom) { 9506 left = borderLeft; 9507 right = borderRight; 9508 } 9509 } 9510 9511 ctx.beginPath(); 9512 ctx.fillStyle = vm.backgroundColor; 9513 ctx.strokeStyle = vm.borderColor; 9514 ctx.lineWidth = borderWidth; 9515 9516 // Corner points, from bottom-left to bottom-right clockwise 9517 // | 1 2 | 9518 // | 0 3 | 9519 var corners = [ 9520 [left, bottom], 9521 [left, top], 9522 [right, top], 9523 [right, bottom] 9524 ]; 9525 9526 // Find first (starting) corner with fallback to 'bottom' 9527 var borders = ['bottom', 'left', 'top', 'right']; 9528 var startCorner = borders.indexOf(borderSkipped, 0); 9529 if (startCorner === -1) { 9530 startCorner = 0; 9531 } 9532 9533 function cornerAt(index) { 9534 return corners[(startCorner + index) % 4]; 9535 } 9536 9537 // Draw rectangle from 'startCorner' 9538 var corner = cornerAt(0); 9539 ctx.moveTo(corner[0], corner[1]); 9540 9541 for (var i = 1; i < 4; i++) { 9542 corner = cornerAt(i); 9543 ctx.lineTo(corner[0], corner[1]); 9544 } 9545 9546 ctx.fill(); 9547 if (borderWidth) { 9548 ctx.stroke(); 9549 } 9550 }, 9551 9552 height: function() { 9553 var vm = this._view; 9554 return vm.base - vm.y; 9555 }, 9556 9557 inRange: function(mouseX, mouseY) { 9558 var inRange = false; 9559 9560 if (this._view) { 9561 var bounds = getBarBounds(this); 9562 inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; 9563 } 9564 9565 return inRange; 9566 }, 9567 9568 inLabelRange: function(mouseX, mouseY) { 9569 var me = this; 9570 if (!me._view) { 9571 return false; 9572 } 9573 9574 var inRange = false; 9575 var bounds = getBarBounds(me); 9576 9577 if (isVertical(me)) { 9578 inRange = mouseX >= bounds.left && mouseX <= bounds.right; 9579 } else { 9580 inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; 9581 } 9582 9583 return inRange; 9584 }, 9585 9586 inXRange: function(mouseX) { 9587 var bounds = getBarBounds(this); 9588 return mouseX >= bounds.left && mouseX <= bounds.right; 9589 }, 9590 9591 inYRange: function(mouseY) { 9592 var bounds = getBarBounds(this); 9593 return mouseY >= bounds.top && mouseY <= bounds.bottom; 9594 }, 9595 9596 getCenterPoint: function() { 9597 var vm = this._view; 9598 var x, y; 9599 if (isVertical(this)) { 9600 x = vm.x; 9601 y = (vm.y + vm.base) / 2; 9602 } else { 9603 x = (vm.x + vm.base) / 2; 9604 y = vm.y; 9605 } 9606 9607 return {x: x, y: y}; 9608 }, 9609 9610 getArea: function() { 9611 var vm = this._view; 9612 return vm.width * Math.abs(vm.y - vm.base); 9613 }, 9614 9615 tooltipPosition: function() { 9616 var vm = this._view; 9617 return { 9618 x: vm.x, 9619 y: vm.y 9620 }; 9621 } 9622 }); 9623 9624 },{"26":26,"27":27}],41:[function(require,module,exports){ 9625 'use strict'; 9626 9627 module.exports = {}; 9628 module.exports.Arc = require(37); 9629 module.exports.Line = require(38); 9630 module.exports.Point = require(39); 9631 module.exports.Rectangle = require(40); 9632 9633 },{"37":37,"38":38,"39":39,"40":40}],42:[function(require,module,exports){ 9634 'use strict'; 9635 9636 var helpers = require(43); 9637 9638 /** 9639 * @namespace Chart.helpers.canvas 9640 */ 9641 var exports = module.exports = { 9642 /** 9643 * Clears the entire canvas associated to the given `chart`. 9644 * @param {Chart} chart - The chart for which to clear the canvas. 9645 */ 9646 clear: function(chart) { 9647 chart.ctx.clearRect(0, 0, chart.width, chart.height); 9648 }, 9649 9650 /** 9651 * Creates a "path" for a rectangle with rounded corners at position (x, y) with a 9652 * given size (width, height) and the same `radius` for all corners. 9653 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. 9654 * @param {Number} x - The x axis of the coordinate for the rectangle starting point. 9655 * @param {Number} y - The y axis of the coordinate for the rectangle starting point. 9656 * @param {Number} width - The rectangle's width. 9657 * @param {Number} height - The rectangle's height. 9658 * @param {Number} radius - The rounded amount (in pixels) for the four corners. 9659 * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? 9660 */ 9661 roundedRect: function(ctx, x, y, width, height, radius) { 9662 if (radius) { 9663 // NOTE(SB) `epsilon` helps to prevent minor artifacts appearing 9664 // on Chrome when `r` is exactly half the height or the width. 9665 var epsilon = 0.0000001; 9666 var r = Math.min(radius, (height / 2) - epsilon, (width / 2) - epsilon); 9667 9668 ctx.moveTo(x + r, y); 9669 ctx.lineTo(x + width - r, y); 9670 ctx.arcTo(x + width, y, x + width, y + r, r); 9671 ctx.lineTo(x + width, y + height - r); 9672 ctx.arcTo(x + width, y + height, x + width - r, y + height, r); 9673 ctx.lineTo(x + r, y + height); 9674 ctx.arcTo(x, y + height, x, y + height - r, r); 9675 ctx.lineTo(x, y + r); 9676 ctx.arcTo(x, y, x + r, y, r); 9677 ctx.closePath(); 9678 ctx.moveTo(x, y); 9679 } else { 9680 ctx.rect(x, y, width, height); 9681 } 9682 }, 9683 9684 drawPoint: function(ctx, style, radius, x, y, rotation) { 9685 var type, edgeLength, xOffset, yOffset, height, size; 9686 rotation = rotation || 0; 9687 9688 if (style && typeof style === 'object') { 9689 type = style.toString(); 9690 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { 9691 ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height); 9692 return; 9693 } 9694 } 9695 9696 if (isNaN(radius) || radius <= 0) { 9697 return; 9698 } 9699 9700 ctx.save(); 9701 ctx.translate(x, y); 9702 ctx.rotate(rotation * Math.PI / 180); 9703 ctx.beginPath(); 9704 9705 switch (style) { 9706 // Default includes circle 9707 default: 9708 ctx.arc(0, 0, radius, 0, Math.PI * 2); 9709 ctx.closePath(); 9710 break; 9711 case 'triangle': 9712 edgeLength = 3 * radius / Math.sqrt(3); 9713 height = edgeLength * Math.sqrt(3) / 2; 9714 ctx.moveTo(-edgeLength / 2, height / 3); 9715 ctx.lineTo(edgeLength / 2, height / 3); 9716 ctx.lineTo(0, -2 * height / 3); 9717 ctx.closePath(); 9718 break; 9719 case 'rect': 9720 size = 1 / Math.SQRT2 * radius; 9721 ctx.rect(-size, -size, 2 * size, 2 * size); 9722 break; 9723 case 'rectRounded': 9724 var offset = radius / Math.SQRT2; 9725 var leftX = -offset; 9726 var topY = -offset; 9727 var sideSize = Math.SQRT2 * radius; 9728 9729 // NOTE(SB) the rounded rect implementation changed to use `arcTo` 9730 // instead of `quadraticCurveTo` since it generates better results 9731 // when rect is almost a circle. 0.425 (instead of 0.5) produces 9732 // results visually closer to the previous impl. 9733 this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius * 0.425); 9734 break; 9735 case 'rectRot': 9736 size = 1 / Math.SQRT2 * radius; 9737 ctx.moveTo(-size, 0); 9738 ctx.lineTo(0, size); 9739 ctx.lineTo(size, 0); 9740 ctx.lineTo(0, -size); 9741 ctx.closePath(); 9742 break; 9743 case 'cross': 9744 ctx.moveTo(0, radius); 9745 ctx.lineTo(0, -radius); 9746 ctx.moveTo(-radius, 0); 9747 ctx.lineTo(radius, 0); 9748 break; 9749 case 'crossRot': 9750 xOffset = Math.cos(Math.PI / 4) * radius; 9751 yOffset = Math.sin(Math.PI / 4) * radius; 9752 ctx.moveTo(-xOffset, -yOffset); 9753 ctx.lineTo(xOffset, yOffset); 9754 ctx.moveTo(-xOffset, yOffset); 9755 ctx.lineTo(xOffset, -yOffset); 9756 break; 9757 case 'star': 9758 ctx.moveTo(0, radius); 9759 ctx.lineTo(0, -radius); 9760 ctx.moveTo(-radius, 0); 9761 ctx.lineTo(radius, 0); 9762 xOffset = Math.cos(Math.PI / 4) * radius; 9763 yOffset = Math.sin(Math.PI / 4) * radius; 9764 ctx.moveTo(-xOffset, -yOffset); 9765 ctx.lineTo(xOffset, yOffset); 9766 ctx.moveTo(-xOffset, yOffset); 9767 ctx.lineTo(xOffset, -yOffset); 9768 break; 9769 case 'line': 9770 ctx.moveTo(-radius, 0); 9771 ctx.lineTo(radius, 0); 9772 break; 9773 case 'dash': 9774 ctx.moveTo(0, 0); 9775 ctx.lineTo(radius, 0); 9776 break; 9777 } 9778 9779 ctx.fill(); 9780 ctx.stroke(); 9781 ctx.restore(); 9782 }, 9783 9784 clipArea: function(ctx, area) { 9785 ctx.save(); 9786 ctx.beginPath(); 9787 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); 9788 ctx.clip(); 9789 }, 9790 9791 unclipArea: function(ctx) { 9792 ctx.restore(); 9793 }, 9794 9795 lineTo: function(ctx, previous, target, flip) { 9796 if (target.steppedLine) { 9797 if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) { 9798 ctx.lineTo(previous.x, target.y); 9799 } else { 9800 ctx.lineTo(target.x, previous.y); 9801 } 9802 ctx.lineTo(target.x, target.y); 9803 return; 9804 } 9805 9806 if (!target.tension) { 9807 ctx.lineTo(target.x, target.y); 9808 return; 9809 } 9810 9811 ctx.bezierCurveTo( 9812 flip ? previous.controlPointPreviousX : previous.controlPointNextX, 9813 flip ? previous.controlPointPreviousY : previous.controlPointNextY, 9814 flip ? target.controlPointNextX : target.controlPointPreviousX, 9815 flip ? target.controlPointNextY : target.controlPointPreviousY, 9816 target.x, 9817 target.y); 9818 } 9819 }; 9820 9821 // DEPRECATIONS 9822 9823 /** 9824 * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. 9825 * @namespace Chart.helpers.clear 9826 * @deprecated since version 2.7.0 9827 * @todo remove at version 3 9828 * @private 9829 */ 9830 helpers.clear = exports.clear; 9831 9832 /** 9833 * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. 9834 * @namespace Chart.helpers.drawRoundedRectangle 9835 * @deprecated since version 2.7.0 9836 * @todo remove at version 3 9837 * @private 9838 */ 9839 helpers.drawRoundedRectangle = function(ctx) { 9840 ctx.beginPath(); 9841 exports.roundedRect.apply(exports, arguments); 9842 }; 9843 9844 },{"43":43}],43:[function(require,module,exports){ 9845 'use strict'; 9846 9847 /** 9848 * @namespace Chart.helpers 9849 */ 9850 var helpers = { 9851 /** 9852 * An empty function that can be used, for example, for optional callback. 9853 */ 9854 noop: function() {}, 9855 9856 /** 9857 * Returns a unique id, sequentially generated from a global variable. 9858 * @returns {Number} 9859 * @function 9860 */ 9861 uid: (function() { 9862 var id = 0; 9863 return function() { 9864 return id++; 9865 }; 9866 }()), 9867 9868 /** 9869 * Returns true if `value` is neither null nor undefined, else returns false. 9870 * @param {*} value - The value to test. 9871 * @returns {Boolean} 9872 * @since 2.7.0 9873 */ 9874 isNullOrUndef: function(value) { 9875 return value === null || typeof value === 'undefined'; 9876 }, 9877 9878 /** 9879 * Returns true if `value` is an array, else returns false. 9880 * @param {*} value - The value to test. 9881 * @returns {Boolean} 9882 * @function 9883 */ 9884 isArray: Array.isArray ? Array.isArray : function(value) { 9885 return Object.prototype.toString.call(value) === '[object Array]'; 9886 }, 9887 9888 /** 9889 * Returns true if `value` is an object (excluding null), else returns false. 9890 * @param {*} value - The value to test. 9891 * @returns {Boolean} 9892 * @since 2.7.0 9893 */ 9894 isObject: function(value) { 9895 return value !== null && Object.prototype.toString.call(value) === '[object Object]'; 9896 }, 9897 9898 /** 9899 * Returns `value` if defined, else returns `defaultValue`. 9900 * @param {*} value - The value to return if defined. 9901 * @param {*} defaultValue - The value to return if `value` is undefined. 9902 * @returns {*} 9903 */ 9904 valueOrDefault: function(value, defaultValue) { 9905 return typeof value === 'undefined' ? defaultValue : value; 9906 }, 9907 9908 /** 9909 * Returns value at the given `index` in array if defined, else returns `defaultValue`. 9910 * @param {Array} value - The array to lookup for value at `index`. 9911 * @param {Number} index - The index in `value` to lookup for value. 9912 * @param {*} defaultValue - The value to return if `value[index]` is undefined. 9913 * @returns {*} 9914 */ 9915 valueAtIndexOrDefault: function(value, index, defaultValue) { 9916 return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); 9917 }, 9918 9919 /** 9920 * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the 9921 * value returned by `fn`. If `fn` is not a function, this method returns undefined. 9922 * @param {Function} fn - The function to call. 9923 * @param {Array|undefined|null} args - The arguments with which `fn` should be called. 9924 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`. 9925 * @returns {*} 9926 */ 9927 callback: function(fn, args, thisArg) { 9928 if (fn && typeof fn.call === 'function') { 9929 return fn.apply(thisArg, args); 9930 } 9931 }, 9932 9933 /** 9934 * Note(SB) for performance sake, this method should only be used when loopable type 9935 * is unknown or in none intensive code (not called often and small loopable). Else 9936 * it's preferable to use a regular for() loop and save extra function calls. 9937 * @param {Object|Array} loopable - The object or array to be iterated. 9938 * @param {Function} fn - The function to call for each item. 9939 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`. 9940 * @param {Boolean} [reverse] - If true, iterates backward on the loopable. 9941 */ 9942 each: function(loopable, fn, thisArg, reverse) { 9943 var i, len, keys; 9944 if (helpers.isArray(loopable)) { 9945 len = loopable.length; 9946 if (reverse) { 9947 for (i = len - 1; i >= 0; i--) { 9948 fn.call(thisArg, loopable[i], i); 9949 } 9950 } else { 9951 for (i = 0; i < len; i++) { 9952 fn.call(thisArg, loopable[i], i); 9953 } 9954 } 9955 } else if (helpers.isObject(loopable)) { 9956 keys = Object.keys(loopable); 9957 len = keys.length; 9958 for (i = 0; i < len; i++) { 9959 fn.call(thisArg, loopable[keys[i]], keys[i]); 9960 } 9961 } 9962 }, 9963 9964 /** 9965 * Returns true if the `a0` and `a1` arrays have the same content, else returns false. 9966 * @see http://stackoverflow.com/a/14853974 9967 * @param {Array} a0 - The array to compare 9968 * @param {Array} a1 - The array to compare 9969 * @returns {Boolean} 9970 */ 9971 arrayEquals: function(a0, a1) { 9972 var i, ilen, v0, v1; 9973 9974 if (!a0 || !a1 || a0.length !== a1.length) { 9975 return false; 9976 } 9977 9978 for (i = 0, ilen = a0.length; i < ilen; ++i) { 9979 v0 = a0[i]; 9980 v1 = a1[i]; 9981 9982 if (v0 instanceof Array && v1 instanceof Array) { 9983 if (!helpers.arrayEquals(v0, v1)) { 9984 return false; 9985 } 9986 } else if (v0 !== v1) { 9987 // NOTE: two different object instances will never be equal: {x:20} != {x:20} 9988 return false; 9989 } 9990 } 9991 9992 return true; 9993 }, 9994 9995 /** 9996 * Returns a deep copy of `source` without keeping references on objects and arrays. 9997 * @param {*} source - The value to clone. 9998 * @returns {*} 9999 */ 10000 clone: function(source) { 10001 if (helpers.isArray(source)) { 10002 return source.map(helpers.clone); 10003 } 10004 10005 if (helpers.isObject(source)) { 10006 var target = {}; 10007 var keys = Object.keys(source); 10008 var klen = keys.length; 10009 var k = 0; 10010 10011 for (; k < klen; ++k) { 10012 target[keys[k]] = helpers.clone(source[keys[k]]); 10013 } 10014 10015 return target; 10016 } 10017 10018 return source; 10019 }, 10020 10021 /** 10022 * The default merger when Chart.helpers.merge is called without merger option. 10023 * Note(SB): this method is also used by configMerge and scaleMerge as fallback. 10024 * @private 10025 */ 10026 _merger: function(key, target, source, options) { 10027 var tval = target[key]; 10028 var sval = source[key]; 10029 10030 if (helpers.isObject(tval) && helpers.isObject(sval)) { 10031 helpers.merge(tval, sval, options); 10032 } else { 10033 target[key] = helpers.clone(sval); 10034 } 10035 }, 10036 10037 /** 10038 * Merges source[key] in target[key] only if target[key] is undefined. 10039 * @private 10040 */ 10041 _mergerIf: function(key, target, source) { 10042 var tval = target[key]; 10043 var sval = source[key]; 10044 10045 if (helpers.isObject(tval) && helpers.isObject(sval)) { 10046 helpers.mergeIf(tval, sval); 10047 } else if (!target.hasOwnProperty(key)) { 10048 target[key] = helpers.clone(sval); 10049 } 10050 }, 10051 10052 /** 10053 * Recursively deep copies `source` properties into `target` with the given `options`. 10054 * IMPORTANT: `target` is not cloned and will be updated with `source` properties. 10055 * @param {Object} target - The target object in which all sources are merged into. 10056 * @param {Object|Array(Object)} source - Object(s) to merge into `target`. 10057 * @param {Object} [options] - Merging options: 10058 * @param {Function} [options.merger] - The merge method (key, target, source, options) 10059 * @returns {Object} The `target` object. 10060 */ 10061 merge: function(target, source, options) { 10062 var sources = helpers.isArray(source) ? source : [source]; 10063 var ilen = sources.length; 10064 var merge, i, keys, klen, k; 10065 10066 if (!helpers.isObject(target)) { 10067 return target; 10068 } 10069 10070 options = options || {}; 10071 merge = options.merger || helpers._merger; 10072 10073 for (i = 0; i < ilen; ++i) { 10074 source = sources[i]; 10075 if (!helpers.isObject(source)) { 10076 continue; 10077 } 10078 10079 keys = Object.keys(source); 10080 for (k = 0, klen = keys.length; k < klen; ++k) { 10081 merge(keys[k], target, source, options); 10082 } 10083 } 10084 10085 return target; 10086 }, 10087 10088 /** 10089 * Recursively deep copies `source` properties into `target` *only* if not defined in target. 10090 * IMPORTANT: `target` is not cloned and will be updated with `source` properties. 10091 * @param {Object} target - The target object in which all sources are merged into. 10092 * @param {Object|Array(Object)} source - Object(s) to merge into `target`. 10093 * @returns {Object} The `target` object. 10094 */ 10095 mergeIf: function(target, source) { 10096 return helpers.merge(target, source, {merger: helpers._mergerIf}); 10097 }, 10098 10099 /** 10100 * Applies the contents of two or more objects together into the first object. 10101 * @param {Object} target - The target object in which all objects are merged into. 10102 * @param {Object} arg1 - Object containing additional properties to merge in target. 10103 * @param {Object} argN - Additional objects containing properties to merge in target. 10104 * @returns {Object} The `target` object. 10105 */ 10106 extend: function(target) { 10107 var setFn = function(value, key) { 10108 target[key] = value; 10109 }; 10110 for (var i = 1, ilen = arguments.length; i < ilen; ++i) { 10111 helpers.each(arguments[i], setFn); 10112 } 10113 return target; 10114 }, 10115 10116 /** 10117 * Basic javascript inheritance based on the model created in Backbone.js 10118 */ 10119 inherits: function(extensions) { 10120 var me = this; 10121 var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { 10122 return me.apply(this, arguments); 10123 }; 10124 10125 var Surrogate = function() { 10126 this.constructor = ChartElement; 10127 }; 10128 10129 Surrogate.prototype = me.prototype; 10130 ChartElement.prototype = new Surrogate(); 10131 ChartElement.extend = helpers.inherits; 10132 10133 if (extensions) { 10134 helpers.extend(ChartElement.prototype, extensions); 10135 } 10136 10137 ChartElement.__super__ = me.prototype; 10138 return ChartElement; 10139 } 10140 }; 10141 10142 module.exports = helpers; 10143 10144 // DEPRECATIONS 10145 10146 /** 10147 * Provided for backward compatibility, use Chart.helpers.callback instead. 10148 * @function Chart.helpers.callCallback 10149 * @deprecated since version 2.6.0 10150 * @todo remove at version 3 10151 * @private 10152 */ 10153 helpers.callCallback = helpers.callback; 10154 10155 /** 10156 * Provided for backward compatibility, use Array.prototype.indexOf instead. 10157 * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ 10158 * @function Chart.helpers.indexOf 10159 * @deprecated since version 2.7.0 10160 * @todo remove at version 3 10161 * @private 10162 */ 10163 helpers.indexOf = function(array, item, fromIndex) { 10164 return Array.prototype.indexOf.call(array, item, fromIndex); 10165 }; 10166 10167 /** 10168 * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. 10169 * @function Chart.helpers.getValueOrDefault 10170 * @deprecated since version 2.7.0 10171 * @todo remove at version 3 10172 * @private 10173 */ 10174 helpers.getValueOrDefault = helpers.valueOrDefault; 10175 10176 /** 10177 * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. 10178 * @function Chart.helpers.getValueAtIndexOrDefault 10179 * @deprecated since version 2.7.0 10180 * @todo remove at version 3 10181 * @private 10182 */ 10183 helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; 10184 10185 },{}],44:[function(require,module,exports){ 10186 'use strict'; 10187 10188 var helpers = require(43); 10189 10190 /** 10191 * Easing functions adapted from Robert Penner's easing equations. 10192 * @namespace Chart.helpers.easingEffects 10193 * @see http://www.robertpenner.com/easing/ 10194 */ 10195 var effects = { 10196 linear: function(t) { 10197 return t; 10198 }, 10199 10200 easeInQuad: function(t) { 10201 return t * t; 10202 }, 10203 10204 easeOutQuad: function(t) { 10205 return -t * (t - 2); 10206 }, 10207 10208 easeInOutQuad: function(t) { 10209 if ((t /= 0.5) < 1) { 10210 return 0.5 * t * t; 10211 } 10212 return -0.5 * ((--t) * (t - 2) - 1); 10213 }, 10214 10215 easeInCubic: function(t) { 10216 return t * t * t; 10217 }, 10218 10219 easeOutCubic: function(t) { 10220 return (t = t - 1) * t * t + 1; 10221 }, 10222 10223 easeInOutCubic: function(t) { 10224 if ((t /= 0.5) < 1) { 10225 return 0.5 * t * t * t; 10226 } 10227 return 0.5 * ((t -= 2) * t * t + 2); 10228 }, 10229 10230 easeInQuart: function(t) { 10231 return t * t * t * t; 10232 }, 10233 10234 easeOutQuart: function(t) { 10235 return -((t = t - 1) * t * t * t - 1); 10236 }, 10237 10238 easeInOutQuart: function(t) { 10239 if ((t /= 0.5) < 1) { 10240 return 0.5 * t * t * t * t; 10241 } 10242 return -0.5 * ((t -= 2) * t * t * t - 2); 10243 }, 10244 10245 easeInQuint: function(t) { 10246 return t * t * t * t * t; 10247 }, 10248 10249 easeOutQuint: function(t) { 10250 return (t = t - 1) * t * t * t * t + 1; 10251 }, 10252 10253 easeInOutQuint: function(t) { 10254 if ((t /= 0.5) < 1) { 10255 return 0.5 * t * t * t * t * t; 10256 } 10257 return 0.5 * ((t -= 2) * t * t * t * t + 2); 10258 }, 10259 10260 easeInSine: function(t) { 10261 return -Math.cos(t * (Math.PI / 2)) + 1; 10262 }, 10263 10264 easeOutSine: function(t) { 10265 return Math.sin(t * (Math.PI / 2)); 10266 }, 10267 10268 easeInOutSine: function(t) { 10269 return -0.5 * (Math.cos(Math.PI * t) - 1); 10270 }, 10271 10272 easeInExpo: function(t) { 10273 return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); 10274 }, 10275 10276 easeOutExpo: function(t) { 10277 return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; 10278 }, 10279 10280 easeInOutExpo: function(t) { 10281 if (t === 0) { 10282 return 0; 10283 } 10284 if (t === 1) { 10285 return 1; 10286 } 10287 if ((t /= 0.5) < 1) { 10288 return 0.5 * Math.pow(2, 10 * (t - 1)); 10289 } 10290 return 0.5 * (-Math.pow(2, -10 * --t) + 2); 10291 }, 10292 10293 easeInCirc: function(t) { 10294 if (t >= 1) { 10295 return t; 10296 } 10297 return -(Math.sqrt(1 - t * t) - 1); 10298 }, 10299 10300 easeOutCirc: function(t) { 10301 return Math.sqrt(1 - (t = t - 1) * t); 10302 }, 10303 10304 easeInOutCirc: function(t) { 10305 if ((t /= 0.5) < 1) { 10306 return -0.5 * (Math.sqrt(1 - t * t) - 1); 10307 } 10308 return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); 10309 }, 10310 10311 easeInElastic: function(t) { 10312 var s = 1.70158; 10313 var p = 0; 10314 var a = 1; 10315 if (t === 0) { 10316 return 0; 10317 } 10318 if (t === 1) { 10319 return 1; 10320 } 10321 if (!p) { 10322 p = 0.3; 10323 } 10324 if (a < 1) { 10325 a = 1; 10326 s = p / 4; 10327 } else { 10328 s = p / (2 * Math.PI) * Math.asin(1 / a); 10329 } 10330 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); 10331 }, 10332 10333 easeOutElastic: function(t) { 10334 var s = 1.70158; 10335 var p = 0; 10336 var a = 1; 10337 if (t === 0) { 10338 return 0; 10339 } 10340 if (t === 1) { 10341 return 1; 10342 } 10343 if (!p) { 10344 p = 0.3; 10345 } 10346 if (a < 1) { 10347 a = 1; 10348 s = p / 4; 10349 } else { 10350 s = p / (2 * Math.PI) * Math.asin(1 / a); 10351 } 10352 return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; 10353 }, 10354 10355 easeInOutElastic: function(t) { 10356 var s = 1.70158; 10357 var p = 0; 10358 var a = 1; 10359 if (t === 0) { 10360 return 0; 10361 } 10362 if ((t /= 0.5) === 2) { 10363 return 1; 10364 } 10365 if (!p) { 10366 p = 0.45; 10367 } 10368 if (a < 1) { 10369 a = 1; 10370 s = p / 4; 10371 } else { 10372 s = p / (2 * Math.PI) * Math.asin(1 / a); 10373 } 10374 if (t < 1) { 10375 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); 10376 } 10377 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; 10378 }, 10379 easeInBack: function(t) { 10380 var s = 1.70158; 10381 return t * t * ((s + 1) * t - s); 10382 }, 10383 10384 easeOutBack: function(t) { 10385 var s = 1.70158; 10386 return (t = t - 1) * t * ((s + 1) * t + s) + 1; 10387 }, 10388 10389 easeInOutBack: function(t) { 10390 var s = 1.70158; 10391 if ((t /= 0.5) < 1) { 10392 return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); 10393 } 10394 return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); 10395 }, 10396 10397 easeInBounce: function(t) { 10398 return 1 - effects.easeOutBounce(1 - t); 10399 }, 10400 10401 easeOutBounce: function(t) { 10402 if (t < (1 / 2.75)) { 10403 return 7.5625 * t * t; 10404 } 10405 if (t < (2 / 2.75)) { 10406 return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; 10407 } 10408 if (t < (2.5 / 2.75)) { 10409 return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; 10410 } 10411 return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; 10412 }, 10413 10414 easeInOutBounce: function(t) { 10415 if (t < 0.5) { 10416 return effects.easeInBounce(t * 2) * 0.5; 10417 } 10418 return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; 10419 } 10420 }; 10421 10422 module.exports = { 10423 effects: effects 10424 }; 10425 10426 // DEPRECATIONS 10427 10428 /** 10429 * Provided for backward compatibility, use Chart.helpers.easing.effects instead. 10430 * @function Chart.helpers.easingEffects 10431 * @deprecated since version 2.7.0 10432 * @todo remove at version 3 10433 * @private 10434 */ 10435 helpers.easingEffects = effects; 10436 10437 },{"43":43}],45:[function(require,module,exports){ 10438 'use strict'; 10439 10440 var helpers = require(43); 10441 10442 /** 10443 * @alias Chart.helpers.options 10444 * @namespace 10445 */ 10446 module.exports = { 10447 /** 10448 * Converts the given line height `value` in pixels for a specific font `size`. 10449 * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). 10450 * @param {Number} size - The font size (in pixels) used to resolve relative `value`. 10451 * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid). 10452 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height 10453 * @since 2.7.0 10454 */ 10455 toLineHeight: function(value, size) { 10456 var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); 10457 if (!matches || matches[1] === 'normal') { 10458 return size * 1.2; 10459 } 10460 10461 value = +matches[2]; 10462 10463 switch (matches[3]) { 10464 case 'px': 10465 return value; 10466 case '%': 10467 value /= 100; 10468 break; 10469 default: 10470 break; 10471 } 10472 10473 return size * value; 10474 }, 10475 10476 /** 10477 * Converts the given value into a padding object with pre-computed width/height. 10478 * @param {Number|Object} value - If a number, set the value to all TRBL component, 10479 * else, if and object, use defined properties and sets undefined ones to 0. 10480 * @returns {Object} The padding values (top, right, bottom, left, width, height) 10481 * @since 2.7.0 10482 */ 10483 toPadding: function(value) { 10484 var t, r, b, l; 10485 10486 if (helpers.isObject(value)) { 10487 t = +value.top || 0; 10488 r = +value.right || 0; 10489 b = +value.bottom || 0; 10490 l = +value.left || 0; 10491 } else { 10492 t = r = b = l = +value || 0; 10493 } 10494 10495 return { 10496 top: t, 10497 right: r, 10498 bottom: b, 10499 left: l, 10500 height: t + b, 10501 width: l + r 10502 }; 10503 }, 10504 10505 /** 10506 * Evaluates the given `inputs` sequentially and returns the first defined value. 10507 * @param {Array[]} inputs - An array of values, falling back to the last value. 10508 * @param {Object} [context] - If defined and the current value is a function, the value 10509 * is called with `context` as first argument and the result becomes the new input. 10510 * @param {Number} [index] - If defined and the current value is an array, the value 10511 * at `index` become the new input. 10512 * @since 2.7.0 10513 */ 10514 resolve: function(inputs, context, index) { 10515 var i, ilen, value; 10516 10517 for (i = 0, ilen = inputs.length; i < ilen; ++i) { 10518 value = inputs[i]; 10519 if (value === undefined) { 10520 continue; 10521 } 10522 if (context !== undefined && typeof value === 'function') { 10523 value = value(context); 10524 } 10525 if (index !== undefined && helpers.isArray(value)) { 10526 value = value[index]; 10527 } 10528 if (value !== undefined) { 10529 return value; 10530 } 10531 } 10532 } 10533 }; 10534 10535 },{"43":43}],46:[function(require,module,exports){ 10536 'use strict'; 10537 10538 module.exports = require(43); 10539 module.exports.easing = require(44); 10540 module.exports.canvas = require(42); 10541 module.exports.options = require(45); 10542 10543 },{"42":42,"43":43,"44":44,"45":45}],47:[function(require,module,exports){ 10544 /** 10545 * Platform fallback implementation (minimal). 10546 * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 10547 */ 10548 10549 module.exports = { 10550 acquireContext: function(item) { 10551 if (item && item.canvas) { 10552 // Support for any object associated to a canvas (including a context2d) 10553 item = item.canvas; 10554 } 10555 10556 return item && item.getContext('2d') || null; 10557 } 10558 }; 10559 10560 },{}],48:[function(require,module,exports){ 10561 /** 10562 * Chart.Platform implementation for targeting a web browser 10563 */ 10564 10565 'use strict'; 10566 10567 var helpers = require(46); 10568 10569 var EXPANDO_KEY = '$chartjs'; 10570 var CSS_PREFIX = 'chartjs-'; 10571 var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; 10572 var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; 10573 var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; 10574 10575 /** 10576 * DOM event types -> Chart.js event types. 10577 * Note: only events with different types are mapped. 10578 * @see https://developer.mozilla.org/en-US/docs/Web/Events 10579 */ 10580 var EVENT_TYPES = { 10581 touchstart: 'mousedown', 10582 touchmove: 'mousemove', 10583 touchend: 'mouseup', 10584 pointerenter: 'mouseenter', 10585 pointerdown: 'mousedown', 10586 pointermove: 'mousemove', 10587 pointerup: 'mouseup', 10588 pointerleave: 'mouseout', 10589 pointerout: 'mouseout' 10590 }; 10591 10592 /** 10593 * The "used" size is the final value of a dimension property after all calculations have 10594 * been performed. This method uses the computed style of `element` but returns undefined 10595 * if the computed style is not expressed in pixels. That can happen in some cases where 10596 * `element` has a size relative to its parent and this last one is not yet displayed, 10597 * for example because of `display: none` on a parent node. 10598 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value 10599 * @returns {Number} Size in pixels or undefined if unknown. 10600 */ 10601 function readUsedSize(element, property) { 10602 var value = helpers.getStyle(element, property); 10603 var matches = value && value.match(/^(\d+)(\.\d+)?px$/); 10604 return matches ? Number(matches[1]) : undefined; 10605 } 10606 10607 /** 10608 * Initializes the canvas style and render size without modifying the canvas display size, 10609 * since responsiveness is handled by the controller.resize() method. The config is used 10610 * to determine the aspect ratio to apply in case no explicit height has been specified. 10611 */ 10612 function initCanvas(canvas, config) { 10613 var style = canvas.style; 10614 10615 // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it 10616 // returns null or '' if no explicit value has been set to the canvas attribute. 10617 var renderHeight = canvas.getAttribute('height'); 10618 var renderWidth = canvas.getAttribute('width'); 10619 10620 // Chart.js modifies some canvas values that we want to restore on destroy 10621 canvas[EXPANDO_KEY] = { 10622 initial: { 10623 height: renderHeight, 10624 width: renderWidth, 10625 style: { 10626 display: style.display, 10627 height: style.height, 10628 width: style.width 10629 } 10630 } 10631 }; 10632 10633 // Force canvas to display as block to avoid extra space caused by inline 10634 // elements, which would interfere with the responsive resize process. 10635 // https://github.com/chartjs/Chart.js/issues/2538 10636 style.display = style.display || 'block'; 10637 10638 if (renderWidth === null || renderWidth === '') { 10639 var displayWidth = readUsedSize(canvas, 'width'); 10640 if (displayWidth !== undefined) { 10641 canvas.width = displayWidth; 10642 } 10643 } 10644 10645 if (renderHeight === null || renderHeight === '') { 10646 if (canvas.style.height === '') { 10647 // If no explicit render height and style height, let's apply the aspect ratio, 10648 // which one can be specified by the user but also by charts as default option 10649 // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. 10650 canvas.height = canvas.width / (config.options.aspectRatio || 2); 10651 } else { 10652 var displayHeight = readUsedSize(canvas, 'height'); 10653 if (displayWidth !== undefined) { 10654 canvas.height = displayHeight; 10655 } 10656 } 10657 } 10658 10659 return canvas; 10660 } 10661 10662 /** 10663 * Detects support for options object argument in addEventListener. 10664 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support 10665 * @private 10666 */ 10667 var supportsEventListenerOptions = (function() { 10668 var supports = false; 10669 try { 10670 var options = Object.defineProperty({}, 'passive', { 10671 get: function() { 10672 supports = true; 10673 } 10674 }); 10675 window.addEventListener('e', null, options); 10676 } catch (e) { 10677 // continue regardless of error 10678 } 10679 return supports; 10680 }()); 10681 10682 // Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. 10683 // https://github.com/chartjs/Chart.js/issues/4287 10684 var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; 10685 10686 function addEventListener(node, type, listener) { 10687 node.addEventListener(type, listener, eventListenerOptions); 10688 } 10689 10690 function removeEventListener(node, type, listener) { 10691 node.removeEventListener(type, listener, eventListenerOptions); 10692 } 10693 10694 function createEvent(type, chart, x, y, nativeEvent) { 10695 return { 10696 type: type, 10697 chart: chart, 10698 native: nativeEvent || null, 10699 x: x !== undefined ? x : null, 10700 y: y !== undefined ? y : null, 10701 }; 10702 } 10703 10704 function fromNativeEvent(event, chart) { 10705 var type = EVENT_TYPES[event.type] || event.type; 10706 var pos = helpers.getRelativePosition(event, chart); 10707 return createEvent(type, chart, pos.x, pos.y, event); 10708 } 10709 10710 function throttled(fn, thisArg) { 10711 var ticking = false; 10712 var args = []; 10713 10714 return function() { 10715 args = Array.prototype.slice.call(arguments); 10716 thisArg = thisArg || this; 10717 10718 if (!ticking) { 10719 ticking = true; 10720 helpers.requestAnimFrame.call(window, function() { 10721 ticking = false; 10722 fn.apply(thisArg, args); 10723 }); 10724 } 10725 }; 10726 } 10727 10728 // Implementation based on https://github.com/marcj/css-element-queries 10729 function createResizer(handler) { 10730 var resizer = document.createElement('div'); 10731 var cls = CSS_PREFIX + 'size-monitor'; 10732 var maxSize = 1000000; 10733 var style = 10734 'position:absolute;' + 10735 'left:0;' + 10736 'top:0;' + 10737 'right:0;' + 10738 'bottom:0;' + 10739 'overflow:hidden;' + 10740 'pointer-events:none;' + 10741 'visibility:hidden;' + 10742 'z-index:-1;'; 10743 10744 resizer.style.cssText = style; 10745 resizer.className = cls; 10746 resizer.innerHTML = 10747 '<div class="' + cls + '-expand" style="' + style + '">' + 10748 '<div style="' + 10749 'position:absolute;' + 10750 'width:' + maxSize + 'px;' + 10751 'height:' + maxSize + 'px;' + 10752 'left:0;' + 10753 'top:0">' + 10754 '</div>' + 10755 '</div>' + 10756 '<div class="' + cls + '-shrink" style="' + style + '">' + 10757 '<div style="' + 10758 'position:absolute;' + 10759 'width:200%;' + 10760 'height:200%;' + 10761 'left:0; ' + 10762 'top:0">' + 10763 '</div>' + 10764 '</div>'; 10765 10766 var expand = resizer.childNodes[0]; 10767 var shrink = resizer.childNodes[1]; 10768 10769 resizer._reset = function() { 10770 expand.scrollLeft = maxSize; 10771 expand.scrollTop = maxSize; 10772 shrink.scrollLeft = maxSize; 10773 shrink.scrollTop = maxSize; 10774 }; 10775 var onScroll = function() { 10776 resizer._reset(); 10777 handler(); 10778 }; 10779 10780 addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand')); 10781 addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); 10782 10783 return resizer; 10784 } 10785 10786 // https://davidwalsh.name/detect-node-insertion 10787 function watchForRender(node, handler) { 10788 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); 10789 var proxy = expando.renderProxy = function(e) { 10790 if (e.animationName === CSS_RENDER_ANIMATION) { 10791 handler(); 10792 } 10793 }; 10794 10795 helpers.each(ANIMATION_START_EVENTS, function(type) { 10796 addEventListener(node, type, proxy); 10797 }); 10798 10799 // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class 10800 // is removed then added back immediately (same animation frame?). Accessing the 10801 // `offsetParent` property will force a reflow and re-evaluate the CSS animation. 10802 // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics 10803 // https://github.com/chartjs/Chart.js/issues/4737 10804 expando.reflow = !!node.offsetParent; 10805 10806 node.classList.add(CSS_RENDER_MONITOR); 10807 } 10808 10809 function unwatchForRender(node) { 10810 var expando = node[EXPANDO_KEY] || {}; 10811 var proxy = expando.renderProxy; 10812 10813 if (proxy) { 10814 helpers.each(ANIMATION_START_EVENTS, function(type) { 10815 removeEventListener(node, type, proxy); 10816 }); 10817 10818 delete expando.renderProxy; 10819 } 10820 10821 node.classList.remove(CSS_RENDER_MONITOR); 10822 } 10823 10824 function addResizeListener(node, listener, chart) { 10825 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); 10826 10827 // Let's keep track of this added resizer and thus avoid DOM query when removing it. 10828 var resizer = expando.resizer = createResizer(throttled(function() { 10829 if (expando.resizer) { 10830 return listener(createEvent('resize', chart)); 10831 } 10832 })); 10833 10834 // The resizer needs to be attached to the node parent, so we first need to be 10835 // sure that `node` is attached to the DOM before injecting the resizer element. 10836 watchForRender(node, function() { 10837 if (expando.resizer) { 10838 var container = node.parentNode; 10839 if (container && container !== resizer.parentNode) { 10840 container.insertBefore(resizer, container.firstChild); 10841 } 10842 10843 // The container size might have changed, let's reset the resizer state. 10844 resizer._reset(); 10845 } 10846 }); 10847 } 10848 10849 function removeResizeListener(node) { 10850 var expando = node[EXPANDO_KEY] || {}; 10851 var resizer = expando.resizer; 10852 10853 delete expando.resizer; 10854 unwatchForRender(node); 10855 10856 if (resizer && resizer.parentNode) { 10857 resizer.parentNode.removeChild(resizer); 10858 } 10859 } 10860 10861 function injectCSS(platform, css) { 10862 // http://stackoverflow.com/q/3922139 10863 var style = platform._style || document.createElement('style'); 10864 if (!platform._style) { 10865 platform._style = style; 10866 css = '/* Chart.js */\n' + css; 10867 style.setAttribute('type', 'text/css'); 10868 document.getElementsByTagName('head')[0].appendChild(style); 10869 } 10870 10871 style.appendChild(document.createTextNode(css)); 10872 } 10873 10874 module.exports = { 10875 /** 10876 * This property holds whether this platform is enabled for the current environment. 10877 * Currently used by platform.js to select the proper implementation. 10878 * @private 10879 */ 10880 _enabled: typeof window !== 'undefined' && typeof document !== 'undefined', 10881 10882 initialize: function() { 10883 var keyframes = 'from{opacity:0.99}to{opacity:1}'; 10884 10885 injectCSS(this, 10886 // DOM rendering detection 10887 // https://davidwalsh.name/detect-node-insertion 10888 '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + 10889 '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + 10890 '.' + CSS_RENDER_MONITOR + '{' + 10891 '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + 10892 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + 10893 '}' 10894 ); 10895 }, 10896 10897 acquireContext: function(item, config) { 10898 if (typeof item === 'string') { 10899 item = document.getElementById(item); 10900 } else if (item.length) { 10901 // Support for array based queries (such as jQuery) 10902 item = item[0]; 10903 } 10904 10905 if (item && item.canvas) { 10906 // Support for any object associated to a canvas (including a context2d) 10907 item = item.canvas; 10908 } 10909 10910 // To prevent canvas fingerprinting, some add-ons undefine the getContext 10911 // method, for example: https://github.com/kkapsner/CanvasBlocker 10912 // https://github.com/chartjs/Chart.js/issues/2807 10913 var context = item && item.getContext && item.getContext('2d'); 10914 10915 // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is 10916 // inside an iframe or when running in a protected environment. We could guess the 10917 // types from their toString() value but let's keep things flexible and assume it's 10918 // a sufficient condition if the item has a context2D which has item as `canvas`. 10919 // https://github.com/chartjs/Chart.js/issues/3887 10920 // https://github.com/chartjs/Chart.js/issues/4102 10921 // https://github.com/chartjs/Chart.js/issues/4152 10922 if (context && context.canvas === item) { 10923 initCanvas(item, config); 10924 return context; 10925 } 10926 10927 return null; 10928 }, 10929 10930 releaseContext: function(context) { 10931 var canvas = context.canvas; 10932 if (!canvas[EXPANDO_KEY]) { 10933 return; 10934 } 10935 10936 var initial = canvas[EXPANDO_KEY].initial; 10937 ['height', 'width'].forEach(function(prop) { 10938 var value = initial[prop]; 10939 if (helpers.isNullOrUndef(value)) { 10940 canvas.removeAttribute(prop); 10941 } else { 10942 canvas.setAttribute(prop, value); 10943 } 10944 }); 10945 10946 helpers.each(initial.style || {}, function(value, key) { 10947 canvas.style[key] = value; 10948 }); 10949 10950 // The canvas render size might have been changed (and thus the state stack discarded), 10951 // we can't use save() and restore() to restore the initial state. So make sure that at 10952 // least the canvas context is reset to the default state by setting the canvas width. 10953 // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html 10954 canvas.width = canvas.width; 10955 10956 delete canvas[EXPANDO_KEY]; 10957 }, 10958 10959 addEventListener: function(chart, type, listener) { 10960 var canvas = chart.canvas; 10961 if (type === 'resize') { 10962 // Note: the resize event is not supported on all browsers. 10963 addResizeListener(canvas, listener, chart); 10964 return; 10965 } 10966 10967 var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); 10968 var proxies = expando.proxies || (expando.proxies = {}); 10969 var proxy = proxies[chart.id + '_' + type] = function(event) { 10970 listener(fromNativeEvent(event, chart)); 10971 }; 10972 10973 addEventListener(canvas, type, proxy); 10974 }, 10975 10976 removeEventListener: function(chart, type, listener) { 10977 var canvas = chart.canvas; 10978 if (type === 'resize') { 10979 // Note: the resize event is not supported on all browsers. 10980 removeResizeListener(canvas, listener); 10981 return; 10982 } 10983 10984 var expando = listener[EXPANDO_KEY] || {}; 10985 var proxies = expando.proxies || {}; 10986 var proxy = proxies[chart.id + '_' + type]; 10987 if (!proxy) { 10988 return; 10989 } 10990 10991 removeEventListener(canvas, type, proxy); 10992 } 10993 }; 10994 10995 // DEPRECATIONS 10996 10997 /** 10998 * Provided for backward compatibility, use EventTarget.addEventListener instead. 10999 * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ 11000 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener 11001 * @function Chart.helpers.addEvent 11002 * @deprecated since version 2.7.0 11003 * @todo remove at version 3 11004 * @private 11005 */ 11006 helpers.addEvent = addEventListener; 11007 11008 /** 11009 * Provided for backward compatibility, use EventTarget.removeEventListener instead. 11010 * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ 11011 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener 11012 * @function Chart.helpers.removeEvent 11013 * @deprecated since version 2.7.0 11014 * @todo remove at version 3 11015 * @private 11016 */ 11017 helpers.removeEvent = removeEventListener; 11018 11019 },{"46":46}],49:[function(require,module,exports){ 11020 'use strict'; 11021 11022 var helpers = require(46); 11023 var basic = require(47); 11024 var dom = require(48); 11025 11026 // @TODO Make possible to select another platform at build time. 11027 var implementation = dom._enabled ? dom : basic; 11028 11029 /** 11030 * @namespace Chart.platform 11031 * @see https://chartjs.gitbooks.io/proposals/content/Platform.html 11032 * @since 2.4.0 11033 */ 11034 module.exports = helpers.extend({ 11035 /** 11036 * @since 2.7.0 11037 */ 11038 initialize: function() {}, 11039 11040 /** 11041 * Called at chart construction time, returns a context2d instance implementing 11042 * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. 11043 * @param {*} item - The native item from which to acquire context (platform specific) 11044 * @param {Object} options - The chart options 11045 * @returns {CanvasRenderingContext2D} context2d instance 11046 */ 11047 acquireContext: function() {}, 11048 11049 /** 11050 * Called at chart destruction time, releases any resources associated to the context 11051 * previously returned by the acquireContext() method. 11052 * @param {CanvasRenderingContext2D} context - The context2d instance 11053 * @returns {Boolean} true if the method succeeded, else false 11054 */ 11055 releaseContext: function() {}, 11056 11057 /** 11058 * Registers the specified listener on the given chart. 11059 * @param {Chart} chart - Chart from which to listen for event 11060 * @param {String} type - The ({@link IEvent}) type to listen for 11061 * @param {Function} listener - Receives a notification (an object that implements 11062 * the {@link IEvent} interface) when an event of the specified type occurs. 11063 */ 11064 addEventListener: function() {}, 11065 11066 /** 11067 * Removes the specified listener previously registered with addEventListener. 11068 * @param {Chart} chart -Chart from which to remove the listener 11069 * @param {String} type - The ({@link IEvent}) type to remove 11070 * @param {Function} listener - The listener function to remove from the event target. 11071 */ 11072 removeEventListener: function() {} 11073 11074 }, implementation); 11075 11076 /** 11077 * @interface IPlatform 11078 * Allows abstracting platform dependencies away from the chart 11079 * @borrows Chart.platform.acquireContext as acquireContext 11080 * @borrows Chart.platform.releaseContext as releaseContext 11081 * @borrows Chart.platform.addEventListener as addEventListener 11082 * @borrows Chart.platform.removeEventListener as removeEventListener 11083 */ 11084 11085 /** 11086 * @interface IEvent 11087 * @prop {String} type - The event type name, possible values are: 11088 * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout', 11089 * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize' 11090 * @prop {*} native - The original native event (null for emulated events, e.g. 'resize') 11091 * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events) 11092 * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events) 11093 */ 11094 11095 },{"46":46,"47":47,"48":48}],50:[function(require,module,exports){ 11096 'use strict'; 11097 11098 module.exports = {}; 11099 module.exports.filler = require(51); 11100 module.exports.legend = require(52); 11101 module.exports.title = require(53); 11102 11103 },{"51":51,"52":52,"53":53}],51:[function(require,module,exports){ 11104 /** 11105 * Plugin based on discussion from the following Chart.js issues: 11106 * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569 11107 * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897 11108 */ 11109 11110 'use strict'; 11111 11112 var defaults = require(26); 11113 var elements = require(41); 11114 var helpers = require(46); 11115 11116 defaults._set('global', { 11117 plugins: { 11118 filler: { 11119 propagate: true 11120 } 11121 } 11122 }); 11123 11124 var mappers = { 11125 dataset: function(source) { 11126 var index = source.fill; 11127 var chart = source.chart; 11128 var meta = chart.getDatasetMeta(index); 11129 var visible = meta && chart.isDatasetVisible(index); 11130 var points = (visible && meta.dataset._children) || []; 11131 var length = points.length || 0; 11132 11133 return !length ? null : function(point, i) { 11134 return (i < length && points[i]._view) || null; 11135 }; 11136 }, 11137 11138 boundary: function(source) { 11139 var boundary = source.boundary; 11140 var x = boundary ? boundary.x : null; 11141 var y = boundary ? boundary.y : null; 11142 11143 return function(point) { 11144 return { 11145 x: x === null ? point.x : x, 11146 y: y === null ? point.y : y, 11147 }; 11148 }; 11149 } 11150 }; 11151 11152 // @todo if (fill[0] === '#') 11153 function decodeFill(el, index, count) { 11154 var model = el._model || {}; 11155 var fill = model.fill; 11156 var target; 11157 11158 if (fill === undefined) { 11159 fill = !!model.backgroundColor; 11160 } 11161 11162 if (fill === false || fill === null) { 11163 return false; 11164 } 11165 11166 if (fill === true) { 11167 return 'origin'; 11168 } 11169 11170 target = parseFloat(fill, 10); 11171 if (isFinite(target) && Math.floor(target) === target) { 11172 if (fill[0] === '-' || fill[0] === '+') { 11173 target = index + target; 11174 } 11175 11176 if (target === index || target < 0 || target >= count) { 11177 return false; 11178 } 11179 11180 return target; 11181 } 11182 11183 switch (fill) { 11184 // compatibility 11185 case 'bottom': 11186 return 'start'; 11187 case 'top': 11188 return 'end'; 11189 case 'zero': 11190 return 'origin'; 11191 // supported boundaries 11192 case 'origin': 11193 case 'start': 11194 case 'end': 11195 return fill; 11196 // invalid fill values 11197 default: 11198 return false; 11199 } 11200 } 11201 11202 function computeBoundary(source) { 11203 var model = source.el._model || {}; 11204 var scale = source.el._scale || {}; 11205 var fill = source.fill; 11206 var target = null; 11207 var horizontal; 11208 11209 if (isFinite(fill)) { 11210 return null; 11211 } 11212 11213 // Backward compatibility: until v3, we still need to support boundary values set on 11214 // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and 11215 // controllers might still use it (e.g. the Smith chart). 11216 11217 if (fill === 'start') { 11218 target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; 11219 } else if (fill === 'end') { 11220 target = model.scaleTop === undefined ? scale.top : model.scaleTop; 11221 } else if (model.scaleZero !== undefined) { 11222 target = model.scaleZero; 11223 } else if (scale.getBasePosition) { 11224 target = scale.getBasePosition(); 11225 } else if (scale.getBasePixel) { 11226 target = scale.getBasePixel(); 11227 } 11228 11229 if (target !== undefined && target !== null) { 11230 if (target.x !== undefined && target.y !== undefined) { 11231 return target; 11232 } 11233 11234 if (typeof target === 'number' && isFinite(target)) { 11235 horizontal = scale.isHorizontal(); 11236 return { 11237 x: horizontal ? target : null, 11238 y: horizontal ? null : target 11239 }; 11240 } 11241 } 11242 11243 return null; 11244 } 11245 11246 function resolveTarget(sources, index, propagate) { 11247 var source = sources[index]; 11248 var fill = source.fill; 11249 var visited = [index]; 11250 var target; 11251 11252 if (!propagate) { 11253 return fill; 11254 } 11255 11256 while (fill !== false && visited.indexOf(fill) === -1) { 11257 if (!isFinite(fill)) { 11258 return fill; 11259 } 11260 11261 target = sources[fill]; 11262 if (!target) { 11263 return false; 11264 } 11265 11266 if (target.visible) { 11267 return fill; 11268 } 11269 11270 visited.push(fill); 11271 fill = target.fill; 11272 } 11273 11274 return false; 11275 } 11276 11277 function createMapper(source) { 11278 var fill = source.fill; 11279 var type = 'dataset'; 11280 11281 if (fill === false) { 11282 return null; 11283 } 11284 11285 if (!isFinite(fill)) { 11286 type = 'boundary'; 11287 } 11288 11289 return mappers[type](source); 11290 } 11291 11292 function isDrawable(point) { 11293 return point && !point.skip; 11294 } 11295 11296 function drawArea(ctx, curve0, curve1, len0, len1) { 11297 var i; 11298 11299 if (!len0 || !len1) { 11300 return; 11301 } 11302 11303 // building first area curve (normal) 11304 ctx.moveTo(curve0[0].x, curve0[0].y); 11305 for (i = 1; i < len0; ++i) { 11306 helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); 11307 } 11308 11309 // joining the two area curves 11310 ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); 11311 11312 // building opposite area curve (reverse) 11313 for (i = len1 - 1; i > 0; --i) { 11314 helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); 11315 } 11316 } 11317 11318 function doFill(ctx, points, mapper, view, color, loop) { 11319 var count = points.length; 11320 var span = view.spanGaps; 11321 var curve0 = []; 11322 var curve1 = []; 11323 var len0 = 0; 11324 var len1 = 0; 11325 var i, ilen, index, p0, p1, d0, d1; 11326 11327 ctx.beginPath(); 11328 11329 for (i = 0, ilen = (count + !!loop); i < ilen; ++i) { 11330 index = i % count; 11331 p0 = points[index]._view; 11332 p1 = mapper(p0, index, view); 11333 d0 = isDrawable(p0); 11334 d1 = isDrawable(p1); 11335 11336 if (d0 && d1) { 11337 len0 = curve0.push(p0); 11338 len1 = curve1.push(p1); 11339 } else if (len0 && len1) { 11340 if (!span) { 11341 drawArea(ctx, curve0, curve1, len0, len1); 11342 len0 = len1 = 0; 11343 curve0 = []; 11344 curve1 = []; 11345 } else { 11346 if (d0) { 11347 curve0.push(p0); 11348 } 11349 if (d1) { 11350 curve1.push(p1); 11351 } 11352 } 11353 } 11354 } 11355 11356 drawArea(ctx, curve0, curve1, len0, len1); 11357 11358 ctx.closePath(); 11359 ctx.fillStyle = color; 11360 ctx.fill(); 11361 } 11362 11363 module.exports = { 11364 id: 'filler', 11365 11366 afterDatasetsUpdate: function(chart, options) { 11367 var count = (chart.data.datasets || []).length; 11368 var propagate = options.propagate; 11369 var sources = []; 11370 var meta, i, el, source; 11371 11372 for (i = 0; i < count; ++i) { 11373 meta = chart.getDatasetMeta(i); 11374 el = meta.dataset; 11375 source = null; 11376 11377 if (el && el._model && el instanceof elements.Line) { 11378 source = { 11379 visible: chart.isDatasetVisible(i), 11380 fill: decodeFill(el, i, count), 11381 chart: chart, 11382 el: el 11383 }; 11384 } 11385 11386 meta.$filler = source; 11387 sources.push(source); 11388 } 11389 11390 for (i = 0; i < count; ++i) { 11391 source = sources[i]; 11392 if (!source) { 11393 continue; 11394 } 11395 11396 source.fill = resolveTarget(sources, i, propagate); 11397 source.boundary = computeBoundary(source); 11398 source.mapper = createMapper(source); 11399 } 11400 }, 11401 11402 beforeDatasetDraw: function(chart, args) { 11403 var meta = args.meta.$filler; 11404 if (!meta) { 11405 return; 11406 } 11407 11408 var ctx = chart.ctx; 11409 var el = meta.el; 11410 var view = el._view; 11411 var points = el._children || []; 11412 var mapper = meta.mapper; 11413 var color = view.backgroundColor || defaults.global.defaultColor; 11414 11415 if (mapper && color && points.length) { 11416 helpers.canvas.clipArea(ctx, chart.chartArea); 11417 doFill(ctx, points, mapper, view, color, el._loop); 11418 helpers.canvas.unclipArea(ctx); 11419 } 11420 } 11421 }; 11422 11423 },{"26":26,"41":41,"46":46}],52:[function(require,module,exports){ 11424 'use strict'; 11425 11426 var defaults = require(26); 11427 var Element = require(27); 11428 var helpers = require(46); 11429 var layouts = require(31); 11430 11431 var noop = helpers.noop; 11432 11433 defaults._set('global', { 11434 legend: { 11435 display: true, 11436 position: 'top', 11437 fullWidth: true, 11438 reverse: false, 11439 weight: 1000, 11440 11441 // a callback that will handle 11442 onClick: function(e, legendItem) { 11443 var index = legendItem.datasetIndex; 11444 var ci = this.chart; 11445 var meta = ci.getDatasetMeta(index); 11446 11447 // See controller.isDatasetVisible comment 11448 meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; 11449 11450 // We hid a dataset ... rerender the chart 11451 ci.update(); 11452 }, 11453 11454 onHover: null, 11455 11456 labels: { 11457 boxWidth: 40, 11458 padding: 10, 11459 // Generates labels shown in the legend 11460 // Valid properties to return: 11461 // text : text to display 11462 // fillStyle : fill of coloured box 11463 // strokeStyle: stroke of coloured box 11464 // hidden : if this legend item refers to a hidden item 11465 // lineCap : cap style for line 11466 // lineDash 11467 // lineDashOffset : 11468 // lineJoin : 11469 // lineWidth : 11470 generateLabels: function(chart) { 11471 var data = chart.data; 11472 return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) { 11473 return { 11474 text: dataset.label, 11475 fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), 11476 hidden: !chart.isDatasetVisible(i), 11477 lineCap: dataset.borderCapStyle, 11478 lineDash: dataset.borderDash, 11479 lineDashOffset: dataset.borderDashOffset, 11480 lineJoin: dataset.borderJoinStyle, 11481 lineWidth: dataset.borderWidth, 11482 strokeStyle: dataset.borderColor, 11483 pointStyle: dataset.pointStyle, 11484 11485 // Below is extra data used for toggling the datasets 11486 datasetIndex: i 11487 }; 11488 }, this) : []; 11489 } 11490 } 11491 }, 11492 11493 legendCallback: function(chart) { 11494 var text = []; 11495 text.push('<ul class="' + chart.id + '-legend">'); 11496 for (var i = 0; i < chart.data.datasets.length; i++) { 11497 text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>'); 11498 if (chart.data.datasets[i].label) { 11499 text.push(chart.data.datasets[i].label); 11500 } 11501 text.push('</li>'); 11502 } 11503 text.push('</ul>'); 11504 return text.join(''); 11505 } 11506 }); 11507 11508 /** 11509 * Helper function to get the box width based on the usePointStyle option 11510 * @param labelopts {Object} the label options on the legend 11511 * @param fontSize {Number} the label font size 11512 * @return {Number} width of the color box area 11513 */ 11514 function getBoxWidth(labelOpts, fontSize) { 11515 return labelOpts.usePointStyle ? 11516 fontSize * Math.SQRT2 : 11517 labelOpts.boxWidth; 11518 } 11519 11520 /** 11521 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! 11522 */ 11523 var Legend = Element.extend({ 11524 11525 initialize: function(config) { 11526 helpers.extend(this, config); 11527 11528 // Contains hit boxes for each dataset (in dataset order) 11529 this.legendHitBoxes = []; 11530 11531 // Are we in doughnut mode which has a different data type 11532 this.doughnutMode = false; 11533 }, 11534 11535 // These methods are ordered by lifecycle. Utilities then follow. 11536 // Any function defined here is inherited by all legend types. 11537 // Any function can be extended by the legend type 11538 11539 beforeUpdate: noop, 11540 update: function(maxWidth, maxHeight, margins) { 11541 var me = this; 11542 11543 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 11544 me.beforeUpdate(); 11545 11546 // Absorb the master measurements 11547 me.maxWidth = maxWidth; 11548 me.maxHeight = maxHeight; 11549 me.margins = margins; 11550 11551 // Dimensions 11552 me.beforeSetDimensions(); 11553 me.setDimensions(); 11554 me.afterSetDimensions(); 11555 // Labels 11556 me.beforeBuildLabels(); 11557 me.buildLabels(); 11558 me.afterBuildLabels(); 11559 11560 // Fit 11561 me.beforeFit(); 11562 me.fit(); 11563 me.afterFit(); 11564 // 11565 me.afterUpdate(); 11566 11567 return me.minSize; 11568 }, 11569 afterUpdate: noop, 11570 11571 // 11572 11573 beforeSetDimensions: noop, 11574 setDimensions: function() { 11575 var me = this; 11576 // Set the unconstrained dimension before label rotation 11577 if (me.isHorizontal()) { 11578 // Reset position before calculating rotation 11579 me.width = me.maxWidth; 11580 me.left = 0; 11581 me.right = me.width; 11582 } else { 11583 me.height = me.maxHeight; 11584 11585 // Reset position before calculating rotation 11586 me.top = 0; 11587 me.bottom = me.height; 11588 } 11589 11590 // Reset padding 11591 me.paddingLeft = 0; 11592 me.paddingTop = 0; 11593 me.paddingRight = 0; 11594 me.paddingBottom = 0; 11595 11596 // Reset minSize 11597 me.minSize = { 11598 width: 0, 11599 height: 0 11600 }; 11601 }, 11602 afterSetDimensions: noop, 11603 11604 // 11605 11606 beforeBuildLabels: noop, 11607 buildLabels: function() { 11608 var me = this; 11609 var labelOpts = me.options.labels || {}; 11610 var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || []; 11611 11612 if (labelOpts.filter) { 11613 legendItems = legendItems.filter(function(item) { 11614 return labelOpts.filter(item, me.chart.data); 11615 }); 11616 } 11617 11618 if (me.options.reverse) { 11619 legendItems.reverse(); 11620 } 11621 11622 me.legendItems = legendItems; 11623 }, 11624 afterBuildLabels: noop, 11625 11626 // 11627 11628 beforeFit: noop, 11629 fit: function() { 11630 var me = this; 11631 var opts = me.options; 11632 var labelOpts = opts.labels; 11633 var display = opts.display; 11634 11635 var ctx = me.ctx; 11636 11637 var globalDefault = defaults.global; 11638 var valueOrDefault = helpers.valueOrDefault; 11639 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); 11640 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); 11641 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); 11642 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); 11643 11644 // Reset hit boxes 11645 var hitboxes = me.legendHitBoxes = []; 11646 11647 var minSize = me.minSize; 11648 var isHorizontal = me.isHorizontal(); 11649 11650 if (isHorizontal) { 11651 minSize.width = me.maxWidth; // fill all the width 11652 minSize.height = display ? 10 : 0; 11653 } else { 11654 minSize.width = display ? 10 : 0; 11655 minSize.height = me.maxHeight; // fill all the height 11656 } 11657 11658 // Increase sizes here 11659 if (display) { 11660 ctx.font = labelFont; 11661 11662 if (isHorizontal) { 11663 // Labels 11664 11665 // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one 11666 var lineWidths = me.lineWidths = [0]; 11667 var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0; 11668 11669 ctx.textAlign = 'left'; 11670 ctx.textBaseline = 'top'; 11671 11672 helpers.each(me.legendItems, function(legendItem, i) { 11673 var boxWidth = getBoxWidth(labelOpts, fontSize); 11674 var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; 11675 11676 if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) { 11677 totalHeight += fontSize + (labelOpts.padding); 11678 lineWidths[lineWidths.length] = me.left; 11679 } 11680 11681 // Store the hitbox width and height here. Final position will be updated in `draw` 11682 hitboxes[i] = { 11683 left: 0, 11684 top: 0, 11685 width: width, 11686 height: fontSize 11687 }; 11688 11689 lineWidths[lineWidths.length - 1] += width + labelOpts.padding; 11690 }); 11691 11692 minSize.height += totalHeight; 11693 11694 } else { 11695 var vPadding = labelOpts.padding; 11696 var columnWidths = me.columnWidths = []; 11697 var totalWidth = labelOpts.padding; 11698 var currentColWidth = 0; 11699 var currentColHeight = 0; 11700 var itemHeight = fontSize + vPadding; 11701 11702 helpers.each(me.legendItems, function(legendItem, i) { 11703 var boxWidth = getBoxWidth(labelOpts, fontSize); 11704 var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; 11705 11706 // If too tall, go to new column 11707 if (currentColHeight + itemHeight > minSize.height) { 11708 totalWidth += currentColWidth + labelOpts.padding; 11709 columnWidths.push(currentColWidth); // previous column width 11710 11711 currentColWidth = 0; 11712 currentColHeight = 0; 11713 } 11714 11715 // Get max width 11716 currentColWidth = Math.max(currentColWidth, itemWidth); 11717 currentColHeight += itemHeight; 11718 11719 // Store the hitbox width and height here. Final position will be updated in `draw` 11720 hitboxes[i] = { 11721 left: 0, 11722 top: 0, 11723 width: itemWidth, 11724 height: fontSize 11725 }; 11726 }); 11727 11728 totalWidth += currentColWidth; 11729 columnWidths.push(currentColWidth); 11730 minSize.width += totalWidth; 11731 } 11732 } 11733 11734 me.width = minSize.width; 11735 me.height = minSize.height; 11736 }, 11737 afterFit: noop, 11738 11739 // Shared Methods 11740 isHorizontal: function() { 11741 return this.options.position === 'top' || this.options.position === 'bottom'; 11742 }, 11743 11744 // Actually draw the legend on the canvas 11745 draw: function() { 11746 var me = this; 11747 var opts = me.options; 11748 var labelOpts = opts.labels; 11749 var globalDefault = defaults.global; 11750 var lineDefault = globalDefault.elements.line; 11751 var legendWidth = me.width; 11752 var lineWidths = me.lineWidths; 11753 11754 if (opts.display) { 11755 var ctx = me.ctx; 11756 var valueOrDefault = helpers.valueOrDefault; 11757 var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor); 11758 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); 11759 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); 11760 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); 11761 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); 11762 var cursor; 11763 11764 // Canvas setup 11765 ctx.textAlign = 'left'; 11766 ctx.textBaseline = 'middle'; 11767 ctx.lineWidth = 0.5; 11768 ctx.strokeStyle = fontColor; // for strikethrough effect 11769 ctx.fillStyle = fontColor; // render in correct colour 11770 ctx.font = labelFont; 11771 11772 var boxWidth = getBoxWidth(labelOpts, fontSize); 11773 var hitboxes = me.legendHitBoxes; 11774 11775 // current position 11776 var drawLegendBox = function(x, y, legendItem) { 11777 if (isNaN(boxWidth) || boxWidth <= 0) { 11778 return; 11779 } 11780 11781 // Set the ctx for the box 11782 ctx.save(); 11783 11784 ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor); 11785 ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); 11786 ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); 11787 ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); 11788 ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth); 11789 ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor); 11790 var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0); 11791 11792 if (ctx.setLineDash) { 11793 // IE 9 and 10 do not support line dash 11794 ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash)); 11795 } 11796 11797 if (opts.labels && opts.labels.usePointStyle) { 11798 // Recalculate x and y for drawPoint() because its expecting 11799 // x and y to be center of figure (instead of top left) 11800 var radius = fontSize * Math.SQRT2 / 2; 11801 var offSet = radius / Math.SQRT2; 11802 var centerX = x + offSet; 11803 var centerY = y + offSet; 11804 11805 // Draw pointStyle as legend symbol 11806 helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); 11807 } else { 11808 // Draw box as legend symbol 11809 if (!isLineWidthZero) { 11810 ctx.strokeRect(x, y, boxWidth, fontSize); 11811 } 11812 ctx.fillRect(x, y, boxWidth, fontSize); 11813 } 11814 11815 ctx.restore(); 11816 }; 11817 var fillText = function(x, y, legendItem, textWidth) { 11818 var halfFontSize = fontSize / 2; 11819 var xLeft = boxWidth + halfFontSize + x; 11820 var yMiddle = y + halfFontSize; 11821 11822 ctx.fillText(legendItem.text, xLeft, yMiddle); 11823 11824 if (legendItem.hidden) { 11825 // Strikethrough the text if hidden 11826 ctx.beginPath(); 11827 ctx.lineWidth = 2; 11828 ctx.moveTo(xLeft, yMiddle); 11829 ctx.lineTo(xLeft + textWidth, yMiddle); 11830 ctx.stroke(); 11831 } 11832 }; 11833 11834 // Horizontal 11835 var isHorizontal = me.isHorizontal(); 11836 if (isHorizontal) { 11837 cursor = { 11838 x: me.left + ((legendWidth - lineWidths[0]) / 2), 11839 y: me.top + labelOpts.padding, 11840 line: 0 11841 }; 11842 } else { 11843 cursor = { 11844 x: me.left + labelOpts.padding, 11845 y: me.top + labelOpts.padding, 11846 line: 0 11847 }; 11848 } 11849 11850 var itemHeight = fontSize + labelOpts.padding; 11851 helpers.each(me.legendItems, function(legendItem, i) { 11852 var textWidth = ctx.measureText(legendItem.text).width; 11853 var width = boxWidth + (fontSize / 2) + textWidth; 11854 var x = cursor.x; 11855 var y = cursor.y; 11856 11857 if (isHorizontal) { 11858 if (x + width >= legendWidth) { 11859 y = cursor.y += itemHeight; 11860 cursor.line++; 11861 x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2); 11862 } 11863 } else if (y + itemHeight > me.bottom) { 11864 x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; 11865 y = cursor.y = me.top + labelOpts.padding; 11866 cursor.line++; 11867 } 11868 11869 drawLegendBox(x, y, legendItem); 11870 11871 hitboxes[i].left = x; 11872 hitboxes[i].top = y; 11873 11874 // Fill the actual label 11875 fillText(x, y, legendItem, textWidth); 11876 11877 if (isHorizontal) { 11878 cursor.x += width + (labelOpts.padding); 11879 } else { 11880 cursor.y += itemHeight; 11881 } 11882 11883 }); 11884 } 11885 }, 11886 11887 /** 11888 * Handle an event 11889 * @private 11890 * @param {IEvent} event - The event to handle 11891 * @return {Boolean} true if a change occured 11892 */ 11893 handleEvent: function(e) { 11894 var me = this; 11895 var opts = me.options; 11896 var type = e.type === 'mouseup' ? 'click' : e.type; 11897 var changed = false; 11898 11899 if (type === 'mousemove') { 11900 if (!opts.onHover) { 11901 return; 11902 } 11903 } else if (type === 'click') { 11904 if (!opts.onClick) { 11905 return; 11906 } 11907 } else { 11908 return; 11909 } 11910 11911 // Chart event already has relative position in it 11912 var x = e.x; 11913 var y = e.y; 11914 11915 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { 11916 // See if we are touching one of the dataset boxes 11917 var lh = me.legendHitBoxes; 11918 for (var i = 0; i < lh.length; ++i) { 11919 var hitBox = lh[i]; 11920 11921 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { 11922 // Touching an element 11923 if (type === 'click') { 11924 // use e.native for backwards compatibility 11925 opts.onClick.call(me, e.native, me.legendItems[i]); 11926 changed = true; 11927 break; 11928 } else if (type === 'mousemove') { 11929 // use e.native for backwards compatibility 11930 opts.onHover.call(me, e.native, me.legendItems[i]); 11931 changed = true; 11932 break; 11933 } 11934 } 11935 } 11936 } 11937 11938 return changed; 11939 } 11940 }); 11941 11942 function createNewLegendAndAttach(chart, legendOpts) { 11943 var legend = new Legend({ 11944 ctx: chart.ctx, 11945 options: legendOpts, 11946 chart: chart 11947 }); 11948 11949 layouts.configure(chart, legend, legendOpts); 11950 layouts.addBox(chart, legend); 11951 chart.legend = legend; 11952 } 11953 11954 module.exports = { 11955 id: 'legend', 11956 11957 /** 11958 * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making 11959 * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of 11960 * the plugin, which one will be re-exposed in the chart.js file. 11961 * https://github.com/chartjs/Chart.js/pull/2640 11962 * @private 11963 */ 11964 _element: Legend, 11965 11966 beforeInit: function(chart) { 11967 var legendOpts = chart.options.legend; 11968 11969 if (legendOpts) { 11970 createNewLegendAndAttach(chart, legendOpts); 11971 } 11972 }, 11973 11974 beforeUpdate: function(chart) { 11975 var legendOpts = chart.options.legend; 11976 var legend = chart.legend; 11977 11978 if (legendOpts) { 11979 helpers.mergeIf(legendOpts, defaults.global.legend); 11980 11981 if (legend) { 11982 layouts.configure(chart, legend, legendOpts); 11983 legend.options = legendOpts; 11984 } else { 11985 createNewLegendAndAttach(chart, legendOpts); 11986 } 11987 } else if (legend) { 11988 layouts.removeBox(chart, legend); 11989 delete chart.legend; 11990 } 11991 }, 11992 11993 afterEvent: function(chart, e) { 11994 var legend = chart.legend; 11995 if (legend) { 11996 legend.handleEvent(e); 11997 } 11998 } 11999 }; 12000 12001 },{"26":26,"27":27,"31":31,"46":46}],53:[function(require,module,exports){ 12002 'use strict'; 12003 12004 var defaults = require(26); 12005 var Element = require(27); 12006 var helpers = require(46); 12007 var layouts = require(31); 12008 12009 var noop = helpers.noop; 12010 12011 defaults._set('global', { 12012 title: { 12013 display: false, 12014 fontStyle: 'bold', 12015 fullWidth: true, 12016 lineHeight: 1.2, 12017 padding: 10, 12018 position: 'top', 12019 text: '', 12020 weight: 2000 // by default greater than legend (1000) to be above 12021 } 12022 }); 12023 12024 /** 12025 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! 12026 */ 12027 var Title = Element.extend({ 12028 initialize: function(config) { 12029 var me = this; 12030 helpers.extend(me, config); 12031 12032 // Contains hit boxes for each dataset (in dataset order) 12033 me.legendHitBoxes = []; 12034 }, 12035 12036 // These methods are ordered by lifecycle. Utilities then follow. 12037 12038 beforeUpdate: noop, 12039 update: function(maxWidth, maxHeight, margins) { 12040 var me = this; 12041 12042 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 12043 me.beforeUpdate(); 12044 12045 // Absorb the master measurements 12046 me.maxWidth = maxWidth; 12047 me.maxHeight = maxHeight; 12048 me.margins = margins; 12049 12050 // Dimensions 12051 me.beforeSetDimensions(); 12052 me.setDimensions(); 12053 me.afterSetDimensions(); 12054 // Labels 12055 me.beforeBuildLabels(); 12056 me.buildLabels(); 12057 me.afterBuildLabels(); 12058 12059 // Fit 12060 me.beforeFit(); 12061 me.fit(); 12062 me.afterFit(); 12063 // 12064 me.afterUpdate(); 12065 12066 return me.minSize; 12067 12068 }, 12069 afterUpdate: noop, 12070 12071 // 12072 12073 beforeSetDimensions: noop, 12074 setDimensions: function() { 12075 var me = this; 12076 // Set the unconstrained dimension before label rotation 12077 if (me.isHorizontal()) { 12078 // Reset position before calculating rotation 12079 me.width = me.maxWidth; 12080 me.left = 0; 12081 me.right = me.width; 12082 } else { 12083 me.height = me.maxHeight; 12084 12085 // Reset position before calculating rotation 12086 me.top = 0; 12087 me.bottom = me.height; 12088 } 12089 12090 // Reset padding 12091 me.paddingLeft = 0; 12092 me.paddingTop = 0; 12093 me.paddingRight = 0; 12094 me.paddingBottom = 0; 12095 12096 // Reset minSize 12097 me.minSize = { 12098 width: 0, 12099 height: 0 12100 }; 12101 }, 12102 afterSetDimensions: noop, 12103 12104 // 12105 12106 beforeBuildLabels: noop, 12107 buildLabels: noop, 12108 afterBuildLabels: noop, 12109 12110 // 12111 12112 beforeFit: noop, 12113 fit: function() { 12114 var me = this; 12115 var valueOrDefault = helpers.valueOrDefault; 12116 var opts = me.options; 12117 var display = opts.display; 12118 var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize); 12119 var minSize = me.minSize; 12120 var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1; 12121 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); 12122 var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0; 12123 12124 if (me.isHorizontal()) { 12125 minSize.width = me.maxWidth; // fill all the width 12126 minSize.height = textSize; 12127 } else { 12128 minSize.width = textSize; 12129 minSize.height = me.maxHeight; // fill all the height 12130 } 12131 12132 me.width = minSize.width; 12133 me.height = minSize.height; 12134 12135 }, 12136 afterFit: noop, 12137 12138 // Shared Methods 12139 isHorizontal: function() { 12140 var pos = this.options.position; 12141 return pos === 'top' || pos === 'bottom'; 12142 }, 12143 12144 // Actually draw the title block on the canvas 12145 draw: function() { 12146 var me = this; 12147 var ctx = me.ctx; 12148 var valueOrDefault = helpers.valueOrDefault; 12149 var opts = me.options; 12150 var globalDefaults = defaults.global; 12151 12152 if (opts.display) { 12153 var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize); 12154 var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle); 12155 var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily); 12156 var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily); 12157 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); 12158 var offset = lineHeight / 2 + opts.padding; 12159 var rotation = 0; 12160 var top = me.top; 12161 var left = me.left; 12162 var bottom = me.bottom; 12163 var right = me.right; 12164 var maxWidth, titleX, titleY; 12165 12166 ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour 12167 ctx.font = titleFont; 12168 12169 // Horizontal 12170 if (me.isHorizontal()) { 12171 titleX = left + ((right - left) / 2); // midpoint of the width 12172 titleY = top + offset; 12173 maxWidth = right - left; 12174 } else { 12175 titleX = opts.position === 'left' ? left + offset : right - offset; 12176 titleY = top + ((bottom - top) / 2); 12177 maxWidth = bottom - top; 12178 rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); 12179 } 12180 12181 ctx.save(); 12182 ctx.translate(titleX, titleY); 12183 ctx.rotate(rotation); 12184 ctx.textAlign = 'center'; 12185 ctx.textBaseline = 'middle'; 12186 12187 var text = opts.text; 12188 if (helpers.isArray(text)) { 12189 var y = 0; 12190 for (var i = 0; i < text.length; ++i) { 12191 ctx.fillText(text[i], 0, y, maxWidth); 12192 y += lineHeight; 12193 } 12194 } else { 12195 ctx.fillText(text, 0, 0, maxWidth); 12196 } 12197 12198 ctx.restore(); 12199 } 12200 } 12201 }); 12202 12203 function createNewTitleBlockAndAttach(chart, titleOpts) { 12204 var title = new Title({ 12205 ctx: chart.ctx, 12206 options: titleOpts, 12207 chart: chart 12208 }); 12209 12210 layouts.configure(chart, title, titleOpts); 12211 layouts.addBox(chart, title); 12212 chart.titleBlock = title; 12213 } 12214 12215 module.exports = { 12216 id: 'title', 12217 12218 /** 12219 * Backward compatibility: since 2.1.5, the title is registered as a plugin, making 12220 * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of 12221 * the plugin, which one will be re-exposed in the chart.js file. 12222 * https://github.com/chartjs/Chart.js/pull/2640 12223 * @private 12224 */ 12225 _element: Title, 12226 12227 beforeInit: function(chart) { 12228 var titleOpts = chart.options.title; 12229 12230 if (titleOpts) { 12231 createNewTitleBlockAndAttach(chart, titleOpts); 12232 } 12233 }, 12234 12235 beforeUpdate: function(chart) { 12236 var titleOpts = chart.options.title; 12237 var titleBlock = chart.titleBlock; 12238 12239 if (titleOpts) { 12240 helpers.mergeIf(titleOpts, defaults.global.title); 12241 12242 if (titleBlock) { 12243 layouts.configure(chart, titleBlock, titleOpts); 12244 titleBlock.options = titleOpts; 12245 } else { 12246 createNewTitleBlockAndAttach(chart, titleOpts); 12247 } 12248 } else if (titleBlock) { 12249 layouts.removeBox(chart, titleBlock); 12250 delete chart.titleBlock; 12251 } 12252 } 12253 }; 12254 12255 },{"26":26,"27":27,"31":31,"46":46}],54:[function(require,module,exports){ 12256 'use strict'; 12257 12258 var Scale = require(33); 12259 var scaleService = require(34); 12260 12261 module.exports = function() { 12262 12263 // Default config for a category scale 12264 var defaultConfig = { 12265 position: 'bottom' 12266 }; 12267 12268 var DatasetScale = Scale.extend({ 12269 /** 12270 * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those 12271 * else fall back to data.labels 12272 * @private 12273 */ 12274 getLabels: function() { 12275 var data = this.chart.data; 12276 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; 12277 }, 12278 12279 determineDataLimits: function() { 12280 var me = this; 12281 var labels = me.getLabels(); 12282 me.minIndex = 0; 12283 me.maxIndex = labels.length - 1; 12284 var findIndex; 12285 12286 if (me.options.ticks.min !== undefined) { 12287 // user specified min value 12288 findIndex = labels.indexOf(me.options.ticks.min); 12289 me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; 12290 } 12291 12292 if (me.options.ticks.max !== undefined) { 12293 // user specified max value 12294 findIndex = labels.indexOf(me.options.ticks.max); 12295 me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; 12296 } 12297 12298 me.min = labels[me.minIndex]; 12299 me.max = labels[me.maxIndex]; 12300 }, 12301 12302 buildTicks: function() { 12303 var me = this; 12304 var labels = me.getLabels(); 12305 // If we are viewing some subset of labels, slice the original array 12306 me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); 12307 }, 12308 12309 getLabelForIndex: function(index, datasetIndex) { 12310 var me = this; 12311 var data = me.chart.data; 12312 var isHorizontal = me.isHorizontal(); 12313 12314 if (data.yLabels && !isHorizontal) { 12315 return me.getRightValue(data.datasets[datasetIndex].data[index]); 12316 } 12317 return me.ticks[index - me.minIndex]; 12318 }, 12319 12320 // Used to get data value locations. Value can either be an index or a numerical value 12321 getPixelForValue: function(value, index) { 12322 var me = this; 12323 var offset = me.options.offset; 12324 // 1 is added because we need the length but we have the indexes 12325 var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1); 12326 12327 // If value is a data object, then index is the index in the data array, 12328 // not the index of the scale. We need to change that. 12329 var valueCategory; 12330 if (value !== undefined && value !== null) { 12331 valueCategory = me.isHorizontal() ? value.x : value.y; 12332 } 12333 if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { 12334 var labels = me.getLabels(); 12335 value = valueCategory || value; 12336 var idx = labels.indexOf(value); 12337 index = idx !== -1 ? idx : index; 12338 } 12339 12340 if (me.isHorizontal()) { 12341 var valueWidth = me.width / offsetAmt; 12342 var widthOffset = (valueWidth * (index - me.minIndex)); 12343 12344 if (offset) { 12345 widthOffset += (valueWidth / 2); 12346 } 12347 12348 return me.left + Math.round(widthOffset); 12349 } 12350 var valueHeight = me.height / offsetAmt; 12351 var heightOffset = (valueHeight * (index - me.minIndex)); 12352 12353 if (offset) { 12354 heightOffset += (valueHeight / 2); 12355 } 12356 12357 return me.top + Math.round(heightOffset); 12358 }, 12359 getPixelForTick: function(index) { 12360 return this.getPixelForValue(this.ticks[index], index + this.minIndex, null); 12361 }, 12362 getValueForPixel: function(pixel) { 12363 var me = this; 12364 var offset = me.options.offset; 12365 var value; 12366 var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1); 12367 var horz = me.isHorizontal(); 12368 var valueDimension = (horz ? me.width : me.height) / offsetAmt; 12369 12370 pixel -= horz ? me.left : me.top; 12371 12372 if (offset) { 12373 pixel -= (valueDimension / 2); 12374 } 12375 12376 if (pixel <= 0) { 12377 value = 0; 12378 } else { 12379 value = Math.round(pixel / valueDimension); 12380 } 12381 12382 return value + me.minIndex; 12383 }, 12384 getBasePixel: function() { 12385 return this.bottom; 12386 } 12387 }); 12388 12389 scaleService.registerScaleType('category', DatasetScale, defaultConfig); 12390 }; 12391 12392 },{"33":33,"34":34}],55:[function(require,module,exports){ 12393 'use strict'; 12394 12395 var defaults = require(26); 12396 var helpers = require(46); 12397 var scaleService = require(34); 12398 var Ticks = require(35); 12399 12400 module.exports = function(Chart) { 12401 12402 var defaultConfig = { 12403 position: 'left', 12404 ticks: { 12405 callback: Ticks.formatters.linear 12406 } 12407 }; 12408 12409 var LinearScale = Chart.LinearScaleBase.extend({ 12410 12411 determineDataLimits: function() { 12412 var me = this; 12413 var opts = me.options; 12414 var chart = me.chart; 12415 var data = chart.data; 12416 var datasets = data.datasets; 12417 var isHorizontal = me.isHorizontal(); 12418 var DEFAULT_MIN = 0; 12419 var DEFAULT_MAX = 1; 12420 12421 function IDMatches(meta) { 12422 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; 12423 } 12424 12425 // First Calculate the range 12426 me.min = null; 12427 me.max = null; 12428 12429 var hasStacks = opts.stacked; 12430 if (hasStacks === undefined) { 12431 helpers.each(datasets, function(dataset, datasetIndex) { 12432 if (hasStacks) { 12433 return; 12434 } 12435 12436 var meta = chart.getDatasetMeta(datasetIndex); 12437 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && 12438 meta.stack !== undefined) { 12439 hasStacks = true; 12440 } 12441 }); 12442 } 12443 12444 if (opts.stacked || hasStacks) { 12445 var valuesPerStack = {}; 12446 12447 helpers.each(datasets, function(dataset, datasetIndex) { 12448 var meta = chart.getDatasetMeta(datasetIndex); 12449 var key = [ 12450 meta.type, 12451 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined 12452 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), 12453 meta.stack 12454 ].join('.'); 12455 12456 if (valuesPerStack[key] === undefined) { 12457 valuesPerStack[key] = { 12458 positiveValues: [], 12459 negativeValues: [] 12460 }; 12461 } 12462 12463 // Store these per type 12464 var positiveValues = valuesPerStack[key].positiveValues; 12465 var negativeValues = valuesPerStack[key].negativeValues; 12466 12467 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 12468 helpers.each(dataset.data, function(rawValue, index) { 12469 var value = +me.getRightValue(rawValue); 12470 if (isNaN(value) || meta.data[index].hidden) { 12471 return; 12472 } 12473 12474 positiveValues[index] = positiveValues[index] || 0; 12475 negativeValues[index] = negativeValues[index] || 0; 12476 12477 if (opts.relativePoints) { 12478 positiveValues[index] = 100; 12479 } else if (value < 0) { 12480 negativeValues[index] += value; 12481 } else { 12482 positiveValues[index] += value; 12483 } 12484 }); 12485 } 12486 }); 12487 12488 helpers.each(valuesPerStack, function(valuesForType) { 12489 var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); 12490 var minVal = helpers.min(values); 12491 var maxVal = helpers.max(values); 12492 me.min = me.min === null ? minVal : Math.min(me.min, minVal); 12493 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); 12494 }); 12495 12496 } else { 12497 helpers.each(datasets, function(dataset, datasetIndex) { 12498 var meta = chart.getDatasetMeta(datasetIndex); 12499 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 12500 helpers.each(dataset.data, function(rawValue, index) { 12501 var value = +me.getRightValue(rawValue); 12502 if (isNaN(value) || meta.data[index].hidden) { 12503 return; 12504 } 12505 12506 if (me.min === null) { 12507 me.min = value; 12508 } else if (value < me.min) { 12509 me.min = value; 12510 } 12511 12512 if (me.max === null) { 12513 me.max = value; 12514 } else if (value > me.max) { 12515 me.max = value; 12516 } 12517 }); 12518 } 12519 }); 12520 } 12521 12522 me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; 12523 me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; 12524 12525 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero 12526 this.handleTickRangeOptions(); 12527 }, 12528 getTickLimit: function() { 12529 var maxTicks; 12530 var me = this; 12531 var tickOpts = me.options.ticks; 12532 12533 if (me.isHorizontal()) { 12534 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); 12535 } else { 12536 // The factor of 2 used to scale the font size has been experimentally determined. 12537 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize); 12538 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); 12539 } 12540 12541 return maxTicks; 12542 }, 12543 // Called after the ticks are built. We need 12544 handleDirectionalChanges: function() { 12545 if (!this.isHorizontal()) { 12546 // We are in a vertical orientation. The top value is the highest. So reverse the array 12547 this.ticks.reverse(); 12548 } 12549 }, 12550 getLabelForIndex: function(index, datasetIndex) { 12551 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); 12552 }, 12553 // Utils 12554 getPixelForValue: function(value) { 12555 // This must be called after fit has been run so that 12556 // this.left, this.top, this.right, and this.bottom have been defined 12557 var me = this; 12558 var start = me.start; 12559 12560 var rightValue = +me.getRightValue(value); 12561 var pixel; 12562 var range = me.end - start; 12563 12564 if (me.isHorizontal()) { 12565 pixel = me.left + (me.width / range * (rightValue - start)); 12566 } else { 12567 pixel = me.bottom - (me.height / range * (rightValue - start)); 12568 } 12569 return pixel; 12570 }, 12571 getValueForPixel: function(pixel) { 12572 var me = this; 12573 var isHorizontal = me.isHorizontal(); 12574 var innerDimension = isHorizontal ? me.width : me.height; 12575 var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension; 12576 return me.start + ((me.end - me.start) * offset); 12577 }, 12578 getPixelForTick: function(index) { 12579 return this.getPixelForValue(this.ticksAsNumbers[index]); 12580 } 12581 }); 12582 12583 scaleService.registerScaleType('linear', LinearScale, defaultConfig); 12584 }; 12585 12586 },{"26":26,"34":34,"35":35,"46":46}],56:[function(require,module,exports){ 12587 'use strict'; 12588 12589 var helpers = require(46); 12590 var Scale = require(33); 12591 12592 /** 12593 * Generate a set of linear ticks 12594 * @param generationOptions the options used to generate the ticks 12595 * @param dataRange the range of the data 12596 * @returns {Array<Number>} array of tick values 12597 */ 12598 function generateTicks(generationOptions, dataRange) { 12599 var ticks = []; 12600 // To get a "nice" value for the tick spacing, we will use the appropriately named 12601 // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks 12602 // for details. 12603 12604 var factor; 12605 var precision; 12606 var spacing; 12607 12608 if (generationOptions.stepSize && generationOptions.stepSize > 0) { 12609 spacing = generationOptions.stepSize; 12610 } else { 12611 var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false); 12612 spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true); 12613 12614 precision = generationOptions.precision; 12615 if (precision !== undefined) { 12616 // If the user specified a precision, round to that number of decimal places 12617 factor = Math.pow(10, precision); 12618 spacing = Math.ceil(spacing * factor) / factor; 12619 } 12620 } 12621 var niceMin = Math.floor(dataRange.min / spacing) * spacing; 12622 var niceMax = Math.ceil(dataRange.max / spacing) * spacing; 12623 12624 // If min, max and stepSize is set and they make an evenly spaced scale use it. 12625 if (!helpers.isNullOrUndef(generationOptions.min) && !helpers.isNullOrUndef(generationOptions.max) && generationOptions.stepSize) { 12626 // If very close to our whole number, use it. 12627 if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) { 12628 niceMin = generationOptions.min; 12629 niceMax = generationOptions.max; 12630 } 12631 } 12632 12633 var numSpaces = (niceMax - niceMin) / spacing; 12634 // If very close to our rounded value, use it. 12635 if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { 12636 numSpaces = Math.round(numSpaces); 12637 } else { 12638 numSpaces = Math.ceil(numSpaces); 12639 } 12640 12641 precision = 1; 12642 if (spacing < 1) { 12643 precision = Math.pow(10, 1 - Math.floor(helpers.log10(spacing))); 12644 niceMin = Math.round(niceMin * precision) / precision; 12645 niceMax = Math.round(niceMax * precision) / precision; 12646 } 12647 ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin); 12648 for (var j = 1; j < numSpaces; ++j) { 12649 ticks.push(Math.round((niceMin + j * spacing) * precision) / precision); 12650 } 12651 ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax); 12652 12653 return ticks; 12654 } 12655 12656 module.exports = function(Chart) { 12657 12658 var noop = helpers.noop; 12659 12660 Chart.LinearScaleBase = Scale.extend({ 12661 getRightValue: function(value) { 12662 if (typeof value === 'string') { 12663 return +value; 12664 } 12665 return Scale.prototype.getRightValue.call(this, value); 12666 }, 12667 12668 handleTickRangeOptions: function() { 12669 var me = this; 12670 var opts = me.options; 12671 var tickOpts = opts.ticks; 12672 12673 // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, 12674 // do nothing since that would make the chart weird. If the user really wants a weird chart 12675 // axis, they can manually override it 12676 if (tickOpts.beginAtZero) { 12677 var minSign = helpers.sign(me.min); 12678 var maxSign = helpers.sign(me.max); 12679 12680 if (minSign < 0 && maxSign < 0) { 12681 // move the top up to 0 12682 me.max = 0; 12683 } else if (minSign > 0 && maxSign > 0) { 12684 // move the bottom down to 0 12685 me.min = 0; 12686 } 12687 } 12688 12689 var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; 12690 var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; 12691 12692 if (tickOpts.min !== undefined) { 12693 me.min = tickOpts.min; 12694 } else if (tickOpts.suggestedMin !== undefined) { 12695 if (me.min === null) { 12696 me.min = tickOpts.suggestedMin; 12697 } else { 12698 me.min = Math.min(me.min, tickOpts.suggestedMin); 12699 } 12700 } 12701 12702 if (tickOpts.max !== undefined) { 12703 me.max = tickOpts.max; 12704 } else if (tickOpts.suggestedMax !== undefined) { 12705 if (me.max === null) { 12706 me.max = tickOpts.suggestedMax; 12707 } else { 12708 me.max = Math.max(me.max, tickOpts.suggestedMax); 12709 } 12710 } 12711 12712 if (setMin !== setMax) { 12713 // We set the min or the max but not both. 12714 // So ensure that our range is good 12715 // Inverted or 0 length range can happen when 12716 // ticks.min is set, and no datasets are visible 12717 if (me.min >= me.max) { 12718 if (setMin) { 12719 me.max = me.min + 1; 12720 } else { 12721 me.min = me.max - 1; 12722 } 12723 } 12724 } 12725 12726 if (me.min === me.max) { 12727 me.max++; 12728 12729 if (!tickOpts.beginAtZero) { 12730 me.min--; 12731 } 12732 } 12733 }, 12734 getTickLimit: noop, 12735 handleDirectionalChanges: noop, 12736 12737 buildTicks: function() { 12738 var me = this; 12739 var opts = me.options; 12740 var tickOpts = opts.ticks; 12741 12742 // Figure out what the max number of ticks we can support it is based on the size of 12743 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 12744 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on 12745 // the graph. Make sure we always have at least 2 ticks 12746 var maxTicks = me.getTickLimit(); 12747 maxTicks = Math.max(2, maxTicks); 12748 12749 var numericGeneratorOptions = { 12750 maxTicks: maxTicks, 12751 min: tickOpts.min, 12752 max: tickOpts.max, 12753 precision: tickOpts.precision, 12754 stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) 12755 }; 12756 var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); 12757 12758 me.handleDirectionalChanges(); 12759 12760 // At this point, we need to update our max and min given the tick values since we have expanded the 12761 // range of the scale 12762 me.max = helpers.max(ticks); 12763 me.min = helpers.min(ticks); 12764 12765 if (tickOpts.reverse) { 12766 ticks.reverse(); 12767 12768 me.start = me.max; 12769 me.end = me.min; 12770 } else { 12771 me.start = me.min; 12772 me.end = me.max; 12773 } 12774 }, 12775 convertTicksToLabels: function() { 12776 var me = this; 12777 me.ticksAsNumbers = me.ticks.slice(); 12778 me.zeroLineIndex = me.ticks.indexOf(0); 12779 12780 Scale.prototype.convertTicksToLabels.call(me); 12781 } 12782 }); 12783 }; 12784 12785 },{"33":33,"46":46}],57:[function(require,module,exports){ 12786 'use strict'; 12787 12788 var helpers = require(46); 12789 var Scale = require(33); 12790 var scaleService = require(34); 12791 var Ticks = require(35); 12792 12793 /** 12794 * Generate a set of logarithmic ticks 12795 * @param generationOptions the options used to generate the ticks 12796 * @param dataRange the range of the data 12797 * @returns {Array<Number>} array of tick values 12798 */ 12799 function generateTicks(generationOptions, dataRange) { 12800 var ticks = []; 12801 var valueOrDefault = helpers.valueOrDefault; 12802 12803 // Figure out what the max number of ticks we can support it is based on the size of 12804 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 12805 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on 12806 // the graph 12807 var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min)))); 12808 12809 var endExp = Math.floor(helpers.log10(dataRange.max)); 12810 var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); 12811 var exp, significand; 12812 12813 if (tickVal === 0) { 12814 exp = Math.floor(helpers.log10(dataRange.minNotZero)); 12815 significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); 12816 12817 ticks.push(tickVal); 12818 tickVal = significand * Math.pow(10, exp); 12819 } else { 12820 exp = Math.floor(helpers.log10(tickVal)); 12821 significand = Math.floor(tickVal / Math.pow(10, exp)); 12822 } 12823 var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; 12824 12825 do { 12826 ticks.push(tickVal); 12827 12828 ++significand; 12829 if (significand === 10) { 12830 significand = 1; 12831 ++exp; 12832 precision = exp >= 0 ? 1 : precision; 12833 } 12834 12835 tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; 12836 } while (exp < endExp || (exp === endExp && significand < endSignificand)); 12837 12838 var lastTick = valueOrDefault(generationOptions.max, tickVal); 12839 ticks.push(lastTick); 12840 12841 return ticks; 12842 } 12843 12844 12845 module.exports = function(Chart) { 12846 12847 var defaultConfig = { 12848 position: 'left', 12849 12850 // label settings 12851 ticks: { 12852 callback: Ticks.formatters.logarithmic 12853 } 12854 }; 12855 12856 var LogarithmicScale = Scale.extend({ 12857 determineDataLimits: function() { 12858 var me = this; 12859 var opts = me.options; 12860 var chart = me.chart; 12861 var data = chart.data; 12862 var datasets = data.datasets; 12863 var isHorizontal = me.isHorizontal(); 12864 function IDMatches(meta) { 12865 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; 12866 } 12867 12868 // Calculate Range 12869 me.min = null; 12870 me.max = null; 12871 me.minNotZero = null; 12872 12873 var hasStacks = opts.stacked; 12874 if (hasStacks === undefined) { 12875 helpers.each(datasets, function(dataset, datasetIndex) { 12876 if (hasStacks) { 12877 return; 12878 } 12879 12880 var meta = chart.getDatasetMeta(datasetIndex); 12881 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && 12882 meta.stack !== undefined) { 12883 hasStacks = true; 12884 } 12885 }); 12886 } 12887 12888 if (opts.stacked || hasStacks) { 12889 var valuesPerStack = {}; 12890 12891 helpers.each(datasets, function(dataset, datasetIndex) { 12892 var meta = chart.getDatasetMeta(datasetIndex); 12893 var key = [ 12894 meta.type, 12895 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined 12896 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), 12897 meta.stack 12898 ].join('.'); 12899 12900 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 12901 if (valuesPerStack[key] === undefined) { 12902 valuesPerStack[key] = []; 12903 } 12904 12905 helpers.each(dataset.data, function(rawValue, index) { 12906 var values = valuesPerStack[key]; 12907 var value = +me.getRightValue(rawValue); 12908 // invalid, hidden and negative values are ignored 12909 if (isNaN(value) || meta.data[index].hidden || value < 0) { 12910 return; 12911 } 12912 values[index] = values[index] || 0; 12913 values[index] += value; 12914 }); 12915 } 12916 }); 12917 12918 helpers.each(valuesPerStack, function(valuesForType) { 12919 if (valuesForType.length > 0) { 12920 var minVal = helpers.min(valuesForType); 12921 var maxVal = helpers.max(valuesForType); 12922 me.min = me.min === null ? minVal : Math.min(me.min, minVal); 12923 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); 12924 } 12925 }); 12926 12927 } else { 12928 helpers.each(datasets, function(dataset, datasetIndex) { 12929 var meta = chart.getDatasetMeta(datasetIndex); 12930 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 12931 helpers.each(dataset.data, function(rawValue, index) { 12932 var value = +me.getRightValue(rawValue); 12933 // invalid, hidden and negative values are ignored 12934 if (isNaN(value) || meta.data[index].hidden || value < 0) { 12935 return; 12936 } 12937 12938 if (me.min === null) { 12939 me.min = value; 12940 } else if (value < me.min) { 12941 me.min = value; 12942 } 12943 12944 if (me.max === null) { 12945 me.max = value; 12946 } else if (value > me.max) { 12947 me.max = value; 12948 } 12949 12950 if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { 12951 me.minNotZero = value; 12952 } 12953 }); 12954 } 12955 }); 12956 } 12957 12958 // Common base implementation to handle ticks.min, ticks.max 12959 this.handleTickRangeOptions(); 12960 }, 12961 handleTickRangeOptions: function() { 12962 var me = this; 12963 var opts = me.options; 12964 var tickOpts = opts.ticks; 12965 var valueOrDefault = helpers.valueOrDefault; 12966 var DEFAULT_MIN = 1; 12967 var DEFAULT_MAX = 10; 12968 12969 me.min = valueOrDefault(tickOpts.min, me.min); 12970 me.max = valueOrDefault(tickOpts.max, me.max); 12971 12972 if (me.min === me.max) { 12973 if (me.min !== 0 && me.min !== null) { 12974 me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); 12975 me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); 12976 } else { 12977 me.min = DEFAULT_MIN; 12978 me.max = DEFAULT_MAX; 12979 } 12980 } 12981 if (me.min === null) { 12982 me.min = Math.pow(10, Math.floor(helpers.log10(me.max)) - 1); 12983 } 12984 if (me.max === null) { 12985 me.max = me.min !== 0 12986 ? Math.pow(10, Math.floor(helpers.log10(me.min)) + 1) 12987 : DEFAULT_MAX; 12988 } 12989 if (me.minNotZero === null) { 12990 if (me.min > 0) { 12991 me.minNotZero = me.min; 12992 } else if (me.max < 1) { 12993 me.minNotZero = Math.pow(10, Math.floor(helpers.log10(me.max))); 12994 } else { 12995 me.minNotZero = DEFAULT_MIN; 12996 } 12997 } 12998 }, 12999 buildTicks: function() { 13000 var me = this; 13001 var opts = me.options; 13002 var tickOpts = opts.ticks; 13003 var reverse = !me.isHorizontal(); 13004 13005 var generationOptions = { 13006 min: tickOpts.min, 13007 max: tickOpts.max 13008 }; 13009 var ticks = me.ticks = generateTicks(generationOptions, me); 13010 13011 // At this point, we need to update our max and min given the tick values since we have expanded the 13012 // range of the scale 13013 me.max = helpers.max(ticks); 13014 me.min = helpers.min(ticks); 13015 13016 if (tickOpts.reverse) { 13017 reverse = !reverse; 13018 me.start = me.max; 13019 me.end = me.min; 13020 } else { 13021 me.start = me.min; 13022 me.end = me.max; 13023 } 13024 if (reverse) { 13025 ticks.reverse(); 13026 } 13027 }, 13028 convertTicksToLabels: function() { 13029 this.tickValues = this.ticks.slice(); 13030 13031 Scale.prototype.convertTicksToLabels.call(this); 13032 }, 13033 // Get the correct tooltip label 13034 getLabelForIndex: function(index, datasetIndex) { 13035 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); 13036 }, 13037 getPixelForTick: function(index) { 13038 return this.getPixelForValue(this.tickValues[index]); 13039 }, 13040 /** 13041 * Returns the value of the first tick. 13042 * @param {Number} value - The minimum not zero value. 13043 * @return {Number} The first tick value. 13044 * @private 13045 */ 13046 _getFirstTickValue: function(value) { 13047 var exp = Math.floor(helpers.log10(value)); 13048 var significand = Math.floor(value / Math.pow(10, exp)); 13049 13050 return significand * Math.pow(10, exp); 13051 }, 13052 getPixelForValue: function(value) { 13053 var me = this; 13054 var reverse = me.options.ticks.reverse; 13055 var log10 = helpers.log10; 13056 var firstTickValue = me._getFirstTickValue(me.minNotZero); 13057 var offset = 0; 13058 var innerDimension, pixel, start, end, sign; 13059 13060 value = +me.getRightValue(value); 13061 if (reverse) { 13062 start = me.end; 13063 end = me.start; 13064 sign = -1; 13065 } else { 13066 start = me.start; 13067 end = me.end; 13068 sign = 1; 13069 } 13070 if (me.isHorizontal()) { 13071 innerDimension = me.width; 13072 pixel = reverse ? me.right : me.left; 13073 } else { 13074 innerDimension = me.height; 13075 sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0) 13076 pixel = reverse ? me.top : me.bottom; 13077 } 13078 if (value !== start) { 13079 if (start === 0) { // include zero tick 13080 offset = helpers.getValueOrDefault( 13081 me.options.ticks.fontSize, 13082 Chart.defaults.global.defaultFontSize 13083 ); 13084 innerDimension -= offset; 13085 start = firstTickValue; 13086 } 13087 if (value !== 0) { 13088 offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start)); 13089 } 13090 pixel += sign * offset; 13091 } 13092 return pixel; 13093 }, 13094 getValueForPixel: function(pixel) { 13095 var me = this; 13096 var reverse = me.options.ticks.reverse; 13097 var log10 = helpers.log10; 13098 var firstTickValue = me._getFirstTickValue(me.minNotZero); 13099 var innerDimension, start, end, value; 13100 13101 if (reverse) { 13102 start = me.end; 13103 end = me.start; 13104 } else { 13105 start = me.start; 13106 end = me.end; 13107 } 13108 if (me.isHorizontal()) { 13109 innerDimension = me.width; 13110 value = reverse ? me.right - pixel : pixel - me.left; 13111 } else { 13112 innerDimension = me.height; 13113 value = reverse ? pixel - me.top : me.bottom - pixel; 13114 } 13115 if (value !== start) { 13116 if (start === 0) { // include zero tick 13117 var offset = helpers.getValueOrDefault( 13118 me.options.ticks.fontSize, 13119 Chart.defaults.global.defaultFontSize 13120 ); 13121 value -= offset; 13122 innerDimension -= offset; 13123 start = firstTickValue; 13124 } 13125 value *= log10(end) - log10(start); 13126 value /= innerDimension; 13127 value = Math.pow(10, log10(start) + value); 13128 } 13129 return value; 13130 } 13131 }); 13132 13133 scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig); 13134 }; 13135 13136 },{"33":33,"34":34,"35":35,"46":46}],58:[function(require,module,exports){ 13137 'use strict'; 13138 13139 var defaults = require(26); 13140 var helpers = require(46); 13141 var scaleService = require(34); 13142 var Ticks = require(35); 13143 13144 module.exports = function(Chart) { 13145 13146 var globalDefaults = defaults.global; 13147 13148 var defaultConfig = { 13149 display: true, 13150 13151 // Boolean - Whether to animate scaling the chart from the centre 13152 animate: true, 13153 position: 'chartArea', 13154 13155 angleLines: { 13156 display: true, 13157 color: 'rgba(0, 0, 0, 0.1)', 13158 lineWidth: 1 13159 }, 13160 13161 gridLines: { 13162 circular: false 13163 }, 13164 13165 // label settings 13166 ticks: { 13167 // Boolean - Show a backdrop to the scale label 13168 showLabelBackdrop: true, 13169 13170 // String - The colour of the label backdrop 13171 backdropColor: 'rgba(255,255,255,0.75)', 13172 13173 // Number - The backdrop padding above & below the label in pixels 13174 backdropPaddingY: 2, 13175 13176 // Number - The backdrop padding to the side of the label in pixels 13177 backdropPaddingX: 2, 13178 13179 callback: Ticks.formatters.linear 13180 }, 13181 13182 pointLabels: { 13183 // Boolean - if true, show point labels 13184 display: true, 13185 13186 // Number - Point label font size in pixels 13187 fontSize: 10, 13188 13189 // Function - Used to convert point labels 13190 callback: function(label) { 13191 return label; 13192 } 13193 } 13194 }; 13195 13196 function getValueCount(scale) { 13197 var opts = scale.options; 13198 return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0; 13199 } 13200 13201 function getPointLabelFontOptions(scale) { 13202 var pointLabelOptions = scale.options.pointLabels; 13203 var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize); 13204 var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle); 13205 var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily); 13206 var font = helpers.fontString(fontSize, fontStyle, fontFamily); 13207 13208 return { 13209 size: fontSize, 13210 style: fontStyle, 13211 family: fontFamily, 13212 font: font 13213 }; 13214 } 13215 13216 function measureLabelSize(ctx, fontSize, label) { 13217 if (helpers.isArray(label)) { 13218 return { 13219 w: helpers.longestText(ctx, ctx.font, label), 13220 h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize) 13221 }; 13222 } 13223 13224 return { 13225 w: ctx.measureText(label).width, 13226 h: fontSize 13227 }; 13228 } 13229 13230 function determineLimits(angle, pos, size, min, max) { 13231 if (angle === min || angle === max) { 13232 return { 13233 start: pos - (size / 2), 13234 end: pos + (size / 2) 13235 }; 13236 } else if (angle < min || angle > max) { 13237 return { 13238 start: pos - size - 5, 13239 end: pos 13240 }; 13241 } 13242 13243 return { 13244 start: pos, 13245 end: pos + size + 5 13246 }; 13247 } 13248 13249 /** 13250 * Helper function to fit a radial linear scale with point labels 13251 */ 13252 function fitWithPointLabels(scale) { 13253 /* 13254 * Right, this is really confusing and there is a lot of maths going on here 13255 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 13256 * 13257 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif 13258 * 13259 * Solution: 13260 * 13261 * We assume the radius of the polygon is half the size of the canvas at first 13262 * at each index we check if the text overlaps. 13263 * 13264 * Where it does, we store that angle and that index. 13265 * 13266 * After finding the largest index and angle we calculate how much we need to remove 13267 * from the shape radius to move the point inwards by that x. 13268 * 13269 * We average the left and right distances to get the maximum shape radius that can fit in the box 13270 * along with labels. 13271 * 13272 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion 13273 * on each side, removing that from the size, halving it and adding the left x protrusion width. 13274 * 13275 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels 13276 * and position it in the most space efficient manner 13277 * 13278 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif 13279 */ 13280 13281 var plFont = getPointLabelFontOptions(scale); 13282 13283 // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. 13284 // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points 13285 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); 13286 var furthestLimits = { 13287 r: scale.width, 13288 l: 0, 13289 t: scale.height, 13290 b: 0 13291 }; 13292 var furthestAngles = {}; 13293 var i, textSize, pointPosition; 13294 13295 scale.ctx.font = plFont.font; 13296 scale._pointLabelSizes = []; 13297 13298 var valueCount = getValueCount(scale); 13299 for (i = 0; i < valueCount; i++) { 13300 pointPosition = scale.getPointPosition(i, largestPossibleRadius); 13301 textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || ''); 13302 scale._pointLabelSizes[i] = textSize; 13303 13304 // Add quarter circle to make degree 0 mean top of circle 13305 var angleRadians = scale.getIndexAngle(i); 13306 var angle = helpers.toDegrees(angleRadians) % 360; 13307 var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); 13308 var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); 13309 13310 if (hLimits.start < furthestLimits.l) { 13311 furthestLimits.l = hLimits.start; 13312 furthestAngles.l = angleRadians; 13313 } 13314 13315 if (hLimits.end > furthestLimits.r) { 13316 furthestLimits.r = hLimits.end; 13317 furthestAngles.r = angleRadians; 13318 } 13319 13320 if (vLimits.start < furthestLimits.t) { 13321 furthestLimits.t = vLimits.start; 13322 furthestAngles.t = angleRadians; 13323 } 13324 13325 if (vLimits.end > furthestLimits.b) { 13326 furthestLimits.b = vLimits.end; 13327 furthestAngles.b = angleRadians; 13328 } 13329 } 13330 13331 scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles); 13332 } 13333 13334 /** 13335 * Helper function to fit a radial linear scale with no point labels 13336 */ 13337 function fit(scale) { 13338 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); 13339 scale.drawingArea = Math.round(largestPossibleRadius); 13340 scale.setCenterPoint(0, 0, 0, 0); 13341 } 13342 13343 function getTextAlignForAngle(angle) { 13344 if (angle === 0 || angle === 180) { 13345 return 'center'; 13346 } else if (angle < 180) { 13347 return 'left'; 13348 } 13349 13350 return 'right'; 13351 } 13352 13353 function fillText(ctx, text, position, fontSize) { 13354 if (helpers.isArray(text)) { 13355 var y = position.y; 13356 var spacing = 1.5 * fontSize; 13357 13358 for (var i = 0; i < text.length; ++i) { 13359 ctx.fillText(text[i], position.x, y); 13360 y += spacing; 13361 } 13362 } else { 13363 ctx.fillText(text, position.x, position.y); 13364 } 13365 } 13366 13367 function adjustPointPositionForLabelHeight(angle, textSize, position) { 13368 if (angle === 90 || angle === 270) { 13369 position.y -= (textSize.h / 2); 13370 } else if (angle > 270 || angle < 90) { 13371 position.y -= textSize.h; 13372 } 13373 } 13374 13375 function drawPointLabels(scale) { 13376 var ctx = scale.ctx; 13377 var opts = scale.options; 13378 var angleLineOpts = opts.angleLines; 13379 var pointLabelOpts = opts.pointLabels; 13380 13381 ctx.lineWidth = angleLineOpts.lineWidth; 13382 ctx.strokeStyle = angleLineOpts.color; 13383 13384 var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); 13385 13386 // Point Label Font 13387 var plFont = getPointLabelFontOptions(scale); 13388 13389 ctx.textBaseline = 'top'; 13390 13391 for (var i = getValueCount(scale) - 1; i >= 0; i--) { 13392 if (angleLineOpts.display) { 13393 var outerPosition = scale.getPointPosition(i, outerDistance); 13394 ctx.beginPath(); 13395 ctx.moveTo(scale.xCenter, scale.yCenter); 13396 ctx.lineTo(outerPosition.x, outerPosition.y); 13397 ctx.stroke(); 13398 ctx.closePath(); 13399 } 13400 13401 if (pointLabelOpts.display) { 13402 // Extra 3px out for some label spacing 13403 var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5); 13404 13405 // Keep this in loop since we may support array properties here 13406 var pointLabelFontColor = helpers.valueAtIndexOrDefault(pointLabelOpts.fontColor, i, globalDefaults.defaultFontColor); 13407 ctx.font = plFont.font; 13408 ctx.fillStyle = pointLabelFontColor; 13409 13410 var angleRadians = scale.getIndexAngle(i); 13411 var angle = helpers.toDegrees(angleRadians); 13412 ctx.textAlign = getTextAlignForAngle(angle); 13413 adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); 13414 fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size); 13415 } 13416 } 13417 } 13418 13419 function drawRadiusLine(scale, gridLineOpts, radius, index) { 13420 var ctx = scale.ctx; 13421 ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1); 13422 ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); 13423 13424 if (scale.options.gridLines.circular) { 13425 // Draw circular arcs between the points 13426 ctx.beginPath(); 13427 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); 13428 ctx.closePath(); 13429 ctx.stroke(); 13430 } else { 13431 // Draw straight lines connecting each index 13432 var valueCount = getValueCount(scale); 13433 13434 if (valueCount === 0) { 13435 return; 13436 } 13437 13438 ctx.beginPath(); 13439 var pointPosition = scale.getPointPosition(0, radius); 13440 ctx.moveTo(pointPosition.x, pointPosition.y); 13441 13442 for (var i = 1; i < valueCount; i++) { 13443 pointPosition = scale.getPointPosition(i, radius); 13444 ctx.lineTo(pointPosition.x, pointPosition.y); 13445 } 13446 13447 ctx.closePath(); 13448 ctx.stroke(); 13449 } 13450 } 13451 13452 function numberOrZero(param) { 13453 return helpers.isNumber(param) ? param : 0; 13454 } 13455 13456 var LinearRadialScale = Chart.LinearScaleBase.extend({ 13457 setDimensions: function() { 13458 var me = this; 13459 var opts = me.options; 13460 var tickOpts = opts.ticks; 13461 // Set the unconstrained dimension before label rotation 13462 me.width = me.maxWidth; 13463 me.height = me.maxHeight; 13464 me.xCenter = Math.round(me.width / 2); 13465 me.yCenter = Math.round(me.height / 2); 13466 13467 var minSize = helpers.min([me.height, me.width]); 13468 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); 13469 me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2); 13470 }, 13471 determineDataLimits: function() { 13472 var me = this; 13473 var chart = me.chart; 13474 var min = Number.POSITIVE_INFINITY; 13475 var max = Number.NEGATIVE_INFINITY; 13476 13477 helpers.each(chart.data.datasets, function(dataset, datasetIndex) { 13478 if (chart.isDatasetVisible(datasetIndex)) { 13479 var meta = chart.getDatasetMeta(datasetIndex); 13480 13481 helpers.each(dataset.data, function(rawValue, index) { 13482 var value = +me.getRightValue(rawValue); 13483 if (isNaN(value) || meta.data[index].hidden) { 13484 return; 13485 } 13486 13487 min = Math.min(value, min); 13488 max = Math.max(value, max); 13489 }); 13490 } 13491 }); 13492 13493 me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); 13494 me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); 13495 13496 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero 13497 me.handleTickRangeOptions(); 13498 }, 13499 getTickLimit: function() { 13500 var tickOpts = this.options.ticks; 13501 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); 13502 return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); 13503 }, 13504 convertTicksToLabels: function() { 13505 var me = this; 13506 13507 Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); 13508 13509 // Point labels 13510 me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); 13511 }, 13512 getLabelForIndex: function(index, datasetIndex) { 13513 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); 13514 }, 13515 fit: function() { 13516 if (this.options.pointLabels.display) { 13517 fitWithPointLabels(this); 13518 } else { 13519 fit(this); 13520 } 13521 }, 13522 /** 13523 * Set radius reductions and determine new radius and center point 13524 * @private 13525 */ 13526 setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { 13527 var me = this; 13528 var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); 13529 var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); 13530 var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); 13531 var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b); 13532 13533 radiusReductionLeft = numberOrZero(radiusReductionLeft); 13534 radiusReductionRight = numberOrZero(radiusReductionRight); 13535 radiusReductionTop = numberOrZero(radiusReductionTop); 13536 radiusReductionBottom = numberOrZero(radiusReductionBottom); 13537 13538 me.drawingArea = Math.min( 13539 Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), 13540 Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); 13541 me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); 13542 }, 13543 setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { 13544 var me = this; 13545 var maxRight = me.width - rightMovement - me.drawingArea; 13546 var maxLeft = leftMovement + me.drawingArea; 13547 var maxTop = topMovement + me.drawingArea; 13548 var maxBottom = me.height - bottomMovement - me.drawingArea; 13549 13550 me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left); 13551 me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top); 13552 }, 13553 13554 getIndexAngle: function(index) { 13555 var angleMultiplier = (Math.PI * 2) / getValueCount(this); 13556 var startAngle = this.chart.options && this.chart.options.startAngle ? 13557 this.chart.options.startAngle : 13558 0; 13559 13560 var startAngleRadians = startAngle * Math.PI * 2 / 360; 13561 13562 // Start from the top instead of right, so remove a quarter of the circle 13563 return index * angleMultiplier + startAngleRadians; 13564 }, 13565 getDistanceFromCenterForValue: function(value) { 13566 var me = this; 13567 13568 if (value === null) { 13569 return 0; // null always in center 13570 } 13571 13572 // Take into account half font size + the yPadding of the top value 13573 var scalingFactor = me.drawingArea / (me.max - me.min); 13574 if (me.options.ticks.reverse) { 13575 return (me.max - value) * scalingFactor; 13576 } 13577 return (value - me.min) * scalingFactor; 13578 }, 13579 getPointPosition: function(index, distanceFromCenter) { 13580 var me = this; 13581 var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); 13582 return { 13583 x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter, 13584 y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter 13585 }; 13586 }, 13587 getPointPositionForValue: function(index, value) { 13588 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); 13589 }, 13590 13591 getBasePosition: function() { 13592 var me = this; 13593 var min = me.min; 13594 var max = me.max; 13595 13596 return me.getPointPositionForValue(0, 13597 me.beginAtZero ? 0 : 13598 min < 0 && max < 0 ? max : 13599 min > 0 && max > 0 ? min : 13600 0); 13601 }, 13602 13603 draw: function() { 13604 var me = this; 13605 var opts = me.options; 13606 var gridLineOpts = opts.gridLines; 13607 var tickOpts = opts.ticks; 13608 var valueOrDefault = helpers.valueOrDefault; 13609 13610 if (opts.display) { 13611 var ctx = me.ctx; 13612 var startAngle = this.getIndexAngle(0); 13613 13614 // Tick Font 13615 var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); 13616 var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); 13617 var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); 13618 var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); 13619 13620 helpers.each(me.ticks, function(label, index) { 13621 // Don't draw a centre value (if it is minimum) 13622 if (index > 0 || tickOpts.reverse) { 13623 var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); 13624 13625 // Draw circular lines around the scale 13626 if (gridLineOpts.display && index !== 0) { 13627 drawRadiusLine(me, gridLineOpts, yCenterOffset, index); 13628 } 13629 13630 if (tickOpts.display) { 13631 var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); 13632 ctx.font = tickLabelFont; 13633 13634 ctx.save(); 13635 ctx.translate(me.xCenter, me.yCenter); 13636 ctx.rotate(startAngle); 13637 13638 if (tickOpts.showLabelBackdrop) { 13639 var labelWidth = ctx.measureText(label).width; 13640 ctx.fillStyle = tickOpts.backdropColor; 13641 ctx.fillRect( 13642 -labelWidth / 2 - tickOpts.backdropPaddingX, 13643 -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY, 13644 labelWidth + tickOpts.backdropPaddingX * 2, 13645 tickFontSize + tickOpts.backdropPaddingY * 2 13646 ); 13647 } 13648 13649 ctx.textAlign = 'center'; 13650 ctx.textBaseline = 'middle'; 13651 ctx.fillStyle = tickFontColor; 13652 ctx.fillText(label, 0, -yCenterOffset); 13653 ctx.restore(); 13654 } 13655 } 13656 }); 13657 13658 if (opts.angleLines.display || opts.pointLabels.display) { 13659 drawPointLabels(me); 13660 } 13661 } 13662 } 13663 }); 13664 13665 scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig); 13666 }; 13667 13668 },{"26":26,"34":34,"35":35,"46":46}],59:[function(require,module,exports){ 13669 /* global window: false */ 13670 'use strict'; 13671 13672 var moment = require(1); 13673 moment = typeof moment === 'function' ? moment : window.moment; 13674 13675 var defaults = require(26); 13676 var helpers = require(46); 13677 var Scale = require(33); 13678 var scaleService = require(34); 13679 13680 // Integer constants are from the ES6 spec. 13681 var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; 13682 var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; 13683 13684 var INTERVALS = { 13685 millisecond: { 13686 common: true, 13687 size: 1, 13688 steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] 13689 }, 13690 second: { 13691 common: true, 13692 size: 1000, 13693 steps: [1, 2, 5, 10, 15, 30] 13694 }, 13695 minute: { 13696 common: true, 13697 size: 60000, 13698 steps: [1, 2, 5, 10, 15, 30] 13699 }, 13700 hour: { 13701 common: true, 13702 size: 3600000, 13703 steps: [1, 2, 3, 6, 12] 13704 }, 13705 day: { 13706 common: true, 13707 size: 86400000, 13708 steps: [1, 2, 5] 13709 }, 13710 week: { 13711 common: false, 13712 size: 604800000, 13713 steps: [1, 2, 3, 4] 13714 }, 13715 month: { 13716 common: true, 13717 size: 2.628e9, 13718 steps: [1, 2, 3] 13719 }, 13720 quarter: { 13721 common: false, 13722 size: 7.884e9, 13723 steps: [1, 2, 3, 4] 13724 }, 13725 year: { 13726 common: true, 13727 size: 3.154e10 13728 } 13729 }; 13730 13731 var UNITS = Object.keys(INTERVALS); 13732 13733 function sorter(a, b) { 13734 return a - b; 13735 } 13736 13737 function arrayUnique(items) { 13738 var hash = {}; 13739 var out = []; 13740 var i, ilen, item; 13741 13742 for (i = 0, ilen = items.length; i < ilen; ++i) { 13743 item = items[i]; 13744 if (!hash[item]) { 13745 hash[item] = true; 13746 out.push(item); 13747 } 13748 } 13749 13750 return out; 13751 } 13752 13753 /** 13754 * Returns an array of {time, pos} objects used to interpolate a specific `time` or position 13755 * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is 13756 * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other 13757 * extremity (left + width or top + height). Note that it would be more optimized to directly 13758 * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need 13759 * to create the lookup table. The table ALWAYS contains at least two items: min and max. 13760 * 13761 * @param {Number[]} timestamps - timestamps sorted from lowest to highest. 13762 * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min 13763 * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. 13764 * If 'series', timestamps will be positioned at the same distance from each other. In this 13765 * case, only timestamps that break the time linearity are registered, meaning that in the 13766 * best case, all timestamps are linear, the table contains only min and max. 13767 */ 13768 function buildLookupTable(timestamps, min, max, distribution) { 13769 if (distribution === 'linear' || !timestamps.length) { 13770 return [ 13771 {time: min, pos: 0}, 13772 {time: max, pos: 1} 13773 ]; 13774 } 13775 13776 var table = []; 13777 var items = [min]; 13778 var i, ilen, prev, curr, next; 13779 13780 for (i = 0, ilen = timestamps.length; i < ilen; ++i) { 13781 curr = timestamps[i]; 13782 if (curr > min && curr < max) { 13783 items.push(curr); 13784 } 13785 } 13786 13787 items.push(max); 13788 13789 for (i = 0, ilen = items.length; i < ilen; ++i) { 13790 next = items[i + 1]; 13791 prev = items[i - 1]; 13792 curr = items[i]; 13793 13794 // only add points that breaks the scale linearity 13795 if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { 13796 table.push({time: curr, pos: i / (ilen - 1)}); 13797 } 13798 } 13799 13800 return table; 13801 } 13802 13803 // @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ 13804 function lookup(table, key, value) { 13805 var lo = 0; 13806 var hi = table.length - 1; 13807 var mid, i0, i1; 13808 13809 while (lo >= 0 && lo <= hi) { 13810 mid = (lo + hi) >> 1; 13811 i0 = table[mid - 1] || null; 13812 i1 = table[mid]; 13813 13814 if (!i0) { 13815 // given value is outside table (before first item) 13816 return {lo: null, hi: i1}; 13817 } else if (i1[key] < value) { 13818 lo = mid + 1; 13819 } else if (i0[key] > value) { 13820 hi = mid - 1; 13821 } else { 13822 return {lo: i0, hi: i1}; 13823 } 13824 } 13825 13826 // given value is outside table (after last item) 13827 return {lo: i1, hi: null}; 13828 } 13829 13830 /** 13831 * Linearly interpolates the given source `value` using the table items `skey` values and 13832 * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') 13833 * returns the position for a timestamp equal to 42. If value is out of bounds, values at 13834 * index [0, 1] or [n - 1, n] are used for the interpolation. 13835 */ 13836 function interpolate(table, skey, sval, tkey) { 13837 var range = lookup(table, skey, sval); 13838 13839 // Note: the lookup table ALWAYS contains at least 2 items (min and max) 13840 var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; 13841 var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; 13842 13843 var span = next[skey] - prev[skey]; 13844 var ratio = span ? (sval - prev[skey]) / span : 0; 13845 var offset = (next[tkey] - prev[tkey]) * ratio; 13846 13847 return prev[tkey] + offset; 13848 } 13849 13850 /** 13851 * Convert the given value to a moment object using the given time options. 13852 * @see http://momentjs.com/docs/#/parsing/ 13853 */ 13854 function momentify(value, options) { 13855 var parser = options.parser; 13856 var format = options.parser || options.format; 13857 13858 if (typeof parser === 'function') { 13859 return parser(value); 13860 } 13861 13862 if (typeof value === 'string' && typeof format === 'string') { 13863 return moment(value, format); 13864 } 13865 13866 if (!(value instanceof moment)) { 13867 value = moment(value); 13868 } 13869 13870 if (value.isValid()) { 13871 return value; 13872 } 13873 13874 // Labels are in an incompatible moment format and no `parser` has been provided. 13875 // The user might still use the deprecated `format` option to convert his inputs. 13876 if (typeof format === 'function') { 13877 return format(value); 13878 } 13879 13880 return value; 13881 } 13882 13883 function parse(input, scale) { 13884 if (helpers.isNullOrUndef(input)) { 13885 return null; 13886 } 13887 13888 var options = scale.options.time; 13889 var value = momentify(scale.getRightValue(input), options); 13890 if (!value.isValid()) { 13891 return null; 13892 } 13893 13894 if (options.round) { 13895 value.startOf(options.round); 13896 } 13897 13898 return value.valueOf(); 13899 } 13900 13901 /** 13902 * Returns the number of unit to skip to be able to display up to `capacity` number of ticks 13903 * in `unit` for the given `min` / `max` range and respecting the interval steps constraints. 13904 */ 13905 function determineStepSize(min, max, unit, capacity) { 13906 var range = max - min; 13907 var interval = INTERVALS[unit]; 13908 var milliseconds = interval.size; 13909 var steps = interval.steps; 13910 var i, ilen, factor; 13911 13912 if (!steps) { 13913 return Math.ceil(range / (capacity * milliseconds)); 13914 } 13915 13916 for (i = 0, ilen = steps.length; i < ilen; ++i) { 13917 factor = steps[i]; 13918 if (Math.ceil(range / (milliseconds * factor)) <= capacity) { 13919 break; 13920 } 13921 } 13922 13923 return factor; 13924 } 13925 13926 /** 13927 * Figures out what unit results in an appropriate number of auto-generated ticks 13928 */ 13929 function determineUnitForAutoTicks(minUnit, min, max, capacity) { 13930 var ilen = UNITS.length; 13931 var i, interval, factor; 13932 13933 for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { 13934 interval = INTERVALS[UNITS[i]]; 13935 factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER; 13936 13937 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { 13938 return UNITS[i]; 13939 } 13940 } 13941 13942 return UNITS[ilen - 1]; 13943 } 13944 13945 /** 13946 * Figures out what unit to format a set of ticks with 13947 */ 13948 function determineUnitForFormatting(ticks, minUnit, min, max) { 13949 var duration = moment.duration(moment(max).diff(moment(min))); 13950 var ilen = UNITS.length; 13951 var i, unit; 13952 13953 for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) { 13954 unit = UNITS[i]; 13955 if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) { 13956 return unit; 13957 } 13958 } 13959 13960 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; 13961 } 13962 13963 function determineMajorUnit(unit) { 13964 for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { 13965 if (INTERVALS[UNITS[i]].common) { 13966 return UNITS[i]; 13967 } 13968 } 13969 } 13970 13971 /** 13972 * Generates a maximum of `capacity` timestamps between min and max, rounded to the 13973 * `minor` unit, aligned on the `major` unit and using the given scale time `options`. 13974 * Important: this method can return ticks outside the min and max range, it's the 13975 * responsibility of the calling code to clamp values if needed. 13976 */ 13977 function generate(min, max, capacity, options) { 13978 var timeOpts = options.time; 13979 var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); 13980 var major = determineMajorUnit(minor); 13981 var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize); 13982 var weekday = minor === 'week' ? timeOpts.isoWeekday : false; 13983 var majorTicksEnabled = options.ticks.major.enabled; 13984 var interval = INTERVALS[minor]; 13985 var first = moment(min); 13986 var last = moment(max); 13987 var ticks = []; 13988 var time; 13989 13990 if (!stepSize) { 13991 stepSize = determineStepSize(min, max, minor, capacity); 13992 } 13993 13994 // For 'week' unit, handle the first day of week option 13995 if (weekday) { 13996 first = first.isoWeekday(weekday); 13997 last = last.isoWeekday(weekday); 13998 } 13999 14000 // Align first/last ticks on unit 14001 first = first.startOf(weekday ? 'day' : minor); 14002 last = last.startOf(weekday ? 'day' : minor); 14003 14004 // Make sure that the last tick include max 14005 if (last < max) { 14006 last.add(1, minor); 14007 } 14008 14009 time = moment(first); 14010 14011 if (majorTicksEnabled && major && !weekday && !timeOpts.round) { 14012 // Align the first tick on the previous `minor` unit aligned on the `major` unit: 14013 // we first aligned time on the previous `major` unit then add the number of full 14014 // stepSize there is between first and the previous major time. 14015 time.startOf(major); 14016 time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor); 14017 } 14018 14019 for (; time < last; time.add(stepSize, minor)) { 14020 ticks.push(+time); 14021 } 14022 14023 ticks.push(+time); 14024 14025 return ticks; 14026 } 14027 14028 /** 14029 * Returns the right and left offsets from edges in the form of {left, right}. 14030 * Offsets are added when the `offset` option is true. 14031 */ 14032 function computeOffsets(table, ticks, min, max, options) { 14033 var left = 0; 14034 var right = 0; 14035 var upper, lower; 14036 14037 if (options.offset && ticks.length) { 14038 if (!options.time.min) { 14039 upper = ticks.length > 1 ? ticks[1] : max; 14040 lower = ticks[0]; 14041 left = ( 14042 interpolate(table, 'time', upper, 'pos') - 14043 interpolate(table, 'time', lower, 'pos') 14044 ) / 2; 14045 } 14046 if (!options.time.max) { 14047 upper = ticks[ticks.length - 1]; 14048 lower = ticks.length > 1 ? ticks[ticks.length - 2] : min; 14049 right = ( 14050 interpolate(table, 'time', upper, 'pos') - 14051 interpolate(table, 'time', lower, 'pos') 14052 ) / 2; 14053 } 14054 } 14055 14056 return {left: left, right: right}; 14057 } 14058 14059 function ticksFromTimestamps(values, majorUnit) { 14060 var ticks = []; 14061 var i, ilen, value, major; 14062 14063 for (i = 0, ilen = values.length; i < ilen; ++i) { 14064 value = values[i]; 14065 major = majorUnit ? value === +moment(value).startOf(majorUnit) : false; 14066 14067 ticks.push({ 14068 value: value, 14069 major: major 14070 }); 14071 } 14072 14073 return ticks; 14074 } 14075 14076 function determineLabelFormat(data, timeOpts) { 14077 var i, momentDate, hasTime; 14078 var ilen = data.length; 14079 14080 // find the label with the most parts (milliseconds, minutes, etc.) 14081 // format all labels with the same level of detail as the most specific label 14082 for (i = 0; i < ilen; i++) { 14083 momentDate = momentify(data[i], timeOpts); 14084 if (momentDate.millisecond() !== 0) { 14085 return 'MMM D, YYYY h:mm:ss.SSS a'; 14086 } 14087 if (momentDate.second() !== 0 || momentDate.minute() !== 0 || momentDate.hour() !== 0) { 14088 hasTime = true; 14089 } 14090 } 14091 if (hasTime) { 14092 return 'MMM D, YYYY h:mm:ss a'; 14093 } 14094 return 'MMM D, YYYY'; 14095 } 14096 14097 module.exports = function() { 14098 14099 var defaultConfig = { 14100 position: 'bottom', 14101 14102 /** 14103 * Data distribution along the scale: 14104 * - 'linear': data are spread according to their time (distances can vary), 14105 * - 'series': data are spread at the same distance from each other. 14106 * @see https://github.com/chartjs/Chart.js/pull/4507 14107 * @since 2.7.0 14108 */ 14109 distribution: 'linear', 14110 14111 /** 14112 * Scale boundary strategy (bypassed by min/max time options) 14113 * - `data`: make sure data are fully visible, ticks outside are removed 14114 * - `ticks`: make sure ticks are fully visible, data outside are truncated 14115 * @see https://github.com/chartjs/Chart.js/pull/4556 14116 * @since 2.7.0 14117 */ 14118 bounds: 'data', 14119 14120 time: { 14121 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 14122 format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/ 14123 unit: false, // false == automatic or override with week, month, year, etc. 14124 round: false, // none, or override with week, month, year, etc. 14125 displayFormat: false, // DEPRECATED 14126 isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/ 14127 minUnit: 'millisecond', 14128 14129 // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ 14130 displayFormats: { 14131 millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, 14132 second: 'h:mm:ss a', // 11:20:01 AM 14133 minute: 'h:mm a', // 11:20 AM 14134 hour: 'hA', // 5PM 14135 day: 'MMM D', // Sep 4 14136 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? 14137 month: 'MMM YYYY', // Sept 2015 14138 quarter: '[Q]Q - YYYY', // Q3 14139 year: 'YYYY' // 2015 14140 }, 14141 }, 14142 ticks: { 14143 autoSkip: false, 14144 14145 /** 14146 * Ticks generation input values: 14147 * - 'auto': generates "optimal" ticks based on scale size and time options. 14148 * - 'data': generates ticks from data (including labels from data {t|x|y} objects). 14149 * - 'labels': generates ticks from user given `data.labels` values ONLY. 14150 * @see https://github.com/chartjs/Chart.js/pull/4507 14151 * @since 2.7.0 14152 */ 14153 source: 'auto', 14154 14155 major: { 14156 enabled: false 14157 } 14158 } 14159 }; 14160 14161 var TimeScale = Scale.extend({ 14162 initialize: function() { 14163 if (!moment) { 14164 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'); 14165 } 14166 14167 this.mergeTicksOptions(); 14168 14169 Scale.prototype.initialize.call(this); 14170 }, 14171 14172 update: function() { 14173 var me = this; 14174 var options = me.options; 14175 14176 // DEPRECATIONS: output a message only one time per update 14177 if (options.time && options.time.format) { 14178 console.warn('options.time.format is deprecated and replaced by options.time.parser.'); 14179 } 14180 14181 return Scale.prototype.update.apply(me, arguments); 14182 }, 14183 14184 /** 14185 * Allows data to be referenced via 't' attribute 14186 */ 14187 getRightValue: function(rawValue) { 14188 if (rawValue && rawValue.t !== undefined) { 14189 rawValue = rawValue.t; 14190 } 14191 return Scale.prototype.getRightValue.call(this, rawValue); 14192 }, 14193 14194 determineDataLimits: function() { 14195 var me = this; 14196 var chart = me.chart; 14197 var timeOpts = me.options.time; 14198 var unit = timeOpts.unit || 'day'; 14199 var min = MAX_INTEGER; 14200 var max = MIN_INTEGER; 14201 var timestamps = []; 14202 var datasets = []; 14203 var labels = []; 14204 var i, j, ilen, jlen, data, timestamp; 14205 14206 // Convert labels to timestamps 14207 for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) { 14208 labels.push(parse(chart.data.labels[i], me)); 14209 } 14210 14211 // Convert data to timestamps 14212 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { 14213 if (chart.isDatasetVisible(i)) { 14214 data = chart.data.datasets[i].data; 14215 14216 // Let's consider that all data have the same format. 14217 if (helpers.isObject(data[0])) { 14218 datasets[i] = []; 14219 14220 for (j = 0, jlen = data.length; j < jlen; ++j) { 14221 timestamp = parse(data[j], me); 14222 timestamps.push(timestamp); 14223 datasets[i][j] = timestamp; 14224 } 14225 } else { 14226 timestamps.push.apply(timestamps, labels); 14227 datasets[i] = labels.slice(0); 14228 } 14229 } else { 14230 datasets[i] = []; 14231 } 14232 } 14233 14234 if (labels.length) { 14235 // Sort labels **after** data have been converted 14236 labels = arrayUnique(labels).sort(sorter); 14237 min = Math.min(min, labels[0]); 14238 max = Math.max(max, labels[labels.length - 1]); 14239 } 14240 14241 if (timestamps.length) { 14242 timestamps = arrayUnique(timestamps).sort(sorter); 14243 min = Math.min(min, timestamps[0]); 14244 max = Math.max(max, timestamps[timestamps.length - 1]); 14245 } 14246 14247 min = parse(timeOpts.min, me) || min; 14248 max = parse(timeOpts.max, me) || max; 14249 14250 // In case there is no valid min/max, set limits based on unit time option 14251 min = min === MAX_INTEGER ? +moment().startOf(unit) : min; 14252 max = max === MIN_INTEGER ? +moment().endOf(unit) + 1 : max; 14253 14254 // Make sure that max is strictly higher than min (required by the lookup table) 14255 me.min = Math.min(min, max); 14256 me.max = Math.max(min + 1, max); 14257 14258 // PRIVATE 14259 me._horizontal = me.isHorizontal(); 14260 me._table = []; 14261 me._timestamps = { 14262 data: timestamps, 14263 datasets: datasets, 14264 labels: labels 14265 }; 14266 }, 14267 14268 buildTicks: function() { 14269 var me = this; 14270 var min = me.min; 14271 var max = me.max; 14272 var options = me.options; 14273 var timeOpts = options.time; 14274 var timestamps = []; 14275 var ticks = []; 14276 var i, ilen, timestamp; 14277 14278 switch (options.ticks.source) { 14279 case 'data': 14280 timestamps = me._timestamps.data; 14281 break; 14282 case 'labels': 14283 timestamps = me._timestamps.labels; 14284 break; 14285 case 'auto': 14286 default: 14287 timestamps = generate(min, max, me.getLabelCapacity(min), options); 14288 } 14289 14290 if (options.bounds === 'ticks' && timestamps.length) { 14291 min = timestamps[0]; 14292 max = timestamps[timestamps.length - 1]; 14293 } 14294 14295 // Enforce limits with user min/max options 14296 min = parse(timeOpts.min, me) || min; 14297 max = parse(timeOpts.max, me) || max; 14298 14299 // Remove ticks outside the min/max range 14300 for (i = 0, ilen = timestamps.length; i < ilen; ++i) { 14301 timestamp = timestamps[i]; 14302 if (timestamp >= min && timestamp <= max) { 14303 ticks.push(timestamp); 14304 } 14305 } 14306 14307 me.min = min; 14308 me.max = max; 14309 14310 // PRIVATE 14311 me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max); 14312 me._majorUnit = determineMajorUnit(me._unit); 14313 me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution); 14314 me._offsets = computeOffsets(me._table, ticks, min, max, options); 14315 me._labelFormat = determineLabelFormat(me._timestamps.data, timeOpts); 14316 14317 return ticksFromTimestamps(ticks, me._majorUnit); 14318 }, 14319 14320 getLabelForIndex: function(index, datasetIndex) { 14321 var me = this; 14322 var data = me.chart.data; 14323 var timeOpts = me.options.time; 14324 var label = data.labels && index < data.labels.length ? data.labels[index] : ''; 14325 var value = data.datasets[datasetIndex].data[index]; 14326 14327 if (helpers.isObject(value)) { 14328 label = me.getRightValue(value); 14329 } 14330 if (timeOpts.tooltipFormat) { 14331 return momentify(label, timeOpts).format(timeOpts.tooltipFormat); 14332 } 14333 if (typeof label === 'string') { 14334 return label; 14335 } 14336 14337 return momentify(label, timeOpts).format(me._labelFormat); 14338 }, 14339 14340 /** 14341 * Function to format an individual tick mark 14342 * @private 14343 */ 14344 tickFormatFunction: function(tick, index, ticks, formatOverride) { 14345 var me = this; 14346 var options = me.options; 14347 var time = tick.valueOf(); 14348 var formats = options.time.displayFormats; 14349 var minorFormat = formats[me._unit]; 14350 var majorUnit = me._majorUnit; 14351 var majorFormat = formats[majorUnit]; 14352 var majorTime = tick.clone().startOf(majorUnit).valueOf(); 14353 var majorTickOpts = options.ticks.major; 14354 var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; 14355 var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); 14356 var tickOpts = major ? majorTickOpts : options.ticks.minor; 14357 var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); 14358 14359 return formatter ? formatter(label, index, ticks) : label; 14360 }, 14361 14362 convertTicksToLabels: function(ticks) { 14363 var labels = []; 14364 var i, ilen; 14365 14366 for (i = 0, ilen = ticks.length; i < ilen; ++i) { 14367 labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks)); 14368 } 14369 14370 return labels; 14371 }, 14372 14373 /** 14374 * @private 14375 */ 14376 getPixelForOffset: function(time) { 14377 var me = this; 14378 var size = me._horizontal ? me.width : me.height; 14379 var start = me._horizontal ? me.left : me.top; 14380 var pos = interpolate(me._table, 'time', time, 'pos'); 14381 14382 return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right); 14383 }, 14384 14385 getPixelForValue: function(value, index, datasetIndex) { 14386 var me = this; 14387 var time = null; 14388 14389 if (index !== undefined && datasetIndex !== undefined) { 14390 time = me._timestamps.datasets[datasetIndex][index]; 14391 } 14392 14393 if (time === null) { 14394 time = parse(value, me); 14395 } 14396 14397 if (time !== null) { 14398 return me.getPixelForOffset(time); 14399 } 14400 }, 14401 14402 getPixelForTick: function(index) { 14403 var ticks = this.getTicks(); 14404 return index >= 0 && index < ticks.length ? 14405 this.getPixelForOffset(ticks[index].value) : 14406 null; 14407 }, 14408 14409 getValueForPixel: function(pixel) { 14410 var me = this; 14411 var size = me._horizontal ? me.width : me.height; 14412 var start = me._horizontal ? me.left : me.top; 14413 var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right; 14414 var time = interpolate(me._table, 'pos', pos, 'time'); 14415 14416 return moment(time); 14417 }, 14418 14419 /** 14420 * Crude approximation of what the label width might be 14421 * @private 14422 */ 14423 getLabelWidth: function(label) { 14424 var me = this; 14425 var ticksOpts = me.options.ticks; 14426 var tickLabelWidth = me.ctx.measureText(label).width; 14427 var angle = helpers.toRadians(ticksOpts.maxRotation); 14428 var cosRotation = Math.cos(angle); 14429 var sinRotation = Math.sin(angle); 14430 var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize); 14431 14432 return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); 14433 }, 14434 14435 /** 14436 * @private 14437 */ 14438 getLabelCapacity: function(exampleTime) { 14439 var me = this; 14440 14441 var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation 14442 14443 var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride); 14444 var tickLabelWidth = me.getLabelWidth(exampleLabel); 14445 var innerWidth = me.isHorizontal() ? me.width : me.height; 14446 14447 var capacity = Math.floor(innerWidth / tickLabelWidth); 14448 return capacity > 0 ? capacity : 1; 14449 } 14450 }); 14451 14452 scaleService.registerScaleType('time', TimeScale, defaultConfig); 14453 }; 14454 14455 },{"1":1,"26":26,"33":33,"34":34,"46":46}]},{},[7])(7) 14456 });