1/*! 2 * Chart.js v2.9.4 3 * https://www.chartjs.org 4 * (c) 2020 Chart.js Contributors 5 * Released under the MIT License 6 */ 7(function (global, factory) { 8typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(function() { try { return require('moment'); } catch(e) { } }()) : 9typeof define === 'function' && define.amd ? define(['require'], function(require) { return factory(function() { try { return require('moment'); } catch(e) { } }()); }) : 10(global = global || self, global.Chart = factory(global.moment)); 11}(this, (function (moment) { 'use strict'; 12 13moment = moment && moment.hasOwnProperty('default') ? moment['default'] : moment; 14 15function createCommonjsModule(fn, module) { 16 return module = { exports: {} }, fn(module, module.exports), module.exports; 17} 18 19function getCjsExportFromNamespace (n) { 20 return n && n['default'] || n; 21} 22 23var colorName = { 24 "aliceblue": [240, 248, 255], 25 "antiquewhite": [250, 235, 215], 26 "aqua": [0, 255, 255], 27 "aquamarine": [127, 255, 212], 28 "azure": [240, 255, 255], 29 "beige": [245, 245, 220], 30 "bisque": [255, 228, 196], 31 "black": [0, 0, 0], 32 "blanchedalmond": [255, 235, 205], 33 "blue": [0, 0, 255], 34 "blueviolet": [138, 43, 226], 35 "brown": [165, 42, 42], 36 "burlywood": [222, 184, 135], 37 "cadetblue": [95, 158, 160], 38 "chartreuse": [127, 255, 0], 39 "chocolate": [210, 105, 30], 40 "coral": [255, 127, 80], 41 "cornflowerblue": [100, 149, 237], 42 "cornsilk": [255, 248, 220], 43 "crimson": [220, 20, 60], 44 "cyan": [0, 255, 255], 45 "darkblue": [0, 0, 139], 46 "darkcyan": [0, 139, 139], 47 "darkgoldenrod": [184, 134, 11], 48 "darkgray": [169, 169, 169], 49 "darkgreen": [0, 100, 0], 50 "darkgrey": [169, 169, 169], 51 "darkkhaki": [189, 183, 107], 52 "darkmagenta": [139, 0, 139], 53 "darkolivegreen": [85, 107, 47], 54 "darkorange": [255, 140, 0], 55 "darkorchid": [153, 50, 204], 56 "darkred": [139, 0, 0], 57 "darksalmon": [233, 150, 122], 58 "darkseagreen": [143, 188, 143], 59 "darkslateblue": [72, 61, 139], 60 "darkslategray": [47, 79, 79], 61 "darkslategrey": [47, 79, 79], 62 "darkturquoise": [0, 206, 209], 63 "darkviolet": [148, 0, 211], 64 "deeppink": [255, 20, 147], 65 "deepskyblue": [0, 191, 255], 66 "dimgray": [105, 105, 105], 67 "dimgrey": [105, 105, 105], 68 "dodgerblue": [30, 144, 255], 69 "firebrick": [178, 34, 34], 70 "floralwhite": [255, 250, 240], 71 "forestgreen": [34, 139, 34], 72 "fuchsia": [255, 0, 255], 73 "gainsboro": [220, 220, 220], 74 "ghostwhite": [248, 248, 255], 75 "gold": [255, 215, 0], 76 "goldenrod": [218, 165, 32], 77 "gray": [128, 128, 128], 78 "green": [0, 128, 0], 79 "greenyellow": [173, 255, 47], 80 "grey": [128, 128, 128], 81 "honeydew": [240, 255, 240], 82 "hotpink": [255, 105, 180], 83 "indianred": [205, 92, 92], 84 "indigo": [75, 0, 130], 85 "ivory": [255, 255, 240], 86 "khaki": [240, 230, 140], 87 "lavender": [230, 230, 250], 88 "lavenderblush": [255, 240, 245], 89 "lawngreen": [124, 252, 0], 90 "lemonchiffon": [255, 250, 205], 91 "lightblue": [173, 216, 230], 92 "lightcoral": [240, 128, 128], 93 "lightcyan": [224, 255, 255], 94 "lightgoldenrodyellow": [250, 250, 210], 95 "lightgray": [211, 211, 211], 96 "lightgreen": [144, 238, 144], 97 "lightgrey": [211, 211, 211], 98 "lightpink": [255, 182, 193], 99 "lightsalmon": [255, 160, 122], 100 "lightseagreen": [32, 178, 170], 101 "lightskyblue": [135, 206, 250], 102 "lightslategray": [119, 136, 153], 103 "lightslategrey": [119, 136, 153], 104 "lightsteelblue": [176, 196, 222], 105 "lightyellow": [255, 255, 224], 106 "lime": [0, 255, 0], 107 "limegreen": [50, 205, 50], 108 "linen": [250, 240, 230], 109 "magenta": [255, 0, 255], 110 "maroon": [128, 0, 0], 111 "mediumaquamarine": [102, 205, 170], 112 "mediumblue": [0, 0, 205], 113 "mediumorchid": [186, 85, 211], 114 "mediumpurple": [147, 112, 219], 115 "mediumseagreen": [60, 179, 113], 116 "mediumslateblue": [123, 104, 238], 117 "mediumspringgreen": [0, 250, 154], 118 "mediumturquoise": [72, 209, 204], 119 "mediumvioletred": [199, 21, 133], 120 "midnightblue": [25, 25, 112], 121 "mintcream": [245, 255, 250], 122 "mistyrose": [255, 228, 225], 123 "moccasin": [255, 228, 181], 124 "navajowhite": [255, 222, 173], 125 "navy": [0, 0, 128], 126 "oldlace": [253, 245, 230], 127 "olive": [128, 128, 0], 128 "olivedrab": [107, 142, 35], 129 "orange": [255, 165, 0], 130 "orangered": [255, 69, 0], 131 "orchid": [218, 112, 214], 132 "palegoldenrod": [238, 232, 170], 133 "palegreen": [152, 251, 152],
vendor: 440,698 bytes, lines 134-16172
134 "paleturquoise": [175, 238, 238], 135 "palevioletred": [219, 112, 147], 136 "papayawhip": [255, 239, 213], 137 "peachpuff": [255, 218, 185], 138 "peru": [205, 133, 63], 139 "pink": [255, 192, 203], 140 "plum": [221, 160, 221], 141 "powderblue": [176, 224, 230], 142 "purple": [128, 0, 128], 143 "rebeccapurple": [102, 51, 153], 144 "red": [255, 0, 0], 145 "rosybrown": [188, 143, 143], 146 "royalblue": [65, 105, 225], 147 "saddlebrown": [139, 69, 19], 148 "salmon": [250, 128, 114], 149 "sandybrown": [244, 164, 96], 150 "seagreen": [46, 139, 87], 151 "seashell": [255, 245, 238], 152 "sienna": [160, 82, 45], 153 "silver": [192, 192, 192], 154 "skyblue": [135, 206, 235], 155 "slateblue": [106, 90, 205], 156 "slategray": [112, 128, 144], 157 "slategrey": [112, 128, 144], 158 "snow": [255, 250, 250], 159 "springgreen": [0, 255, 127], 160 "steelblue": [70, 130, 180], 161 "tan": [210, 180, 140], 162 "teal": [0, 128, 128], 163 "thistle": [216, 191, 216], 164 "tomato": [255, 99, 71], 165 "turquoise": [64, 224, 208], 166 "violet": [238, 130, 238], 167 "wheat": [245, 222, 179], 168 "white": [255, 255, 255], 169 "whitesmoke": [245, 245, 245], 170 "yellow": [255, 255, 0], 171 "yellowgreen": [154, 205, 50] 172}; 173 174var conversions = createCommonjsModule(function (module) { 175/* MIT license */ 176 177 178// NOTE: conversions should only return primitive values (i.e. arrays, or 179// values that give correct `typeof` results). 180// do not use box values types (i.e. Number(), String(), etc.) 181 182var reverseKeywords = {}; 183for (var key in colorName) { 184 if (colorName.hasOwnProperty(key)) { 185 reverseKeywords[colorName[key]] = key; 186 } 187} 188 189var convert = module.exports = { 190 rgb: {channels: 3, labels: 'rgb'}, 191 hsl: {channels: 3, labels: 'hsl'}, 192 hsv: {channels: 3, labels: 'hsv'}, 193 hwb: {channels: 3, labels: 'hwb'}, 194 cmyk: {channels: 4, labels: 'cmyk'}, 195 xyz: {channels: 3, labels: 'xyz'}, 196 lab: {channels: 3, labels: 'lab'}, 197 lch: {channels: 3, labels: 'lch'}, 198 hex: {channels: 1, labels: ['hex']}, 199 keyword: {channels: 1, labels: ['keyword']}, 200 ansi16: {channels: 1, labels: ['ansi16']}, 201 ansi256: {channels: 1, labels: ['ansi256']}, 202 hcg: {channels: 3, labels: ['h', 'c', 'g']}, 203 apple: {channels: 3, labels: ['r16', 'g16', 'b16']}, 204 gray: {channels: 1, labels: ['gray']} 205}; 206 207// hide .channels and .labels properties 208for (var model in convert) { 209 if (convert.hasOwnProperty(model)) { 210 if (!('channels' in convert[model])) { 211 throw new Error('missing channels property: ' + model); 212 } 213 214 if (!('labels' in convert[model])) { 215 throw new Error('missing channel labels property: ' + model); 216 } 217 218 if (convert[model].labels.length !== convert[model].channels) { 219 throw new Error('channel and label counts mismatch: ' + model); 220 } 221 222 var channels = convert[model].channels; 223 var labels = convert[model].labels; 224 delete convert[model].channels; 225 delete convert[model].labels; 226 Object.defineProperty(convert[model], 'channels', {value: channels}); 227 Object.defineProperty(convert[model], 'labels', {value: labels}); 228 } 229} 230 231convert.rgb.hsl = function (rgb) { 232 var r = rgb[0] / 255; 233 var g = rgb[1] / 255; 234 var b = rgb[2] / 255; 235 var min = Math.min(r, g, b); 236 var max = Math.max(r, g, b); 237 var delta = max - min; 238 var h; 239 var s; 240 var l; 241 242 if (max === min) { 243 h = 0; 244 } else if (r === max) { 245 h = (g - b) / delta; 246 } else if (g === max) { 247 h = 2 + (b - r) / delta; 248 } else if (b === max) { 249 h = 4 + (r - g) / delta; 250 } 251 252 h = Math.min(h * 60, 360); 253 254 if (h < 0) { 255 h += 360; 256 } 257 258 l = (min + max) / 2; 259 260 if (max === min) { 261 s = 0; 262 } else if (l <= 0.5) { 263 s = delta / (max + min); 264 } else { 265 s = delta / (2 - max - min); 266 } 267 268 return [h, s * 100, l * 100]; 269}; 270 271convert.rgb.hsv = function (rgb) { 272 var rdif; 273 var gdif; 274 var bdif; 275 var h; 276 var s; 277 278 var r = rgb[0] / 255; 279 var g = rgb[1] / 255; 280 var b = rgb[2] / 255; 281 var v = Math.max(r, g, b); 282 var diff = v - Math.min(r, g, b); 283 var diffc = function (c) { 284 return (v - c) / 6 / diff + 1 / 2; 285 }; 286 287 if (diff === 0) { 288 h = s = 0; 289 } else { 290 s = diff / v; 291 rdif = diffc(r); 292 gdif = diffc(g); 293 bdif = diffc(b); 294 295 if (r === v) { 296 h = bdif - gdif; 297 } else if (g === v) { 298 h = (1 / 3) + rdif - bdif; 299 } else if (b === v) { 300 h = (2 / 3) + gdif - rdif; 301 } 302 if (h < 0) { 303 h += 1; 304 } else if (h > 1) { 305 h -= 1; 306 } 307 } 308 309 return [ 310 h * 360, 311 s * 100, 312 v * 100 313 ]; 314}; 315 316convert.rgb.hwb = function (rgb) { 317 var r = rgb[0]; 318 var g = rgb[1]; 319 var b = rgb[2]; 320 var h = convert.rgb.hsl(rgb)[0]; 321 var w = 1 / 255 * Math.min(r, Math.min(g, b)); 322 323 b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); 324 325 return [h, w * 100, b * 100]; 326}; 327 328convert.rgb.cmyk = function (rgb) { 329 var r = rgb[0] / 255; 330 var g = rgb[1] / 255; 331 var b = rgb[2] / 255; 332 var c; 333 var m; 334 var y; 335 var k; 336 337 k = Math.min(1 - r, 1 - g, 1 - b); 338 c = (1 - r - k) / (1 - k) || 0; 339 m = (1 - g - k) / (1 - k) || 0; 340 y = (1 - b - k) / (1 - k) || 0; 341 342 return [c * 100, m * 100, y * 100, k * 100]; 343}; 344 345/** 346 * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance 347 * */ 348function comparativeDistance(x, y) { 349 return ( 350 Math.pow(x[0] - y[0], 2) + 351 Math.pow(x[1] - y[1], 2) + 352 Math.pow(x[2] - y[2], 2) 353 ); 354} 355 356convert.rgb.keyword = function (rgb) { 357 var reversed = reverseKeywords[rgb]; 358 if (reversed) { 359 return reversed; 360 } 361 362 var currentClosestDistance = Infinity; 363 var currentClosestKeyword; 364 365 for (var keyword in colorName) { 366 if (colorName.hasOwnProperty(keyword)) { 367 var value = colorName[keyword]; 368 369 // Compute comparative distance 370 var distance = comparativeDistance(rgb, value); 371 372 // Check if its less, if so set as closest 373 if (distance < currentClosestDistance) { 374 currentClosestDistance = distance; 375 currentClosestKeyword = keyword; 376 } 377 } 378 } 379 380 return currentClosestKeyword; 381}; 382 383convert.keyword.rgb = function (keyword) { 384 return colorName[keyword]; 385}; 386 387convert.rgb.xyz = function (rgb) { 388 var r = rgb[0] / 255; 389 var g = rgb[1] / 255; 390 var b = rgb[2] / 255; 391 392 // assume sRGB 393 r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); 394 g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); 395 b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); 396 397 var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); 398 var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); 399 var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); 400 401 return [x * 100, y * 100, z * 100]; 402}; 403 404convert.rgb.lab = function (rgb) { 405 var xyz = convert.rgb.xyz(rgb); 406 var x = xyz[0]; 407 var y = xyz[1]; 408 var z = xyz[2]; 409 var l; 410 var a; 411 var b; 412 413 x /= 95.047; 414 y /= 100; 415 z /= 108.883; 416 417 x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); 418 y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); 419 z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); 420 421 l = (116 * y) - 16; 422 a = 500 * (x - y); 423 b = 200 * (y - z); 424 425 return [l, a, b]; 426}; 427 428convert.hsl.rgb = function (hsl) { 429 var h = hsl[0] / 360; 430 var s = hsl[1] / 100; 431 var l = hsl[2] / 100; 432 var t1; 433 var t2; 434 var t3; 435 var rgb; 436 var val; 437 438 if (s === 0) { 439 val = l * 255; 440 return [val, val, val]; 441 } 442 443 if (l < 0.5) { 444 t2 = l * (1 + s); 445 } else { 446 t2 = l + s - l * s; 447 } 448 449 t1 = 2 * l - t2; 450 451 rgb = [0, 0, 0]; 452 for (var i = 0; i < 3; i++) { 453 t3 = h + 1 / 3 * -(i - 1); 454 if (t3 < 0) { 455 t3++; 456 } 457 if (t3 > 1) { 458 t3--; 459 } 460 461 if (6 * t3 < 1) { 462 val = t1 + (t2 - t1) * 6 * t3; 463 } else if (2 * t3 < 1) { 464 val = t2; 465 } else if (3 * t3 < 2) { 466 val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; 467 } else { 468 val = t1; 469 } 470 471 rgb[i] = val * 255; 472 } 473 474 return rgb; 475}; 476 477convert.hsl.hsv = function (hsl) { 478 var h = hsl[0]; 479 var s = hsl[1] / 100; 480 var l = hsl[2] / 100; 481 var smin = s; 482 var lmin = Math.max(l, 0.01); 483 var sv; 484 var v; 485 486 l *= 2; 487 s *= (l <= 1) ? l : 2 - l; 488 smin *= lmin <= 1 ? lmin : 2 - lmin; 489 v = (l + s) / 2; 490 sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); 491 492 return [h, sv * 100, v * 100]; 493}; 494 495convert.hsv.rgb = function (hsv) { 496 var h = hsv[0] / 60; 497 var s = hsv[1] / 100; 498 var v = hsv[2] / 100; 499 var hi = Math.floor(h) % 6; 500 501 var f = h - Math.floor(h); 502 var p = 255 * v * (1 - s); 503 var q = 255 * v * (1 - (s * f)); 504 var t = 255 * v * (1 - (s * (1 - f))); 505 v *= 255; 506 507 switch (hi) { 508 case 0: 509 return [v, t, p]; 510 case 1: 511 return [q, v, p]; 512 case 2: 513 return [p, v, t]; 514 case 3: 515 return [p, q, v]; 516 case 4: 517 return [t, p, v]; 518 case 5: 519 return [v, p, q]; 520 } 521}; 522 523convert.hsv.hsl = function (hsv) { 524 var h = hsv[0]; 525 var s = hsv[1] / 100; 526 var v = hsv[2] / 100; 527 var vmin = Math.max(v, 0.01); 528 var lmin; 529 var sl; 530 var l; 531 532 l = (2 - s) * v; 533 lmin = (2 - s) * vmin; 534 sl = s * vmin; 535 sl /= (lmin <= 1) ? lmin : 2 - lmin; 536 sl = sl || 0; 537 l /= 2; 538 539 return [h, sl * 100, l * 100]; 540}; 541 542// http://dev.w3.org/csswg/css-color/#hwb-to-rgb 543convert.hwb.rgb = function (hwb) { 544 var h = hwb[0] / 360; 545 var wh = hwb[1] / 100; 546 var bl = hwb[2] / 100; 547 var ratio = wh + bl; 548 var i; 549 var v; 550 var f; 551 var n; 552 553 // wh + bl cant be > 1 554 if (ratio > 1) { 555 wh /= ratio; 556 bl /= ratio; 557 } 558 559 i = Math.floor(6 * h); 560 v = 1 - bl; 561 f = 6 * h - i; 562 563 if ((i & 0x01) !== 0) { 564 f = 1 - f; 565 } 566 567 n = wh + f * (v - wh); // linear interpolation 568 569 var r; 570 var g; 571 var b; 572 switch (i) { 573 default: 574 case 6: 575 case 0: r = v; g = n; b = wh; break; 576 case 1: r = n; g = v; b = wh; break; 577 case 2: r = wh; g = v; b = n; break; 578 case 3: r = wh; g = n; b = v; break; 579 case 4: r = n; g = wh; b = v; break; 580 case 5: r = v; g = wh; b = n; break; 581 } 582 583 return [r * 255, g * 255, b * 255]; 584}; 585 586convert.cmyk.rgb = function (cmyk) { 587 var c = cmyk[0] / 100; 588 var m = cmyk[1] / 100; 589 var y = cmyk[2] / 100; 590 var k = cmyk[3] / 100; 591 var r; 592 var g; 593 var b; 594 595 r = 1 - Math.min(1, c * (1 - k) + k); 596 g = 1 - Math.min(1, m * (1 - k) + k); 597 b = 1 - Math.min(1, y * (1 - k) + k); 598 599 return [r * 255, g * 255, b * 255]; 600}; 601 602convert.xyz.rgb = function (xyz) { 603 var x = xyz[0] / 100; 604 var y = xyz[1] / 100; 605 var z = xyz[2] / 100; 606 var r; 607 var g; 608 var b; 609 610 r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); 611 g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); 612 b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); 613 614 // assume sRGB 615 r = r > 0.0031308 616 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) 617 : r * 12.92; 618 619 g = g > 0.0031308 620 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) 621 : g * 12.92; 622 623 b = b > 0.0031308 624 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) 625 : b * 12.92; 626 627 r = Math.min(Math.max(0, r), 1); 628 g = Math.min(Math.max(0, g), 1); 629 b = Math.min(Math.max(0, b), 1); 630 631 return [r * 255, g * 255, b * 255]; 632}; 633 634convert.xyz.lab = function (xyz) { 635 var x = xyz[0]; 636 var y = xyz[1]; 637 var z = xyz[2]; 638 var l; 639 var a; 640 var b; 641 642 x /= 95.047; 643 y /= 100; 644 z /= 108.883; 645 646 x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); 647 y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); 648 z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); 649 650 l = (116 * y) - 16; 651 a = 500 * (x - y); 652 b = 200 * (y - z); 653 654 return [l, a, b]; 655}; 656 657convert.lab.xyz = function (lab) { 658 var l = lab[0]; 659 var a = lab[1]; 660 var b = lab[2]; 661 var x; 662 var y; 663 var z; 664 665 y = (l + 16) / 116; 666 x = a / 500 + y; 667 z = y - b / 200; 668 669 var y2 = Math.pow(y, 3); 670 var x2 = Math.pow(x, 3); 671 var z2 = Math.pow(z, 3); 672 y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; 673 x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; 674 z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; 675 676 x *= 95.047; 677 y *= 100; 678 z *= 108.883; 679 680 return [x, y, z]; 681}; 682 683convert.lab.lch = function (lab) { 684 var l = lab[0]; 685 var a = lab[1]; 686 var b = lab[2]; 687 var hr; 688 var h; 689 var c; 690 691 hr = Math.atan2(b, a); 692 h = hr * 360 / 2 / Math.PI; 693 694 if (h < 0) { 695 h += 360; 696 } 697 698 c = Math.sqrt(a * a + b * b); 699 700 return [l, c, h]; 701}; 702 703convert.lch.lab = function (lch) { 704 var l = lch[0]; 705 var c = lch[1]; 706 var h = lch[2]; 707 var a; 708 var b; 709 var hr; 710 711 hr = h / 360 * 2 * Math.PI; 712 a = c * Math.cos(hr); 713 b = c * Math.sin(hr); 714 715 return [l, a, b]; 716}; 717 718convert.rgb.ansi16 = function (args) { 719 var r = args[0]; 720 var g = args[1]; 721 var b = args[2]; 722 var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization 723 724 value = Math.round(value / 50); 725 726 if (value === 0) { 727 return 30; 728 } 729 730 var ansi = 30 731 + ((Math.round(b / 255) << 2) 732 | (Math.round(g / 255) << 1) 733 | Math.round(r / 255)); 734 735 if (value === 2) { 736 ansi += 60; 737 } 738 739 return ansi; 740}; 741 742convert.hsv.ansi16 = function (args) { 743 // optimization here; we already know the value and don't need to get 744 // it converted for us. 745 return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); 746}; 747 748convert.rgb.ansi256 = function (args) { 749 var r = args[0]; 750 var g = args[1]; 751 var b = args[2]; 752 753 // we use the extended greyscale palette here, with the exception of 754 // black and white. normal palette only has 4 greyscale shades. 755 if (r === g && g === b) { 756 if (r < 8) { 757 return 16; 758 } 759 760 if (r > 248) { 761 return 231; 762 } 763 764 return Math.round(((r - 8) / 247) * 24) + 232; 765 } 766 767 var ansi = 16 768 + (36 * Math.round(r / 255 * 5)) 769 + (6 * Math.round(g / 255 * 5)) 770 + Math.round(b / 255 * 5); 771 772 return ansi; 773}; 774 775convert.ansi16.rgb = function (args) { 776 var color = args % 10; 777 778 // handle greyscale 779 if (color === 0 || color === 7) { 780 if (args > 50) { 781 color += 3.5; 782 } 783 784 color = color / 10.5 * 255; 785 786 return [color, color, color]; 787 } 788 789 var mult = (~~(args > 50) + 1) * 0.5; 790 var r = ((color & 1) * mult) * 255; 791 var g = (((color >> 1) & 1) * mult) * 255; 792 var b = (((color >> 2) & 1) * mult) * 255; 793 794 return [r, g, b]; 795}; 796 797convert.ansi256.rgb = function (args) { 798 // handle greyscale 799 if (args >= 232) { 800 var c = (args - 232) * 10 + 8; 801 return [c, c, c]; 802 } 803 804 args -= 16; 805 806 var rem; 807 var r = Math.floor(args / 36) / 5 * 255; 808 var g = Math.floor((rem = args % 36) / 6) / 5 * 255; 809 var b = (rem % 6) / 5 * 255; 810 811 return [r, g, b]; 812}; 813 814convert.rgb.hex = function (args) { 815 var integer = ((Math.round(args[0]) & 0xFF) << 16) 816 + ((Math.round(args[1]) & 0xFF) << 8) 817 + (Math.round(args[2]) & 0xFF); 818 819 var string = integer.toString(16).toUpperCase(); 820 return '000000'.substring(string.length) + string; 821}; 822 823convert.hex.rgb = function (args) { 824 var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); 825 if (!match) { 826 return [0, 0, 0]; 827 } 828 829 var colorString = match[0]; 830 831 if (match[0].length === 3) { 832 colorString = colorString.split('').map(function (char) { 833 return char + char; 834 }).join(''); 835 } 836 837 var integer = parseInt(colorString, 16); 838 var r = (integer >> 16) & 0xFF; 839 var g = (integer >> 8) & 0xFF; 840 var b = integer & 0xFF; 841 842 return [r, g, b]; 843}; 844 845convert.rgb.hcg = function (rgb) { 846 var r = rgb[0] / 255; 847 var g = rgb[1] / 255; 848 var b = rgb[2] / 255; 849 var max = Math.max(Math.max(r, g), b); 850 var min = Math.min(Math.min(r, g), b); 851 var chroma = (max - min); 852 var grayscale; 853 var hue; 854 855 if (chroma < 1) { 856 grayscale = min / (1 - chroma); 857 } else { 858 grayscale = 0; 859 } 860 861 if (chroma <= 0) { 862 hue = 0; 863 } else 864 if (max === r) { 865 hue = ((g - b) / chroma) % 6; 866 } else 867 if (max === g) { 868 hue = 2 + (b - r) / chroma; 869 } else { 870 hue = 4 + (r - g) / chroma + 4; 871 } 872 873 hue /= 6; 874 hue %= 1; 875 876 return [hue * 360, chroma * 100, grayscale * 100]; 877}; 878 879convert.hsl.hcg = function (hsl) { 880 var s = hsl[1] / 100; 881 var l = hsl[2] / 100; 882 var c = 1; 883 var f = 0; 884 885 if (l < 0.5) { 886 c = 2.0 * s * l; 887 } else { 888 c = 2.0 * s * (1.0 - l); 889 } 890 891 if (c < 1.0) { 892 f = (l - 0.5 * c) / (1.0 - c); 893 } 894 895 return [hsl[0], c * 100, f * 100]; 896}; 897 898convert.hsv.hcg = function (hsv) { 899 var s = hsv[1] / 100; 900 var v = hsv[2] / 100; 901 902 var c = s * v; 903 var f = 0; 904 905 if (c < 1.0) { 906 f = (v - c) / (1 - c); 907 } 908 909 return [hsv[0], c * 100, f * 100]; 910}; 911 912convert.hcg.rgb = function (hcg) { 913 var h = hcg[0] / 360; 914 var c = hcg[1] / 100; 915 var g = hcg[2] / 100; 916 917 if (c === 0.0) { 918 return [g * 255, g * 255, g * 255]; 919 } 920 921 var pure = [0, 0, 0]; 922 var hi = (h % 1) * 6; 923 var v = hi % 1; 924 var w = 1 - v; 925 var mg = 0; 926 927 switch (Math.floor(hi)) { 928 case 0: 929 pure[0] = 1; pure[1] = v; pure[2] = 0; break; 930 case 1: 931 pure[0] = w; pure[1] = 1; pure[2] = 0; break; 932 case 2: 933 pure[0] = 0; pure[1] = 1; pure[2] = v; break; 934 case 3: 935 pure[0] = 0; pure[1] = w; pure[2] = 1; break; 936 case 4: 937 pure[0] = v; pure[1] = 0; pure[2] = 1; break; 938 default: 939 pure[0] = 1; pure[1] = 0; pure[2] = w; 940 } 941 942 mg = (1.0 - c) * g; 943 944 return [ 945 (c * pure[0] + mg) * 255, 946 (c * pure[1] + mg) * 255, 947 (c * pure[2] + mg) * 255 948 ]; 949}; 950 951convert.hcg.hsv = function (hcg) { 952 var c = hcg[1] / 100; 953 var g = hcg[2] / 100; 954 955 var v = c + g * (1.0 - c); 956 var f = 0; 957 958 if (v > 0.0) { 959 f = c / v; 960 } 961 962 return [hcg[0], f * 100, v * 100]; 963}; 964 965convert.hcg.hsl = function (hcg) { 966 var c = hcg[1] / 100; 967 var g = hcg[2] / 100; 968 969 var l = g * (1.0 - c) + 0.5 * c; 970 var s = 0; 971 972 if (l > 0.0 && l < 0.5) { 973 s = c / (2 * l); 974 } else 975 if (l >= 0.5 && l < 1.0) { 976 s = c / (2 * (1 - l)); 977 } 978 979 return [hcg[0], s * 100, l * 100]; 980}; 981 982convert.hcg.hwb = function (hcg) { 983 var c = hcg[1] / 100; 984 var g = hcg[2] / 100; 985 var v = c + g * (1.0 - c); 986 return [hcg[0], (v - c) * 100, (1 - v) * 100]; 987}; 988 989convert.hwb.hcg = function (hwb) { 990 var w = hwb[1] / 100; 991 var b = hwb[2] / 100; 992 var v = 1 - b; 993 var c = v - w; 994 var g = 0; 995 996 if (c < 1) { 997 g = (v - c) / (1 - c); 998 } 999 1000 return [hwb[0], c * 100, g * 100]; 1001}; 1002 1003convert.apple.rgb = function (apple) { 1004 return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; 1005}; 1006 1007convert.rgb.apple = function (rgb) { 1008 return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; 1009}; 1010 1011convert.gray.rgb = function (args) { 1012 return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; 1013}; 1014 1015convert.gray.hsl = convert.gray.hsv = function (args) { 1016 return [0, 0, args[0]]; 1017}; 1018 1019convert.gray.hwb = function (gray) { 1020 return [0, 100, gray[0]]; 1021}; 1022 1023convert.gray.cmyk = function (gray) { 1024 return [0, 0, 0, gray[0]]; 1025}; 1026 1027convert.gray.lab = function (gray) { 1028 return [gray[0], 0, 0]; 1029}; 1030 1031convert.gray.hex = function (gray) { 1032 var val = Math.round(gray[0] / 100 * 255) & 0xFF; 1033 var integer = (val << 16) + (val << 8) + val; 1034 1035 var string = integer.toString(16).toUpperCase(); 1036 return '000000'.substring(string.length) + string; 1037}; 1038 1039convert.rgb.gray = function (rgb) { 1040 var val = (rgb[0] + rgb[1] + rgb[2]) / 3; 1041 return [val / 255 * 100]; 1042}; 1043}); 1044var conversions_1 = conversions.rgb; 1045var conversions_2 = conversions.hsl; 1046var conversions_3 = conversions.hsv; 1047var conversions_4 = conversions.hwb; 1048var conversions_5 = conversions.cmyk; 1049var conversions_6 = conversions.xyz; 1050var conversions_7 = conversions.lab; 1051var conversions_8 = conversions.lch; 1052var conversions_9 = conversions.hex; 1053var conversions_10 = conversions.keyword; 1054var conversions_11 = conversions.ansi16; 1055var conversions_12 = conversions.ansi256; 1056var conversions_13 = conversions.hcg; 1057var conversions_14 = conversions.apple; 1058var conversions_15 = conversions.gray; 1059 1060/* 1061 this function routes a model to all other models. 1062 1063 all functions that are routed have a property `.conversion` attached 1064 to the returned synthetic function. This property is an array 1065 of strings, each with the steps in between the 'from' and 'to' 1066 color models (inclusive). 1067 1068 conversions that are not possible simply are not included. 1069*/ 1070 1071function buildGraph() { 1072 var graph = {}; 1073 // https://jsperf.com/object-keys-vs-for-in-with-closure/3 1074 var models = Object.keys(conversions); 1075 1076 for (var len = models.length, i = 0; i < len; i++) { 1077 graph[models[i]] = { 1078 // http://jsperf.com/1-vs-infinity 1079 // micro-opt, but this is simple. 1080 distance: -1, 1081 parent: null 1082 }; 1083 } 1084 1085 return graph; 1086} 1087 1088// https://en.wikipedia.org/wiki/Breadth-first_search 1089function deriveBFS(fromModel) { 1090 var graph = buildGraph(); 1091 var queue = [fromModel]; // unshift -> queue -> pop 1092 1093 graph[fromModel].distance = 0; 1094 1095 while (queue.length) { 1096 var current = queue.pop(); 1097 var adjacents = Object.keys(conversions[current]); 1098 1099 for (var len = adjacents.length, i = 0; i < len; i++) { 1100 var adjacent = adjacents[i]; 1101 var node = graph[adjacent]; 1102 1103 if (node.distance === -1) { 1104 node.distance = graph[current].distance + 1; 1105 node.parent = current; 1106 queue.unshift(adjacent); 1107 } 1108 } 1109 } 1110 1111 return graph; 1112} 1113 1114function link(from, to) { 1115 return function (args) { 1116 return to(from(args)); 1117 }; 1118} 1119 1120function wrapConversion(toModel, graph) { 1121 var path = [graph[toModel].parent, toModel]; 1122 var fn = conversions[graph[toModel].parent][toModel]; 1123 1124 var cur = graph[toModel].parent; 1125 while (graph[cur].parent) { 1126 path.unshift(graph[cur].parent); 1127 fn = link(conversions[graph[cur].parent][cur], fn); 1128 cur = graph[cur].parent; 1129 } 1130 1131 fn.conversion = path; 1132 return fn; 1133} 1134 1135var route = function (fromModel) { 1136 var graph = deriveBFS(fromModel); 1137 var conversion = {}; 1138 1139 var models = Object.keys(graph); 1140 for (var len = models.length, i = 0; i < len; i++) { 1141 var toModel = models[i]; 1142 var node = graph[toModel]; 1143 1144 if (node.parent === null) { 1145 // no possible conversion, or this node is the source model. 1146 continue; 1147 } 1148 1149 conversion[toModel] = wrapConversion(toModel, graph); 1150 } 1151 1152 return conversion; 1153}; 1154 1155var convert = {}; 1156 1157var models = Object.keys(conversions); 1158 1159function wrapRaw(fn) { 1160 var wrappedFn = function (args) { 1161 if (args === undefined || args === null) { 1162 return args; 1163 } 1164 1165 if (arguments.length > 1) { 1166 args = Array.prototype.slice.call(arguments); 1167 } 1168 1169 return fn(args); 1170 }; 1171 1172 // preserve .conversion property if there is one 1173 if ('conversion' in fn) { 1174 wrappedFn.conversion = fn.conversion; 1175 } 1176 1177 return wrappedFn; 1178} 1179 1180function wrapRounded(fn) { 1181 var wrappedFn = function (args) { 1182 if (args === undefined || args === null) { 1183 return args; 1184 } 1185 1186 if (arguments.length > 1) { 1187 args = Array.prototype.slice.call(arguments); 1188 } 1189 1190 var result = fn(args); 1191 1192 // we're assuming the result is an array here. 1193 // see notice in conversions.js; don't use box types 1194 // in conversion functions. 1195 if (typeof result === 'object') { 1196 for (var len = result.length, i = 0; i < len; i++) { 1197 result[i] = Math.round(result[i]); 1198 } 1199 } 1200 1201 return result; 1202 }; 1203 1204 // preserve .conversion property if there is one 1205 if ('conversion' in fn) { 1206 wrappedFn.conversion = fn.conversion; 1207 } 1208 1209 return wrappedFn; 1210} 1211 1212models.forEach(function (fromModel) { 1213 convert[fromModel] = {}; 1214 1215 Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); 1216 Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); 1217 1218 var routes = route(fromModel); 1219 var routeModels = Object.keys(routes); 1220 1221 routeModels.forEach(function (toModel) { 1222 var fn = routes[toModel]; 1223 1224 convert[fromModel][toModel] = wrapRounded(fn); 1225 convert[fromModel][toModel].raw = wrapRaw(fn); 1226 }); 1227}); 1228 1229var colorConvert = convert; 1230 1231var colorName$1 = { 1232 "aliceblue": [240, 248, 255], 1233 "antiquewhite": [250, 235, 215], 1234 "aqua": [0, 255, 255], 1235 "aquamarine": [127, 255, 212], 1236 "azure": [240, 255, 255], 1237 "beige": [245, 245, 220], 1238 "bisque": [255, 228, 196], 1239 "black": [0, 0, 0], 1240 "blanchedalmond": [255, 235, 205], 1241 "blue": [0, 0, 255], 1242 "blueviolet": [138, 43, 226], 1243 "brown": [165, 42, 42], 1244 "burlywood": [222, 184, 135], 1245 "cadetblue": [95, 158, 160], 1246 "chartreuse": [127, 255, 0], 1247 "chocolate": [210, 105, 30], 1248 "coral": [255, 127, 80], 1249 "cornflowerblue": [100, 149, 237], 1250 "cornsilk": [255, 248, 220], 1251 "crimson": [220, 20, 60], 1252 "cyan": [0, 255, 255], 1253 "darkblue": [0, 0, 139], 1254 "darkcyan": [0, 139, 139], 1255 "darkgoldenrod": [184, 134, 11], 1256 "darkgray": [169, 169, 169], 1257 "darkgreen": [0, 100, 0], 1258 "darkgrey": [169, 169, 169], 1259 "darkkhaki": [189, 183, 107], 1260 "darkmagenta": [139, 0, 139], 1261 "darkolivegreen": [85, 107, 47], 1262 "darkorange": [255, 140, 0], 1263 "darkorchid": [153, 50, 204], 1264 "darkred": [139, 0, 0], 1265 "darksalmon": [233, 150, 122], 1266 "darkseagreen": [143, 188, 143], 1267 "darkslateblue": [72, 61, 139], 1268 "darkslategray": [47, 79, 79], 1269 "darkslategrey": [47, 79, 79], 1270 "darkturquoise": [0, 206, 209], 1271 "darkviolet": [148, 0, 211], 1272 "deeppink": [255, 20, 147], 1273 "deepskyblue": [0, 191, 255], 1274 "dimgray": [105, 105, 105], 1275 "dimgrey": [105, 105, 105], 1276 "dodgerblue": [30, 144, 255], 1277 "firebrick": [178, 34, 34], 1278 "floralwhite": [255, 250, 240], 1279 "forestgreen": [34, 139, 34], 1280 "fuchsia": [255, 0, 255], 1281 "gainsboro": [220, 220, 220], 1282 "ghostwhite": [248, 248, 255], 1283 "gold": [255, 215, 0], 1284 "goldenrod": [218, 165, 32], 1285 "gray": [128, 128, 128], 1286 "green": [0, 128, 0], 1287 "greenyellow": [173, 255, 47], 1288 "grey": [128, 128, 128], 1289 "honeydew": [240, 255, 240], 1290 "hotpink": [255, 105, 180], 1291 "indianred": [205, 92, 92], 1292 "indigo": [75, 0, 130], 1293 "ivory": [255, 255, 240], 1294 "khaki": [240, 230, 140], 1295 "lavender": [230, 230, 250], 1296 "lavenderblush": [255, 240, 245], 1297 "lawngreen": [124, 252, 0], 1298 "lemonchiffon": [255, 250, 205], 1299 "lightblue": [173, 216, 230], 1300 "lightcoral": [240, 128, 128], 1301 "lightcyan": [224, 255, 255], 1302 "lightgoldenrodyellow": [250, 250, 210], 1303 "lightgray": [211, 211, 211], 1304 "lightgreen": [144, 238, 144], 1305 "lightgrey": [211, 211, 211], 1306 "lightpink": [255, 182, 193], 1307 "lightsalmon": [255, 160, 122], 1308 "lightseagreen": [32, 178, 170], 1309 "lightskyblue": [135, 206, 250], 1310 "lightslategray": [119, 136, 153], 1311 "lightslategrey": [119, 136, 153], 1312 "lightsteelblue": [176, 196, 222], 1313 "lightyellow": [255, 255, 224], 1314 "lime": [0, 255, 0], 1315 "limegreen": [50, 205, 50], 1316 "linen": [250, 240, 230], 1317 "magenta": [255, 0, 255], 1318 "maroon": [128, 0, 0], 1319 "mediumaquamarine": [102, 205, 170], 1320 "mediumblue": [0, 0, 205], 1321 "mediumorchid": [186, 85, 211], 1322 "mediumpurple": [147, 112, 219], 1323 "mediumseagreen": [60, 179, 113], 1324 "mediumslateblue": [123, 104, 238], 1325 "mediumspringgreen": [0, 250, 154], 1326 "mediumturquoise": [72, 209, 204], 1327 "mediumvioletred": [199, 21, 133], 1328 "midnightblue": [25, 25, 112], 1329 "mintcream": [245, 255, 250], 1330 "mistyrose": [255, 228, 225], 1331 "moccasin": [255, 228, 181], 1332 "navajowhite": [255, 222, 173], 1333 "navy": [0, 0, 128], 1334 "oldlace": [253, 245, 230], 1335 "olive": [128, 128, 0], 1336 "olivedrab": [107, 142, 35], 1337 "orange": [255, 165, 0], 1338 "orangered": [255, 69, 0], 1339 "orchid": [218, 112, 214], 1340 "palegoldenrod": [238, 232, 170], 1341 "palegreen": [152, 251, 152], 1342 "paleturquoise": [175, 238, 238], 1343 "palevioletred": [219, 112, 147], 1344 "papayawhip": [255, 239, 213], 1345 "peachpuff": [255, 218, 185], 1346 "peru": [205, 133, 63], 1347 "pink": [255, 192, 203], 1348 "plum": [221, 160, 221], 1349 "powderblue": [176, 224, 230], 1350 "purple": [128, 0, 128], 1351 "rebeccapurple": [102, 51, 153], 1352 "red": [255, 0, 0], 1353 "rosybrown": [188, 143, 143], 1354 "royalblue": [65, 105, 225], 1355 "saddlebrown": [139, 69, 19], 1356 "salmon": [250, 128, 114], 1357 "sandybrown": [244, 164, 96], 1358 "seagreen": [46, 139, 87], 1359 "seashell": [255, 245, 238], 1360 "sienna": [160, 82, 45], 1361 "silver": [192, 192, 192], 1362 "skyblue": [135, 206, 235], 1363 "slateblue": [106, 90, 205], 1364 "slategray": [112, 128, 144], 1365 "slategrey": [112, 128, 144], 1366 "snow": [255, 250, 250], 1367 "springgreen": [0, 255, 127], 1368 "steelblue": [70, 130, 180], 1369 "tan": [210, 180, 140], 1370 "teal": [0, 128, 128], 1371 "thistle": [216, 191, 216], 1372 "tomato": [255, 99, 71], 1373 "turquoise": [64, 224, 208], 1374 "violet": [238, 130, 238], 1375 "wheat": [245, 222, 179], 1376 "white": [255, 255, 255], 1377 "whitesmoke": [245, 245, 245], 1378 "yellow": [255, 255, 0], 1379 "yellowgreen": [154, 205, 50] 1380}; 1381 1382/* MIT license */ 1383 1384 1385var colorString = { 1386 getRgba: getRgba, 1387 getHsla: getHsla, 1388 getRgb: getRgb, 1389 getHsl: getHsl, 1390 getHwb: getHwb, 1391 getAlpha: getAlpha, 1392 1393 hexString: hexString, 1394 rgbString: rgbString, 1395 rgbaString: rgbaString, 1396 percentString: percentString, 1397 percentaString: percentaString, 1398 hslString: hslString, 1399 hslaString: hslaString, 1400 hwbString: hwbString, 1401 keyword: keyword 1402}; 1403 1404function getRgba(string) { 1405 if (!string) { 1406 return; 1407 } 1408 var abbr = /^#([a-fA-F0-9]{3,4})$/i, 1409 hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i, 1410 rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, 1411 per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, 1412 keyword = /(\w+)/; 1413 1414 var rgb = [0, 0, 0], 1415 a = 1, 1416 match = string.match(abbr), 1417 hexAlpha = ""; 1418 if (match) { 1419 match = match[1]; 1420 hexAlpha = match[3]; 1421 for (var i = 0; i < rgb.length; i++) { 1422 rgb[i] = parseInt(match[i] + match[i], 16); 1423 } 1424 if (hexAlpha) { 1425 a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100; 1426 } 1427 } 1428 else if (match = string.match(hex)) { 1429 hexAlpha = match[2]; 1430 match = match[1]; 1431 for (var i = 0; i < rgb.length; i++) { 1432 rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); 1433 } 1434 if (hexAlpha) { 1435 a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100; 1436 } 1437 } 1438 else if (match = string.match(rgba)) { 1439 for (var i = 0; i < rgb.length; i++) { 1440 rgb[i] = parseInt(match[i + 1]); 1441 } 1442 a = parseFloat(match[4]); 1443 } 1444 else if (match = string.match(per)) { 1445 for (var i = 0; i < rgb.length; i++) { 1446 rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); 1447 } 1448 a = parseFloat(match[4]); 1449 } 1450 else if (match = string.match(keyword)) { 1451 if (match[1] == "transparent") { 1452 return [0, 0, 0, 0]; 1453 } 1454 rgb = colorName$1[match[1]]; 1455 if (!rgb) { 1456 return; 1457 } 1458 } 1459 1460 for (var i = 0; i < rgb.length; i++) { 1461 rgb[i] = scale(rgb[i], 0, 255); 1462 } 1463 if (!a && a != 0) { 1464 a = 1; 1465 } 1466 else { 1467 a = scale(a, 0, 1); 1468 } 1469 rgb[3] = a; 1470 return rgb; 1471} 1472 1473function getHsla(string) { 1474 if (!string) { 1475 return; 1476 } 1477 var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; 1478 var match = string.match(hsl); 1479 if (match) { 1480 var alpha = parseFloat(match[4]); 1481 var h = scale(parseInt(match[1]), 0, 360), 1482 s = scale(parseFloat(match[2]), 0, 100), 1483 l = scale(parseFloat(match[3]), 0, 100), 1484 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); 1485 return [h, s, l, a]; 1486 } 1487} 1488 1489function getHwb(string) { 1490 if (!string) { 1491 return; 1492 } 1493 var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; 1494 var match = string.match(hwb); 1495 if (match) { 1496 var alpha = parseFloat(match[4]); 1497 var h = scale(parseInt(match[1]), 0, 360), 1498 w = scale(parseFloat(match[2]), 0, 100), 1499 b = scale(parseFloat(match[3]), 0, 100), 1500 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); 1501 return [h, w, b, a]; 1502 } 1503} 1504 1505function getRgb(string) { 1506 var rgba = getRgba(string); 1507 return rgba && rgba.slice(0, 3); 1508} 1509 1510function getHsl(string) { 1511 var hsla = getHsla(string); 1512 return hsla && hsla.slice(0, 3); 1513} 1514 1515function getAlpha(string) { 1516 var vals = getRgba(string); 1517 if (vals) { 1518 return vals[3]; 1519 } 1520 else if (vals = getHsla(string)) { 1521 return vals[3]; 1522 } 1523 else if (vals = getHwb(string)) { 1524 return vals[3]; 1525 } 1526} 1527 1528// generators 1529function hexString(rgba, a) { 1530 var a = (a !== undefined && rgba.length === 3) ? a : rgba[3]; 1531 return "#" + hexDouble(rgba[0]) 1532 + hexDouble(rgba[1]) 1533 + hexDouble(rgba[2]) 1534 + ( 1535 (a >= 0 && a < 1) 1536 ? hexDouble(Math.round(a * 255)) 1537 : "" 1538 ); 1539} 1540 1541function rgbString(rgba, alpha) { 1542 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { 1543 return rgbaString(rgba, alpha); 1544 } 1545 return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; 1546} 1547 1548function rgbaString(rgba, alpha) { 1549 if (alpha === undefined) { 1550 alpha = (rgba[3] !== undefined ? rgba[3] : 1); 1551 } 1552 return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] 1553 + ", " + alpha + ")"; 1554} 1555 1556function percentString(rgba, alpha) { 1557 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { 1558 return percentaString(rgba, alpha); 1559 } 1560 var r = Math.round(rgba[0]/255 * 100), 1561 g = Math.round(rgba[1]/255 * 100), 1562 b = Math.round(rgba[2]/255 * 100); 1563 1564 return "rgb(" + r + "%, " + g + "%, " + b + "%)"; 1565} 1566 1567function percentaString(rgba, alpha) { 1568 var r = Math.round(rgba[0]/255 * 100), 1569 g = Math.round(rgba[1]/255 * 100), 1570 b = Math.round(rgba[2]/255 * 100); 1571 return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; 1572} 1573 1574function hslString(hsla, alpha) { 1575 if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { 1576 return hslaString(hsla, alpha); 1577 } 1578 return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; 1579} 1580 1581function hslaString(hsla, alpha) { 1582 if (alpha === undefined) { 1583 alpha = (hsla[3] !== undefined ? hsla[3] : 1); 1584 } 1585 return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " 1586 + alpha + ")"; 1587} 1588 1589// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax 1590// (hwb have alpha optional & 1 is default value) 1591function hwbString(hwb, alpha) { 1592 if (alpha === undefined) { 1593 alpha = (hwb[3] !== undefined ? hwb[3] : 1); 1594 } 1595 return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" 1596 + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; 1597} 1598 1599function keyword(rgb) { 1600 return reverseNames[rgb.slice(0, 3)]; 1601} 1602 1603// helpers 1604function scale(num, min, max) { 1605 return Math.min(Math.max(min, num), max); 1606} 1607 1608function hexDouble(num) { 1609 var str = num.toString(16).toUpperCase(); 1610 return (str.length < 2) ? "0" + str : str; 1611} 1612 1613 1614//create a list of reverse color names 1615var reverseNames = {}; 1616for (var name in colorName$1) { 1617 reverseNames[colorName$1[name]] = name; 1618} 1619 1620/* MIT license */ 1621 1622 1623 1624var Color = function (obj) { 1625 if (obj instanceof Color) { 1626 return obj; 1627 } 1628 if (!(this instanceof Color)) { 1629 return new Color(obj); 1630 } 1631 1632 this.valid = false; 1633 this.values = { 1634 rgb: [0, 0, 0], 1635 hsl: [0, 0, 0], 1636 hsv: [0, 0, 0], 1637 hwb: [0, 0, 0], 1638 cmyk: [0, 0, 0, 0], 1639 alpha: 1 1640 }; 1641 1642 // parse Color() argument 1643 var vals; 1644 if (typeof obj === 'string') { 1645 vals = colorString.getRgba(obj); 1646 if (vals) { 1647 this.setValues('rgb', vals); 1648 } else if (vals = colorString.getHsla(obj)) { 1649 this.setValues('hsl', vals); 1650 } else if (vals = colorString.getHwb(obj)) { 1651 this.setValues('hwb', vals); 1652 } 1653 } else if (typeof obj === 'object') { 1654 vals = obj; 1655 if (vals.r !== undefined || vals.red !== undefined) { 1656 this.setValues('rgb', vals); 1657 } else if (vals.l !== undefined || vals.lightness !== undefined) { 1658 this.setValues('hsl', vals); 1659 } else if (vals.v !== undefined || vals.value !== undefined) { 1660 this.setValues('hsv', vals); 1661 } else if (vals.w !== undefined || vals.whiteness !== undefined) { 1662 this.setValues('hwb', vals); 1663 } else if (vals.c !== undefined || vals.cyan !== undefined) { 1664 this.setValues('cmyk', vals); 1665 } 1666 } 1667}; 1668 1669Color.prototype = { 1670 isValid: function () { 1671 return this.valid; 1672 }, 1673 rgb: function () { 1674 return this.setSpace('rgb', arguments); 1675 }, 1676 hsl: function () { 1677 return this.setSpace('hsl', arguments); 1678 }, 1679 hsv: function () { 1680 return this.setSpace('hsv', arguments); 1681 }, 1682 hwb: function () { 1683 return this.setSpace('hwb', arguments); 1684 }, 1685 cmyk: function () { 1686 return this.setSpace('cmyk', arguments); 1687 }, 1688 1689 rgbArray: function () { 1690 return this.values.rgb; 1691 }, 1692 hslArray: function () { 1693 return this.values.hsl; 1694 }, 1695 hsvArray: function () { 1696 return this.values.hsv; 1697 }, 1698 hwbArray: function () { 1699 var values = this.values; 1700 if (values.alpha !== 1) { 1701 return values.hwb.concat([values.alpha]); 1702 } 1703 return values.hwb; 1704 }, 1705 cmykArray: function () { 1706 return this.values.cmyk; 1707 }, 1708 rgbaArray: function () { 1709 var values = this.values; 1710 return values.rgb.concat([values.alpha]); 1711 }, 1712 hslaArray: function () { 1713 var values = this.values; 1714 return values.hsl.concat([values.alpha]); 1715 }, 1716 alpha: function (val) { 1717 if (val === undefined) { 1718 return this.values.alpha; 1719 } 1720 this.setValues('alpha', val); 1721 return this; 1722 }, 1723 1724 red: function (val) { 1725 return this.setChannel('rgb', 0, val); 1726 }, 1727 green: function (val) { 1728 return this.setChannel('rgb', 1, val); 1729 }, 1730 blue: function (val) { 1731 return this.setChannel('rgb', 2, val); 1732 }, 1733 hue: function (val) { 1734 if (val) { 1735 val %= 360; 1736 val = val < 0 ? 360 + val : val; 1737 } 1738 return this.setChannel('hsl', 0, val); 1739 }, 1740 saturation: function (val) { 1741 return this.setChannel('hsl', 1, val); 1742 }, 1743 lightness: function (val) { 1744 return this.setChannel('hsl', 2, val); 1745 }, 1746 saturationv: function (val) { 1747 return this.setChannel('hsv', 1, val); 1748 }, 1749 whiteness: function (val) { 1750 return this.setChannel('hwb', 1, val); 1751 }, 1752 blackness: function (val) { 1753 return this.setChannel('hwb', 2, val); 1754 }, 1755 value: function (val) { 1756 return this.setChannel('hsv', 2, val); 1757 }, 1758 cyan: function (val) { 1759 return this.setChannel('cmyk', 0, val); 1760 }, 1761 magenta: function (val) { 1762 return this.setChannel('cmyk', 1, val); 1763 }, 1764 yellow: function (val) { 1765 return this.setChannel('cmyk', 2, val); 1766 }, 1767 black: function (val) { 1768 return this.setChannel('cmyk', 3, val); 1769 }, 1770 1771 hexString: function () { 1772 return colorString.hexString(this.values.rgb); 1773 }, 1774 rgbString: function () { 1775 return colorString.rgbString(this.values.rgb, this.values.alpha); 1776 }, 1777 rgbaString: function () { 1778 return colorString.rgbaString(this.values.rgb, this.values.alpha); 1779 }, 1780 percentString: function () { 1781 return colorString.percentString(this.values.rgb, this.values.alpha); 1782 }, 1783 hslString: function () { 1784 return colorString.hslString(this.values.hsl, this.values.alpha); 1785 }, 1786 hslaString: function () { 1787 return colorString.hslaString(this.values.hsl, this.values.alpha); 1788 }, 1789 hwbString: function () { 1790 return colorString.hwbString(this.values.hwb, this.values.alpha); 1791 }, 1792 keyword: function () { 1793 return colorString.keyword(this.values.rgb, this.values.alpha); 1794 }, 1795 1796 rgbNumber: function () { 1797 var rgb = this.values.rgb; 1798 return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; 1799 }, 1800 1801 luminosity: function () { 1802 // http://www.w3.org/TR/WCAG20/#relativeluminancedef 1803 var rgb = this.values.rgb; 1804 var lum = []; 1805 for (var i = 0; i < rgb.length; i++) { 1806 var chan = rgb[i] / 255; 1807 lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); 1808 } 1809 return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; 1810 }, 1811 1812 contrast: function (color2) { 1813 // http://www.w3.org/TR/WCAG20/#contrast-ratiodef 1814 var lum1 = this.luminosity(); 1815 var lum2 = color2.luminosity(); 1816 if (lum1 > lum2) { 1817 return (lum1 + 0.05) / (lum2 + 0.05); 1818 } 1819 return (lum2 + 0.05) / (lum1 + 0.05); 1820 }, 1821 1822 level: function (color2) { 1823 var contrastRatio = this.contrast(color2); 1824 if (contrastRatio >= 7.1) { 1825 return 'AAA'; 1826 } 1827 1828 return (contrastRatio >= 4.5) ? 'AA' : ''; 1829 }, 1830 1831 dark: function () { 1832 // YIQ equation from http://24ways.org/2010/calculating-color-contrast 1833 var rgb = this.values.rgb; 1834 var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; 1835 return yiq < 128; 1836 }, 1837 1838 light: function () { 1839 return !this.dark(); 1840 }, 1841 1842 negate: function () { 1843 var rgb = []; 1844 for (var i = 0; i < 3; i++) { 1845 rgb[i] = 255 - this.values.rgb[i]; 1846 } 1847 this.setValues('rgb', rgb); 1848 return this; 1849 }, 1850 1851 lighten: function (ratio) { 1852 var hsl = this.values.hsl; 1853 hsl[2] += hsl[2] * ratio; 1854 this.setValues('hsl', hsl); 1855 return this; 1856 }, 1857 1858 darken: function (ratio) { 1859 var hsl = this.values.hsl; 1860 hsl[2] -= hsl[2] * ratio; 1861 this.setValues('hsl', hsl); 1862 return this; 1863 }, 1864 1865 saturate: function (ratio) { 1866 var hsl = this.values.hsl; 1867 hsl[1] += hsl[1] * ratio; 1868 this.setValues('hsl', hsl); 1869 return this; 1870 }, 1871 1872 desaturate: function (ratio) { 1873 var hsl = this.values.hsl; 1874 hsl[1] -= hsl[1] * ratio; 1875 this.setValues('hsl', hsl); 1876 return this; 1877 }, 1878 1879 whiten: function (ratio) { 1880 var hwb = this.values.hwb; 1881 hwb[1] += hwb[1] * ratio; 1882 this.setValues('hwb', hwb); 1883 return this; 1884 }, 1885 1886 blacken: function (ratio) { 1887 var hwb = this.values.hwb; 1888 hwb[2] += hwb[2] * ratio; 1889 this.setValues('hwb', hwb); 1890 return this; 1891 }, 1892 1893 greyscale: function () { 1894 var rgb = this.values.rgb; 1895 // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale 1896 var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; 1897 this.setValues('rgb', [val, val, val]); 1898 return this; 1899 }, 1900 1901 clearer: function (ratio) { 1902 var alpha = this.values.alpha; 1903 this.setValues('alpha', alpha - (alpha * ratio)); 1904 return this; 1905 }, 1906 1907 opaquer: function (ratio) { 1908 var alpha = this.values.alpha; 1909 this.setValues('alpha', alpha + (alpha * ratio)); 1910 return this; 1911 }, 1912 1913 rotate: function (degrees) { 1914 var hsl = this.values.hsl; 1915 var hue = (hsl[0] + degrees) % 360; 1916 hsl[0] = hue < 0 ? 360 + hue : hue; 1917 this.setValues('hsl', hsl); 1918 return this; 1919 }, 1920 1921 /** 1922 * Ported from sass implementation in C 1923 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 1924 */ 1925 mix: function (mixinColor, weight) { 1926 var color1 = this; 1927 var color2 = mixinColor; 1928 var p = weight === undefined ? 0.5 : weight; 1929 1930 var w = 2 * p - 1; 1931 var a = color1.alpha() - color2.alpha(); 1932 1933 var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; 1934 var w2 = 1 - w1; 1935 1936 return this 1937 .rgb( 1938 w1 * color1.red() + w2 * color2.red(), 1939 w1 * color1.green() + w2 * color2.green(), 1940 w1 * color1.blue() + w2 * color2.blue() 1941 ) 1942 .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); 1943 }, 1944 1945 toJSON: function () { 1946 return this.rgb(); 1947 }, 1948 1949 clone: function () { 1950 // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, 1951 // making the final build way to big to embed in Chart.js. So let's do it manually, 1952 // assuming that values to clone are 1 dimension arrays containing only numbers, 1953 // except 'alpha' which is a number. 1954 var result = new Color(); 1955 var source = this.values; 1956 var target = result.values; 1957 var value, type; 1958 1959 for (var prop in source) { 1960 if (source.hasOwnProperty(prop)) { 1961 value = source[prop]; 1962 type = ({}).toString.call(value); 1963 if (type === '[object Array]') { 1964 target[prop] = value.slice(0); 1965 } else if (type === '[object Number]') { 1966 target[prop] = value; 1967 } else { 1968 console.error('unexpected color value:', value); 1969 } 1970 } 1971 } 1972 1973 return result; 1974 } 1975}; 1976 1977Color.prototype.spaces = { 1978 rgb: ['red', 'green', 'blue'], 1979 hsl: ['hue', 'saturation', 'lightness'], 1980 hsv: ['hue', 'saturation', 'value'], 1981 hwb: ['hue', 'whiteness', 'blackness'], 1982 cmyk: ['cyan', 'magenta', 'yellow', 'black'] 1983}; 1984 1985Color.prototype.maxes = { 1986 rgb: [255, 255, 255], 1987 hsl: [360, 100, 100], 1988 hsv: [360, 100, 100], 1989 hwb: [360, 100, 100], 1990 cmyk: [100, 100, 100, 100] 1991}; 1992 1993Color.prototype.getValues = function (space) { 1994 var values = this.values; 1995 var vals = {}; 1996 1997 for (var i = 0; i < space.length; i++) { 1998 vals[space.charAt(i)] = values[space][i]; 1999 } 2000 2001 if (values.alpha !== 1) { 2002 vals.a = values.alpha; 2003 } 2004 2005 // {r: 255, g: 255, b: 255, a: 0.4} 2006 return vals; 2007}; 2008 2009Color.prototype.setValues = function (space, vals) { 2010 var values = this.values; 2011 var spaces = this.spaces; 2012 var maxes = this.maxes; 2013 var alpha = 1; 2014 var i; 2015 2016 this.valid = true; 2017 2018 if (space === 'alpha') { 2019 alpha = vals; 2020 } else if (vals.length) { 2021 // [10, 10, 10] 2022 values[space] = vals.slice(0, space.length); 2023 alpha = vals[space.length]; 2024 } else if (vals[space.charAt(0)] !== undefined) { 2025 // {r: 10, g: 10, b: 10} 2026 for (i = 0; i < space.length; i++) { 2027 values[space][i] = vals[space.charAt(i)]; 2028 } 2029 2030 alpha = vals.a; 2031 } else if (vals[spaces[space][0]] !== undefined) { 2032 // {red: 10, green: 10, blue: 10} 2033 var chans = spaces[space]; 2034 2035 for (i = 0; i < space.length; i++) { 2036 values[space][i] = vals[chans[i]]; 2037 } 2038 2039 alpha = vals.alpha; 2040 } 2041 2042 values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); 2043 2044 if (space === 'alpha') { 2045 return false; 2046 } 2047 2048 var capped; 2049 2050 // cap values of the space prior converting all values 2051 for (i = 0; i < space.length; i++) { 2052 capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); 2053 values[space][i] = Math.round(capped); 2054 } 2055 2056 // convert to all the other color spaces 2057 for (var sname in spaces) { 2058 if (sname !== space) { 2059 values[sname] = colorConvert[space][sname](values[space]); 2060 } 2061 } 2062 2063 return true; 2064}; 2065 2066Color.prototype.setSpace = function (space, args) { 2067 var vals = args[0]; 2068 2069 if (vals === undefined) { 2070 // color.rgb() 2071 return this.getValues(space); 2072 } 2073 2074 // color.rgb(10, 10, 10) 2075 if (typeof vals === 'number') { 2076 vals = Array.prototype.slice.call(args); 2077 } 2078 2079 this.setValues(space, vals); 2080 return this; 2081}; 2082 2083Color.prototype.setChannel = function (space, index, val) { 2084 var svalues = this.values[space]; 2085 if (val === undefined) { 2086 // color.red() 2087 return svalues[index]; 2088 } else if (val === svalues[index]) { 2089 // color.red(color.red()) 2090 return this; 2091 } 2092 2093 // color.red(100) 2094 svalues[index] = val; 2095 this.setValues(space, svalues); 2096 2097 return this; 2098}; 2099 2100if (typeof window !== 'undefined') { 2101 window.Color = Color; 2102} 2103 2104var chartjsColor = Color; 2105 2106function isValidKey(key) { 2107 return ['__proto__', 'prototype', 'constructor'].indexOf(key) === -1; 2108} 2109 2110/** 2111 * @namespace Chart.helpers 2112 */ 2113var helpers = { 2114 /** 2115 * An empty function that can be used, for example, for optional callback. 2116 */ 2117 noop: function() {}, 2118 2119 /** 2120 * Returns a unique id, sequentially generated from a global variable. 2121 * @returns {number} 2122 * @function 2123 */ 2124 uid: (function() { 2125 var id = 0; 2126 return function() { 2127 return id++; 2128 }; 2129 }()), 2130 2131 /** 2132 * Returns true if `value` is neither null nor undefined, else returns false. 2133 * @param {*} value - The value to test. 2134 * @returns {boolean} 2135 * @since 2.7.0 2136 */ 2137 isNullOrUndef: function(value) { 2138 return value === null || typeof value === 'undefined'; 2139 }, 2140 2141 /** 2142 * Returns true if `value` is an array (including typed arrays), else returns false. 2143 * @param {*} value - The value to test. 2144 * @returns {boolean} 2145 * @function 2146 */ 2147 isArray: function(value) { 2148 if (Array.isArray && Array.isArray(value)) { 2149 return true; 2150 } 2151 var type = Object.prototype.toString.call(value); 2152 if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { 2153 return true; 2154 } 2155 return false; 2156 }, 2157 2158 /** 2159 * Returns true if `value` is an object (excluding null), else returns false. 2160 * @param {*} value - The value to test. 2161 * @returns {boolean} 2162 * @since 2.7.0 2163 */ 2164 isObject: function(value) { 2165 return value !== null && Object.prototype.toString.call(value) === '[object Object]'; 2166 }, 2167 2168 /** 2169 * Returns true if `value` is a finite number, else returns false 2170 * @param {*} value - The value to test. 2171 * @returns {boolean} 2172 */ 2173 isFinite: function(value) { 2174 return (typeof value === 'number' || value instanceof Number) && isFinite(value); 2175 }, 2176 2177 /** 2178 * Returns `value` if defined, else returns `defaultValue`. 2179 * @param {*} value - The value to return if defined. 2180 * @param {*} defaultValue - The value to return if `value` is undefined. 2181 * @returns {*} 2182 */ 2183 valueOrDefault: function(value, defaultValue) { 2184 return typeof value === 'undefined' ? defaultValue : value; 2185 }, 2186 2187 /** 2188 * Returns value at the given `index` in array if defined, else returns `defaultValue`. 2189 * @param {Array} value - The array to lookup for value at `index`. 2190 * @param {number} index - The index in `value` to lookup for value. 2191 * @param {*} defaultValue - The value to return if `value[index]` is undefined. 2192 * @returns {*} 2193 */ 2194 valueAtIndexOrDefault: function(value, index, defaultValue) { 2195 return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); 2196 }, 2197 2198 /** 2199 * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the 2200 * value returned by `fn`. If `fn` is not a function, this method returns undefined. 2201 * @param {function} fn - The function to call. 2202 * @param {Array|undefined|null} args - The arguments with which `fn` should be called. 2203 * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. 2204 * @returns {*} 2205 */ 2206 callback: function(fn, args, thisArg) { 2207 if (fn && typeof fn.call === 'function') { 2208 return fn.apply(thisArg, args); 2209 } 2210 }, 2211 2212 /** 2213 * Note(SB) for performance sake, this method should only be used when loopable type 2214 * is unknown or in none intensive code (not called often and small loopable). Else 2215 * it's preferable to use a regular for() loop and save extra function calls. 2216 * @param {object|Array} loopable - The object or array to be iterated. 2217 * @param {function} fn - The function to call for each item. 2218 * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. 2219 * @param {boolean} [reverse] - If true, iterates backward on the loopable. 2220 */ 2221 each: function(loopable, fn, thisArg, reverse) { 2222 var i, len, keys; 2223 if (helpers.isArray(loopable)) { 2224 len = loopable.length; 2225 if (reverse) { 2226 for (i = len - 1; i >= 0; i--) { 2227 fn.call(thisArg, loopable[i], i); 2228 } 2229 } else { 2230 for (i = 0; i < len; i++) { 2231 fn.call(thisArg, loopable[i], i); 2232 } 2233 } 2234 } else if (helpers.isObject(loopable)) { 2235 keys = Object.keys(loopable); 2236 len = keys.length; 2237 for (i = 0; i < len; i++) { 2238 fn.call(thisArg, loopable[keys[i]], keys[i]); 2239 } 2240 } 2241 }, 2242 2243 /** 2244 * Returns true if the `a0` and `a1` arrays have the same content, else returns false. 2245 * @see https://stackoverflow.com/a/14853974 2246 * @param {Array} a0 - The array to compare 2247 * @param {Array} a1 - The array to compare 2248 * @returns {boolean} 2249 */ 2250 arrayEquals: function(a0, a1) { 2251 var i, ilen, v0, v1; 2252 2253 if (!a0 || !a1 || a0.length !== a1.length) { 2254 return false; 2255 } 2256 2257 for (i = 0, ilen = a0.length; i < ilen; ++i) { 2258 v0 = a0[i]; 2259 v1 = a1[i]; 2260 2261 if (v0 instanceof Array && v1 instanceof Array) { 2262 if (!helpers.arrayEquals(v0, v1)) { 2263 return false; 2264 } 2265 } else if (v0 !== v1) { 2266 // NOTE: two different object instances will never be equal: {x:20} != {x:20} 2267 return false; 2268 } 2269 } 2270 2271 return true; 2272 }, 2273 2274 /** 2275 * Returns a deep copy of `source` without keeping references on objects and arrays. 2276 * @param {*} source - The value to clone. 2277 * @returns {*} 2278 */ 2279 clone: function(source) { 2280 if (helpers.isArray(source)) { 2281 return source.map(helpers.clone); 2282 } 2283 2284 if (helpers.isObject(source)) { 2285 var target = Object.create(source); 2286 var keys = Object.keys(source); 2287 var klen = keys.length; 2288 var k = 0; 2289 2290 for (; k < klen; ++k) { 2291 target[keys[k]] = helpers.clone(source[keys[k]]); 2292 } 2293 2294 return target; 2295 } 2296 2297 return source; 2298 }, 2299 2300 /** 2301 * The default merger when Chart.helpers.merge is called without merger option. 2302 * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback. 2303 * @private 2304 */ 2305 _merger: function(key, target, source, options) { 2306 if (!isValidKey(key)) { 2307 // We want to ensure we do not copy prototypes over 2308 // as this can pollute global namespaces 2309 return; 2310 } 2311 2312 var tval = target[key]; 2313 var sval = source[key]; 2314 2315 if (helpers.isObject(tval) && helpers.isObject(sval)) { 2316 helpers.merge(tval, sval, options); 2317 } else { 2318 target[key] = helpers.clone(sval); 2319 } 2320 }, 2321 2322 /** 2323 * Merges source[key] in target[key] only if target[key] is undefined. 2324 * @private 2325 */ 2326 _mergerIf: function(key, target, source) { 2327 if (!isValidKey(key)) { 2328 // We want to ensure we do not copy prototypes over 2329 // as this can pollute global namespaces 2330 return; 2331 } 2332 2333 var tval = target[key]; 2334 var sval = source[key]; 2335 2336 if (helpers.isObject(tval) && helpers.isObject(sval)) { 2337 helpers.mergeIf(tval, sval); 2338 } else if (!target.hasOwnProperty(key)) { 2339 target[key] = helpers.clone(sval); 2340 } 2341 }, 2342 2343 /** 2344 * Recursively deep copies `source` properties into `target` with the given `options`. 2345 * IMPORTANT: `target` is not cloned and will be updated with `source` properties. 2346 * @param {object} target - The target object in which all sources are merged into. 2347 * @param {object|object[]} source - Object(s) to merge into `target`. 2348 * @param {object} [options] - Merging options: 2349 * @param {function} [options.merger] - The merge method (key, target, source, options) 2350 * @returns {object} The `target` object. 2351 */ 2352 merge: function(target, source, options) { 2353 var sources = helpers.isArray(source) ? source : [source]; 2354 var ilen = sources.length; 2355 var merge, i, keys, klen, k; 2356 2357 if (!helpers.isObject(target)) { 2358 return target; 2359 } 2360 2361 options = options || {}; 2362 merge = options.merger || helpers._merger; 2363 2364 for (i = 0; i < ilen; ++i) { 2365 source = sources[i]; 2366 if (!helpers.isObject(source)) { 2367 continue; 2368 } 2369 2370 keys = Object.keys(source); 2371 for (k = 0, klen = keys.length; k < klen; ++k) { 2372 merge(keys[k], target, source, options); 2373 } 2374 } 2375 2376 return target; 2377 }, 2378 2379 /** 2380 * Recursively deep copies `source` properties into `target` *only* if not defined in target. 2381 * IMPORTANT: `target` is not cloned and will be updated with `source` properties. 2382 * @param {object} target - The target object in which all sources are merged into. 2383 * @param {object|object[]} source - Object(s) to merge into `target`. 2384 * @returns {object} The `target` object. 2385 */ 2386 mergeIf: function(target, source) { 2387 return helpers.merge(target, source, {merger: helpers._mergerIf}); 2388 }, 2389 2390 /** 2391 * Applies the contents of two or more objects together into the first object. 2392 * @param {object} target - The target object in which all objects are merged into. 2393 * @param {object} arg1 - Object containing additional properties to merge in target. 2394 * @param {object} argN - Additional objects containing properties to merge in target. 2395 * @returns {object} The `target` object. 2396 */ 2397 extend: Object.assign || function(target) { 2398 return helpers.merge(target, [].slice.call(arguments, 1), { 2399 merger: function(key, dst, src) { 2400 dst[key] = src[key]; 2401 } 2402 }); 2403 }, 2404 2405 /** 2406 * Basic javascript inheritance based on the model created in Backbone.js 2407 */ 2408 inherits: function(extensions) { 2409 var me = this; 2410 var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { 2411 return me.apply(this, arguments); 2412 }; 2413 2414 var Surrogate = function() { 2415 this.constructor = ChartElement; 2416 }; 2417 2418 Surrogate.prototype = me.prototype; 2419 ChartElement.prototype = new Surrogate(); 2420 ChartElement.extend = helpers.inherits; 2421 2422 if (extensions) { 2423 helpers.extend(ChartElement.prototype, extensions); 2424 } 2425 2426 ChartElement.__super__ = me.prototype; 2427 return ChartElement; 2428 }, 2429 2430 _deprecated: function(scope, value, previous, current) { 2431 if (value !== undefined) { 2432 console.warn(scope + ': "' + previous + 2433 '" is deprecated. Please use "' + current + '" instead'); 2434 } 2435 } 2436}; 2437 2438var helpers_core = helpers; 2439 2440// DEPRECATIONS 2441 2442/** 2443 * Provided for backward compatibility, use Chart.helpers.callback instead. 2444 * @function Chart.helpers.callCallback 2445 * @deprecated since version 2.6.0 2446 * @todo remove at version 3 2447 * @private 2448 */ 2449helpers.callCallback = helpers.callback; 2450 2451/** 2452 * Provided for backward compatibility, use Array.prototype.indexOf instead. 2453 * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ 2454 * @function Chart.helpers.indexOf 2455 * @deprecated since version 2.7.0 2456 * @todo remove at version 3 2457 * @private 2458 */ 2459helpers.indexOf = function(array, item, fromIndex) { 2460 return Array.prototype.indexOf.call(array, item, fromIndex); 2461}; 2462 2463/** 2464 * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. 2465 * @function Chart.helpers.getValueOrDefault 2466 * @deprecated since version 2.7.0 2467 * @todo remove at version 3 2468 * @private 2469 */ 2470helpers.getValueOrDefault = helpers.valueOrDefault; 2471 2472/** 2473 * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. 2474 * @function Chart.helpers.getValueAtIndexOrDefault 2475 * @deprecated since version 2.7.0 2476 * @todo remove at version 3 2477 * @private 2478 */ 2479helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; 2480 2481/** 2482 * Easing functions adapted from Robert Penner's easing equations. 2483 * @namespace Chart.helpers.easingEffects 2484 * @see http://www.robertpenner.com/easing/ 2485 */ 2486var effects = { 2487 linear: function(t) { 2488 return t; 2489 }, 2490 2491 easeInQuad: function(t) { 2492 return t * t; 2493 }, 2494 2495 easeOutQuad: function(t) { 2496 return -t * (t - 2); 2497 }, 2498 2499 easeInOutQuad: function(t) { 2500 if ((t /= 0.5) < 1) { 2501 return 0.5 * t * t; 2502 } 2503 return -0.5 * ((--t) * (t - 2) - 1); 2504 }, 2505 2506 easeInCubic: function(t) { 2507 return t * t * t; 2508 }, 2509 2510 easeOutCubic: function(t) { 2511 return (t = t - 1) * t * t + 1; 2512 }, 2513 2514 easeInOutCubic: function(t) { 2515 if ((t /= 0.5) < 1) { 2516 return 0.5 * t * t * t; 2517 } 2518 return 0.5 * ((t -= 2) * t * t + 2); 2519 }, 2520 2521 easeInQuart: function(t) { 2522 return t * t * t * t; 2523 }, 2524 2525 easeOutQuart: function(t) { 2526 return -((t = t - 1) * t * t * t - 1); 2527 }, 2528 2529 easeInOutQuart: function(t) { 2530 if ((t /= 0.5) < 1) { 2531 return 0.5 * t * t * t * t; 2532 } 2533 return -0.5 * ((t -= 2) * t * t * t - 2); 2534 }, 2535 2536 easeInQuint: function(t) { 2537 return t * t * t * t * t; 2538 }, 2539 2540 easeOutQuint: function(t) { 2541 return (t = t - 1) * t * t * t * t + 1; 2542 }, 2543 2544 easeInOutQuint: function(t) { 2545 if ((t /= 0.5) < 1) { 2546 return 0.5 * t * t * t * t * t; 2547 } 2548 return 0.5 * ((t -= 2) * t * t * t * t + 2); 2549 }, 2550 2551 easeInSine: function(t) { 2552 return -Math.cos(t * (Math.PI / 2)) + 1; 2553 }, 2554 2555 easeOutSine: function(t) { 2556 return Math.sin(t * (Math.PI / 2)); 2557 }, 2558 2559 easeInOutSine: function(t) { 2560 return -0.5 * (Math.cos(Math.PI * t) - 1); 2561 }, 2562 2563 easeInExpo: function(t) { 2564 return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); 2565 }, 2566 2567 easeOutExpo: function(t) { 2568 return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; 2569 }, 2570 2571 easeInOutExpo: function(t) { 2572 if (t === 0) { 2573 return 0; 2574 } 2575 if (t === 1) { 2576 return 1; 2577 } 2578 if ((t /= 0.5) < 1) { 2579 return 0.5 * Math.pow(2, 10 * (t - 1)); 2580 } 2581 return 0.5 * (-Math.pow(2, -10 * --t) + 2); 2582 }, 2583 2584 easeInCirc: function(t) { 2585 if (t >= 1) { 2586 return t; 2587 } 2588 return -(Math.sqrt(1 - t * t) - 1); 2589 }, 2590 2591 easeOutCirc: function(t) { 2592 return Math.sqrt(1 - (t = t - 1) * t); 2593 }, 2594 2595 easeInOutCirc: function(t) { 2596 if ((t /= 0.5) < 1) { 2597 return -0.5 * (Math.sqrt(1 - t * t) - 1); 2598 } 2599 return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); 2600 }, 2601 2602 easeInElastic: function(t) { 2603 var s = 1.70158; 2604 var p = 0; 2605 var a = 1; 2606 if (t === 0) { 2607 return 0; 2608 } 2609 if (t === 1) { 2610 return 1; 2611 } 2612 if (!p) { 2613 p = 0.3; 2614 } 2615 if (a < 1) { 2616 a = 1; 2617 s = p / 4; 2618 } else { 2619 s = p / (2 * Math.PI) * Math.asin(1 / a); 2620 } 2621 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); 2622 }, 2623 2624 easeOutElastic: function(t) { 2625 var s = 1.70158; 2626 var p = 0; 2627 var a = 1; 2628 if (t === 0) { 2629 return 0; 2630 } 2631 if (t === 1) { 2632 return 1; 2633 } 2634 if (!p) { 2635 p = 0.3; 2636 } 2637 if (a < 1) { 2638 a = 1; 2639 s = p / 4; 2640 } else { 2641 s = p / (2 * Math.PI) * Math.asin(1 / a); 2642 } 2643 return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; 2644 }, 2645 2646 easeInOutElastic: function(t) { 2647 var s = 1.70158; 2648 var p = 0; 2649 var a = 1; 2650 if (t === 0) { 2651 return 0; 2652 } 2653 if ((t /= 0.5) === 2) { 2654 return 1; 2655 } 2656 if (!p) { 2657 p = 0.45; 2658 } 2659 if (a < 1) { 2660 a = 1; 2661 s = p / 4; 2662 } else { 2663 s = p / (2 * Math.PI) * Math.asin(1 / a); 2664 } 2665 if (t < 1) { 2666 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); 2667 } 2668 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; 2669 }, 2670 easeInBack: function(t) { 2671 var s = 1.70158; 2672 return t * t * ((s + 1) * t - s); 2673 }, 2674 2675 easeOutBack: function(t) { 2676 var s = 1.70158; 2677 return (t = t - 1) * t * ((s + 1) * t + s) + 1; 2678 }, 2679 2680 easeInOutBack: function(t) { 2681 var s = 1.70158; 2682 if ((t /= 0.5) < 1) { 2683 return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); 2684 } 2685 return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); 2686 }, 2687 2688 easeInBounce: function(t) { 2689 return 1 - effects.easeOutBounce(1 - t); 2690 }, 2691 2692 easeOutBounce: function(t) { 2693 if (t < (1 / 2.75)) { 2694 return 7.5625 * t * t; 2695 } 2696 if (t < (2 / 2.75)) { 2697 return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; 2698 } 2699 if (t < (2.5 / 2.75)) { 2700 return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; 2701 } 2702 return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; 2703 }, 2704 2705 easeInOutBounce: function(t) { 2706 if (t < 0.5) { 2707 return effects.easeInBounce(t * 2) * 0.5; 2708 } 2709 return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; 2710 } 2711}; 2712 2713var helpers_easing = { 2714 effects: effects 2715}; 2716 2717// DEPRECATIONS 2718 2719/** 2720 * Provided for backward compatibility, use Chart.helpers.easing.effects instead. 2721 * @function Chart.helpers.easingEffects 2722 * @deprecated since version 2.7.0 2723 * @todo remove at version 3 2724 * @private 2725 */ 2726helpers_core.easingEffects = effects; 2727 2728var PI = Math.PI; 2729var RAD_PER_DEG = PI / 180; 2730var DOUBLE_PI = PI * 2; 2731var HALF_PI = PI / 2; 2732var QUARTER_PI = PI / 4; 2733var TWO_THIRDS_PI = PI * 2 / 3; 2734 2735/** 2736 * @namespace Chart.helpers.canvas 2737 */ 2738var exports$1 = { 2739 /** 2740 * Clears the entire canvas associated to the given `chart`. 2741 * @param {Chart} chart - The chart for which to clear the canvas. 2742 */ 2743 clear: function(chart) { 2744 chart.ctx.clearRect(0, 0, chart.width, chart.height); 2745 }, 2746 2747 /** 2748 * Creates a "path" for a rectangle with rounded corners at position (x, y) with a 2749 * given size (width, height) and the same `radius` for all corners. 2750 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. 2751 * @param {number} x - The x axis of the coordinate for the rectangle starting point. 2752 * @param {number} y - The y axis of the coordinate for the rectangle starting point. 2753 * @param {number} width - The rectangle's width. 2754 * @param {number} height - The rectangle's height. 2755 * @param {number} radius - The rounded amount (in pixels) for the four corners. 2756 * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? 2757 */ 2758 roundedRect: function(ctx, x, y, width, height, radius) { 2759 if (radius) { 2760 var r = Math.min(radius, height / 2, width / 2); 2761 var left = x + r; 2762 var top = y + r; 2763 var right = x + width - r; 2764 var bottom = y + height - r; 2765 2766 ctx.moveTo(x, top); 2767 if (left < right && top < bottom) { 2768 ctx.arc(left, top, r, -PI, -HALF_PI); 2769 ctx.arc(right, top, r, -HALF_PI, 0); 2770 ctx.arc(right, bottom, r, 0, HALF_PI); 2771 ctx.arc(left, bottom, r, HALF_PI, PI); 2772 } else if (left < right) { 2773 ctx.moveTo(left, y); 2774 ctx.arc(right, top, r, -HALF_PI, HALF_PI); 2775 ctx.arc(left, top, r, HALF_PI, PI + HALF_PI); 2776 } else if (top < bottom) { 2777 ctx.arc(left, top, r, -PI, 0); 2778 ctx.arc(left, bottom, r, 0, PI); 2779 } else { 2780 ctx.arc(left, top, r, -PI, PI); 2781 } 2782 ctx.closePath(); 2783 ctx.moveTo(x, y); 2784 } else { 2785 ctx.rect(x, y, width, height); 2786 } 2787 }, 2788 2789 drawPoint: function(ctx, style, radius, x, y, rotation) { 2790 var type, xOffset, yOffset, size, cornerRadius; 2791 var rad = (rotation || 0) * RAD_PER_DEG; 2792 2793 if (style && typeof style === 'object') { 2794 type = style.toString(); 2795 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { 2796 ctx.save(); 2797 ctx.translate(x, y); 2798 ctx.rotate(rad); 2799 ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); 2800 ctx.restore(); 2801 return; 2802 } 2803 } 2804 2805 if (isNaN(radius) || radius <= 0) { 2806 return; 2807 } 2808 2809 ctx.beginPath(); 2810 2811 switch (style) { 2812 // Default includes circle 2813 default: 2814 ctx.arc(x, y, radius, 0, DOUBLE_PI); 2815 ctx.closePath(); 2816 break; 2817 case 'triangle': 2818 ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); 2819 rad += TWO_THIRDS_PI; 2820 ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); 2821 rad += TWO_THIRDS_PI; 2822 ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); 2823 ctx.closePath(); 2824 break; 2825 case 'rectRounded': 2826 // NOTE: the rounded rect implementation changed to use `arc` instead of 2827 // `quadraticCurveTo` since it generates better results when rect is 2828 // almost a circle. 0.516 (instead of 0.5) produces results with visually 2829 // closer proportion to the previous impl and it is inscribed in the 2830 // circle with `radius`. For more details, see the following PRs: 2831 // https://github.com/chartjs/Chart.js/issues/5597 2832 // https://github.com/chartjs/Chart.js/issues/5858 2833 cornerRadius = radius * 0.516; 2834 size = radius - cornerRadius; 2835 xOffset = Math.cos(rad + QUARTER_PI) * size; 2836 yOffset = Math.sin(rad + QUARTER_PI) * size; 2837 ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); 2838 ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); 2839 ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); 2840 ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); 2841 ctx.closePath(); 2842 break; 2843 case 'rect': 2844 if (!rotation) { 2845 size = Math.SQRT1_2 * radius; 2846 ctx.rect(x - size, y - size, 2 * size, 2 * size); 2847 break; 2848 } 2849 rad += QUARTER_PI; 2850 /* falls through */ 2851 case 'rectRot': 2852 xOffset = Math.cos(rad) * radius; 2853 yOffset = Math.sin(rad) * radius; 2854 ctx.moveTo(x - xOffset, y - yOffset); 2855 ctx.lineTo(x + yOffset, y - xOffset); 2856 ctx.lineTo(x + xOffset, y + yOffset); 2857 ctx.lineTo(x - yOffset, y + xOffset); 2858 ctx.closePath(); 2859 break; 2860 case 'crossRot': 2861 rad += QUARTER_PI; 2862 /* falls through */ 2863 case 'cross': 2864 xOffset = Math.cos(rad) * radius; 2865 yOffset = Math.sin(rad) * radius; 2866 ctx.moveTo(x - xOffset, y - yOffset); 2867 ctx.lineTo(x + xOffset, y + yOffset); 2868 ctx.moveTo(x + yOffset, y - xOffset); 2869 ctx.lineTo(x - yOffset, y + xOffset); 2870 break; 2871 case 'star': 2872 xOffset = Math.cos(rad) * radius; 2873 yOffset = Math.sin(rad) * radius; 2874 ctx.moveTo(x - xOffset, y - yOffset); 2875 ctx.lineTo(x + xOffset, y + yOffset); 2876 ctx.moveTo(x + yOffset, y - xOffset); 2877 ctx.lineTo(x - yOffset, y + xOffset); 2878 rad += QUARTER_PI; 2879 xOffset = Math.cos(rad) * radius; 2880 yOffset = Math.sin(rad) * radius; 2881 ctx.moveTo(x - xOffset, y - yOffset); 2882 ctx.lineTo(x + xOffset, y + yOffset); 2883 ctx.moveTo(x + yOffset, y - xOffset); 2884 ctx.lineTo(x - yOffset, y + xOffset); 2885 break; 2886 case 'line': 2887 xOffset = Math.cos(rad) * radius; 2888 yOffset = Math.sin(rad) * radius; 2889 ctx.moveTo(x - xOffset, y - yOffset); 2890 ctx.lineTo(x + xOffset, y + yOffset); 2891 break; 2892 case 'dash': 2893 ctx.moveTo(x, y); 2894 ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); 2895 break; 2896 } 2897 2898 ctx.fill(); 2899 ctx.stroke(); 2900 }, 2901 2902 /** 2903 * Returns true if the point is inside the rectangle 2904 * @param {object} point - The point to test 2905 * @param {object} area - The rectangle 2906 * @returns {boolean} 2907 * @private 2908 */ 2909 _isPointInArea: function(point, area) { 2910 var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. 2911 2912 return point.x > area.left - epsilon && point.x < area.right + epsilon && 2913 point.y > area.top - epsilon && point.y < area.bottom + epsilon; 2914 }, 2915 2916 clipArea: function(ctx, area) { 2917 ctx.save(); 2918 ctx.beginPath(); 2919 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); 2920 ctx.clip(); 2921 }, 2922 2923 unclipArea: function(ctx) { 2924 ctx.restore(); 2925 }, 2926 2927 lineTo: function(ctx, previous, target, flip) { 2928 var stepped = target.steppedLine; 2929 if (stepped) { 2930 if (stepped === 'middle') { 2931 var midpoint = (previous.x + target.x) / 2.0; 2932 ctx.lineTo(midpoint, flip ? target.y : previous.y); 2933 ctx.lineTo(midpoint, flip ? previous.y : target.y); 2934 } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) { 2935 ctx.lineTo(previous.x, target.y); 2936 } else { 2937 ctx.lineTo(target.x, previous.y); 2938 } 2939 ctx.lineTo(target.x, target.y); 2940 return; 2941 } 2942 2943 if (!target.tension) { 2944 ctx.lineTo(target.x, target.y); 2945 return; 2946 } 2947 2948 ctx.bezierCurveTo( 2949 flip ? previous.controlPointPreviousX : previous.controlPointNextX, 2950 flip ? previous.controlPointPreviousY : previous.controlPointNextY, 2951 flip ? target.controlPointNextX : target.controlPointPreviousX, 2952 flip ? target.controlPointNextY : target.controlPointPreviousY, 2953 target.x, 2954 target.y); 2955 } 2956}; 2957 2958var helpers_canvas = exports$1; 2959 2960// DEPRECATIONS 2961 2962/** 2963 * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. 2964 * @namespace Chart.helpers.clear 2965 * @deprecated since version 2.7.0 2966 * @todo remove at version 3 2967 * @private 2968 */ 2969helpers_core.clear = exports$1.clear; 2970 2971/** 2972 * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. 2973 * @namespace Chart.helpers.drawRoundedRectangle 2974 * @deprecated since version 2.7.0 2975 * @todo remove at version 3 2976 * @private 2977 */ 2978helpers_core.drawRoundedRectangle = function(ctx) { 2979 ctx.beginPath(); 2980 exports$1.roundedRect.apply(exports$1, arguments); 2981}; 2982 2983var defaults = { 2984 /** 2985 * @private 2986 */ 2987 _set: function(scope, values) { 2988 return helpers_core.merge(this[scope] || (this[scope] = {}), values); 2989 } 2990}; 2991 2992// TODO(v3): remove 'global' from namespace. all default are global and 2993// there's inconsistency around which options are under 'global' 2994defaults._set('global', { 2995 defaultColor: 'rgba(0,0,0,0.1)', 2996 defaultFontColor: '#666', 2997 defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", 2998 defaultFontSize: 12, 2999 defaultFontStyle: 'normal', 3000 defaultLineHeight: 1.2, 3001 showLines: true 3002}); 3003 3004var core_defaults = defaults; 3005 3006var valueOrDefault = helpers_core.valueOrDefault; 3007 3008/** 3009 * Converts the given font object into a CSS font string. 3010 * @param {object} font - A font object. 3011 * @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font 3012 * @private 3013 */ 3014function toFontString(font) { 3015 if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) { 3016 return null; 3017 } 3018 3019 return (font.style ? font.style + ' ' : '') 3020 + (font.weight ? font.weight + ' ' : '') 3021 + font.size + 'px ' 3022 + font.family; 3023} 3024 3025/** 3026 * @alias Chart.helpers.options 3027 * @namespace 3028 */ 3029var helpers_options = { 3030 /** 3031 * Converts the given line height `value` in pixels for a specific font `size`. 3032 * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). 3033 * @param {number} size - The font size (in pixels) used to resolve relative `value`. 3034 * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid). 3035 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height 3036 * @since 2.7.0 3037 */ 3038 toLineHeight: function(value, size) { 3039 var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); 3040 if (!matches || matches[1] === 'normal') { 3041 return size * 1.2; 3042 } 3043 3044 value = +matches[2]; 3045 3046 switch (matches[3]) { 3047 case 'px': 3048 return value; 3049 case '%': 3050 value /= 100; 3051 break; 3052 } 3053 3054 return size * value; 3055 }, 3056 3057 /** 3058 * Converts the given value into a padding object with pre-computed width/height. 3059 * @param {number|object} value - If a number, set the value to all TRBL component, 3060 * else, if and object, use defined properties and sets undefined ones to 0. 3061 * @returns {object} The padding values (top, right, bottom, left, width, height) 3062 * @since 2.7.0 3063 */ 3064 toPadding: function(value) { 3065 var t, r, b, l; 3066 3067 if (helpers_core.isObject(value)) { 3068 t = +value.top || 0; 3069 r = +value.right || 0; 3070 b = +value.bottom || 0; 3071 l = +value.left || 0; 3072 } else { 3073 t = r = b = l = +value || 0; 3074 } 3075 3076 return { 3077 top: t, 3078 right: r, 3079 bottom: b, 3080 left: l, 3081 height: t + b, 3082 width: l + r 3083 }; 3084 }, 3085 3086 /** 3087 * Parses font options and returns the font object. 3088 * @param {object} options - A object that contains font options to be parsed. 3089 * @return {object} The font object. 3090 * @todo Support font.* options and renamed to toFont(). 3091 * @private 3092 */ 3093 _parseFont: function(options) { 3094 var globalDefaults = core_defaults.global; 3095 var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); 3096 var font = { 3097 family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), 3098 lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), 3099 size: size, 3100 style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), 3101 weight: null, 3102 string: '' 3103 }; 3104 3105 font.string = toFontString(font); 3106 return font; 3107 }, 3108 3109 /** 3110 * Evaluates the given `inputs` sequentially and returns the first defined value. 3111 * @param {Array} inputs - An array of values, falling back to the last value. 3112 * @param {object} [context] - If defined and the current value is a function, the value 3113 * is called with `context` as first argument and the result becomes the new input. 3114 * @param {number} [index] - If defined and the current value is an array, the value 3115 * at `index` become the new input. 3116 * @param {object} [info] - object to return information about resolution in 3117 * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable. 3118 * @since 2.7.0 3119 */ 3120 resolve: function(inputs, context, index, info) { 3121 var cacheable = true; 3122 var i, ilen, value; 3123 3124 for (i = 0, ilen = inputs.length; i < ilen; ++i) { 3125 value = inputs[i]; 3126 if (value === undefined) { 3127 continue; 3128 } 3129 if (context !== undefined && typeof value === 'function') { 3130 value = value(context); 3131 cacheable = false; 3132 } 3133 if (index !== undefined && helpers_core.isArray(value)) { 3134 value = value[index]; 3135 cacheable = false; 3136 } 3137 if (value !== undefined) { 3138 if (info && !cacheable) { 3139 info.cacheable = false; 3140 } 3141 return value; 3142 } 3143 } 3144 } 3145}; 3146 3147/** 3148 * @alias Chart.helpers.math 3149 * @namespace 3150 */ 3151var exports$2 = { 3152 /** 3153 * Returns an array of factors sorted from 1 to sqrt(value) 3154 * @private 3155 */ 3156 _factorize: function(value) { 3157 var result = []; 3158 var sqrt = Math.sqrt(value); 3159 var i; 3160 3161 for (i = 1; i < sqrt; i++) { 3162 if (value % i === 0) { 3163 result.push(i); 3164 result.push(value / i); 3165 } 3166 } 3167 if (sqrt === (sqrt | 0)) { // if value is a square number 3168 result.push(sqrt); 3169 } 3170 3171 result.sort(function(a, b) { 3172 return a - b; 3173 }).pop(); 3174 return result; 3175 }, 3176 3177 log10: Math.log10 || function(x) { 3178 var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. 3179 // Check for whole powers of 10, 3180 // which due to floating point rounding error should be corrected. 3181 var powerOf10 = Math.round(exponent); 3182 var isPowerOf10 = x === Math.pow(10, powerOf10); 3183 3184 return isPowerOf10 ? powerOf10 : exponent; 3185 } 3186}; 3187 3188var helpers_math = exports$2; 3189 3190// DEPRECATIONS 3191 3192/** 3193 * Provided for backward compatibility, use Chart.helpers.math.log10 instead. 3194 * @namespace Chart.helpers.log10 3195 * @deprecated since version 2.9.0 3196 * @todo remove at version 3 3197 * @private 3198 */ 3199helpers_core.log10 = exports$2.log10; 3200 3201var getRtlAdapter = function(rectX, width) { 3202 return { 3203 x: function(x) { 3204 return rectX + rectX + width - x; 3205 }, 3206 setWidth: function(w) { 3207 width = w; 3208 }, 3209 textAlign: function(align) { 3210 if (align === 'center') { 3211 return align; 3212 } 3213 return align === 'right' ? 'left' : 'right'; 3214 }, 3215 xPlus: function(x, value) { 3216 return x - value; 3217 }, 3218 leftForLtr: function(x, itemWidth) { 3219 return x - itemWidth; 3220 }, 3221 }; 3222}; 3223 3224var getLtrAdapter = function() { 3225 return { 3226 x: function(x) { 3227 return x; 3228 }, 3229 setWidth: function(w) { // eslint-disable-line no-unused-vars 3230 }, 3231 textAlign: function(align) { 3232 return align; 3233 }, 3234 xPlus: function(x, value) { 3235 return x + value; 3236 }, 3237 leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars 3238 return x; 3239 }, 3240 }; 3241}; 3242 3243var getAdapter = function(rtl, rectX, width) { 3244 return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter(); 3245}; 3246 3247var overrideTextDirection = function(ctx, direction) { 3248 var style, original; 3249 if (direction === 'ltr' || direction === 'rtl') { 3250 style = ctx.canvas.style; 3251 original = [ 3252 style.getPropertyValue('direction'), 3253 style.getPropertyPriority('direction'), 3254 ]; 3255 3256 style.setProperty('direction', direction, 'important'); 3257 ctx.prevTextDirection = original; 3258 } 3259}; 3260 3261var restoreTextDirection = function(ctx) { 3262 var original = ctx.prevTextDirection; 3263 if (original !== undefined) { 3264 delete ctx.prevTextDirection; 3265 ctx.canvas.style.setProperty('direction', original[0], original[1]); 3266 } 3267}; 3268 3269var helpers_rtl = { 3270 getRtlAdapter: getAdapter, 3271 overrideTextDirection: overrideTextDirection, 3272 restoreTextDirection: restoreTextDirection, 3273}; 3274 3275var helpers$1 = helpers_core; 3276var easing = helpers_easing; 3277var canvas = helpers_canvas; 3278var options = helpers_options; 3279var math = helpers_math; 3280var rtl = helpers_rtl; 3281helpers$1.easing = easing; 3282helpers$1.canvas = canvas; 3283helpers$1.options = options; 3284helpers$1.math = math; 3285helpers$1.rtl = rtl; 3286 3287function interpolate(start, view, model, ease) { 3288 var keys = Object.keys(model); 3289 var i, ilen, key, actual, origin, target, type, c0, c1; 3290 3291 for (i = 0, ilen = keys.length; i < ilen; ++i) { 3292 key = keys[i]; 3293 3294 target = model[key]; 3295 3296 // if a value is added to the model after pivot() has been called, the view 3297 // doesn't contain it, so let's initialize the view to the target value. 3298 if (!view.hasOwnProperty(key)) { 3299 view[key] = target; 3300 } 3301 3302 actual = view[key]; 3303 3304 if (actual === target || key[0] === '_') { 3305 continue; 3306 } 3307 3308 if (!start.hasOwnProperty(key)) { 3309 start[key] = actual; 3310 } 3311 3312 origin = start[key]; 3313 3314 type = typeof target; 3315 3316 if (type === typeof origin) { 3317 if (type === 'string') { 3318 c0 = chartjsColor(origin); 3319 if (c0.valid) { 3320 c1 = chartjsColor(target); 3321 if (c1.valid) { 3322 view[key] = c1.mix(c0, ease).rgbString(); 3323 continue; 3324 } 3325 } 3326 } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) { 3327 view[key] = origin + (target - origin) * ease; 3328 continue; 3329 } 3330 } 3331 3332 view[key] = target; 3333 } 3334} 3335 3336var Element = function(configuration) { 3337 helpers$1.extend(this, configuration); 3338 this.initialize.apply(this, arguments); 3339}; 3340 3341helpers$1.extend(Element.prototype, { 3342 _type: undefined, 3343 3344 initialize: function() { 3345 this.hidden = false; 3346 }, 3347 3348 pivot: function() { 3349 var me = this; 3350 if (!me._view) { 3351 me._view = helpers$1.extend({}, me._model); 3352 } 3353 me._start = {}; 3354 return me; 3355 }, 3356 3357 transition: function(ease) { 3358 var me = this; 3359 var model = me._model; 3360 var start = me._start; 3361 var view = me._view; 3362 3363 // No animation -> No Transition 3364 if (!model || ease === 1) { 3365 me._view = helpers$1.extend({}, model); 3366 me._start = null; 3367 return me; 3368 } 3369 3370 if (!view) { 3371 view = me._view = {}; 3372 } 3373 3374 if (!start) { 3375 start = me._start = {}; 3376 } 3377 3378 interpolate(start, view, model, ease); 3379 3380 return me; 3381 }, 3382 3383 tooltipPosition: function() { 3384 return { 3385 x: this._model.x, 3386 y: this._model.y 3387 }; 3388 }, 3389 3390 hasValue: function() { 3391 return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y); 3392 } 3393}); 3394 3395Element.extend = helpers$1.inherits; 3396 3397var core_element = Element; 3398 3399var exports$3 = core_element.extend({ 3400 chart: null, // the animation associated chart instance 3401 currentStep: 0, // the current animation step 3402 numSteps: 60, // default number of steps 3403 easing: '', // the easing to use for this animation 3404 render: null, // render function used by the animation service 3405 3406 onAnimationProgress: null, // user specified callback to fire on each step of the animation 3407 onAnimationComplete: null, // user specified callback to fire when the animation finishes 3408}); 3409 3410var core_animation = exports$3; 3411 3412// DEPRECATIONS 3413 3414/** 3415 * Provided for backward compatibility, use Chart.Animation instead 3416 * @prop Chart.Animation#animationObject 3417 * @deprecated since version 2.6.0 3418 * @todo remove at version 3 3419 */ 3420Object.defineProperty(exports$3.prototype, 'animationObject', { 3421 get: function() { 3422 return this; 3423 } 3424}); 3425 3426/** 3427 * Provided for backward compatibility, use Chart.Animation#chart instead 3428 * @prop Chart.Animation#chartInstance 3429 * @deprecated since version 2.6.0 3430 * @todo remove at version 3 3431 */ 3432Object.defineProperty(exports$3.prototype, 'chartInstance', { 3433 get: function() { 3434 return this.chart; 3435 }, 3436 set: function(value) { 3437 this.chart = value; 3438 } 3439}); 3440 3441core_defaults._set('global', { 3442 animation: { 3443 duration: 1000, 3444 easing: 'easeOutQuart', 3445 onProgress: helpers$1.noop, 3446 onComplete: helpers$1.noop 3447 } 3448}); 3449 3450var core_animations = { 3451 animations: [], 3452 request: null, 3453 3454 /** 3455 * @param {Chart} chart - The chart to animate. 3456 * @param {Chart.Animation} animation - The animation that we will animate. 3457 * @param {number} duration - The animation duration in ms. 3458 * @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions 3459 */ 3460 addAnimation: function(chart, animation, duration, lazy) { 3461 var animations = this.animations; 3462 var i, ilen; 3463 3464 animation.chart = chart; 3465 animation.startTime = Date.now(); 3466 animation.duration = duration; 3467 3468 if (!lazy) { 3469 chart.animating = true; 3470 } 3471 3472 for (i = 0, ilen = animations.length; i < ilen; ++i) { 3473 if (animations[i].chart === chart) { 3474 animations[i] = animation; 3475 return; 3476 } 3477 } 3478 3479 animations.push(animation); 3480 3481 // If there are no animations queued, manually kickstart a digest, for lack of a better word 3482 if (animations.length === 1) { 3483 this.requestAnimationFrame(); 3484 } 3485 }, 3486 3487 cancelAnimation: function(chart) { 3488 var index = helpers$1.findIndex(this.animations, function(animation) { 3489 return animation.chart === chart; 3490 }); 3491 3492 if (index !== -1) { 3493 this.animations.splice(index, 1); 3494 chart.animating = false; 3495 } 3496 }, 3497 3498 requestAnimationFrame: function() { 3499 var me = this; 3500 if (me.request === null) { 3501 // Skip animation frame requests until the active one is executed. 3502 // This can happen when processing mouse events, e.g. 'mousemove' 3503 // and 'mouseout' events will trigger multiple renders. 3504 me.request = helpers$1.requestAnimFrame.call(window, function() { 3505 me.request = null; 3506 me.startDigest(); 3507 }); 3508 } 3509 }, 3510 3511 /** 3512 * @private 3513 */ 3514 startDigest: function() { 3515 var me = this; 3516 3517 me.advance(); 3518 3519 // Do we have more stuff to animate? 3520 if (me.animations.length > 0) { 3521 me.requestAnimationFrame(); 3522 } 3523 }, 3524 3525 /** 3526 * @private 3527 */ 3528 advance: function() { 3529 var animations = this.animations; 3530 var animation, chart, numSteps, nextStep; 3531 var i = 0; 3532 3533 // 1 animation per chart, so we are looping charts here 3534 while (i < animations.length) { 3535 animation = animations[i]; 3536 chart = animation.chart; 3537 numSteps = animation.numSteps; 3538 3539 // Make sure that currentStep starts at 1 3540 // https://github.com/chartjs/Chart.js/issues/6104 3541 nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1; 3542 animation.currentStep = Math.min(nextStep, numSteps); 3543 3544 helpers$1.callback(animation.render, [chart, animation], chart); 3545 helpers$1.callback(animation.onAnimationProgress, [animation], chart); 3546 3547 if (animation.currentStep >= numSteps) { 3548 helpers$1.callback(animation.onAnimationComplete, [animation], chart); 3549 chart.animating = false; 3550 animations.splice(i, 1); 3551 } else { 3552 ++i; 3553 } 3554 } 3555 } 3556}; 3557 3558var resolve = helpers$1.options.resolve; 3559 3560var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; 3561 3562/** 3563 * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', 3564 * 'unshift') and notify the listener AFTER the array has been altered. Listeners are 3565 * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. 3566 */ 3567function listenArrayEvents(array, listener) { 3568 if (array._chartjs) { 3569 array._chartjs.listeners.push(listener); 3570 return; 3571 } 3572 3573 Object.defineProperty(array, '_chartjs', { 3574 configurable: true, 3575 enumerable: false, 3576 value: { 3577 listeners: [listener] 3578 } 3579 }); 3580 3581 arrayEvents.forEach(function(key) { 3582 var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); 3583 var base = array[key]; 3584 3585 Object.defineProperty(array, key, { 3586 configurable: true, 3587 enumerable: false, 3588 value: function() { 3589 var args = Array.prototype.slice.call(arguments); 3590 var res = base.apply(this, args); 3591 3592 helpers$1.each(array._chartjs.listeners, function(object) { 3593 if (typeof object[method] === 'function') { 3594 object[method].apply(object, args); 3595 } 3596 }); 3597 3598 return res; 3599 } 3600 }); 3601 }); 3602} 3603 3604/** 3605 * Removes the given array event listener and cleanup extra attached properties (such as 3606 * the _chartjs stub and overridden methods) if array doesn't have any more listeners. 3607 */ 3608function unlistenArrayEvents(array, listener) { 3609 var stub = array._chartjs; 3610 if (!stub) { 3611 return; 3612 } 3613 3614 var listeners = stub.listeners; 3615 var index = listeners.indexOf(listener); 3616 if (index !== -1) { 3617 listeners.splice(index, 1); 3618 } 3619 3620 if (listeners.length > 0) { 3621 return; 3622 } 3623 3624 arrayEvents.forEach(function(key) { 3625 delete array[key]; 3626 }); 3627 3628 delete array._chartjs; 3629} 3630 3631// Base class for all dataset controllers (line, bar, etc) 3632var DatasetController = function(chart, datasetIndex) { 3633 this.initialize(chart, datasetIndex); 3634}; 3635 3636helpers$1.extend(DatasetController.prototype, { 3637 3638 /** 3639 * Element type used to generate a meta dataset (e.g. Chart.element.Line). 3640 * @type {Chart.core.element} 3641 */ 3642 datasetElementType: null, 3643 3644 /** 3645 * Element type used to generate a meta data (e.g. Chart.element.Point). 3646 * @type {Chart.core.element} 3647 */ 3648 dataElementType: null, 3649 3650 /** 3651 * Dataset element option keys to be resolved in _resolveDatasetElementOptions. 3652 * A derived controller may override this to resolve controller-specific options. 3653 * The keys defined here are for backward compatibility for legend styles. 3654 * @private 3655 */ 3656 _datasetElementOptions: [ 3657 'backgroundColor', 3658 'borderCapStyle', 3659 'borderColor', 3660 'borderDash', 3661 'borderDashOffset', 3662 'borderJoinStyle', 3663 'borderWidth' 3664 ], 3665 3666 /** 3667 * Data element option keys to be resolved in _resolveDataElementOptions. 3668 * A derived controller may override this to resolve controller-specific options. 3669 * The keys defined here are for backward compatibility for legend styles. 3670 * @private 3671 */ 3672 _dataElementOptions: [ 3673 'backgroundColor', 3674 'borderColor', 3675 'borderWidth', 3676 'pointStyle' 3677 ], 3678 3679 initialize: function(chart, datasetIndex) { 3680 var me = this; 3681 me.chart = chart; 3682 me.index = datasetIndex; 3683 me.linkScales(); 3684 me.addElements(); 3685 me._type = me.getMeta().type; 3686 }, 3687 3688 updateIndex: function(datasetIndex) { 3689 this.index = datasetIndex; 3690 }, 3691 3692 linkScales: function() { 3693 var me = this; 3694 var meta = me.getMeta(); 3695 var chart = me.chart; 3696 var scales = chart.scales; 3697 var dataset = me.getDataset(); 3698 var scalesOpts = chart.options.scales; 3699 3700 if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) { 3701 meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id; 3702 } 3703 if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) { 3704 meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id; 3705 } 3706 }, 3707 3708 getDataset: function() { 3709 return this.chart.data.datasets[this.index]; 3710 }, 3711 3712 getMeta: function() { 3713 return this.chart.getDatasetMeta(this.index); 3714 }, 3715 3716 getScaleForId: function(scaleID) { 3717 return this.chart.scales[scaleID]; 3718 }, 3719 3720 /** 3721 * @private 3722 */ 3723 _getValueScaleId: function() { 3724 return this.getMeta().yAxisID; 3725 }, 3726 3727 /** 3728 * @private 3729 */ 3730 _getIndexScaleId: function() { 3731 return this.getMeta().xAxisID; 3732 }, 3733 3734 /** 3735 * @private 3736 */ 3737 _getValueScale: function() { 3738 return this.getScaleForId(this._getValueScaleId()); 3739 }, 3740 3741 /** 3742 * @private 3743 */ 3744 _getIndexScale: function() { 3745 return this.getScaleForId(this._getIndexScaleId()); 3746 }, 3747 3748 reset: function() { 3749 this._update(true); 3750 }, 3751 3752 /** 3753 * @private 3754 */ 3755 destroy: function() { 3756 if (this._data) { 3757 unlistenArrayEvents(this._data, this); 3758 } 3759 }, 3760 3761 createMetaDataset: function() { 3762 var me = this; 3763 var type = me.datasetElementType; 3764 return type && new type({ 3765 _chart: me.chart, 3766 _datasetIndex: me.index 3767 }); 3768 }, 3769 3770 createMetaData: function(index) { 3771 var me = this; 3772 var type = me.dataElementType; 3773 return type && new type({ 3774 _chart: me.chart, 3775 _datasetIndex: me.index, 3776 _index: index 3777 }); 3778 }, 3779 3780 addElements: function() { 3781 var me = this; 3782 var meta = me.getMeta(); 3783 var data = me.getDataset().data || []; 3784 var metaData = meta.data; 3785 var i, ilen; 3786 3787 for (i = 0, ilen = data.length; i < ilen; ++i) { 3788 metaData[i] = metaData[i] || me.createMetaData(i); 3789 } 3790 3791 meta.dataset = meta.dataset || me.createMetaDataset(); 3792 }, 3793 3794 addElementAndReset: function(index) { 3795 var element = this.createMetaData(index); 3796 this.getMeta().data.splice(index, 0, element); 3797 this.updateElement(element, index, true); 3798 }, 3799 3800 buildOrUpdateElements: function() { 3801 var me = this; 3802 var dataset = me.getDataset(); 3803 var data = dataset.data || (dataset.data = []); 3804 3805 // In order to correctly handle data addition/deletion animation (an thus simulate 3806 // real-time charts), we need to monitor these data modifications and synchronize 3807 // the internal meta data accordingly. 3808 if (me._data !== data) { 3809 if (me._data) { 3810 // This case happens when the user replaced the data array instance. 3811 unlistenArrayEvents(me._data, me); 3812 } 3813 3814 if (data && Object.isExtensible(data)) { 3815 listenArrayEvents(data, me); 3816 } 3817 me._data = data; 3818 } 3819 3820 // Re-sync meta data in case the user replaced the data array or if we missed 3821 // any updates and so make sure that we handle number of datapoints changing. 3822 me.resyncElements(); 3823 }, 3824 3825 /** 3826 * Returns the merged user-supplied and default dataset-level options 3827 * @private 3828 */ 3829 _configure: function() { 3830 var me = this; 3831 me._config = helpers$1.merge(Object.create(null), [ 3832 me.chart.options.datasets[me._type], 3833 me.getDataset(), 3834 ], { 3835 merger: function(key, target, source) { 3836 if (key !== '_meta' && key !== 'data') { 3837 helpers$1._merger(key, target, source); 3838 } 3839 } 3840 }); 3841 }, 3842 3843 _update: function(reset) { 3844 var me = this; 3845 me._configure(); 3846 me._cachedDataOpts = null; 3847 me.update(reset); 3848 }, 3849 3850 update: helpers$1.noop, 3851 3852 transition: function(easingValue) { 3853 var meta = this.getMeta(); 3854 var elements = meta.data || []; 3855 var ilen = elements.length; 3856 var i = 0; 3857 3858 for (; i < ilen; ++i) { 3859 elements[i].transition(easingValue); 3860 } 3861 3862 if (meta.dataset) { 3863 meta.dataset.transition(easingValue); 3864 } 3865 }, 3866 3867 draw: function() { 3868 var meta = this.getMeta(); 3869 var elements = meta.data || []; 3870 var ilen = elements.length; 3871 var i = 0; 3872 3873 if (meta.dataset) { 3874 meta.dataset.draw(); 3875 } 3876 3877 for (; i < ilen; ++i) { 3878 elements[i].draw(); 3879 } 3880 }, 3881 3882 /** 3883 * Returns a set of predefined style properties that should be used to represent the dataset 3884 * or the data if the index is specified 3885 * @param {number} index - data index 3886 * @return {IStyleInterface} style object 3887 */ 3888 getStyle: function(index) { 3889 var me = this; 3890 var meta = me.getMeta(); 3891 var dataset = meta.dataset; 3892 var style; 3893 3894 me._configure(); 3895 if (dataset && index === undefined) { 3896 style = me._resolveDatasetElementOptions(dataset || {}); 3897 } else { 3898 index = index || 0; 3899 style = me._resolveDataElementOptions(meta.data[index] || {}, index); 3900 } 3901 3902 if (style.fill === false || style.fill === null) { 3903 style.backgroundColor = style.borderColor; 3904 } 3905 3906 return style; 3907 }, 3908 3909 /** 3910 * @private 3911 */ 3912 _resolveDatasetElementOptions: function(element, hover) { 3913 var me = this; 3914 var chart = me.chart; 3915 var datasetOpts = me._config; 3916 var custom = element.custom || {}; 3917 var options = chart.options.elements[me.datasetElementType.prototype._type] || {}; 3918 var elementOptions = me._datasetElementOptions; 3919 var values = {}; 3920 var i, ilen, key, readKey; 3921 3922 // Scriptable options 3923 var context = { 3924 chart: chart, 3925 dataset: me.getDataset(), 3926 datasetIndex: me.index, 3927 hover: hover 3928 }; 3929 3930 for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { 3931 key = elementOptions[i]; 3932 readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key; 3933 values[key] = resolve([ 3934 custom[readKey], 3935 datasetOpts[readKey], 3936 options[readKey] 3937 ], context); 3938 } 3939 3940 return values; 3941 }, 3942 3943 /** 3944 * @private 3945 */ 3946 _resolveDataElementOptions: function(element, index) { 3947 var me = this; 3948 var custom = element && element.custom; 3949 var cached = me._cachedDataOpts; 3950 if (cached && !custom) { 3951 return cached; 3952 } 3953 var chart = me.chart; 3954 var datasetOpts = me._config; 3955 var options = chart.options.elements[me.dataElementType.prototype._type] || {}; 3956 var elementOptions = me._dataElementOptions; 3957 var values = {}; 3958 3959 // Scriptable options 3960 var context = { 3961 chart: chart, 3962 dataIndex: index, 3963 dataset: me.getDataset(), 3964 datasetIndex: me.index 3965 }; 3966 3967 // `resolve` sets cacheable to `false` if any option is indexed or scripted 3968 var info = {cacheable: !custom}; 3969 3970 var keys, i, ilen, key; 3971 3972 custom = custom || {}; 3973 3974 if (helpers$1.isArray(elementOptions)) { 3975 for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { 3976 key = elementOptions[i]; 3977 values[key] = resolve([ 3978 custom[key], 3979 datasetOpts[key], 3980 options[key] 3981 ], context, index, info); 3982 } 3983 } else { 3984 keys = Object.keys(elementOptions); 3985 for (i = 0, ilen = keys.length; i < ilen; ++i) { 3986 key = keys[i]; 3987 values[key] = resolve([ 3988 custom[key], 3989 datasetOpts[elementOptions[key]], 3990 datasetOpts[key], 3991 options[key] 3992 ], context, index, info); 3993 } 3994 } 3995 3996 if (info.cacheable) { 3997 me._cachedDataOpts = Object.freeze(values); 3998 } 3999 4000 return values; 4001 }, 4002 4003 removeHoverStyle: function(element) { 4004 helpers$1.merge(element._model, element.$previousStyle || {}); 4005 delete element.$previousStyle; 4006 }, 4007 4008 setHoverStyle: function(element) { 4009 var dataset = this.chart.data.datasets[element._datasetIndex]; 4010 var index = element._index; 4011 var custom = element.custom || {}; 4012 var model = element._model; 4013 var getHoverColor = helpers$1.getHoverColor; 4014 4015 element.$previousStyle = { 4016 backgroundColor: model.backgroundColor, 4017 borderColor: model.borderColor, 4018 borderWidth: model.borderWidth 4019 }; 4020 4021 model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index); 4022 model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index); 4023 model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index); 4024 }, 4025 4026 /** 4027 * @private 4028 */ 4029 _removeDatasetHoverStyle: function() { 4030 var element = this.getMeta().dataset; 4031 4032 if (element) { 4033 this.removeHoverStyle(element); 4034 } 4035 }, 4036 4037 /** 4038 * @private 4039 */ 4040 _setDatasetHoverStyle: function() { 4041 var element = this.getMeta().dataset; 4042 var prev = {}; 4043 var i, ilen, key, keys, hoverOptions, model; 4044 4045 if (!element) { 4046 return; 4047 } 4048 4049 model = element._model; 4050 hoverOptions = this._resolveDatasetElementOptions(element, true); 4051 4052 keys = Object.keys(hoverOptions); 4053 for (i = 0, ilen = keys.length; i < ilen; ++i) { 4054 key = keys[i]; 4055 prev[key] = model[key]; 4056 model[key] = hoverOptions[key]; 4057 } 4058 4059 element.$previousStyle = prev; 4060 }, 4061 4062 /** 4063 * @private 4064 */ 4065 resyncElements: function() { 4066 var me = this; 4067 var meta = me.getMeta(); 4068 var data = me.getDataset().data; 4069 var numMeta = meta.data.length; 4070 var numData = data.length; 4071 4072 if (numData < numMeta) { 4073 meta.data.splice(numData, numMeta - numData); 4074 } else if (numData > numMeta) { 4075 me.insertElements(numMeta, numData - numMeta); 4076 } 4077 }, 4078 4079 /** 4080 * @private 4081 */ 4082 insertElements: function(start, count) { 4083 for (var i = 0; i < count; ++i) { 4084 this.addElementAndReset(start + i); 4085 } 4086 }, 4087 4088 /** 4089 * @private 4090 */ 4091 onDataPush: function() { 4092 var count = arguments.length; 4093 this.insertElements(this.getDataset().data.length - count, count); 4094 }, 4095 4096 /** 4097 * @private 4098 */ 4099 onDataPop: function() { 4100 this.getMeta().data.pop(); 4101 }, 4102 4103 /** 4104 * @private 4105 */ 4106 onDataShift: function() { 4107 this.getMeta().data.shift(); 4108 }, 4109 4110 /** 4111 * @private 4112 */ 4113 onDataSplice: function(start, count) { 4114 this.getMeta().data.splice(start, count); 4115 this.insertElements(start, arguments.length - 2); 4116 }, 4117 4118 /** 4119 * @private 4120 */ 4121 onDataUnshift: function() { 4122 this.insertElements(0, arguments.length); 4123 } 4124}); 4125 4126DatasetController.extend = helpers$1.inherits; 4127 4128var core_datasetController = DatasetController; 4129 4130var TAU = Math.PI * 2; 4131 4132core_defaults._set('global', { 4133 elements: { 4134 arc: { 4135 backgroundColor: core_defaults.global.defaultColor, 4136 borderColor: '#fff', 4137 borderWidth: 2, 4138 borderAlign: 'center' 4139 } 4140 } 4141}); 4142 4143function clipArc(ctx, arc) { 4144 var startAngle = arc.startAngle; 4145 var endAngle = arc.endAngle; 4146 var pixelMargin = arc.pixelMargin; 4147 var angleMargin = pixelMargin / arc.outerRadius; 4148 var x = arc.x; 4149 var y = arc.y; 4150 4151 // Draw an inner border by cliping the arc and drawing a double-width border 4152 // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders 4153 ctx.beginPath(); 4154 ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin); 4155 if (arc.innerRadius > pixelMargin) { 4156 angleMargin = pixelMargin / arc.innerRadius; 4157 ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true); 4158 } else { 4159 ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2); 4160 } 4161 ctx.closePath(); 4162 ctx.clip(); 4163} 4164 4165function drawFullCircleBorders(ctx, vm, arc, inner) { 4166 var endAngle = arc.endAngle; 4167 var i; 4168 4169 if (inner) { 4170 arc.endAngle = arc.startAngle + TAU; 4171 clipArc(ctx, arc); 4172 arc.endAngle = endAngle; 4173 if (arc.endAngle === arc.startAngle && arc.fullCircles) { 4174 arc.endAngle += TAU; 4175 arc.fullCircles--; 4176 } 4177 } 4178 4179 ctx.beginPath(); 4180 ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true); 4181 for (i = 0; i < arc.fullCircles; ++i) { 4182 ctx.stroke(); 4183 } 4184 4185 ctx.beginPath(); 4186 ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU); 4187 for (i = 0; i < arc.fullCircles; ++i) { 4188 ctx.stroke(); 4189 } 4190} 4191 4192function drawBorder(ctx, vm, arc) { 4193 var inner = vm.borderAlign === 'inner'; 4194 4195 if (inner) { 4196 ctx.lineWidth = vm.borderWidth * 2; 4197 ctx.lineJoin = 'round'; 4198 } else { 4199 ctx.lineWidth = vm.borderWidth; 4200 ctx.lineJoin = 'bevel'; 4201 } 4202 4203 if (arc.fullCircles) { 4204 drawFullCircleBorders(ctx, vm, arc, inner); 4205 } 4206 4207 if (inner) { 4208 clipArc(ctx, arc); 4209 } 4210 4211 ctx.beginPath(); 4212 ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle); 4213 ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); 4214 ctx.closePath(); 4215 ctx.stroke(); 4216} 4217 4218var element_arc = core_element.extend({ 4219 _type: 'arc', 4220 4221 inLabelRange: function(mouseX) { 4222 var vm = this._view; 4223 4224 if (vm) { 4225 return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); 4226 } 4227 return false; 4228 }, 4229 4230 inRange: function(chartX, chartY) { 4231 var vm = this._view; 4232 4233 if (vm) { 4234 var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY}); 4235 var angle = pointRelativePosition.angle; 4236 var distance = pointRelativePosition.distance; 4237 4238 // Sanitise angle range 4239 var startAngle = vm.startAngle; 4240 var endAngle = vm.endAngle; 4241 while (endAngle < startAngle) { 4242 endAngle += TAU; 4243 } 4244 while (angle > endAngle) { 4245 angle -= TAU; 4246 } 4247 while (angle < startAngle) { 4248 angle += TAU; 4249 } 4250 4251 // Check if within the range of the open/close angle 4252 var betweenAngles = (angle >= startAngle && angle <= endAngle); 4253 var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); 4254 4255 return (betweenAngles && withinRadius); 4256 } 4257 return false; 4258 }, 4259 4260 getCenterPoint: function() { 4261 var vm = this._view; 4262 var halfAngle = (vm.startAngle + vm.endAngle) / 2; 4263 var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; 4264 return { 4265 x: vm.x + Math.cos(halfAngle) * halfRadius, 4266 y: vm.y + Math.sin(halfAngle) * halfRadius 4267 }; 4268 }, 4269 4270 getArea: function() { 4271 var vm = this._view; 4272 return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); 4273 }, 4274 4275 tooltipPosition: function() { 4276 var vm = this._view; 4277 var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); 4278 var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; 4279 4280 return { 4281 x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), 4282 y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) 4283 }; 4284 }, 4285 4286 draw: function() { 4287 var ctx = this._chart.ctx; 4288 var vm = this._view; 4289 var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0; 4290 var arc = { 4291 x: vm.x, 4292 y: vm.y, 4293 innerRadius: vm.innerRadius, 4294 outerRadius: Math.max(vm.outerRadius - pixelMargin, 0), 4295 pixelMargin: pixelMargin, 4296 startAngle: vm.startAngle, 4297 endAngle: vm.endAngle, 4298 fullCircles: Math.floor(vm.circumference / TAU) 4299 }; 4300 var i; 4301 4302 ctx.save(); 4303 4304 ctx.fillStyle = vm.backgroundColor; 4305 ctx.strokeStyle = vm.borderColor; 4306 4307 if (arc.fullCircles) { 4308 arc.endAngle = arc.startAngle + TAU; 4309 ctx.beginPath(); 4310 ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); 4311 ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); 4312 ctx.closePath(); 4313 for (i = 0; i < arc.fullCircles; ++i) { 4314 ctx.fill(); 4315 } 4316 arc.endAngle = arc.startAngle + vm.circumference % TAU; 4317 } 4318 4319 ctx.beginPath(); 4320 ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); 4321 ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); 4322 ctx.closePath(); 4323 ctx.fill(); 4324 4325 if (vm.borderWidth) { 4326 drawBorder(ctx, vm, arc); 4327 } 4328 4329 ctx.restore(); 4330 } 4331}); 4332 4333var valueOrDefault$1 = helpers$1.valueOrDefault; 4334 4335var defaultColor = core_defaults.global.defaultColor; 4336 4337core_defaults._set('global', { 4338 elements: { 4339 line: { 4340 tension: 0.4, 4341 backgroundColor: defaultColor, 4342 borderWidth: 3, 4343 borderColor: defaultColor, 4344 borderCapStyle: 'butt', 4345 borderDash: [], 4346 borderDashOffset: 0.0, 4347 borderJoinStyle: 'miter', 4348 capBezierPoints: true, 4349 fill: true, // do we fill in the area between the line and its base axis 4350 } 4351 } 4352}); 4353 4354var element_line = core_element.extend({ 4355 _type: 'line', 4356 4357 draw: function() { 4358 var me = this; 4359 var vm = me._view; 4360 var ctx = me._chart.ctx; 4361 var spanGaps = vm.spanGaps; 4362 var points = me._children.slice(); // clone array 4363 var globalDefaults = core_defaults.global; 4364 var globalOptionLineElements = globalDefaults.elements.line; 4365 var lastDrawnIndex = -1; 4366 var closePath = me._loop; 4367 var index, previous, currentVM; 4368 4369 if (!points.length) { 4370 return; 4371 } 4372 4373 if (me._loop) { 4374 for (index = 0; index < points.length; ++index) { 4375 previous = helpers$1.previousItem(points, index); 4376 // If the line has an open path, shift the point array 4377 if (!points[index]._view.skip && previous._view.skip) { 4378 points = points.slice(index).concat(points.slice(0, index)); 4379 closePath = spanGaps; 4380 break; 4381 } 4382 } 4383 // If the line has a close path, add the first point again 4384 if (closePath) { 4385 points.push(points[0]); 4386 } 4387 } 4388 4389 ctx.save(); 4390 4391 // Stroke Line Options 4392 ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; 4393 4394 // IE 9 and 10 do not support line dash 4395 if (ctx.setLineDash) { 4396 ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); 4397 } 4398 4399 ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset); 4400 ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; 4401 ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth); 4402 ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; 4403 4404 // Stroke Line 4405 ctx.beginPath(); 4406 4407 // First point moves to it's starting position no matter what 4408 currentVM = points[0]._view; 4409 if (!currentVM.skip) { 4410 ctx.moveTo(currentVM.x, currentVM.y); 4411 lastDrawnIndex = 0; 4412 } 4413 4414 for (index = 1; index < points.length; ++index) { 4415 currentVM = points[index]._view; 4416 previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex]; 4417 4418 if (!currentVM.skip) { 4419 if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { 4420 // There was a gap and this is the first point after the gap 4421 ctx.moveTo(currentVM.x, currentVM.y); 4422 } else { 4423 // Line to next point 4424 helpers$1.canvas.lineTo(ctx, previous._view, currentVM); 4425 } 4426 lastDrawnIndex = index; 4427 } 4428 } 4429 4430 if (closePath) { 4431 ctx.closePath(); 4432 } 4433 4434 ctx.stroke(); 4435 ctx.restore(); 4436 } 4437}); 4438 4439var valueOrDefault$2 = helpers$1.valueOrDefault; 4440 4441var defaultColor$1 = core_defaults.global.defaultColor; 4442 4443core_defaults._set('global', { 4444 elements: { 4445 point: { 4446 radius: 3, 4447 pointStyle: 'circle', 4448 backgroundColor: defaultColor$1, 4449 borderColor: defaultColor$1, 4450 borderWidth: 1, 4451 // Hover 4452 hitRadius: 1, 4453 hoverRadius: 4, 4454 hoverBorderWidth: 1 4455 } 4456 } 4457}); 4458 4459function xRange(mouseX) { 4460 var vm = this._view; 4461 return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; 4462} 4463 4464function yRange(mouseY) { 4465 var vm = this._view; 4466 return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; 4467} 4468 4469var element_point = core_element.extend({ 4470 _type: 'point', 4471 4472 inRange: function(mouseX, mouseY) { 4473 var vm = this._view; 4474 return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; 4475 }, 4476 4477 inLabelRange: xRange, 4478 inXRange: xRange, 4479 inYRange: yRange, 4480 4481 getCenterPoint: function() { 4482 var vm = this._view; 4483 return { 4484 x: vm.x, 4485 y: vm.y 4486 }; 4487 }, 4488 4489 getArea: function() { 4490 return Math.PI * Math.pow(this._view.radius, 2); 4491 }, 4492 4493 tooltipPosition: function() { 4494 var vm = this._view; 4495 return { 4496 x: vm.x, 4497 y: vm.y, 4498 padding: vm.radius + vm.borderWidth 4499 }; 4500 }, 4501 4502 draw: function(chartArea) { 4503 var vm = this._view; 4504 var ctx = this._chart.ctx; 4505 var pointStyle = vm.pointStyle; 4506 var rotation = vm.rotation; 4507 var radius = vm.radius; 4508 var x = vm.x; 4509 var y = vm.y; 4510 var globalDefaults = core_defaults.global; 4511 var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow 4512 4513 if (vm.skip) { 4514 return; 4515 } 4516 4517 // Clipping for Points. 4518 if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) { 4519 ctx.strokeStyle = vm.borderColor || defaultColor; 4520 ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth); 4521 ctx.fillStyle = vm.backgroundColor || defaultColor; 4522 helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); 4523 } 4524 } 4525}); 4526 4527var defaultColor$2 = core_defaults.global.defaultColor; 4528 4529core_defaults._set('global', { 4530 elements: { 4531 rectangle: { 4532 backgroundColor: defaultColor$2, 4533 borderColor: defaultColor$2, 4534 borderSkipped: 'bottom', 4535 borderWidth: 0 4536 } 4537 } 4538}); 4539 4540function isVertical(vm) { 4541 return vm && vm.width !== undefined; 4542} 4543 4544/** 4545 * Helper function to get the bounds of the bar regardless of the orientation 4546 * @param bar {Chart.Element.Rectangle} the bar 4547 * @return {Bounds} bounds of the bar 4548 * @private 4549 */ 4550function getBarBounds(vm) { 4551 var x1, x2, y1, y2, half; 4552 4553 if (isVertical(vm)) { 4554 half = vm.width / 2; 4555 x1 = vm.x - half; 4556 x2 = vm.x + half; 4557 y1 = Math.min(vm.y, vm.base); 4558 y2 = Math.max(vm.y, vm.base); 4559 } else { 4560 half = vm.height / 2; 4561 x1 = Math.min(vm.x, vm.base); 4562 x2 = Math.max(vm.x, vm.base); 4563 y1 = vm.y - half; 4564 y2 = vm.y + half; 4565 } 4566 4567 return { 4568 left: x1, 4569 top: y1, 4570 right: x2, 4571 bottom: y2 4572 }; 4573} 4574 4575function swap(orig, v1, v2) { 4576 return orig === v1 ? v2 : orig === v2 ? v1 : orig; 4577} 4578 4579function parseBorderSkipped(vm) { 4580 var edge = vm.borderSkipped; 4581 var res = {}; 4582 4583 if (!edge) { 4584 return res; 4585 } 4586 4587 if (vm.horizontal) { 4588 if (vm.base > vm.x) { 4589 edge = swap(edge, 'left', 'right'); 4590 } 4591 } else if (vm.base < vm.y) { 4592 edge = swap(edge, 'bottom', 'top'); 4593 } 4594 4595 res[edge] = true; 4596 return res; 4597} 4598 4599function parseBorderWidth(vm, maxW, maxH) { 4600 var value = vm.borderWidth; 4601 var skip = parseBorderSkipped(vm); 4602 var t, r, b, l; 4603 4604 if (helpers$1.isObject(value)) { 4605 t = +value.top || 0; 4606 r = +value.right || 0; 4607 b = +value.bottom || 0; 4608 l = +value.left || 0; 4609 } else { 4610 t = r = b = l = +value || 0; 4611 } 4612 4613 return { 4614 t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, 4615 r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, 4616 b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, 4617 l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l 4618 }; 4619} 4620 4621function boundingRects(vm) { 4622 var bounds = getBarBounds(vm); 4623 var width = bounds.right - bounds.left; 4624 var height = bounds.bottom - bounds.top; 4625 var border = parseBorderWidth(vm, width / 2, height / 2); 4626 4627 return { 4628 outer: { 4629 x: bounds.left, 4630 y: bounds.top, 4631 w: width, 4632 h: height 4633 }, 4634 inner: { 4635 x: bounds.left + border.l, 4636 y: bounds.top + border.t, 4637 w: width - border.l - border.r, 4638 h: height - border.t - border.b 4639 } 4640 }; 4641} 4642 4643function inRange(vm, x, y) { 4644 var skipX = x === null; 4645 var skipY = y === null; 4646 var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm); 4647 4648 return bounds 4649 && (skipX || x >= bounds.left && x <= bounds.right) 4650 && (skipY || y >= bounds.top && y <= bounds.bottom); 4651} 4652 4653var element_rectangle = core_element.extend({ 4654 _type: 'rectangle', 4655 4656 draw: function() { 4657 var ctx = this._chart.ctx; 4658 var vm = this._view; 4659 var rects = boundingRects(vm); 4660 var outer = rects.outer; 4661 var inner = rects.inner; 4662 4663 ctx.fillStyle = vm.backgroundColor; 4664 ctx.fillRect(outer.x, outer.y, outer.w, outer.h); 4665 4666 if (outer.w === inner.w && outer.h === inner.h) { 4667 return; 4668 } 4669 4670 ctx.save(); 4671 ctx.beginPath(); 4672 ctx.rect(outer.x, outer.y, outer.w, outer.h); 4673 ctx.clip(); 4674 ctx.fillStyle = vm.borderColor; 4675 ctx.rect(inner.x, inner.y, inner.w, inner.h); 4676 ctx.fill('evenodd'); 4677 ctx.restore(); 4678 }, 4679 4680 height: function() { 4681 var vm = this._view; 4682 return vm.base - vm.y; 4683 }, 4684 4685 inRange: function(mouseX, mouseY) { 4686 return inRange(this._view, mouseX, mouseY); 4687 }, 4688 4689 inLabelRange: function(mouseX, mouseY) { 4690 var vm = this._view; 4691 return isVertical(vm) 4692 ? inRange(vm, mouseX, null) 4693 : inRange(vm, null, mouseY); 4694 }, 4695 4696 inXRange: function(mouseX) { 4697 return inRange(this._view, mouseX, null); 4698 }, 4699 4700 inYRange: function(mouseY) { 4701 return inRange(this._view, null, mouseY); 4702 }, 4703 4704 getCenterPoint: function() { 4705 var vm = this._view; 4706 var x, y; 4707 if (isVertical(vm)) { 4708 x = vm.x; 4709 y = (vm.y + vm.base) / 2; 4710 } else { 4711 x = (vm.x + vm.base) / 2; 4712 y = vm.y; 4713 } 4714 4715 return {x: x, y: y}; 4716 }, 4717 4718 getArea: function() { 4719 var vm = this._view; 4720 4721 return isVertical(vm) 4722 ? vm.width * Math.abs(vm.y - vm.base) 4723 : vm.height * Math.abs(vm.x - vm.base); 4724 }, 4725 4726 tooltipPosition: function() { 4727 var vm = this._view; 4728 return { 4729 x: vm.x, 4730 y: vm.y 4731 }; 4732 } 4733}); 4734 4735var elements = {}; 4736var Arc = element_arc; 4737var Line = element_line; 4738var Point = element_point; 4739var Rectangle = element_rectangle; 4740elements.Arc = Arc; 4741elements.Line = Line; 4742elements.Point = Point; 4743elements.Rectangle = Rectangle; 4744 4745var deprecated = helpers$1._deprecated; 4746var valueOrDefault$3 = helpers$1.valueOrDefault; 4747 4748core_defaults._set('bar', { 4749 hover: { 4750 mode: 'label' 4751 }, 4752 4753 scales: { 4754 xAxes: [{ 4755 type: 'category', 4756 offset: true, 4757 gridLines: { 4758 offsetGridLines: true 4759 } 4760 }], 4761 4762 yAxes: [{ 4763 type: 'linear' 4764 }] 4765 } 4766}); 4767 4768core_defaults._set('global', { 4769 datasets: { 4770 bar: { 4771 categoryPercentage: 0.8, 4772 barPercentage: 0.9 4773 } 4774 } 4775}); 4776 4777/** 4778 * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. 4779 * @private 4780 */ 4781function computeMinSampleSize(scale, pixels) { 4782 var min = scale._length; 4783 var prev, curr, i, ilen; 4784 4785 for (i = 1, ilen = pixels.length; i < ilen; ++i) { 4786 min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1])); 4787 } 4788 4789 for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) { 4790 curr = scale.getPixelForTick(i); 4791 min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min; 4792 prev = curr; 4793 } 4794 4795 return min; 4796} 4797 4798/** 4799 * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, 4800 * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This 4801 * mode currently always generates bars equally sized (until we introduce scriptable options?). 4802 * @private 4803 */ 4804function computeFitCategoryTraits(index, ruler, options) { 4805 var thickness = options.barThickness; 4806 var count = ruler.stackCount; 4807 var curr = ruler.pixels[index]; 4808 var min = helpers$1.isNullOrUndef(thickness) 4809 ? computeMinSampleSize(ruler.scale, ruler.pixels) 4810 : -1; 4811 var size, ratio; 4812 4813 if (helpers$1.isNullOrUndef(thickness)) { 4814 size = min * options.categoryPercentage; 4815 ratio = options.barPercentage; 4816 } else { 4817 // When bar thickness is enforced, category and bar percentages are ignored. 4818 // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') 4819 // and deprecate barPercentage since this value is ignored when thickness is absolute. 4820 size = thickness * count; 4821 ratio = 1; 4822 } 4823 4824 return { 4825 chunk: size / count, 4826 ratio: ratio, 4827 start: curr - (size / 2) 4828 }; 4829} 4830 4831/** 4832 * Computes an "optimal" category that globally arranges bars side by side (no gap when 4833 * percentage options are 1), based on the previous and following categories. This mode 4834 * generates bars with different widths when data are not evenly spaced. 4835 * @private 4836 */ 4837function computeFlexCategoryTraits(index, ruler, options) { 4838 var pixels = ruler.pixels; 4839 var curr = pixels[index]; 4840 var prev = index > 0 ? pixels[index - 1] : null; 4841 var next = index < pixels.length - 1 ? pixels[index + 1] : null; 4842 var percent = options.categoryPercentage; 4843 var start, size; 4844 4845 if (prev === null) { 4846 // first data: its size is double based on the next point or, 4847 // if it's also the last data, we use the scale size. 4848 prev = curr - (next === null ? ruler.end - ruler.start : next - curr); 4849 } 4850 4851 if (next === null) { 4852 // last data: its size is also double based on the previous point. 4853 next = curr + curr - prev; 4854 } 4855 4856 start = curr - (curr - Math.min(prev, next)) / 2 * percent; 4857 size = Math.abs(next - prev) / 2 * percent; 4858 4859 return { 4860 chunk: size / ruler.stackCount, 4861 ratio: options.barPercentage, 4862 start: start 4863 }; 4864} 4865 4866var controller_bar = core_datasetController.extend({ 4867 4868 dataElementType: elements.Rectangle, 4869 4870 /** 4871 * @private 4872 */ 4873 _dataElementOptions: [ 4874 'backgroundColor', 4875 'borderColor', 4876 'borderSkipped', 4877 'borderWidth', 4878 'barPercentage', 4879 'barThickness', 4880 'categoryPercentage', 4881 'maxBarThickness', 4882 'minBarLength' 4883 ], 4884 4885 initialize: function() { 4886 var me = this; 4887 var meta, scaleOpts; 4888 4889 core_datasetController.prototype.initialize.apply(me, arguments); 4890 4891 meta = me.getMeta(); 4892 meta.stack = me.getDataset().stack; 4893 meta.bar = true; 4894 4895 scaleOpts = me._getIndexScale().options; 4896 deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage'); 4897 deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness'); 4898 deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage'); 4899 deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength'); 4900 deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness'); 4901 }, 4902 4903 update: function(reset) { 4904 var me = this; 4905 var rects = me.getMeta().data; 4906 var i, ilen; 4907 4908 me._ruler = me.getRuler(); 4909 4910 for (i = 0, ilen = rects.length; i < ilen; ++i) { 4911 me.updateElement(rects[i], i, reset); 4912 } 4913 }, 4914 4915 updateElement: function(rectangle, index, reset) { 4916 var me = this; 4917 var meta = me.getMeta(); 4918 var dataset = me.getDataset(); 4919 var options = me._resolveDataElementOptions(rectangle, index); 4920 4921 rectangle._xScale = me.getScaleForId(meta.xAxisID); 4922 rectangle._yScale = me.getScaleForId(meta.yAxisID); 4923 rectangle._datasetIndex = me.index; 4924 rectangle._index = index; 4925 rectangle._model = { 4926 backgroundColor: options.backgroundColor, 4927 borderColor: options.borderColor, 4928 borderSkipped: options.borderSkipped, 4929 borderWidth: options.borderWidth, 4930 datasetLabel: dataset.label, 4931 label: me.chart.data.labels[index] 4932 }; 4933 4934 if (helpers$1.isArray(dataset.data[index])) { 4935 rectangle._model.borderSkipped = null; 4936 } 4937 4938 me._updateElementGeometry(rectangle, index, reset, options); 4939 4940 rectangle.pivot(); 4941 }, 4942 4943 /** 4944 * @private 4945 */ 4946 _updateElementGeometry: function(rectangle, index, reset, options) { 4947 var me = this; 4948 var model = rectangle._model; 4949 var vscale = me._getValueScale(); 4950 var base = vscale.getBasePixel(); 4951 var horizontal = vscale.isHorizontal(); 4952 var ruler = me._ruler || me.getRuler(); 4953 var vpixels = me.calculateBarValuePixels(me.index, index, options); 4954 var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options); 4955 4956 model.horizontal = horizontal; 4957 model.base = reset ? base : vpixels.base; 4958 model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; 4959 model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; 4960 model.height = horizontal ? ipixels.size : undefined; 4961 model.width = horizontal ? undefined : ipixels.size; 4962 }, 4963 4964 /** 4965 * Returns the stacks based on groups and bar visibility. 4966 * @param {number} [last] - The dataset index 4967 * @returns {string[]} The list of stack IDs 4968 * @private 4969 */ 4970 _getStacks: function(last) { 4971 var me = this; 4972 var scale = me._getIndexScale(); 4973 var metasets = scale._getMatchingVisibleMetas(me._type); 4974 var stacked = scale.options.stacked; 4975 var ilen = metasets.length; 4976 var stacks = []; 4977 var i, meta; 4978 4979 for (i = 0; i < ilen; ++i) { 4980 meta = metasets[i]; 4981 // stacked | meta.stack 4982 // | found | not found | undefined 4983 // false | x | x | x 4984 // true | | x | 4985 // undefined | | x | x 4986 if (stacked === false || stacks.indexOf(meta.stack) === -1 || 4987 (stacked === undefined && meta.stack === undefined)) { 4988 stacks.push(meta.stack); 4989 } 4990 if (meta.index === last) { 4991 break; 4992 } 4993 } 4994 4995 return stacks; 4996 }, 4997 4998 /** 4999 * Returns the effective number of stacks based on groups and bar visibility. 5000 * @private 5001 */ 5002 getStackCount: function() { 5003 return this._getStacks().length; 5004 }, 5005 5006 /** 5007 * Returns the stack index for the given dataset based on groups and bar visibility. 5008 * @param {number} [datasetIndex] - The dataset index 5009 * @param {string} [name] - The stack name to find 5010 * @returns {number} The stack index 5011 * @private 5012 */ 5013 getStackIndex: function(datasetIndex, name) { 5014 var stacks = this._getStacks(datasetIndex); 5015 var index = (name !== undefined) 5016 ? stacks.indexOf(name) 5017 : -1; // indexOf returns -1 if element is not present 5018 5019 return (index === -1) 5020 ? stacks.length - 1 5021 : index; 5022 }, 5023 5024 /** 5025 * @private 5026 */ 5027 getRuler: function() { 5028 var me = this; 5029 var scale = me._getIndexScale(); 5030 var pixels = []; 5031 var i, ilen; 5032 5033 for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { 5034 pixels.push(scale.getPixelForValue(null, i, me.index)); 5035 } 5036 5037 return { 5038 pixels: pixels, 5039 start: scale._startPixel, 5040 end: scale._endPixel, 5041 stackCount: me.getStackCount(), 5042 scale: scale 5043 }; 5044 }, 5045 5046 /** 5047 * Note: pixel values are not clamped to the scale area. 5048 * @private 5049 */ 5050 calculateBarValuePixels: function(datasetIndex, index, options) { 5051 var me = this; 5052 var chart = me.chart; 5053 var scale = me._getValueScale(); 5054 var isHorizontal = scale.isHorizontal(); 5055 var datasets = chart.data.datasets; 5056 var metasets = scale._getMatchingVisibleMetas(me._type); 5057 var value = scale._parseValue(datasets[datasetIndex].data[index]); 5058 var minBarLength = options.minBarLength; 5059 var stacked = scale.options.stacked; 5060 var stack = me.getMeta().stack; 5061 var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max; 5062 var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max; 5063 var ilen = metasets.length; 5064 var i, imeta, ivalue, base, head, size, stackLength; 5065 5066 if (stacked || (stacked === undefined && stack !== undefined)) { 5067 for (i = 0; i < ilen; ++i) { 5068 imeta = metasets[i]; 5069 5070 if (imeta.index === datasetIndex) { 5071 break; 5072 } 5073 5074 if (imeta.stack === stack) { 5075 stackLength = scale._parseValue(datasets[imeta.index].data[index]); 5076 ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min; 5077 5078 if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) { 5079 start += ivalue; 5080 } 5081 } 5082 } 5083 } 5084 5085 base = scale.getPixelForValue(start); 5086 head = scale.getPixelForValue(start + length); 5087 size = head - base; 5088 5089 if (minBarLength !== undefined && Math.abs(size) < minBarLength) { 5090 size = minBarLength; 5091 if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) { 5092 head = base - minBarLength; 5093 } else { 5094 head = base + minBarLength; 5095 } 5096 } 5097 5098 return { 5099 size: size, 5100 base: base, 5101 head: head, 5102 center: head + size / 2 5103 }; 5104 }, 5105 5106 /** 5107 * @private 5108 */ 5109 calculateBarIndexPixels: function(datasetIndex, index, ruler, options) { 5110 var me = this; 5111 var range = options.barThickness === 'flex' 5112 ? computeFlexCategoryTraits(index, ruler, options) 5113 : computeFitCategoryTraits(index, ruler, options); 5114 5115 var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); 5116 var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); 5117 var size = Math.min( 5118 valueOrDefault$3(options.maxBarThickness, Infinity), 5119 range.chunk * range.ratio); 5120 5121 return { 5122 base: center - size / 2, 5123 head: center + size / 2, 5124 center: center, 5125 size: size 5126 }; 5127 }, 5128 5129 draw: function() { 5130 var me = this; 5131 var chart = me.chart; 5132 var scale = me._getValueScale(); 5133 var rects = me.getMeta().data; 5134 var dataset = me.getDataset(); 5135 var ilen = rects.length; 5136 var i = 0; 5137 5138 helpers$1.canvas.clipArea(chart.ctx, chart.chartArea); 5139 5140 for (; i < ilen; ++i) { 5141 var val = scale._parseValue(dataset.data[i]); 5142 if (!isNaN(val.min) && !isNaN(val.max)) { 5143 rects[i].draw(); 5144 } 5145 } 5146 5147 helpers$1.canvas.unclipArea(chart.ctx); 5148 }, 5149 5150 /** 5151 * @private 5152 */ 5153 _resolveDataElementOptions: function() { 5154 var me = this; 5155 var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments)); 5156 var indexOpts = me._getIndexScale().options; 5157 var valueOpts = me._getValueScale().options; 5158 5159 values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage); 5160 values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness); 5161 values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage); 5162 values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness); 5163 values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength); 5164 5165 return values; 5166 } 5167 5168}); 5169 5170var valueOrDefault$4 = helpers$1.valueOrDefault; 5171var resolve$1 = helpers$1.options.resolve; 5172 5173core_defaults._set('bubble', { 5174 hover: { 5175 mode: 'single' 5176 }, 5177 5178 scales: { 5179 xAxes: [{ 5180 type: 'linear', // bubble should probably use a linear scale by default 5181 position: 'bottom', 5182 id: 'x-axis-0' // need an ID so datasets can reference the scale 5183 }], 5184 yAxes: [{ 5185 type: 'linear', 5186 position: 'left', 5187 id: 'y-axis-0' 5188 }] 5189 }, 5190 5191 tooltips: { 5192 callbacks: { 5193 title: function() { 5194 // Title doesn't make sense for scatter since we format the data as a point 5195 return ''; 5196 }, 5197 label: function(item, data) { 5198 var datasetLabel = data.datasets[item.datasetIndex].label || ''; 5199 var dataPoint = data.datasets[item.datasetIndex].data[item.index]; 5200 return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; 5201 } 5202 } 5203 } 5204}); 5205 5206var controller_bubble = core_datasetController.extend({ 5207 /** 5208 * @protected 5209 */ 5210 dataElementType: elements.Point, 5211 5212 /** 5213 * @private 5214 */ 5215 _dataElementOptions: [ 5216 'backgroundColor', 5217 'borderColor', 5218 'borderWidth', 5219 'hoverBackgroundColor', 5220 'hoverBorderColor', 5221 'hoverBorderWidth', 5222 'hoverRadius', 5223 'hitRadius', 5224 'pointStyle', 5225 'rotation' 5226 ], 5227 5228 /** 5229 * @protected 5230 */ 5231 update: function(reset) { 5232 var me = this; 5233 var meta = me.getMeta(); 5234 var points = meta.data; 5235 5236 // Update Points 5237 helpers$1.each(points, function(point, index) { 5238 me.updateElement(point, index, reset); 5239 }); 5240 }, 5241 5242 /** 5243 * @protected 5244 */ 5245 updateElement: function(point, index, reset) { 5246 var me = this; 5247 var meta = me.getMeta(); 5248 var custom = point.custom || {}; 5249 var xScale = me.getScaleForId(meta.xAxisID); 5250 var yScale = me.getScaleForId(meta.yAxisID); 5251 var options = me._resolveDataElementOptions(point, index); 5252 var data = me.getDataset().data[index]; 5253 var dsIndex = me.index; 5254 5255 var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); 5256 var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); 5257 5258 point._xScale = xScale; 5259 point._yScale = yScale; 5260 point._options = options; 5261 point._datasetIndex = dsIndex; 5262 point._index = index; 5263 point._model = { 5264 backgroundColor: options.backgroundColor, 5265 borderColor: options.borderColor, 5266 borderWidth: options.borderWidth, 5267 hitRadius: options.hitRadius, 5268 pointStyle: options.pointStyle, 5269 rotation: options.rotation, 5270 radius: reset ? 0 : options.radius, 5271 skip: custom.skip || isNaN(x) || isNaN(y), 5272 x: x, 5273 y: y, 5274 }; 5275 5276 point.pivot(); 5277 }, 5278 5279 /** 5280 * @protected 5281 */ 5282 setHoverStyle: function(point) { 5283 var model = point._model; 5284 var options = point._options; 5285 var getHoverColor = helpers$1.getHoverColor; 5286 5287 point.$previousStyle = { 5288 backgroundColor: model.backgroundColor, 5289 borderColor: model.borderColor, 5290 borderWidth: model.borderWidth, 5291 radius: model.radius 5292 }; 5293 5294 model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); 5295 model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor)); 5296 model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth); 5297 model.radius = options.radius + options.hoverRadius; 5298 }, 5299 5300 /** 5301 * @private 5302 */ 5303 _resolveDataElementOptions: function(point, index) { 5304 var me = this; 5305 var chart = me.chart; 5306 var dataset = me.getDataset(); 5307 var custom = point.custom || {}; 5308 var data = dataset.data[index] || {}; 5309 var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments); 5310 5311 // Scriptable options 5312 var context = { 5313 chart: chart, 5314 dataIndex: index, 5315 dataset: dataset, 5316 datasetIndex: me.index 5317 }; 5318 5319 // In case values were cached (and thus frozen), we need to clone the values 5320 if (me._cachedDataOpts === values) { 5321 values = helpers$1.extend({}, values); 5322 } 5323 5324 // Custom radius resolution 5325 values.radius = resolve$1([ 5326 custom.radius, 5327 data.r, 5328 me._config.radius, 5329 chart.options.elements.point.radius 5330 ], context, index); 5331 5332 return values; 5333 } 5334}); 5335 5336var valueOrDefault$5 = helpers$1.valueOrDefault; 5337 5338var PI$1 = Math.PI; 5339var DOUBLE_PI$1 = PI$1 * 2; 5340var HALF_PI$1 = PI$1 / 2; 5341 5342core_defaults._set('doughnut', { 5343 animation: { 5344 // Boolean - Whether we animate the rotation of the Doughnut 5345 animateRotate: true, 5346 // Boolean - Whether we animate scaling the Doughnut from the centre 5347 animateScale: false 5348 }, 5349 hover: { 5350 mode: 'single' 5351 }, 5352 legendCallback: function(chart) { 5353 var list = document.createElement('ul'); 5354 var data = chart.data; 5355 var datasets = data.datasets; 5356 var labels = data.labels; 5357 var i, ilen, listItem, listItemSpan; 5358 5359 list.setAttribute('class', chart.id + '-legend'); 5360 if (datasets.length) { 5361 for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { 5362 listItem = list.appendChild(document.createElement('li')); 5363 listItemSpan = listItem.appendChild(document.createElement('span')); 5364 listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; 5365 if (labels[i]) { 5366 listItem.appendChild(document.createTextNode(labels[i])); 5367 } 5368 } 5369 } 5370 5371 return list.outerHTML; 5372 }, 5373 legend: { 5374 labels: { 5375 generateLabels: function(chart) { 5376 var data = chart.data; 5377 if (data.labels.length && data.datasets.length) { 5378 return data.labels.map(function(label, i) { 5379 var meta = chart.getDatasetMeta(0); 5380 var style = meta.controller.getStyle(i); 5381 5382 return { 5383 text: label, 5384 fillStyle: style.backgroundColor, 5385 strokeStyle: style.borderColor, 5386 lineWidth: style.borderWidth, 5387 hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, 5388 5389 // Extra data used for toggling the correct item 5390 index: i 5391 }; 5392 }); 5393 } 5394 return []; 5395 } 5396 }, 5397 5398 onClick: function(e, legendItem) { 5399 var index = legendItem.index; 5400 var chart = this.chart; 5401 var i, ilen, meta; 5402 5403 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { 5404 meta = chart.getDatasetMeta(i); 5405 // toggle visibility of index if exists 5406 if (meta.data[index]) { 5407 meta.data[index].hidden = !meta.data[index].hidden; 5408 } 5409 } 5410 5411 chart.update(); 5412 } 5413 }, 5414 5415 // The percentage of the chart that we cut out of the middle. 5416 cutoutPercentage: 50, 5417 5418 // The rotation of the chart, where the first data arc begins. 5419 rotation: -HALF_PI$1, 5420 5421 // The total circumference of the chart. 5422 circumference: DOUBLE_PI$1, 5423 5424 // Need to override these to give a nice default 5425 tooltips: { 5426 callbacks: { 5427 title: function() { 5428 return ''; 5429 }, 5430 label: function(tooltipItem, data) { 5431 var dataLabel = data.labels[tooltipItem.index]; 5432 var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; 5433 5434 if (helpers$1.isArray(dataLabel)) { 5435 // show value on first line of multiline label 5436 // need to clone because we are changing the value 5437 dataLabel = dataLabel.slice(); 5438 dataLabel[0] += value; 5439 } else { 5440 dataLabel += value; 5441 } 5442 5443 return dataLabel; 5444 } 5445 } 5446 } 5447}); 5448 5449var controller_doughnut = core_datasetController.extend({ 5450 5451 dataElementType: elements.Arc, 5452 5453 linkScales: helpers$1.noop, 5454 5455 /** 5456 * @private 5457 */ 5458 _dataElementOptions: [ 5459 'backgroundColor', 5460 'borderColor', 5461 'borderWidth', 5462 'borderAlign', 5463 'hoverBackgroundColor', 5464 'hoverBorderColor', 5465 'hoverBorderWidth', 5466 ], 5467 5468 // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly 5469 getRingIndex: function(datasetIndex) { 5470 var ringIndex = 0; 5471 5472 for (var j = 0; j < datasetIndex; ++j) { 5473 if (this.chart.isDatasetVisible(j)) { 5474 ++ringIndex; 5475 } 5476 } 5477 5478 return ringIndex; 5479 }, 5480 5481 update: function(reset) { 5482 var me = this; 5483 var chart = me.chart; 5484 var chartArea = chart.chartArea; 5485 var opts = chart.options; 5486 var ratioX = 1; 5487 var ratioY = 1; 5488 var offsetX = 0; 5489 var offsetY = 0; 5490 var meta = me.getMeta(); 5491 var arcs = meta.data; 5492 var cutout = opts.cutoutPercentage / 100 || 0; 5493 var circumference = opts.circumference; 5494 var chartWeight = me._getRingWeight(me.index); 5495 var maxWidth, maxHeight, i, ilen; 5496 5497 // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc 5498 if (circumference < DOUBLE_PI$1) { 5499 var startAngle = opts.rotation % DOUBLE_PI$1; 5500 startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0; 5501 var endAngle = startAngle + circumference; 5502 var startX = Math.cos(startAngle); 5503 var startY = Math.sin(startAngle); 5504 var endX = Math.cos(endAngle); 5505 var endY = Math.sin(endAngle); 5506 var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI$1; 5507 var contains90 = (startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1) || endAngle >= DOUBLE_PI$1 + HALF_PI$1; 5508 var contains180 = startAngle === -PI$1 || endAngle >= PI$1; 5509 var contains270 = (startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1) || endAngle >= PI$1 + HALF_PI$1; 5510 var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout); 5511 var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout); 5512 var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout); 5513 var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout); 5514 ratioX = (maxX - minX) / 2; 5515 ratioY = (maxY - minY) / 2; 5516 offsetX = -(maxX + minX) / 2; 5517 offsetY = -(maxY + minY) / 2; 5518 } 5519 5520 for (i = 0, ilen = arcs.length; i < ilen; ++i) { 5521 arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); 5522 } 5523 5524 chart.borderWidth = me.getMaxBorderWidth(); 5525 maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX; 5526 maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY; 5527 chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); 5528 chart.innerRadius = Math.max(chart.outerRadius * cutout, 0); 5529 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1); 5530 chart.offsetX = offsetX * chart.outerRadius; 5531 chart.offsetY = offsetY * chart.outerRadius; 5532 5533 meta.total = me.calculateTotal(); 5534 5535 me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index); 5536 me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0); 5537 5538 for (i = 0, ilen = arcs.length; i < ilen; ++i) { 5539 me.updateElement(arcs[i], i, reset); 5540 } 5541 }, 5542 5543 updateElement: function(arc, index, reset) { 5544 var me = this; 5545 var chart = me.chart; 5546 var chartArea = chart.chartArea; 5547 var opts = chart.options; 5548 var animationOpts = opts.animation; 5549 var centerX = (chartArea.left + chartArea.right) / 2; 5550 var centerY = (chartArea.top + chartArea.bottom) / 2; 5551 var startAngle = opts.rotation; // non reset case handled later 5552 var endAngle = opts.rotation; // non reset case handled later 5553 var dataset = me.getDataset(); 5554 var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1); 5555 var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; 5556 var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; 5557 var options = arc._options || {}; 5558 5559 helpers$1.extend(arc, { 5560 // Utility 5561 _datasetIndex: me.index, 5562 _index: index, 5563 5564 // Desired view properties 5565 _model: { 5566 backgroundColor: options.backgroundColor, 5567 borderColor: options.borderColor, 5568 borderWidth: options.borderWidth, 5569 borderAlign: options.borderAlign, 5570 x: centerX + chart.offsetX, 5571 y: centerY + chart.offsetY, 5572 startAngle: startAngle, 5573 endAngle: endAngle, 5574 circumference: circumference, 5575 outerRadius: outerRadius, 5576 innerRadius: innerRadius, 5577 label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) 5578 } 5579 }); 5580 5581 var model = arc._model; 5582 5583 // Set correct angles if not resetting 5584 if (!reset || !animationOpts.animateRotate) { 5585 if (index === 0) { 5586 model.startAngle = opts.rotation; 5587 } else { 5588 model.startAngle = me.getMeta().data[index - 1]._model.endAngle; 5589 } 5590 5591 model.endAngle = model.startAngle + model.circumference; 5592 } 5593 5594 arc.pivot(); 5595 }, 5596 5597 calculateTotal: function() { 5598 var dataset = this.getDataset(); 5599 var meta = this.getMeta(); 5600 var total = 0; 5601 var value; 5602 5603 helpers$1.each(meta.data, function(element, index) { 5604 value = dataset.data[index]; 5605 if (!isNaN(value) && !element.hidden) { 5606 total += Math.abs(value); 5607 } 5608 }); 5609 5610 /* if (total === 0) { 5611 total = NaN; 5612 }*/ 5613 5614 return total; 5615 }, 5616 5617 calculateCircumference: function(value) { 5618 var total = this.getMeta().total; 5619 if (total > 0 && !isNaN(value)) { 5620 return DOUBLE_PI$1 * (Math.abs(value) / total); 5621 } 5622 return 0; 5623 }, 5624 5625 // gets the max border or hover width to properly scale pie charts 5626 getMaxBorderWidth: function(arcs) { 5627 var me = this; 5628 var max = 0; 5629 var chart = me.chart; 5630 var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth; 5631 5632 if (!arcs) { 5633 // Find the outmost visible dataset 5634 for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { 5635 if (chart.isDatasetVisible(i)) { 5636 meta = chart.getDatasetMeta(i); 5637 arcs = meta.data; 5638 if (i !== me.index) { 5639 controller = meta.controller; 5640 } 5641 break; 5642 } 5643 } 5644 } 5645 5646 if (!arcs) { 5647 return 0; 5648 } 5649 5650 for (i = 0, ilen = arcs.length; i < ilen; ++i) { 5651 arc = arcs[i]; 5652 if (controller) { 5653 controller._configure(); 5654 options = controller._resolveDataElementOptions(arc, i); 5655 } else { 5656 options = arc._options; 5657 } 5658 if (options.borderAlign !== 'inner') { 5659 borderWidth = options.borderWidth; 5660 hoverWidth = options.hoverBorderWidth; 5661 5662 max = borderWidth > max ? borderWidth : max; 5663 max = hoverWidth > max ? hoverWidth : max; 5664 } 5665 } 5666 return max; 5667 }, 5668 5669 /** 5670 * @protected 5671 */ 5672 setHoverStyle: function(arc) { 5673 var model = arc._model; 5674 var options = arc._options; 5675 var getHoverColor = helpers$1.getHoverColor; 5676 5677 arc.$previousStyle = { 5678 backgroundColor: model.backgroundColor, 5679 borderColor: model.borderColor, 5680 borderWidth: model.borderWidth, 5681 }; 5682 5683 model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); 5684 model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor)); 5685 model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth); 5686 }, 5687 5688 /** 5689 * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly 5690 * @private 5691 */ 5692 _getRingWeightOffset: function(datasetIndex) { 5693 var ringWeightOffset = 0; 5694 5695 for (var i = 0; i < datasetIndex; ++i) { 5696 if (this.chart.isDatasetVisible(i)) { 5697 ringWeightOffset += this._getRingWeight(i); 5698 } 5699 } 5700 5701 return ringWeightOffset; 5702 }, 5703 5704 /** 5705 * @private 5706 */ 5707 _getRingWeight: function(dataSetIndex) { 5708 return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0); 5709 }, 5710 5711 /** 5712 * Returns the sum of all visibile data set weights. This value can be 0. 5713 * @private 5714 */ 5715 _getVisibleDatasetWeightTotal: function() { 5716 return this._getRingWeightOffset(this.chart.data.datasets.length); 5717 } 5718}); 5719 5720core_defaults._set('horizontalBar', { 5721 hover: { 5722 mode: 'index', 5723 axis: 'y' 5724 }, 5725 5726 scales: { 5727 xAxes: [{ 5728 type: 'linear', 5729 position: 'bottom' 5730 }], 5731 5732 yAxes: [{ 5733 type: 'category', 5734 position: 'left', 5735 offset: true, 5736 gridLines: { 5737 offsetGridLines: true 5738 } 5739 }] 5740 }, 5741 5742 elements: { 5743 rectangle: { 5744 borderSkipped: 'left' 5745 } 5746 }, 5747 5748 tooltips: { 5749 mode: 'index', 5750 axis: 'y' 5751 } 5752}); 5753 5754core_defaults._set('global', { 5755 datasets: { 5756 horizontalBar: { 5757 categoryPercentage: 0.8, 5758 barPercentage: 0.9 5759 } 5760 } 5761}); 5762 5763var controller_horizontalBar = controller_bar.extend({ 5764 /** 5765 * @private 5766 */ 5767 _getValueScaleId: function() { 5768 return this.getMeta().xAxisID; 5769 }, 5770 5771 /** 5772 * @private 5773 */ 5774 _getIndexScaleId: function() { 5775 return this.getMeta().yAxisID; 5776 } 5777}); 5778 5779var valueOrDefault$6 = helpers$1.valueOrDefault; 5780var resolve$2 = helpers$1.options.resolve; 5781var isPointInArea = helpers$1.canvas._isPointInArea; 5782 5783core_defaults._set('line', { 5784 showLines: true, 5785 spanGaps: false, 5786 5787 hover: { 5788 mode: 'label' 5789 }, 5790 5791 scales: { 5792 xAxes: [{ 5793 type: 'category', 5794 id: 'x-axis-0' 5795 }], 5796 yAxes: [{ 5797 type: 'linear', 5798 id: 'y-axis-0' 5799 }] 5800 } 5801}); 5802 5803function scaleClip(scale, halfBorderWidth) { 5804 var tickOpts = scale && scale.options.ticks || {}; 5805 var reverse = tickOpts.reverse; 5806 var min = tickOpts.min === undefined ? halfBorderWidth : 0; 5807 var max = tickOpts.max === undefined ? halfBorderWidth : 0; 5808 return { 5809 start: reverse ? max : min, 5810 end: reverse ? min : max 5811 }; 5812} 5813 5814function defaultClip(xScale, yScale, borderWidth) { 5815 var halfBorderWidth = borderWidth / 2; 5816 var x = scaleClip(xScale, halfBorderWidth); 5817 var y = scaleClip(yScale, halfBorderWidth); 5818 5819 return { 5820 top: y.end, 5821 right: x.end, 5822 bottom: y.start, 5823 left: x.start 5824 }; 5825} 5826 5827function toClip(value) { 5828 var t, r, b, l; 5829 5830 if (helpers$1.isObject(value)) { 5831 t = value.top; 5832 r = value.right; 5833 b = value.bottom; 5834 l = value.left; 5835 } else { 5836 t = r = b = l = value; 5837 } 5838 5839 return { 5840 top: t, 5841 right: r, 5842 bottom: b, 5843 left: l 5844 }; 5845} 5846 5847 5848var controller_line = core_datasetController.extend({ 5849 5850 datasetElementType: elements.Line, 5851 5852 dataElementType: elements.Point, 5853 5854 /** 5855 * @private 5856 */ 5857 _datasetElementOptions: [ 5858 'backgroundColor', 5859 'borderCapStyle', 5860 'borderColor', 5861 'borderDash', 5862 'borderDashOffset', 5863 'borderJoinStyle', 5864 'borderWidth', 5865 'cubicInterpolationMode', 5866 'fill' 5867 ], 5868 5869 /** 5870 * @private 5871 */ 5872 _dataElementOptions: { 5873 backgroundColor: 'pointBackgroundColor', 5874 borderColor: 'pointBorderColor', 5875 borderWidth: 'pointBorderWidth', 5876 hitRadius: 'pointHitRadius', 5877 hoverBackgroundColor: 'pointHoverBackgroundColor', 5878 hoverBorderColor: 'pointHoverBorderColor', 5879 hoverBorderWidth: 'pointHoverBorderWidth', 5880 hoverRadius: 'pointHoverRadius', 5881 pointStyle: 'pointStyle', 5882 radius: 'pointRadius', 5883 rotation: 'pointRotation' 5884 }, 5885 5886 update: function(reset) { 5887 var me = this; 5888 var meta = me.getMeta(); 5889 var line = meta.dataset; 5890 var points = meta.data || []; 5891 var options = me.chart.options; 5892 var config = me._config; 5893 var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines); 5894 var i, ilen; 5895 5896 me._xScale = me.getScaleForId(meta.xAxisID); 5897 me._yScale = me.getScaleForId(meta.yAxisID); 5898 5899 // Update Line 5900 if (showLine) { 5901 // Compatibility: If the properties are defined with only the old name, use those values 5902 if (config.tension !== undefined && config.lineTension === undefined) { 5903 config.lineTension = config.tension; 5904 } 5905 5906 // Utility 5907 line._scale = me._yScale; 5908 line._datasetIndex = me.index; 5909 // Data 5910 line._children = points; 5911 // Model 5912 line._model = me._resolveDatasetElementOptions(line); 5913 5914 line.pivot(); 5915 } 5916 5917 // Update Points 5918 for (i = 0, ilen = points.length; i < ilen; ++i) { 5919 me.updateElement(points[i], i, reset); 5920 } 5921 5922 if (showLine && line._model.tension !== 0) { 5923 me.updateBezierControlPoints(); 5924 } 5925 5926 // Now pivot the point for animation 5927 for (i = 0, ilen = points.length; i < ilen; ++i) { 5928 points[i].pivot(); 5929 } 5930 }, 5931 5932 updateElement: function(point, index, reset) { 5933 var me = this; 5934 var meta = me.getMeta(); 5935 var custom = point.custom || {}; 5936 var dataset = me.getDataset(); 5937 var datasetIndex = me.index; 5938 var value = dataset.data[index]; 5939 var xScale = me._xScale; 5940 var yScale = me._yScale; 5941 var lineModel = meta.dataset._model; 5942 var x, y; 5943 5944 var options = me._resolveDataElementOptions(point, index); 5945 5946 x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); 5947 y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); 5948 5949 // Utility 5950 point._xScale = xScale; 5951 point._yScale = yScale; 5952 point._options = options; 5953 point._datasetIndex = datasetIndex; 5954 point._index = index; 5955 5956 // Desired view properties 5957 point._model = { 5958 x: x, 5959 y: y, 5960 skip: custom.skip || isNaN(x) || isNaN(y), 5961 // Appearance 5962 radius: options.radius, 5963 pointStyle: options.pointStyle, 5964 rotation: options.rotation, 5965 backgroundColor: options.backgroundColor, 5966 borderColor: options.borderColor, 5967 borderWidth: options.borderWidth, 5968 tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0), 5969 steppedLine: lineModel ? lineModel.steppedLine : false, 5970 // Tooltip 5971 hitRadius: options.hitRadius 5972 }; 5973 }, 5974 5975 /** 5976 * @private 5977 */ 5978 _resolveDatasetElementOptions: function(element) { 5979 var me = this; 5980 var config = me._config; 5981 var custom = element.custom || {}; 5982 var options = me.chart.options; 5983 var lineOptions = options.elements.line; 5984 var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); 5985 5986 // The default behavior of lines is to break at null values, according 5987 // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 5988 // This option gives lines the ability to span gaps 5989 values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps); 5990 values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension); 5991 values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]); 5992 values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth))); 5993 5994 return values; 5995 }, 5996 5997 calculatePointY: function(value, index, datasetIndex) { 5998 var me = this; 5999 var chart = me.chart; 6000 var yScale = me._yScale; 6001 var sumPos = 0; 6002 var sumNeg = 0; 6003 var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen; 6004 6005 if (yScale.options.stacked) { 6006 rightValue = +yScale.getRightValue(value); 6007 metasets = chart._getSortedVisibleDatasetMetas(); 6008 ilen = metasets.length; 6009 6010 for (i = 0; i < ilen; ++i) { 6011 dsMeta = metasets[i]; 6012 if (dsMeta.index === datasetIndex) { 6013 break; 6014 } 6015 6016 ds = chart.data.datasets[dsMeta.index]; 6017 if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) { 6018 stackedRightValue = +yScale.getRightValue(ds.data[index]); 6019 if (stackedRightValue < 0) { 6020 sumNeg += stackedRightValue || 0; 6021 } else { 6022 sumPos += stackedRightValue || 0; 6023 } 6024 } 6025 } 6026 6027 if (rightValue < 0) { 6028 return yScale.getPixelForValue(sumNeg + rightValue); 6029 } 6030 return yScale.getPixelForValue(sumPos + rightValue); 6031 } 6032 return yScale.getPixelForValue(value); 6033 }, 6034 6035 updateBezierControlPoints: function() { 6036 var me = this; 6037 var chart = me.chart; 6038 var meta = me.getMeta(); 6039 var lineModel = meta.dataset._model; 6040 var area = chart.chartArea; 6041 var points = meta.data || []; 6042 var i, ilen, model, controlPoints; 6043 6044 // Only consider points that are drawn in case the spanGaps option is used 6045 if (lineModel.spanGaps) { 6046 points = points.filter(function(pt) { 6047 return !pt._model.skip; 6048 }); 6049 } 6050 6051 function capControlPoint(pt, min, max) { 6052 return Math.max(Math.min(pt, max), min); 6053 } 6054 6055 if (lineModel.cubicInterpolationMode === 'monotone') { 6056 helpers$1.splineCurveMonotone(points); 6057 } else { 6058 for (i = 0, ilen = points.length; i < ilen; ++i) { 6059 model = points[i]._model; 6060 controlPoints = helpers$1.splineCurve( 6061 helpers$1.previousItem(points, i)._model, 6062 model, 6063 helpers$1.nextItem(points, i)._model, 6064 lineModel.tension 6065 ); 6066 model.controlPointPreviousX = controlPoints.previous.x; 6067 model.controlPointPreviousY = controlPoints.previous.y; 6068 model.controlPointNextX = controlPoints.next.x; 6069 model.controlPointNextY = controlPoints.next.y; 6070 } 6071 } 6072 6073 if (chart.options.elements.line.capBezierPoints) { 6074 for (i = 0, ilen = points.length; i < ilen; ++i) { 6075 model = points[i]._model; 6076 if (isPointInArea(model, area)) { 6077 if (i > 0 && isPointInArea(points[i - 1]._model, area)) { 6078 model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); 6079 model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); 6080 } 6081 if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) { 6082 model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); 6083 model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); 6084 } 6085 } 6086 } 6087 } 6088 }, 6089 6090 draw: function() { 6091 var me = this; 6092 var chart = me.chart; 6093 var meta = me.getMeta(); 6094 var points = meta.data || []; 6095 var area = chart.chartArea; 6096 var canvas = chart.canvas; 6097 var i = 0; 6098 var ilen = points.length; 6099 var clip; 6100 6101 if (me._showLine) { 6102 clip = meta.dataset._model.clip; 6103 6104 helpers$1.canvas.clipArea(chart.ctx, { 6105 left: clip.left === false ? 0 : area.left - clip.left, 6106 right: clip.right === false ? canvas.width : area.right + clip.right, 6107 top: clip.top === false ? 0 : area.top - clip.top, 6108 bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom 6109 }); 6110 6111 meta.dataset.draw(); 6112 6113 helpers$1.canvas.unclipArea(chart.ctx); 6114 } 6115 6116 // Draw the points 6117 for (; i < ilen; ++i) { 6118 points[i].draw(area); 6119 } 6120 }, 6121 6122 /** 6123 * @protected 6124 */ 6125 setHoverStyle: function(point) { 6126 var model = point._model; 6127 var options = point._options; 6128 var getHoverColor = helpers$1.getHoverColor; 6129 6130 point.$previousStyle = { 6131 backgroundColor: model.backgroundColor, 6132 borderColor: model.borderColor, 6133 borderWidth: model.borderWidth, 6134 radius: model.radius 6135 }; 6136 6137 model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); 6138 model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor)); 6139 model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth); 6140 model.radius = valueOrDefault$6(options.hoverRadius, options.radius); 6141 }, 6142}); 6143 6144var resolve$3 = helpers$1.options.resolve; 6145 6146core_defaults._set('polarArea', { 6147 scale: { 6148 type: 'radialLinear', 6149 angleLines: { 6150 display: false 6151 }, 6152 gridLines: { 6153 circular: true 6154 }, 6155 pointLabels: { 6156 display: false 6157 }, 6158 ticks: { 6159 beginAtZero: true 6160 } 6161 }, 6162 6163 // Boolean - Whether to animate the rotation of the chart 6164 animation: { 6165 animateRotate: true, 6166 animateScale: true 6167 }, 6168 6169 startAngle: -0.5 * Math.PI, 6170 legendCallback: function(chart) { 6171 var list = document.createElement('ul'); 6172 var data = chart.data; 6173 var datasets = data.datasets; 6174 var labels = data.labels; 6175 var i, ilen, listItem, listItemSpan; 6176 6177 list.setAttribute('class', chart.id + '-legend'); 6178 if (datasets.length) { 6179 for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { 6180 listItem = list.appendChild(document.createElement('li')); 6181 listItemSpan = listItem.appendChild(document.createElement('span')); 6182 listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; 6183 if (labels[i]) { 6184 listItem.appendChild(document.createTextNode(labels[i])); 6185 } 6186 } 6187 } 6188 6189 return list.outerHTML; 6190 }, 6191 legend: { 6192 labels: { 6193 generateLabels: function(chart) { 6194 var data = chart.data; 6195 if (data.labels.length && data.datasets.length) { 6196 return data.labels.map(function(label, i) { 6197 var meta = chart.getDatasetMeta(0); 6198 var style = meta.controller.getStyle(i); 6199 6200 return { 6201 text: label, 6202 fillStyle: style.backgroundColor, 6203 strokeStyle: style.borderColor, 6204 lineWidth: style.borderWidth, 6205 hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, 6206 6207 // Extra data used for toggling the correct item 6208 index: i 6209 }; 6210 }); 6211 } 6212 return []; 6213 } 6214 }, 6215 6216 onClick: function(e, legendItem) { 6217 var index = legendItem.index; 6218 var chart = this.chart; 6219 var i, ilen, meta; 6220 6221 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { 6222 meta = chart.getDatasetMeta(i); 6223 meta.data[index].hidden = !meta.data[index].hidden; 6224 } 6225 6226 chart.update(); 6227 } 6228 }, 6229 6230 // Need to override these to give a nice default 6231 tooltips: { 6232 callbacks: { 6233 title: function() { 6234 return ''; 6235 }, 6236 label: function(item, data) { 6237 return data.labels[item.index] + ': ' + item.yLabel; 6238 } 6239 } 6240 } 6241}); 6242 6243var controller_polarArea = core_datasetController.extend({ 6244 6245 dataElementType: elements.Arc, 6246 6247 linkScales: helpers$1.noop, 6248 6249 /** 6250 * @private 6251 */ 6252 _dataElementOptions: [ 6253 'backgroundColor', 6254 'borderColor', 6255 'borderWidth', 6256 'borderAlign', 6257 'hoverBackgroundColor', 6258 'hoverBorderColor', 6259 'hoverBorderWidth', 6260 ], 6261 6262 /** 6263 * @private 6264 */ 6265 _getIndexScaleId: function() { 6266 return this.chart.scale.id; 6267 }, 6268 6269 /** 6270 * @private 6271 */ 6272 _getValueScaleId: function() { 6273 return this.chart.scale.id; 6274 }, 6275 6276 update: function(reset) { 6277 var me = this; 6278 var dataset = me.getDataset(); 6279 var meta = me.getMeta(); 6280 var start = me.chart.options.startAngle || 0; 6281 var starts = me._starts = []; 6282 var angles = me._angles = []; 6283 var arcs = meta.data; 6284 var i, ilen, angle; 6285 6286 me._updateRadius(); 6287 6288 meta.count = me.countVisibleElements(); 6289 6290 for (i = 0, ilen = dataset.data.length; i < ilen; i++) { 6291 starts[i] = start; 6292 angle = me._computeAngle(i); 6293 angles[i] = angle; 6294 start += angle; 6295 } 6296 6297 for (i = 0, ilen = arcs.length; i < ilen; ++i) { 6298 arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); 6299 me.updateElement(arcs[i], i, reset); 6300 } 6301 }, 6302 6303 /** 6304 * @private 6305 */ 6306 _updateRadius: function() { 6307 var me = this; 6308 var chart = me.chart; 6309 var chartArea = chart.chartArea; 6310 var opts = chart.options; 6311 var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); 6312 6313 chart.outerRadius = Math.max(minSize / 2, 0); 6314 chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); 6315 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); 6316 6317 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); 6318 me.innerRadius = me.outerRadius - chart.radiusLength; 6319 }, 6320 6321 updateElement: function(arc, index, reset) { 6322 var me = this; 6323 var chart = me.chart; 6324 var dataset = me.getDataset(); 6325 var opts = chart.options; 6326 var animationOpts = opts.animation; 6327 var scale = chart.scale; 6328 var labels = chart.data.labels; 6329 6330 var centerX = scale.xCenter; 6331 var centerY = scale.yCenter; 6332 6333 // var negHalfPI = -0.5 * Math.PI; 6334 var datasetStartAngle = opts.startAngle; 6335 var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); 6336 var startAngle = me._starts[index]; 6337 var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); 6338 6339 var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); 6340 var options = arc._options || {}; 6341 6342 helpers$1.extend(arc, { 6343 // Utility 6344 _datasetIndex: me.index, 6345 _index: index, 6346 _scale: scale, 6347 6348 // Desired view properties 6349 _model: { 6350 backgroundColor: options.backgroundColor, 6351 borderColor: options.borderColor, 6352 borderWidth: options.borderWidth, 6353 borderAlign: options.borderAlign, 6354 x: centerX, 6355 y: centerY, 6356 innerRadius: 0, 6357 outerRadius: reset ? resetRadius : distance, 6358 startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, 6359 endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, 6360 label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index]) 6361 } 6362 }); 6363 6364 arc.pivot(); 6365 }, 6366 6367 countVisibleElements: function() { 6368 var dataset = this.getDataset(); 6369 var meta = this.getMeta(); 6370 var count = 0; 6371 6372 helpers$1.each(meta.data, function(element, index) { 6373 if (!isNaN(dataset.data[index]) && !element.hidden) { 6374 count++; 6375 } 6376 }); 6377 6378 return count; 6379 }, 6380 6381 /** 6382 * @protected 6383 */ 6384 setHoverStyle: function(arc) { 6385 var model = arc._model; 6386 var options = arc._options; 6387 var getHoverColor = helpers$1.getHoverColor; 6388 var valueOrDefault = helpers$1.valueOrDefault; 6389 6390 arc.$previousStyle = { 6391 backgroundColor: model.backgroundColor, 6392 borderColor: model.borderColor, 6393 borderWidth: model.borderWidth, 6394 }; 6395 6396 model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); 6397 model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor)); 6398 model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth); 6399 }, 6400 6401 /** 6402 * @private 6403 */ 6404 _computeAngle: function(index) { 6405 var me = this; 6406 var count = this.getMeta().count; 6407 var dataset = me.getDataset(); 6408 var meta = me.getMeta(); 6409 6410 if (isNaN(dataset.data[index]) || meta.data[index].hidden) { 6411 return 0; 6412 } 6413 6414 // Scriptable options 6415 var context = { 6416 chart: me.chart, 6417 dataIndex: index, 6418 dataset: dataset, 6419 datasetIndex: me.index 6420 }; 6421 6422 return resolve$3([ 6423 me.chart.options.elements.arc.angle, 6424 (2 * Math.PI) / count 6425 ], context, index); 6426 } 6427}); 6428 6429core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut)); 6430core_defaults._set('pie', { 6431 cutoutPercentage: 0 6432}); 6433 6434// Pie charts are Doughnut chart with different defaults 6435var controller_pie = controller_doughnut; 6436 6437var valueOrDefault$7 = helpers$1.valueOrDefault; 6438 6439core_defaults._set('radar', { 6440 spanGaps: false, 6441 scale: { 6442 type: 'radialLinear' 6443 }, 6444 elements: { 6445 line: { 6446 fill: 'start', 6447 tension: 0 // no bezier in radar 6448 } 6449 } 6450}); 6451 6452var controller_radar = core_datasetController.extend({ 6453 datasetElementType: elements.Line, 6454 6455 dataElementType: elements.Point, 6456 6457 linkScales: helpers$1.noop, 6458 6459 /** 6460 * @private 6461 */ 6462 _datasetElementOptions: [ 6463 'backgroundColor', 6464 'borderWidth', 6465 'borderColor', 6466 'borderCapStyle', 6467 'borderDash', 6468 'borderDashOffset', 6469 'borderJoinStyle', 6470 'fill' 6471 ], 6472 6473 /** 6474 * @private 6475 */ 6476 _dataElementOptions: { 6477 backgroundColor: 'pointBackgroundColor', 6478 borderColor: 'pointBorderColor', 6479 borderWidth: 'pointBorderWidth', 6480 hitRadius: 'pointHitRadius', 6481 hoverBackgroundColor: 'pointHoverBackgroundColor', 6482 hoverBorderColor: 'pointHoverBorderColor', 6483 hoverBorderWidth: 'pointHoverBorderWidth', 6484 hoverRadius: 'pointHoverRadius', 6485 pointStyle: 'pointStyle', 6486 radius: 'pointRadius', 6487 rotation: 'pointRotation' 6488 }, 6489 6490 /** 6491 * @private 6492 */ 6493 _getIndexScaleId: function() { 6494 return this.chart.scale.id; 6495 }, 6496 6497 /** 6498 * @private 6499 */ 6500 _getValueScaleId: function() { 6501 return this.chart.scale.id; 6502 }, 6503 6504 update: function(reset) { 6505 var me = this; 6506 var meta = me.getMeta(); 6507 var line = meta.dataset; 6508 var points = meta.data || []; 6509 var scale = me.chart.scale; 6510 var config = me._config; 6511 var i, ilen; 6512 6513 // Compatibility: If the properties are defined with only the old name, use those values 6514 if (config.tension !== undefined && config.lineTension === undefined) { 6515 config.lineTension = config.tension; 6516 } 6517 6518 // Utility 6519 line._scale = scale; 6520 line._datasetIndex = me.index; 6521 // Data 6522 line._children = points; 6523 line._loop = true; 6524 // Model 6525 line._model = me._resolveDatasetElementOptions(line); 6526 6527 line.pivot(); 6528 6529 // Update Points 6530 for (i = 0, ilen = points.length; i < ilen; ++i) { 6531 me.updateElement(points[i], i, reset); 6532 } 6533 6534 // Update bezier control points 6535 me.updateBezierControlPoints(); 6536 6537 // Now pivot the point for animation 6538 for (i = 0, ilen = points.length; i < ilen; ++i) { 6539 points[i].pivot(); 6540 } 6541 }, 6542 6543 updateElement: function(point, index, reset) { 6544 var me = this; 6545 var custom = point.custom || {}; 6546 var dataset = me.getDataset(); 6547 var scale = me.chart.scale; 6548 var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); 6549 var options = me._resolveDataElementOptions(point, index); 6550 var lineModel = me.getMeta().dataset._model; 6551 var x = reset ? scale.xCenter : pointPosition.x; 6552 var y = reset ? scale.yCenter : pointPosition.y; 6553 6554 // Utility 6555 point._scale = scale; 6556 point._options = options; 6557 point._datasetIndex = me.index; 6558 point._index = index; 6559 6560 // Desired view properties 6561 point._model = { 6562 x: x, // value not used in dataset scale, but we want a consistent API between scales 6563 y: y, 6564 skip: custom.skip || isNaN(x) || isNaN(y), 6565 // Appearance 6566 radius: options.radius, 6567 pointStyle: options.pointStyle, 6568 rotation: options.rotation, 6569 backgroundColor: options.backgroundColor, 6570 borderColor: options.borderColor, 6571 borderWidth: options.borderWidth, 6572 tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0), 6573 6574 // Tooltip 6575 hitRadius: options.hitRadius 6576 }; 6577 }, 6578 6579 /** 6580 * @private 6581 */ 6582 _resolveDatasetElementOptions: function() { 6583 var me = this; 6584 var config = me._config; 6585 var options = me.chart.options; 6586 var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); 6587 6588 values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps); 6589 values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension); 6590 6591 return values; 6592 }, 6593 6594 updateBezierControlPoints: function() { 6595 var me = this; 6596 var meta = me.getMeta(); 6597 var area = me.chart.chartArea; 6598 var points = meta.data || []; 6599 var i, ilen, model, controlPoints; 6600 6601 // Only consider points that are drawn in case the spanGaps option is used 6602 if (meta.dataset._model.spanGaps) { 6603 points = points.filter(function(pt) { 6604 return !pt._model.skip; 6605 }); 6606 } 6607 6608 function capControlPoint(pt, min, max) { 6609 return Math.max(Math.min(pt, max), min); 6610 } 6611 6612 for (i = 0, ilen = points.length; i < ilen; ++i) { 6613 model = points[i]._model; 6614 controlPoints = helpers$1.splineCurve( 6615 helpers$1.previousItem(points, i, true)._model, 6616 model, 6617 helpers$1.nextItem(points, i, true)._model, 6618 model.tension 6619 ); 6620 6621 // Prevent the bezier going outside of the bounds of the graph 6622 model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); 6623 model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); 6624 model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); 6625 model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); 6626 } 6627 }, 6628 6629 setHoverStyle: function(point) { 6630 var model = point._model; 6631 var options = point._options; 6632 var getHoverColor = helpers$1.getHoverColor; 6633 6634 point.$previousStyle = { 6635 backgroundColor: model.backgroundColor, 6636 borderColor: model.borderColor, 6637 borderWidth: model.borderWidth, 6638 radius: model.radius 6639 }; 6640 6641 model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); 6642 model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor)); 6643 model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth); 6644 model.radius = valueOrDefault$7(options.hoverRadius, options.radius); 6645 } 6646}); 6647 6648core_defaults._set('scatter', { 6649 hover: { 6650 mode: 'single' 6651 }, 6652 6653 scales: { 6654 xAxes: [{ 6655 id: 'x-axis-1', // need an ID so datasets can reference the scale 6656 type: 'linear', // scatter should not use a category axis 6657 position: 'bottom' 6658 }], 6659 yAxes: [{ 6660 id: 'y-axis-1', 6661 type: 'linear', 6662 position: 'left' 6663 }] 6664 }, 6665 6666 tooltips: { 6667 callbacks: { 6668 title: function() { 6669 return ''; // doesn't make sense for scatter since data are formatted as a point 6670 }, 6671 label: function(item) { 6672 return '(' + item.xLabel + ', ' + item.yLabel + ')'; 6673 } 6674 } 6675 } 6676}); 6677 6678core_defaults._set('global', { 6679 datasets: { 6680 scatter: { 6681 showLine: false 6682 } 6683 } 6684}); 6685 6686// Scatter charts use line controllers 6687var controller_scatter = controller_line; 6688 6689// NOTE export a map in which the key represents the controller type, not 6690// the class, and so must be CamelCase in order to be correctly retrieved 6691// by the controller in core.controller.js (`controllers[meta.type]`). 6692 6693var controllers = { 6694 bar: controller_bar, 6695 bubble: controller_bubble, 6696 doughnut: controller_doughnut, 6697 horizontalBar: controller_horizontalBar, 6698 line: controller_line, 6699 polarArea: controller_polarArea, 6700 pie: controller_pie, 6701 radar: controller_radar, 6702 scatter: controller_scatter 6703}; 6704 6705/** 6706 * Helper function to get relative position for an event 6707 * @param {Event|IEvent} event - The event to get the position for 6708 * @param {Chart} chart - The chart 6709 * @returns {object} the event position 6710 */ 6711function getRelativePosition(e, chart) { 6712 if (e.native) { 6713 return { 6714 x: e.x, 6715 y: e.y 6716 }; 6717 } 6718 6719 return helpers$1.getRelativePosition(e, chart); 6720} 6721 6722/** 6723 * Helper function to traverse all of the visible elements in the chart 6724 * @param {Chart} chart - the chart 6725 * @param {function} handler - the callback to execute for each visible item 6726 */ 6727function parseVisibleItems(chart, handler) { 6728 var metasets = chart._getSortedVisibleDatasetMetas(); 6729 var metadata, i, j, ilen, jlen, element; 6730 6731 for (i = 0, ilen = metasets.length; i < ilen; ++i) { 6732 metadata = metasets[i].data; 6733 for (j = 0, jlen = metadata.length; j < jlen; ++j) { 6734 element = metadata[j]; 6735 if (!element._view.skip) { 6736 handler(element); 6737 } 6738 } 6739 } 6740} 6741 6742/** 6743 * Helper function to get the items that intersect the event position 6744 * @param {ChartElement[]} items - elements to filter 6745 * @param {object} position - the point to be nearest to 6746 * @return {ChartElement[]} the nearest items 6747 */ 6748function getIntersectItems(chart, position) { 6749 var elements = []; 6750 6751 parseVisibleItems(chart, function(element) { 6752 if (element.inRange(position.x, position.y)) { 6753 elements.push(element); 6754 } 6755 }); 6756 6757 return elements; 6758} 6759 6760/** 6761 * Helper function to get the items nearest to the event position considering all visible items in teh chart 6762 * @param {Chart} chart - the chart to look at elements from 6763 * @param {object} position - the point to be nearest to 6764 * @param {boolean} intersect - if true, only consider items that intersect the position 6765 * @param {function} distanceMetric - function to provide the distance between points 6766 * @return {ChartElement[]} the nearest items 6767 */ 6768function getNearestItems(chart, position, intersect, distanceMetric) { 6769 var minDistance = Number.POSITIVE_INFINITY; 6770 var nearestItems = []; 6771 6772 parseVisibleItems(chart, function(element) { 6773 if (intersect && !element.inRange(position.x, position.y)) { 6774 return; 6775 } 6776 6777 var center = element.getCenterPoint(); 6778 var distance = distanceMetric(position, center); 6779 if (distance < minDistance) { 6780 nearestItems = [element]; 6781 minDistance = distance; 6782 } else if (distance === minDistance) { 6783 // Can have multiple items at the same distance in which case we sort by size 6784 nearestItems.push(element); 6785 } 6786 }); 6787 6788 return nearestItems; 6789} 6790 6791/** 6792 * Get a distance metric function for two points based on the 6793 * axis mode setting 6794 * @param {string} axis - the axis mode. x|y|xy 6795 */ 6796function getDistanceMetricForAxis(axis) { 6797 var useX = axis.indexOf('x') !== -1; 6798 var useY = axis.indexOf('y') !== -1; 6799 6800 return function(pt1, pt2) { 6801 var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; 6802 var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; 6803 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); 6804 }; 6805} 6806 6807function indexMode(chart, e, options) { 6808 var position = getRelativePosition(e, chart); 6809 // Default axis for index mode is 'x' to match old behaviour 6810 options.axis = options.axis || 'x'; 6811 var distanceMetric = getDistanceMetricForAxis(options.axis); 6812 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); 6813 var elements = []; 6814 6815 if (!items.length) { 6816 return []; 6817 } 6818 6819 chart._getSortedVisibleDatasetMetas().forEach(function(meta) { 6820 var element = meta.data[items[0]._index]; 6821 6822 // don't count items that are skipped (null data) 6823 if (element && !element._view.skip) { 6824 elements.push(element); 6825 } 6826 }); 6827 6828 return elements; 6829} 6830 6831/** 6832 * @interface IInteractionOptions 6833 */ 6834/** 6835 * If true, only consider items that intersect the point 6836 * @name IInterfaceOptions#boolean 6837 * @type Boolean 6838 */ 6839 6840/** 6841 * Contains interaction related functions 6842 * @namespace Chart.Interaction 6843 */ 6844var core_interaction = { 6845 // Helper function for different modes 6846 modes: { 6847 single: function(chart, e) { 6848 var position = getRelativePosition(e, chart); 6849 var elements = []; 6850 6851 parseVisibleItems(chart, function(element) { 6852 if (element.inRange(position.x, position.y)) { 6853 elements.push(element); 6854 return elements; 6855 } 6856 }); 6857 6858 return elements.slice(0, 1); 6859 }, 6860 6861 /** 6862 * @function Chart.Interaction.modes.label 6863 * @deprecated since version 2.4.0 6864 * @todo remove at version 3 6865 * @private 6866 */ 6867 label: indexMode, 6868 6869 /** 6870 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something 6871 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item 6872 * @function Chart.Interaction.modes.index 6873 * @since v2.4.0 6874 * @param {Chart} chart - the chart we are returning items from 6875 * @param {Event} e - the event we are find things at 6876 * @param {IInteractionOptions} options - options to use during interaction 6877 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6878 */ 6879 index: indexMode, 6880 6881 /** 6882 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something 6883 * If the options.intersect is false, we find the nearest item and return the items in that dataset 6884 * @function Chart.Interaction.modes.dataset 6885 * @param {Chart} chart - the chart we are returning items from 6886 * @param {Event} e - the event we are find things at 6887 * @param {IInteractionOptions} options - options to use during interaction 6888 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6889 */ 6890 dataset: function(chart, e, options) { 6891 var position = getRelativePosition(e, chart); 6892 options.axis = options.axis || 'xy'; 6893 var distanceMetric = getDistanceMetricForAxis(options.axis); 6894 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); 6895 6896 if (items.length > 0) { 6897 items = chart.getDatasetMeta(items[0]._datasetIndex).data; 6898 } 6899 6900 return items; 6901 }, 6902 6903 /** 6904 * @function Chart.Interaction.modes.x-axis 6905 * @deprecated since version 2.4.0. Use index mode and intersect == true 6906 * @todo remove at version 3 6907 * @private 6908 */ 6909 'x-axis': function(chart, e) { 6910 return indexMode(chart, e, {intersect: false}); 6911 }, 6912 6913 /** 6914 * Point mode returns all elements that hit test based on the event position 6915 * of the event 6916 * @function Chart.Interaction.modes.intersect 6917 * @param {Chart} chart - the chart we are returning items from 6918 * @param {Event} e - the event we are find things at 6919 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6920 */ 6921 point: function(chart, e) { 6922 var position = getRelativePosition(e, chart); 6923 return getIntersectItems(chart, position); 6924 }, 6925 6926 /** 6927 * nearest mode returns the element closest to the point 6928 * @function Chart.Interaction.modes.intersect 6929 * @param {Chart} chart - the chart we are returning items from 6930 * @param {Event} e - the event we are find things at 6931 * @param {IInteractionOptions} options - options to use 6932 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6933 */ 6934 nearest: function(chart, e, options) { 6935 var position = getRelativePosition(e, chart); 6936 options.axis = options.axis || 'xy'; 6937 var distanceMetric = getDistanceMetricForAxis(options.axis); 6938 return getNearestItems(chart, position, options.intersect, distanceMetric); 6939 }, 6940 6941 /** 6942 * x mode returns the elements that hit-test at the current x coordinate 6943 * @function Chart.Interaction.modes.x 6944 * @param {Chart} chart - the chart we are returning items from 6945 * @param {Event} e - the event we are find things at 6946 * @param {IInteractionOptions} options - options to use 6947 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6948 */ 6949 x: function(chart, e, options) { 6950 var position = getRelativePosition(e, chart); 6951 var items = []; 6952 var intersectsItem = false; 6953 6954 parseVisibleItems(chart, function(element) { 6955 if (element.inXRange(position.x)) { 6956 items.push(element); 6957 } 6958 6959 if (element.inRange(position.x, position.y)) { 6960 intersectsItem = true; 6961 } 6962 }); 6963 6964 // If we want to trigger on an intersect and we don't have any items 6965 // that intersect the position, return nothing 6966 if (options.intersect && !intersectsItem) { 6967 items = []; 6968 } 6969 return items; 6970 }, 6971 6972 /** 6973 * y mode returns the elements that hit-test at the current y coordinate 6974 * @function Chart.Interaction.modes.y 6975 * @param {Chart} chart - the chart we are returning items from 6976 * @param {Event} e - the event we are find things at 6977 * @param {IInteractionOptions} options - options to use 6978 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6979 */ 6980 y: function(chart, e, options) { 6981 var position = getRelativePosition(e, chart); 6982 var items = []; 6983 var intersectsItem = false; 6984 6985 parseVisibleItems(chart, function(element) { 6986 if (element.inYRange(position.y)) { 6987 items.push(element); 6988 } 6989 6990 if (element.inRange(position.x, position.y)) { 6991 intersectsItem = true; 6992 } 6993 }); 6994 6995 // If we want to trigger on an intersect and we don't have any items 6996 // that intersect the position, return nothing 6997 if (options.intersect && !intersectsItem) { 6998 items = []; 6999 } 7000 return items; 7001 } 7002 } 7003}; 7004 7005var extend = helpers$1.extend; 7006 7007function filterByPosition(array, position) { 7008 return helpers$1.where(array, function(v) { 7009 return v.pos === position; 7010 }); 7011} 7012 7013function sortByWeight(array, reverse) { 7014 return array.sort(function(a, b) { 7015 var v0 = reverse ? b : a; 7016 var v1 = reverse ? a : b; 7017 return v0.weight === v1.weight ? 7018 v0.index - v1.index : 7019 v0.weight - v1.weight; 7020 }); 7021} 7022 7023function wrapBoxes(boxes) { 7024 var layoutBoxes = []; 7025 var i, ilen, box; 7026 7027 for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { 7028 box = boxes[i]; 7029 layoutBoxes.push({ 7030 index: i, 7031 box: box, 7032 pos: box.position, 7033 horizontal: box.isHorizontal(), 7034 weight: box.weight 7035 }); 7036 } 7037 return layoutBoxes; 7038} 7039 7040function setLayoutDims(layouts, params) { 7041 var i, ilen, layout; 7042 for (i = 0, ilen = layouts.length; i < ilen; ++i) { 7043 layout = layouts[i]; 7044 // store width used instead of chartArea.w in fitBoxes 7045 layout.width = layout.horizontal 7046 ? layout.box.fullWidth && params.availableWidth 7047 : params.vBoxMaxWidth; 7048 // store height used instead of chartArea.h in fitBoxes 7049 layout.height = layout.horizontal && params.hBoxMaxHeight; 7050 } 7051} 7052 7053function buildLayoutBoxes(boxes) { 7054 var layoutBoxes = wrapBoxes(boxes); 7055 var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); 7056 var right = sortByWeight(filterByPosition(layoutBoxes, 'right')); 7057 var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); 7058 var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); 7059 7060 return { 7061 leftAndTop: left.concat(top), 7062 rightAndBottom: right.concat(bottom), 7063 chartArea: filterByPosition(layoutBoxes, 'chartArea'), 7064 vertical: left.concat(right), 7065 horizontal: top.concat(bottom) 7066 }; 7067} 7068 7069function getCombinedMax(maxPadding, chartArea, a, b) { 7070 return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); 7071} 7072 7073function updateDims(chartArea, params, layout) { 7074 var box = layout.box; 7075 var maxPadding = chartArea.maxPadding; 7076 var newWidth, newHeight; 7077 7078 if (layout.size) { 7079 // this layout was already counted for, lets first reduce old size 7080 chartArea[layout.pos] -= layout.size; 7081 } 7082 layout.size = layout.horizontal ? box.height : box.width; 7083 chartArea[layout.pos] += layout.size; 7084 7085 if (box.getPadding) { 7086 var boxPadding = box.getPadding(); 7087 maxPadding.top = Math.max(maxPadding.top, boxPadding.top); 7088 maxPadding.left = Math.max(maxPadding.left, boxPadding.left); 7089 maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); 7090 maxPadding.right = Math.max(maxPadding.right, boxPadding.right); 7091 } 7092 7093 newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'); 7094 newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'); 7095 7096 if (newWidth !== chartArea.w || newHeight !== chartArea.h) { 7097 chartArea.w = newWidth; 7098 chartArea.h = newHeight; 7099 7100 // return true if chart area changed in layout's direction 7101 var sizes = layout.horizontal ? [newWidth, chartArea.w] : [newHeight, chartArea.h]; 7102 return sizes[0] !== sizes[1] && (!isNaN(sizes[0]) || !isNaN(sizes[1])); 7103 } 7104} 7105 7106function handleMaxPadding(chartArea) { 7107 var maxPadding = chartArea.maxPadding; 7108 7109 function updatePos(pos) { 7110 var change = Math.max(maxPadding[pos] - chartArea[pos], 0); 7111 chartArea[pos] += change; 7112 return change; 7113 } 7114 chartArea.y += updatePos('top'); 7115 chartArea.x += updatePos('left'); 7116 updatePos('right'); 7117 updatePos('bottom'); 7118} 7119 7120function getMargins(horizontal, chartArea) { 7121 var maxPadding = chartArea.maxPadding; 7122 7123 function marginForPositions(positions) { 7124 var margin = {left: 0, top: 0, right: 0, bottom: 0}; 7125 positions.forEach(function(pos) { 7126 margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); 7127 }); 7128 return margin; 7129 } 7130 7131 return horizontal 7132 ? marginForPositions(['left', 'right']) 7133 : marginForPositions(['top', 'bottom']); 7134} 7135 7136function fitBoxes(boxes, chartArea, params) { 7137 var refitBoxes = []; 7138 var i, ilen, layout, box, refit, changed; 7139 7140 for (i = 0, ilen = boxes.length; i < ilen; ++i) { 7141 layout = boxes[i]; 7142 box = layout.box; 7143 7144 box.update( 7145 layout.width || chartArea.w, 7146 layout.height || chartArea.h, 7147 getMargins(layout.horizontal, chartArea) 7148 ); 7149 if (updateDims(chartArea, params, layout)) { 7150 changed = true; 7151 if (refitBoxes.length) { 7152 // Dimensions changed and there were non full width boxes before this 7153 // -> we have to refit those 7154 refit = true; 7155 } 7156 } 7157 if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case 7158 refitBoxes.push(layout); 7159 } 7160 } 7161 7162 return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed; 7163} 7164 7165function placeBoxes(boxes, chartArea, params) { 7166 var userPadding = params.padding; 7167 var x = chartArea.x; 7168 var y = chartArea.y; 7169 var i, ilen, layout, box; 7170 7171 for (i = 0, ilen = boxes.length; i < ilen; ++i) { 7172 layout = boxes[i]; 7173 box = layout.box; 7174 if (layout.horizontal) { 7175 box.left = box.fullWidth ? userPadding.left : chartArea.left; 7176 box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; 7177 box.top = y; 7178 box.bottom = y + box.height; 7179 box.width = box.right - box.left; 7180 y = box.bottom; 7181 } else { 7182 box.left = x; 7183 box.right = x + box.width; 7184 box.top = chartArea.top; 7185 box.bottom = chartArea.top + chartArea.h; 7186 box.height = box.bottom - box.top; 7187 x = box.right; 7188 } 7189 } 7190 7191 chartArea.x = x; 7192 chartArea.y = y; 7193} 7194 7195core_defaults._set('global', { 7196 layout: { 7197 padding: { 7198 top: 0, 7199 right: 0, 7200 bottom: 0, 7201 left: 0 7202 } 7203 } 7204}); 7205 7206/** 7207 * @interface ILayoutItem 7208 * @prop {string} position - The position of the item in the chart layout. Possible values are 7209 * 'left', 'top', 'right', 'bottom', and 'chartArea' 7210 * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area 7211 * @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down 7212 * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) 7213 * @prop {function} update - Takes two parameters: width and height. Returns size of item 7214 * @prop {function} getPadding - Returns an object with padding on the edges 7215 * @prop {number} width - Width of item. Must be valid after update() 7216 * @prop {number} height - Height of item. Must be valid after update() 7217 * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update 7218 * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update 7219 * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update 7220 * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update 7221 */ 7222 7223// The layout service is very self explanatory. It's responsible for the layout within a chart. 7224// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need 7225// It is this service's responsibility of carrying out that layout. 7226var core_layouts = { 7227 defaults: {}, 7228 7229 /** 7230 * Register a box to a chart. 7231 * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. 7232 * @param {Chart} chart - the chart to use 7233 * @param {ILayoutItem} item - the item to add to be layed out 7234 */ 7235 addBox: function(chart, item) { 7236 if (!chart.boxes) { 7237 chart.boxes = []; 7238 } 7239 7240 // initialize item with default values 7241 item.fullWidth = item.fullWidth || false; 7242 item.position = item.position || 'top'; 7243 item.weight = item.weight || 0; 7244 item._layers = item._layers || function() { 7245 return [{ 7246 z: 0, 7247 draw: function() { 7248 item.draw.apply(item, arguments); 7249 } 7250 }]; 7251 }; 7252 7253 chart.boxes.push(item); 7254 }, 7255 7256 /** 7257 * Remove a layoutItem from a chart 7258 * @param {Chart} chart - the chart to remove the box from 7259 * @param {ILayoutItem} layoutItem - the item to remove from the layout 7260 */ 7261 removeBox: function(chart, layoutItem) { 7262 var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; 7263 if (index !== -1) { 7264 chart.boxes.splice(index, 1); 7265 } 7266 }, 7267 7268 /** 7269 * Sets (or updates) options on the given `item`. 7270 * @param {Chart} chart - the chart in which the item lives (or will be added to) 7271 * @param {ILayoutItem} item - the item to configure with the given options 7272 * @param {object} options - the new item options. 7273 */ 7274 configure: function(chart, item, options) { 7275 var props = ['fullWidth', 'position', 'weight']; 7276 var ilen = props.length; 7277 var i = 0; 7278 var prop; 7279 7280 for (; i < ilen; ++i) { 7281 prop = props[i]; 7282 if (options.hasOwnProperty(prop)) { 7283 item[prop] = options[prop]; 7284 } 7285 } 7286 }, 7287 7288 /** 7289 * Fits boxes of the given chart into the given size by having each box measure itself 7290 * then running a fitting algorithm 7291 * @param {Chart} chart - the chart 7292 * @param {number} width - the width to fit into 7293 * @param {number} height - the height to fit into 7294 */ 7295 update: function(chart, width, height) { 7296 if (!chart) { 7297 return; 7298 } 7299 7300 var layoutOptions = chart.options.layout || {}; 7301 var padding = helpers$1.options.toPadding(layoutOptions.padding); 7302 7303 var availableWidth = width - padding.width; 7304 var availableHeight = height - padding.height; 7305 var boxes = buildLayoutBoxes(chart.boxes); 7306 var verticalBoxes = boxes.vertical; 7307 var horizontalBoxes = boxes.horizontal; 7308 7309 // Essentially we now have any number of boxes on each of the 4 sides. 7310 // Our canvas looks like the following. 7311 // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and 7312 // B1 is the bottom axis 7313 // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays 7314 // These locations are single-box locations only, when trying to register a chartArea location that is already taken, 7315 // an error will be thrown. 7316 // 7317 // |----------------------------------------------------| 7318 // | T1 (Full Width) | 7319 // |----------------------------------------------------| 7320 // | | | T2 | | 7321 // | |----|-------------------------------------|----| 7322 // | | | C1 | | C2 | | 7323 // | | |----| |----| | 7324 // | | | | | 7325 // | L1 | L2 | ChartArea (C0) | R1 | 7326 // | | | | | 7327 // | | |----| |----| | 7328 // | | | C3 | | C4 | | 7329 // | |----|-------------------------------------|----| 7330 // | | | B1 | | 7331 // |----------------------------------------------------| 7332 // | B2 (Full Width) | 7333 // |----------------------------------------------------| 7334 // 7335 7336 var params = Object.freeze({ 7337 outerWidth: width, 7338 outerHeight: height, 7339 padding: padding, 7340 availableWidth: availableWidth, 7341 vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length, 7342 hBoxMaxHeight: availableHeight / 2 7343 }); 7344 var chartArea = extend({ 7345 maxPadding: extend({}, padding), 7346 w: availableWidth, 7347 h: availableHeight, 7348 x: padding.left, 7349 y: padding.top 7350 }, padding); 7351 7352 setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); 7353 7354 // First fit vertical boxes 7355 fitBoxes(verticalBoxes, chartArea, params); 7356 7357 // Then fit horizontal boxes 7358 if (fitBoxes(horizontalBoxes, chartArea, params)) { 7359 // if the area changed, re-fit vertical boxes 7360 fitBoxes(verticalBoxes, chartArea, params); 7361 } 7362 7363 handleMaxPadding(chartArea); 7364 7365 // Finally place the boxes to correct coordinates 7366 placeBoxes(boxes.leftAndTop, chartArea, params); 7367 7368 // Move to opposite side of chart 7369 chartArea.x += chartArea.w; 7370 chartArea.y += chartArea.h; 7371 7372 placeBoxes(boxes.rightAndBottom, chartArea, params); 7373 7374 chart.chartArea = { 7375 left: chartArea.left, 7376 top: chartArea.top, 7377 right: chartArea.left + chartArea.w, 7378 bottom: chartArea.top + chartArea.h 7379 }; 7380 7381 // Finally update boxes in chartArea (radial scale for example) 7382 helpers$1.each(boxes.chartArea, function(layout) { 7383 var box = layout.box; 7384 extend(box, chart.chartArea); 7385 box.update(chartArea.w, chartArea.h); 7386 }); 7387 } 7388}; 7389 7390/** 7391 * Platform fallback implementation (minimal). 7392 * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 7393 */ 7394 7395var platform_basic = { 7396 acquireContext: function(item) { 7397 if (item && item.canvas) { 7398 // Support for any object associated to a canvas (including a context2d) 7399 item = item.canvas; 7400 } 7401 7402 return item && item.getContext('2d') || null; 7403 } 7404}; 7405 7406var platform_dom = "/*\r\n * DOM element rendering detection\r\n * https://davidwalsh.name/detect-node-insertion\r\n */\r\n@keyframes chartjs-render-animation {\r\n\tfrom { opacity: 0.99; }\r\n\tto { opacity: 1; }\r\n}\r\n\r\n.chartjs-render-monitor {\r\n\tanimation: chartjs-render-animation 0.001s;\r\n}\r\n\r\n/*\r\n * DOM element resizing detection\r\n * https://github.com/marcj/css-element-queries\r\n */\r\n.chartjs-size-monitor,\r\n.chartjs-size-monitor-expand,\r\n.chartjs-size-monitor-shrink {\r\n\tposition: absolute;\r\n\tdirection: ltr;\r\n\tleft: 0;\r\n\ttop: 0;\r\n\tright: 0;\r\n\tbottom: 0;\r\n\toverflow: hidden;\r\n\tpointer-events: none;\r\n\tvisibility: hidden;\r\n\tz-index: -1;\r\n}\r\n\r\n.chartjs-size-monitor-expand > div {\r\n\tposition: absolute;\r\n\twidth: 1000000px;\r\n\theight: 1000000px;\r\n\tleft: 0;\r\n\ttop: 0;\r\n}\r\n\r\n.chartjs-size-monitor-shrink > div {\r\n\tposition: absolute;\r\n\twidth: 200%;\r\n\theight: 200%;\r\n\tleft: 0;\r\n\ttop: 0;\r\n}\r\n"; 7407 7408var platform_dom$1 = /*#__PURE__*/Object.freeze({ 7409__proto__: null, 7410'default': platform_dom 7411}); 7412 7413var stylesheet = getCjsExportFromNamespace(platform_dom$1); 7414 7415var EXPANDO_KEY = '$chartjs'; 7416var CSS_PREFIX = 'chartjs-'; 7417var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor'; 7418var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; 7419var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; 7420var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; 7421 7422/** 7423 * DOM event types -> Chart.js event types. 7424 * Note: only events with different types are mapped. 7425 * @see https://developer.mozilla.org/en-US/docs/Web/Events 7426 */ 7427var EVENT_TYPES = { 7428 touchstart: 'mousedown', 7429 touchmove: 'mousemove', 7430 touchend: 'mouseup', 7431 pointerenter: 'mouseenter', 7432 pointerdown: 'mousedown', 7433 pointermove: 'mousemove', 7434 pointerup: 'mouseup', 7435 pointerleave: 'mouseout', 7436 pointerout: 'mouseout' 7437}; 7438 7439/** 7440 * The "used" size is the final value of a dimension property after all calculations have 7441 * been performed. This method uses the computed style of `element` but returns undefined 7442 * if the computed style is not expressed in pixels. That can happen in some cases where 7443 * `element` has a size relative to its parent and this last one is not yet displayed, 7444 * for example because of `display: none` on a parent node. 7445 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value 7446 * @returns {number} Size in pixels or undefined if unknown. 7447 */ 7448function readUsedSize(element, property) { 7449 var value = helpers$1.getStyle(element, property); 7450 var matches = value && value.match(/^(\d+)(\.\d+)?px$/); 7451 return matches ? Number(matches[1]) : undefined; 7452} 7453 7454/** 7455 * Initializes the canvas style and render size without modifying the canvas display size, 7456 * since responsiveness is handled by the controller.resize() method. The config is used 7457 * to determine the aspect ratio to apply in case no explicit height has been specified. 7458 */ 7459function initCanvas(canvas, config) { 7460 var style = canvas.style; 7461 7462 // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it 7463 // returns null or '' if no explicit value has been set to the canvas attribute. 7464 var renderHeight = canvas.getAttribute('height'); 7465 var renderWidth = canvas.getAttribute('width'); 7466 7467 // Chart.js modifies some canvas values that we want to restore on destroy 7468 canvas[EXPANDO_KEY] = { 7469 initial: { 7470 height: renderHeight, 7471 width: renderWidth, 7472 style: { 7473 display: style.display, 7474 height: style.height, 7475 width: style.width 7476 } 7477 } 7478 }; 7479 7480 // Force canvas to display as block to avoid extra space caused by inline 7481 // elements, which would interfere with the responsive resize process. 7482 // https://github.com/chartjs/Chart.js/issues/2538 7483 style.display = style.display || 'block'; 7484 7485 if (renderWidth === null || renderWidth === '') { 7486 var displayWidth = readUsedSize(canvas, 'width'); 7487 if (displayWidth !== undefined) { 7488 canvas.width = displayWidth; 7489 } 7490 } 7491 7492 if (renderHeight === null || renderHeight === '') { 7493 if (canvas.style.height === '') { 7494 // If no explicit render height and style height, let's apply the aspect ratio, 7495 // which one can be specified by the user but also by charts as default option 7496 // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. 7497 canvas.height = canvas.width / (config.options.aspectRatio || 2); 7498 } else { 7499 var displayHeight = readUsedSize(canvas, 'height'); 7500 if (displayWidth !== undefined) { 7501 canvas.height = displayHeight; 7502 } 7503 } 7504 } 7505 7506 return canvas; 7507} 7508 7509/** 7510 * Detects support for options object argument in addEventListener. 7511 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support 7512 * @private 7513 */ 7514var supportsEventListenerOptions = (function() { 7515 var supports = false; 7516 try { 7517 var options = Object.defineProperty({}, 'passive', { 7518 // eslint-disable-next-line getter-return 7519 get: function() { 7520 supports = true; 7521 } 7522 }); 7523 window.addEventListener('e', null, options); 7524 } catch (e) { 7525 // continue regardless of error 7526 } 7527 return supports; 7528}()); 7529 7530// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. 7531// https://github.com/chartjs/Chart.js/issues/4287 7532var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; 7533 7534function addListener(node, type, listener) { 7535 node.addEventListener(type, listener, eventListenerOptions); 7536} 7537 7538function removeListener(node, type, listener) { 7539 node.removeEventListener(type, listener, eventListenerOptions); 7540} 7541 7542function createEvent(type, chart, x, y, nativeEvent) { 7543 return { 7544 type: type, 7545 chart: chart, 7546 native: nativeEvent || null, 7547 x: x !== undefined ? x : null, 7548 y: y !== undefined ? y : null, 7549 }; 7550} 7551 7552function fromNativeEvent(event, chart) { 7553 var type = EVENT_TYPES[event.type] || event.type; 7554 var pos = helpers$1.getRelativePosition(event, chart); 7555 return createEvent(type, chart, pos.x, pos.y, event); 7556} 7557 7558function throttled(fn, thisArg) { 7559 var ticking = false; 7560 var args = []; 7561 7562 return function() { 7563 args = Array.prototype.slice.call(arguments); 7564 thisArg = thisArg || this; 7565 7566 if (!ticking) { 7567 ticking = true; 7568 helpers$1.requestAnimFrame.call(window, function() { 7569 ticking = false; 7570 fn.apply(thisArg, args); 7571 }); 7572 } 7573 }; 7574} 7575 7576function createDiv(cls) { 7577 var el = document.createElement('div'); 7578 el.className = cls || ''; 7579 return el; 7580} 7581 7582// Implementation based on https://github.com/marcj/css-element-queries 7583function createResizer(handler) { 7584 var maxSize = 1000000; 7585 7586 // NOTE(SB) Don't use innerHTML because it could be considered unsafe. 7587 // https://github.com/chartjs/Chart.js/issues/5902 7588 var resizer = createDiv(CSS_SIZE_MONITOR); 7589 var expand = createDiv(CSS_SIZE_MONITOR + '-expand'); 7590 var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink'); 7591 7592 expand.appendChild(createDiv()); 7593 shrink.appendChild(createDiv()); 7594 7595 resizer.appendChild(expand); 7596 resizer.appendChild(shrink); 7597 resizer._reset = function() { 7598 expand.scrollLeft = maxSize; 7599 expand.scrollTop = maxSize; 7600 shrink.scrollLeft = maxSize; 7601 shrink.scrollTop = maxSize; 7602 }; 7603 7604 var onScroll = function() { 7605 resizer._reset(); 7606 handler(); 7607 }; 7608 7609 addListener(expand, 'scroll', onScroll.bind(expand, 'expand')); 7610 addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); 7611 7612 return resizer; 7613} 7614 7615// https://davidwalsh.name/detect-node-insertion 7616function watchForRender(node, handler) { 7617 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); 7618 var proxy = expando.renderProxy = function(e) { 7619 if (e.animationName === CSS_RENDER_ANIMATION) { 7620 handler(); 7621 } 7622 }; 7623 7624 helpers$1.each(ANIMATION_START_EVENTS, function(type) { 7625 addListener(node, type, proxy); 7626 }); 7627 7628 // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class 7629 // is removed then added back immediately (same animation frame?). Accessing the 7630 // `offsetParent` property will force a reflow and re-evaluate the CSS animation. 7631 // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics 7632 // https://github.com/chartjs/Chart.js/issues/4737 7633 expando.reflow = !!node.offsetParent; 7634 7635 node.classList.add(CSS_RENDER_MONITOR); 7636} 7637 7638function unwatchForRender(node) { 7639 var expando = node[EXPANDO_KEY] || {}; 7640 var proxy = expando.renderProxy; 7641 7642 if (proxy) { 7643 helpers$1.each(ANIMATION_START_EVENTS, function(type) { 7644 removeListener(node, type, proxy); 7645 }); 7646 7647 delete expando.renderProxy; 7648 } 7649 7650 node.classList.remove(CSS_RENDER_MONITOR); 7651} 7652 7653function addResizeListener(node, listener, chart) { 7654 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); 7655 7656 // Let's keep track of this added resizer and thus avoid DOM query when removing it. 7657 var resizer = expando.resizer = createResizer(throttled(function() { 7658 if (expando.resizer) { 7659 var container = chart.options.maintainAspectRatio && node.parentNode; 7660 var w = container ? container.clientWidth : 0; 7661 listener(createEvent('resize', chart)); 7662 if (container && container.clientWidth < w && chart.canvas) { 7663 // If the container size shrank during chart resize, let's assume 7664 // scrollbar appeared. So we resize again with the scrollbar visible - 7665 // effectively making chart smaller and the scrollbar hidden again. 7666 // Because we are inside `throttled`, and currently `ticking`, scroll 7667 // events are ignored during this whole 2 resize process. 7668 // If we assumed wrong and something else happened, we are resizing 7669 // twice in a frame (potential performance issue) 7670 listener(createEvent('resize', chart)); 7671 } 7672 } 7673 })); 7674 7675 // The resizer needs to be attached to the node parent, so we first need to be 7676 // sure that `node` is attached to the DOM before injecting the resizer element. 7677 watchForRender(node, function() { 7678 if (expando.resizer) { 7679 var container = node.parentNode; 7680 if (container && container !== resizer.parentNode) { 7681 container.insertBefore(resizer, container.firstChild); 7682 } 7683 7684 // The container size might have changed, let's reset the resizer state. 7685 resizer._reset(); 7686 } 7687 }); 7688} 7689 7690function removeResizeListener(node) { 7691 var expando = node[EXPANDO_KEY] || {}; 7692 var resizer = expando.resizer; 7693 7694 delete expando.resizer; 7695 unwatchForRender(node); 7696 7697 if (resizer && resizer.parentNode) { 7698 resizer.parentNode.removeChild(resizer); 7699 } 7700} 7701 7702/** 7703 * Injects CSS styles inline if the styles are not already present. 7704 * @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the <style>. 7705 * @param {string} css - the CSS to be injected. 7706 */ 7707function injectCSS(rootNode, css) { 7708 // https://stackoverflow.com/q/3922139 7709 var expando = rootNode[EXPANDO_KEY] || (rootNode[EXPANDO_KEY] = {}); 7710 if (!expando.containsStyles) { 7711 expando.containsStyles = true; 7712 css = '/* Chart.js */\n' + css; 7713 var style = document.createElement('style'); 7714 style.setAttribute('type', 'text/css'); 7715 style.appendChild(document.createTextNode(css)); 7716 rootNode.appendChild(style); 7717 } 7718} 7719 7720var platform_dom$2 = { 7721 /** 7722 * When `true`, prevents the automatic injection of the stylesheet required to 7723 * correctly detect when the chart is added to the DOM and then resized. This 7724 * switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`) 7725 * to be manually imported to make this library compatible with any CSP. 7726 * See https://github.com/chartjs/Chart.js/issues/5208 7727 */ 7728 disableCSSInjection: false, 7729 7730 /** 7731 * This property holds whether this platform is enabled for the current environment. 7732 * Currently used by platform.js to select the proper implementation. 7733 * @private 7734 */ 7735 _enabled: typeof window !== 'undefined' && typeof document !== 'undefined', 7736 7737 /** 7738 * Initializes resources that depend on platform options. 7739 * @param {HTMLCanvasElement} canvas - The Canvas element. 7740 * @private 7741 */ 7742 _ensureLoaded: function(canvas) { 7743 if (!this.disableCSSInjection) { 7744 // If the canvas is in a shadow DOM, then the styles must also be inserted 7745 // into the same shadow DOM. 7746 // https://github.com/chartjs/Chart.js/issues/5763 7747 var root = canvas.getRootNode ? canvas.getRootNode() : document; 7748 var targetNode = root.host ? root : document.head; 7749 injectCSS(targetNode, stylesheet); 7750 } 7751 }, 7752 7753 acquireContext: function(item, config) { 7754 if (typeof item === 'string') { 7755 item = document.getElementById(item); 7756 } else if (item.length) { 7757 // Support for array based queries (such as jQuery) 7758 item = item[0]; 7759 } 7760 7761 if (item && item.canvas) { 7762 // Support for any object associated to a canvas (including a context2d) 7763 item = item.canvas; 7764 } 7765 7766 // To prevent canvas fingerprinting, some add-ons undefine the getContext 7767 // method, for example: https://github.com/kkapsner/CanvasBlocker 7768 // https://github.com/chartjs/Chart.js/issues/2807 7769 var context = item && item.getContext && item.getContext('2d'); 7770 7771 // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is 7772 // inside an iframe or when running in a protected environment. We could guess the 7773 // types from their toString() value but let's keep things flexible and assume it's 7774 // a sufficient condition if the item has a context2D which has item as `canvas`. 7775 // https://github.com/chartjs/Chart.js/issues/3887 7776 // https://github.com/chartjs/Chart.js/issues/4102 7777 // https://github.com/chartjs/Chart.js/issues/4152 7778 if (context && context.canvas === item) { 7779 // Load platform resources on first chart creation, to make it possible to 7780 // import the library before setting platform options. 7781 this._ensureLoaded(item); 7782 initCanvas(item, config); 7783 return context; 7784 } 7785 7786 return null; 7787 }, 7788 7789 releaseContext: function(context) { 7790 var canvas = context.canvas; 7791 if (!canvas[EXPANDO_KEY]) { 7792 return; 7793 } 7794 7795 var initial = canvas[EXPANDO_KEY].initial; 7796 ['height', 'width'].forEach(function(prop) { 7797 var value = initial[prop]; 7798 if (helpers$1.isNullOrUndef(value)) { 7799 canvas.removeAttribute(prop); 7800 } else { 7801 canvas.setAttribute(prop, value); 7802 } 7803 }); 7804 7805 helpers$1.each(initial.style || {}, function(value, key) { 7806 canvas.style[key] = value; 7807 }); 7808 7809 // The canvas render size might have been changed (and thus the state stack discarded), 7810 // we can't use save() and restore() to restore the initial state. So make sure that at 7811 // least the canvas context is reset to the default state by setting the canvas width. 7812 // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html 7813 // eslint-disable-next-line no-self-assign 7814 canvas.width = canvas.width; 7815 7816 delete canvas[EXPANDO_KEY]; 7817 }, 7818 7819 addEventListener: function(chart, type, listener) { 7820 var canvas = chart.canvas; 7821 if (type === 'resize') { 7822 // Note: the resize event is not supported on all browsers. 7823 addResizeListener(canvas, listener, chart); 7824 return; 7825 } 7826 7827 var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); 7828 var proxies = expando.proxies || (expando.proxies = {}); 7829 var proxy = proxies[chart.id + '_' + type] = function(event) { 7830 listener(fromNativeEvent(event, chart)); 7831 }; 7832 7833 addListener(canvas, type, proxy); 7834 }, 7835 7836 removeEventListener: function(chart, type, listener) { 7837 var canvas = chart.canvas; 7838 if (type === 'resize') { 7839 // Note: the resize event is not supported on all browsers. 7840 removeResizeListener(canvas); 7841 return; 7842 } 7843 7844 var expando = listener[EXPANDO_KEY] || {}; 7845 var proxies = expando.proxies || {}; 7846 var proxy = proxies[chart.id + '_' + type]; 7847 if (!proxy) { 7848 return; 7849 } 7850 7851 removeListener(canvas, type, proxy); 7852 } 7853}; 7854 7855// DEPRECATIONS 7856 7857/** 7858 * Provided for backward compatibility, use EventTarget.addEventListener instead. 7859 * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ 7860 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener 7861 * @function Chart.helpers.addEvent 7862 * @deprecated since version 2.7.0 7863 * @todo remove at version 3 7864 * @private 7865 */ 7866helpers$1.addEvent = addListener; 7867 7868/** 7869 * Provided for backward compatibility, use EventTarget.removeEventListener instead. 7870 * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ 7871 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener 7872 * @function Chart.helpers.removeEvent 7873 * @deprecated since version 2.7.0 7874 * @todo remove at version 3 7875 * @private 7876 */ 7877helpers$1.removeEvent = removeListener; 7878 7879// @TODO Make possible to select another platform at build time. 7880var implementation = platform_dom$2._enabled ? platform_dom$2 : platform_basic; 7881 7882/** 7883 * @namespace Chart.platform 7884 * @see https://chartjs.gitbooks.io/proposals/content/Platform.html 7885 * @since 2.4.0 7886 */ 7887var platform = helpers$1.extend({ 7888 /** 7889 * @since 2.7.0 7890 */ 7891 initialize: function() {}, 7892 7893 /** 7894 * Called at chart construction time, returns a context2d instance implementing 7895 * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. 7896 * @param {*} item - The native item from which to acquire context (platform specific) 7897 * @param {object} options - The chart options 7898 * @returns {CanvasRenderingContext2D} context2d instance 7899 */ 7900 acquireContext: function() {}, 7901 7902 /** 7903 * Called at chart destruction time, releases any resources associated to the context 7904 * previously returned by the acquireContext() method. 7905 * @param {CanvasRenderingContext2D} context - The context2d instance 7906 * @returns {boolean} true if the method succeeded, else false 7907 */ 7908 releaseContext: function() {}, 7909 7910 /** 7911 * Registers the specified listener on the given chart. 7912 * @param {Chart} chart - Chart from which to listen for event 7913 * @param {string} type - The ({@link IEvent}) type to listen for 7914 * @param {function} listener - Receives a notification (an object that implements 7915 * the {@link IEvent} interface) when an event of the specified type occurs. 7916 */ 7917 addEventListener: function() {}, 7918 7919 /** 7920 * Removes the specified listener previously registered with addEventListener. 7921 * @param {Chart} chart - Chart from which to remove the listener 7922 * @param {string} type - The ({@link IEvent}) type to remove 7923 * @param {function} listener - The listener function to remove from the event target. 7924 */ 7925 removeEventListener: function() {} 7926 7927}, implementation); 7928 7929core_defaults._set('global', { 7930 plugins: {} 7931}); 7932 7933/** 7934 * The plugin service singleton 7935 * @namespace Chart.plugins 7936 * @since 2.1.0 7937 */ 7938var core_plugins = { 7939 /** 7940 * Globally registered plugins. 7941 * @private 7942 */ 7943 _plugins: [], 7944 7945 /** 7946 * This identifier is used to invalidate the descriptors cache attached to each chart 7947 * when a global plugin is registered or unregistered. In this case, the cache ID is 7948 * incremented and descriptors are regenerated during following API calls. 7949 * @private 7950 */ 7951 _cacheId: 0, 7952 7953 /** 7954 * Registers the given plugin(s) if not already registered. 7955 * @param {IPlugin[]|IPlugin} plugins plugin instance(s). 7956 */ 7957 register: function(plugins) { 7958 var p = this._plugins; 7959 ([]).concat(plugins).forEach(function(plugin) { 7960 if (p.indexOf(plugin) === -1) { 7961 p.push(plugin); 7962 } 7963 }); 7964 7965 this._cacheId++; 7966 }, 7967 7968 /** 7969 * Unregisters the given plugin(s) only if registered. 7970 * @param {IPlugin[]|IPlugin} plugins plugin instance(s). 7971 */ 7972 unregister: function(plugins) { 7973 var p = this._plugins; 7974 ([]).concat(plugins).forEach(function(plugin) { 7975 var idx = p.indexOf(plugin); 7976 if (idx !== -1) { 7977 p.splice(idx, 1); 7978 } 7979 }); 7980 7981 this._cacheId++; 7982 }, 7983 7984 /** 7985 * Remove all registered plugins. 7986 * @since 2.1.5 7987 */ 7988 clear: function() { 7989 this._plugins = []; 7990 this._cacheId++; 7991 }, 7992 7993 /** 7994 * Returns the number of registered plugins? 7995 * @returns {number} 7996 * @since 2.1.5 7997 */ 7998 count: function() { 7999 return this._plugins.length; 8000 }, 8001 8002 /** 8003 * Returns all registered plugin instances. 8004 * @returns {IPlugin[]} array of plugin objects. 8005 * @since 2.1.5 8006 */ 8007 getAll: function() { 8008 return this._plugins; 8009 }, 8010 8011 /** 8012 * Calls enabled plugins for `chart` on the specified hook and with the given args. 8013 * This method immediately returns as soon as a plugin explicitly returns false. The 8014 * returned value can be used, for instance, to interrupt the current action. 8015 * @param {Chart} chart - The chart instance for which plugins should be called. 8016 * @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). 8017 * @param {Array} [args] - Extra arguments to apply to the hook call. 8018 * @returns {boolean} false if any of the plugins return false, else returns true. 8019 */ 8020 notify: function(chart, hook, args) { 8021 var descriptors = this.descriptors(chart); 8022 var ilen = descriptors.length; 8023 var i, descriptor, plugin, params, method; 8024 8025 for (i = 0; i < ilen; ++i) { 8026 descriptor = descriptors[i]; 8027 plugin = descriptor.plugin; 8028 method = plugin[hook]; 8029 if (typeof method === 'function') { 8030 params = [chart].concat(args || []); 8031 params.push(descriptor.options); 8032 if (method.apply(plugin, params) === false) { 8033 return false; 8034 } 8035 } 8036 } 8037 8038 return true; 8039 }, 8040 8041 /** 8042 * Returns descriptors of enabled plugins for the given chart. 8043 * @returns {object[]} [{ plugin, options }] 8044 * @private 8045 */ 8046 descriptors: function(chart) { 8047 var cache = chart.$plugins || (chart.$plugins = {}); 8048 if (cache.id === this._cacheId) { 8049 return cache.descriptors; 8050 } 8051 8052 var plugins = []; 8053 var descriptors = []; 8054 var config = (chart && chart.config) || {}; 8055 var options = (config.options && config.options.plugins) || {}; 8056 8057 this._plugins.concat(config.plugins || []).forEach(function(plugin) { 8058 var idx = plugins.indexOf(plugin); 8059 if (idx !== -1) { 8060 return; 8061 } 8062 8063 var id = plugin.id; 8064 var opts = options[id]; 8065 if (opts === false) { 8066 return; 8067 } 8068 8069 if (opts === true) { 8070 opts = helpers$1.clone(core_defaults.global.plugins[id]); 8071 } 8072 8073 plugins.push(plugin); 8074 descriptors.push({ 8075 plugin: plugin, 8076 options: opts || {} 8077 }); 8078 }); 8079 8080 cache.descriptors = descriptors; 8081 cache.id = this._cacheId; 8082 return descriptors; 8083 }, 8084 8085 /** 8086 * Invalidates cache for the given chart: descriptors hold a reference on plugin option, 8087 * but in some cases, this reference can be changed by the user when updating options. 8088 * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 8089 * @private 8090 */ 8091 _invalidate: function(chart) { 8092 delete chart.$plugins; 8093 } 8094}; 8095 8096var core_scaleService = { 8097 // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then 8098 // use the new chart options to grab the correct scale 8099 constructors: {}, 8100 // Use a registration function so that we can move to an ES6 map when we no longer need to support 8101 // old browsers 8102 8103 // Scale config defaults 8104 defaults: {}, 8105 registerScaleType: function(type, scaleConstructor, scaleDefaults) { 8106 this.constructors[type] = scaleConstructor; 8107 this.defaults[type] = helpers$1.clone(scaleDefaults); 8108 }, 8109 getScaleConstructor: function(type) { 8110 return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; 8111 }, 8112 getScaleDefaults: function(type) { 8113 // Return the scale defaults merged with the global settings so that we always use the latest ones 8114 return this.defaults.hasOwnProperty(type) ? helpers$1.merge(Object.create(null), [core_defaults.scale, this.defaults[type]]) : {}; 8115 }, 8116 updateScaleDefaults: function(type, additions) { 8117 var me = this; 8118 if (me.defaults.hasOwnProperty(type)) { 8119 me.defaults[type] = helpers$1.extend(me.defaults[type], additions); 8120 } 8121 }, 8122 addScalesToLayout: function(chart) { 8123 // Adds each scale to the chart.boxes array to be sized accordingly 8124 helpers$1.each(chart.scales, function(scale) { 8125 // Set ILayoutItem parameters for backwards compatibility 8126 scale.fullWidth = scale.options.fullWidth; 8127 scale.position = scale.options.position; 8128 scale.weight = scale.options.weight; 8129 core_layouts.addBox(chart, scale); 8130 }); 8131 } 8132}; 8133 8134var valueOrDefault$8 = helpers$1.valueOrDefault; 8135var getRtlHelper = helpers$1.rtl.getRtlAdapter; 8136 8137core_defaults._set('global', { 8138 tooltips: { 8139 enabled: true, 8140 custom: null, 8141 mode: 'nearest', 8142 position: 'average', 8143 intersect: true, 8144 backgroundColor: 'rgba(0,0,0,0.8)', 8145 titleFontStyle: 'bold', 8146 titleSpacing: 2, 8147 titleMarginBottom: 6, 8148 titleFontColor: '#fff', 8149 titleAlign: 'left', 8150 bodySpacing: 2, 8151 bodyFontColor: '#fff', 8152 bodyAlign: 'left', 8153 footerFontStyle: 'bold', 8154 footerSpacing: 2, 8155 footerMarginTop: 6, 8156 footerFontColor: '#fff', 8157 footerAlign: 'left', 8158 yPadding: 6, 8159 xPadding: 6, 8160 caretPadding: 2, 8161 caretSize: 5, 8162 cornerRadius: 6, 8163 multiKeyBackground: '#fff', 8164 displayColors: true, 8165 borderColor: 'rgba(0,0,0,0)', 8166 borderWidth: 0, 8167 callbacks: { 8168 // Args are: (tooltipItems, data) 8169 beforeTitle: helpers$1.noop, 8170 title: function(tooltipItems, data) { 8171 var title = ''; 8172 var labels = data.labels; 8173 var labelCount = labels ? labels.length : 0; 8174 8175 if (tooltipItems.length > 0) { 8176 var item = tooltipItems[0]; 8177 if (item.label) { 8178 title = item.label; 8179 } else if (item.xLabel) { 8180 title = item.xLabel; 8181 } else if (labelCount > 0 && item.index < labelCount) { 8182 title = labels[item.index]; 8183 } 8184 } 8185 8186 return title; 8187 }, 8188 afterTitle: helpers$1.noop, 8189 8190 // Args are: (tooltipItems, data) 8191 beforeBody: helpers$1.noop, 8192 8193 // Args are: (tooltipItem, data) 8194 beforeLabel: helpers$1.noop, 8195 label: function(tooltipItem, data) { 8196 var label = data.datasets[tooltipItem.datasetIndex].label || ''; 8197 8198 if (label) { 8199 label += ': '; 8200 } 8201 if (!helpers$1.isNullOrUndef(tooltipItem.value)) { 8202 label += tooltipItem.value; 8203 } else { 8204 label += tooltipItem.yLabel; 8205 } 8206 return label; 8207 }, 8208 labelColor: function(tooltipItem, chart) { 8209 var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); 8210 var activeElement = meta.data[tooltipItem.index]; 8211 var view = activeElement._view; 8212 return { 8213 borderColor: view.borderColor, 8214 backgroundColor: view.backgroundColor 8215 }; 8216 }, 8217 labelTextColor: function() { 8218 return this._options.bodyFontColor; 8219 }, 8220 afterLabel: helpers$1.noop, 8221 8222 // Args are: (tooltipItems, data) 8223 afterBody: helpers$1.noop, 8224 8225 // Args are: (tooltipItems, data) 8226 beforeFooter: helpers$1.noop, 8227 footer: helpers$1.noop, 8228 afterFooter: helpers$1.noop 8229 } 8230 } 8231}); 8232 8233var positioners = { 8234 /** 8235 * Average mode places the tooltip at the average position of the elements shown 8236 * @function Chart.Tooltip.positioners.average 8237 * @param elements {ChartElement[]} the elements being displayed in the tooltip 8238 * @returns {object} tooltip position 8239 */ 8240 average: function(elements) { 8241 if (!elements.length) { 8242 return false; 8243 } 8244 8245 var i, len; 8246 var x = 0; 8247 var y = 0; 8248 var count = 0; 8249 8250 for (i = 0, len = elements.length; i < len; ++i) { 8251 var el = elements[i]; 8252 if (el && el.hasValue()) { 8253 var pos = el.tooltipPosition(); 8254 x += pos.x; 8255 y += pos.y; 8256 ++count; 8257 } 8258 } 8259 8260 return { 8261 x: x / count, 8262 y: y / count 8263 }; 8264 }, 8265 8266 /** 8267 * Gets the tooltip position nearest of the item nearest to the event position 8268 * @function Chart.Tooltip.positioners.nearest 8269 * @param elements {Chart.Element[]} the tooltip elements 8270 * @param eventPosition {object} the position of the event in canvas coordinates 8271 * @returns {object} the tooltip position 8272 */ 8273 nearest: function(elements, eventPosition) { 8274 var x = eventPosition.x; 8275 var y = eventPosition.y; 8276 var minDistance = Number.POSITIVE_INFINITY; 8277 var i, len, nearestElement; 8278 8279 for (i = 0, len = elements.length; i < len; ++i) { 8280 var el = elements[i]; 8281 if (el && el.hasValue()) { 8282 var center = el.getCenterPoint(); 8283 var d = helpers$1.distanceBetweenPoints(eventPosition, center); 8284 8285 if (d < minDistance) { 8286 minDistance = d; 8287 nearestElement = el; 8288 } 8289 } 8290 } 8291 8292 if (nearestElement) { 8293 var tp = nearestElement.tooltipPosition(); 8294 x = tp.x; 8295 y = tp.y; 8296 } 8297 8298 return { 8299 x: x, 8300 y: y 8301 }; 8302 } 8303}; 8304 8305// Helper to push or concat based on if the 2nd parameter is an array or not 8306function pushOrConcat(base, toPush) { 8307 if (toPush) { 8308 if (helpers$1.isArray(toPush)) { 8309 // base = base.concat(toPush); 8310 Array.prototype.push.apply(base, toPush); 8311 } else { 8312 base.push(toPush); 8313 } 8314 } 8315 8316 return base; 8317} 8318 8319/** 8320 * Returns array of strings split by newline 8321 * @param {string} value - The value to split by newline. 8322 * @returns {string[]} value if newline present - Returned from String split() method 8323 * @function 8324 */ 8325function splitNewlines(str) { 8326 if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { 8327 return str.split('\n'); 8328 } 8329 return str; 8330} 8331 8332 8333/** 8334 * Private helper to create a tooltip item model 8335 * @param element - the chart element (point, arc, bar) to create the tooltip item for 8336 * @return new tooltip item 8337 */ 8338function createTooltipItem(element) { 8339 var xScale = element._xScale; 8340 var yScale = element._yScale || element._scale; // handle radar || polarArea charts 8341 var index = element._index; 8342 var datasetIndex = element._datasetIndex; 8343 var controller = element._chart.getDatasetMeta(datasetIndex).controller; 8344 var indexScale = controller._getIndexScale(); 8345 var valueScale = controller._getValueScale(); 8346 8347 return { 8348 xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', 8349 yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', 8350 label: indexScale ? '' + indexScale.getLabelForIndex(index, datasetIndex) : '', 8351 value: valueScale ? '' + valueScale.getLabelForIndex(index, datasetIndex) : '', 8352 index: index, 8353 datasetIndex: datasetIndex, 8354 x: element._model.x, 8355 y: element._model.y 8356 }; 8357} 8358 8359/** 8360 * Helper to get the reset model for the tooltip 8361 * @param tooltipOpts {object} the tooltip options 8362 */ 8363function getBaseModel(tooltipOpts) { 8364 var globalDefaults = core_defaults.global; 8365 8366 return { 8367 // Positioning 8368 xPadding: tooltipOpts.xPadding, 8369 yPadding: tooltipOpts.yPadding, 8370 xAlign: tooltipOpts.xAlign, 8371 yAlign: tooltipOpts.yAlign, 8372 8373 // Drawing direction and text direction 8374 rtl: tooltipOpts.rtl, 8375 textDirection: tooltipOpts.textDirection, 8376 8377 // Body 8378 bodyFontColor: tooltipOpts.bodyFontColor, 8379 _bodyFontFamily: valueOrDefault$8(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), 8380 _bodyFontStyle: valueOrDefault$8(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), 8381 _bodyAlign: tooltipOpts.bodyAlign, 8382 bodyFontSize: valueOrDefault$8(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), 8383 bodySpacing: tooltipOpts.bodySpacing, 8384 8385 // Title 8386 titleFontColor: tooltipOpts.titleFontColor, 8387 _titleFontFamily: valueOrDefault$8(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), 8388 _titleFontStyle: valueOrDefault$8(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), 8389 titleFontSize: valueOrDefault$8(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), 8390 _titleAlign: tooltipOpts.titleAlign, 8391 titleSpacing: tooltipOpts.titleSpacing, 8392 titleMarginBottom: tooltipOpts.titleMarginBottom, 8393 8394 // Footer 8395 footerFontColor: tooltipOpts.footerFontColor, 8396 _footerFontFamily: valueOrDefault$8(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), 8397 _footerFontStyle: valueOrDefault$8(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), 8398 footerFontSize: valueOrDefault$8(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), 8399 _footerAlign: tooltipOpts.footerAlign, 8400 footerSpacing: tooltipOpts.footerSpacing, 8401 footerMarginTop: tooltipOpts.footerMarginTop, 8402 8403 // Appearance 8404 caretSize: tooltipOpts.caretSize, 8405 cornerRadius: tooltipOpts.cornerRadius, 8406 backgroundColor: tooltipOpts.backgroundColor, 8407 opacity: 0, 8408 legendColorBackground: tooltipOpts.multiKeyBackground, 8409 displayColors: tooltipOpts.displayColors, 8410 borderColor: tooltipOpts.borderColor, 8411 borderWidth: tooltipOpts.borderWidth 8412 }; 8413} 8414 8415/** 8416 * Get the size of the tooltip 8417 */ 8418function getTooltipSize(tooltip, model) { 8419 var ctx = tooltip._chart.ctx; 8420 8421 var height = model.yPadding * 2; // Tooltip Padding 8422 var width = 0; 8423 8424 // Count of all lines in the body 8425 var body = model.body; 8426 var combinedBodyLength = body.reduce(function(count, bodyItem) { 8427 return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; 8428 }, 0); 8429 combinedBodyLength += model.beforeBody.length + model.afterBody.length; 8430 8431 var titleLineCount = model.title.length; 8432 var footerLineCount = model.footer.length; 8433 var titleFontSize = model.titleFontSize; 8434 var bodyFontSize = model.bodyFontSize; 8435 var footerFontSize = model.footerFontSize; 8436 8437 height += titleLineCount * titleFontSize; // Title Lines 8438 height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing 8439 height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin 8440 height += combinedBodyLength * bodyFontSize; // Body Lines 8441 height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing 8442 height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin 8443 height += footerLineCount * (footerFontSize); // Footer Lines 8444 height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing 8445 8446 // Title width 8447 var widthPadding = 0; 8448 var maxLineWidth = function(line) { 8449 width = Math.max(width, ctx.measureText(line).width + widthPadding); 8450 }; 8451 8452 ctx.font = helpers$1.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); 8453 helpers$1.each(model.title, maxLineWidth); 8454 8455 // Body width 8456 ctx.font = helpers$1.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); 8457 helpers$1.each(model.beforeBody.concat(model.afterBody), maxLineWidth); 8458 8459 // Body lines may include some extra width due to the color box 8460 widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; 8461 helpers$1.each(body, function(bodyItem) { 8462 helpers$1.each(bodyItem.before, maxLineWidth); 8463 helpers$1.each(bodyItem.lines, maxLineWidth); 8464 helpers$1.each(bodyItem.after, maxLineWidth); 8465 }); 8466 8467 // Reset back to 0 8468 widthPadding = 0; 8469 8470 // Footer width 8471 ctx.font = helpers$1.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); 8472 helpers$1.each(model.footer, maxLineWidth); 8473 8474 // Add padding 8475 width += 2 * model.xPadding; 8476 8477 return { 8478 width: width, 8479 height: height 8480 }; 8481} 8482 8483/** 8484 * Helper to get the alignment of a tooltip given the size 8485 */ 8486function determineAlignment(tooltip, size) { 8487 var model = tooltip._model; 8488 var chart = tooltip._chart; 8489 var chartArea = tooltip._chart.chartArea; 8490 var xAlign = 'center'; 8491 var yAlign = 'center'; 8492 8493 if (model.y < size.height) { 8494 yAlign = 'top'; 8495 } else if (model.y > (chart.height - size.height)) { 8496 yAlign = 'bottom'; 8497 } 8498 8499 var lf, rf; // functions to determine left, right alignment 8500 var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart 8501 var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges 8502 var midX = (chartArea.left + chartArea.right) / 2; 8503 var midY = (chartArea.top + chartArea.bottom) / 2; 8504 8505 if (yAlign === 'center') { 8506 lf = function(x) { 8507 return x <= midX; 8508 }; 8509 rf = function(x) { 8510 return x > midX; 8511 }; 8512 } else { 8513 lf = function(x) { 8514 return x <= (size.width / 2); 8515 }; 8516 rf = function(x) { 8517 return x >= (chart.width - (size.width / 2)); 8518 }; 8519 } 8520 8521 olf = function(x) { 8522 return x + size.width + model.caretSize + model.caretPadding > chart.width; 8523 }; 8524 orf = function(x) { 8525 return x - size.width - model.caretSize - model.caretPadding < 0; 8526 }; 8527 yf = function(y) { 8528 return y <= midY ? 'top' : 'bottom'; 8529 }; 8530 8531 if (lf(model.x)) { 8532 xAlign = 'left'; 8533 8534 // Is tooltip too wide and goes over the right side of the chart.? 8535 if (olf(model.x)) { 8536 xAlign = 'center'; 8537 yAlign = yf(model.y); 8538 } 8539 } else if (rf(model.x)) { 8540 xAlign = 'right'; 8541 8542 // Is tooltip too wide and goes outside left edge of canvas? 8543 if (orf(model.x)) { 8544 xAlign = 'center'; 8545 yAlign = yf(model.y); 8546 } 8547 } 8548 8549 var opts = tooltip._options; 8550 return { 8551 xAlign: opts.xAlign ? opts.xAlign : xAlign, 8552 yAlign: opts.yAlign ? opts.yAlign : yAlign 8553 }; 8554} 8555 8556/** 8557 * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment 8558 */ 8559function getBackgroundPoint(vm, size, alignment, chart) { 8560 // Background Position 8561 var x = vm.x; 8562 var y = vm.y; 8563 8564 var caretSize = vm.caretSize; 8565 var caretPadding = vm.caretPadding; 8566 var cornerRadius = vm.cornerRadius; 8567 var xAlign = alignment.xAlign; 8568 var yAlign = alignment.yAlign; 8569 var paddingAndSize = caretSize + caretPadding; 8570 var radiusAndPadding = cornerRadius + caretPadding; 8571 8572 if (xAlign === 'right') { 8573 x -= size.width; 8574 } else if (xAlign === 'center') { 8575 x -= (size.width / 2); 8576 if (x + size.width > chart.width) { 8577 x = chart.width - size.width; 8578 } 8579 if (x < 0) { 8580 x = 0; 8581 } 8582 } 8583 8584 if (yAlign === 'top') { 8585 y += paddingAndSize; 8586 } else if (yAlign === 'bottom') { 8587 y -= size.height + paddingAndSize; 8588 } else { 8589 y -= (size.height / 2); 8590 } 8591 8592 if (yAlign === 'center') { 8593 if (xAlign === 'left') { 8594 x += paddingAndSize; 8595 } else if (xAlign === 'right') { 8596 x -= paddingAndSize; 8597 } 8598 } else if (xAlign === 'left') { 8599 x -= radiusAndPadding; 8600 } else if (xAlign === 'right') { 8601 x += radiusAndPadding; 8602 } 8603 8604 return { 8605 x: x, 8606 y: y 8607 }; 8608} 8609 8610function getAlignedX(vm, align) { 8611 return align === 'center' 8612 ? vm.x + vm.width / 2 8613 : align === 'right' 8614 ? vm.x + vm.width - vm.xPadding 8615 : vm.x + vm.xPadding; 8616} 8617 8618/** 8619 * Helper to build before and after body lines 8620 */ 8621function getBeforeAfterBodyLines(callback) { 8622 return pushOrConcat([], splitNewlines(callback)); 8623} 8624 8625var exports$4 = core_element.extend({ 8626 initialize: function() { 8627 this._model = getBaseModel(this._options); 8628 this._lastActive = []; 8629 }, 8630 8631 // Get the title 8632 // Args are: (tooltipItem, data) 8633 getTitle: function() { 8634 var me = this; 8635 var opts = me._options; 8636 var callbacks = opts.callbacks; 8637 8638 var beforeTitle = callbacks.beforeTitle.apply(me, arguments); 8639 var title = callbacks.title.apply(me, arguments); 8640 var afterTitle = callbacks.afterTitle.apply(me, arguments); 8641 8642 var lines = []; 8643 lines = pushOrConcat(lines, splitNewlines(beforeTitle)); 8644 lines = pushOrConcat(lines, splitNewlines(title)); 8645 lines = pushOrConcat(lines, splitNewlines(afterTitle)); 8646 8647 return lines; 8648 }, 8649 8650 // Args are: (tooltipItem, data) 8651 getBeforeBody: function() { 8652 return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments)); 8653 }, 8654 8655 // Args are: (tooltipItem, data) 8656 getBody: function(tooltipItems, data) { 8657 var me = this; 8658 var callbacks = me._options.callbacks; 8659 var bodyItems = []; 8660 8661 helpers$1.each(tooltipItems, function(tooltipItem) { 8662 var bodyItem = { 8663 before: [], 8664 lines: [], 8665 after: [] 8666 }; 8667 pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data))); 8668 pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); 8669 pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data))); 8670 8671 bodyItems.push(bodyItem); 8672 }); 8673 8674 return bodyItems; 8675 }, 8676 8677 // Args are: (tooltipItem, data) 8678 getAfterBody: function() { 8679 return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments)); 8680 }, 8681 8682 // Get the footer and beforeFooter and afterFooter lines 8683 // Args are: (tooltipItem, data) 8684 getFooter: function() { 8685 var me = this; 8686 var callbacks = me._options.callbacks; 8687 8688 var beforeFooter = callbacks.beforeFooter.apply(me, arguments); 8689 var footer = callbacks.footer.apply(me, arguments); 8690 var afterFooter = callbacks.afterFooter.apply(me, arguments); 8691 8692 var lines = []; 8693 lines = pushOrConcat(lines, splitNewlines(beforeFooter)); 8694 lines = pushOrConcat(lines, splitNewlines(footer)); 8695 lines = pushOrConcat(lines, splitNewlines(afterFooter)); 8696 8697 return lines; 8698 }, 8699 8700 update: function(changed) { 8701 var me = this; 8702 var opts = me._options; 8703 8704 // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition 8705 // 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 8706 // which breaks any animations. 8707 var existingModel = me._model; 8708 var model = me._model = getBaseModel(opts); 8709 var active = me._active; 8710 8711 var data = me._data; 8712 8713 // In the case where active.length === 0 we need to keep these at existing values for good animations 8714 var alignment = { 8715 xAlign: existingModel.xAlign, 8716 yAlign: existingModel.yAlign 8717 }; 8718 var backgroundPoint = { 8719 x: existingModel.x, 8720 y: existingModel.y 8721 }; 8722 var tooltipSize = { 8723 width: existingModel.width, 8724 height: existingModel.height 8725 }; 8726 var tooltipPosition = { 8727 x: existingModel.caretX, 8728 y: existingModel.caretY 8729 }; 8730 8731 var i, len; 8732 8733 if (active.length) { 8734 model.opacity = 1; 8735 8736 var labelColors = []; 8737 var labelTextColors = []; 8738 tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition); 8739 8740 var tooltipItems = []; 8741 for (i = 0, len = active.length; i < len; ++i) { 8742 tooltipItems.push(createTooltipItem(active[i])); 8743 } 8744 8745 // If the user provided a filter function, use it to modify the tooltip items 8746 if (opts.filter) { 8747 tooltipItems = tooltipItems.filter(function(a) { 8748 return opts.filter(a, data); 8749 }); 8750 } 8751 8752 // If the user provided a sorting function, use it to modify the tooltip items 8753 if (opts.itemSort) { 8754 tooltipItems = tooltipItems.sort(function(a, b) { 8755 return opts.itemSort(a, b, data); 8756 }); 8757 } 8758 8759 // Determine colors for boxes 8760 helpers$1.each(tooltipItems, function(tooltipItem) { 8761 labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); 8762 labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); 8763 }); 8764 8765 8766 // Build the Text Lines 8767 model.title = me.getTitle(tooltipItems, data); 8768 model.beforeBody = me.getBeforeBody(tooltipItems, data); 8769 model.body = me.getBody(tooltipItems, data); 8770 model.afterBody = me.getAfterBody(tooltipItems, data); 8771 model.footer = me.getFooter(tooltipItems, data); 8772 8773 // Initial positioning and colors 8774 model.x = tooltipPosition.x; 8775 model.y = tooltipPosition.y; 8776 model.caretPadding = opts.caretPadding; 8777 model.labelColors = labelColors; 8778 model.labelTextColors = labelTextColors; 8779 8780 // data points 8781 model.dataPoints = tooltipItems; 8782 8783 // We need to determine alignment of the tooltip 8784 tooltipSize = getTooltipSize(this, model); 8785 alignment = determineAlignment(this, tooltipSize); 8786 // Final Size and Position 8787 backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart); 8788 } else { 8789 model.opacity = 0; 8790 } 8791 8792 model.xAlign = alignment.xAlign; 8793 model.yAlign = alignment.yAlign; 8794 model.x = backgroundPoint.x; 8795 model.y = backgroundPoint.y; 8796 model.width = tooltipSize.width; 8797 model.height = tooltipSize.height; 8798 8799 // Point where the caret on the tooltip points to 8800 model.caretX = tooltipPosition.x; 8801 model.caretY = tooltipPosition.y; 8802 8803 me._model = model; 8804 8805 if (changed && opts.custom) { 8806 opts.custom.call(me, model); 8807 } 8808 8809 return me; 8810 }, 8811 8812 drawCaret: function(tooltipPoint, size) { 8813 var ctx = this._chart.ctx; 8814 var vm = this._view; 8815 var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); 8816 8817 ctx.lineTo(caretPosition.x1, caretPosition.y1); 8818 ctx.lineTo(caretPosition.x2, caretPosition.y2); 8819 ctx.lineTo(caretPosition.x3, caretPosition.y3); 8820 }, 8821 getCaretPosition: function(tooltipPoint, size, vm) { 8822 var x1, x2, x3, y1, y2, y3; 8823 var caretSize = vm.caretSize; 8824 var cornerRadius = vm.cornerRadius; 8825 var xAlign = vm.xAlign; 8826 var yAlign = vm.yAlign; 8827 var ptX = tooltipPoint.x; 8828 var ptY = tooltipPoint.y; 8829 var width = size.width; 8830 var height = size.height; 8831 8832 if (yAlign === 'center') { 8833 y2 = ptY + (height / 2); 8834 8835 if (xAlign === 'left') { 8836 x1 = ptX; 8837 x2 = x1 - caretSize; 8838 x3 = x1; 8839 8840 y1 = y2 + caretSize; 8841 y3 = y2 - caretSize; 8842 } else { 8843 x1 = ptX + width; 8844 x2 = x1 + caretSize; 8845 x3 = x1; 8846 8847 y1 = y2 - caretSize; 8848 y3 = y2 + caretSize; 8849 } 8850 } else { 8851 if (xAlign === 'left') { 8852 x2 = ptX + cornerRadius + (caretSize); 8853 x1 = x2 - caretSize; 8854 x3 = x2 + caretSize; 8855 } else if (xAlign === 'right') { 8856 x2 = ptX + width - cornerRadius - caretSize; 8857 x1 = x2 - caretSize; 8858 x3 = x2 + caretSize; 8859 } else { 8860 x2 = vm.caretX; 8861 x1 = x2 - caretSize; 8862 x3 = x2 + caretSize; 8863 } 8864 if (yAlign === 'top') { 8865 y1 = ptY; 8866 y2 = y1 - caretSize; 8867 y3 = y1; 8868 } else { 8869 y1 = ptY + height; 8870 y2 = y1 + caretSize; 8871 y3 = y1; 8872 // invert drawing order 8873 var tmp = x3; 8874 x3 = x1; 8875 x1 = tmp; 8876 } 8877 } 8878 return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; 8879 }, 8880 8881 drawTitle: function(pt, vm, ctx) { 8882 var title = vm.title; 8883 var length = title.length; 8884 var titleFontSize, titleSpacing, i; 8885 8886 if (length) { 8887 var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); 8888 8889 pt.x = getAlignedX(vm, vm._titleAlign); 8890 8891 ctx.textAlign = rtlHelper.textAlign(vm._titleAlign); 8892 ctx.textBaseline = 'middle'; 8893 8894 titleFontSize = vm.titleFontSize; 8895 titleSpacing = vm.titleSpacing; 8896 8897 ctx.fillStyle = vm.titleFontColor; 8898 ctx.font = helpers$1.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); 8899 8900 for (i = 0; i < length; ++i) { 8901 ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFontSize / 2); 8902 pt.y += titleFontSize + titleSpacing; // Line Height and spacing 8903 8904 if (i + 1 === length) { 8905 pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing 8906 } 8907 } 8908 } 8909 }, 8910 8911 drawBody: function(pt, vm, ctx) { 8912 var bodyFontSize = vm.bodyFontSize; 8913 var bodySpacing = vm.bodySpacing; 8914 var bodyAlign = vm._bodyAlign; 8915 var body = vm.body; 8916 var drawColorBoxes = vm.displayColors; 8917 var xLinePadding = 0; 8918 var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0; 8919 8920 var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); 8921 8922 var fillLineOfText = function(line) { 8923 ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyFontSize / 2); 8924 pt.y += bodyFontSize + bodySpacing; 8925 }; 8926 8927 var bodyItem, textColor, labelColors, lines, i, j, ilen, jlen; 8928 var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign); 8929 8930 ctx.textAlign = bodyAlign; 8931 ctx.textBaseline = 'middle'; 8932 ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); 8933 8934 pt.x = getAlignedX(vm, bodyAlignForCalculation); 8935 8936 // Before body lines 8937 ctx.fillStyle = vm.bodyFontColor; 8938 helpers$1.each(vm.beforeBody, fillLineOfText); 8939 8940 xLinePadding = drawColorBoxes && bodyAlignForCalculation !== 'right' 8941 ? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2) 8942 : 0; 8943 8944 // Draw body lines now 8945 for (i = 0, ilen = body.length; i < ilen; ++i) { 8946 bodyItem = body[i]; 8947 textColor = vm.labelTextColors[i]; 8948 labelColors = vm.labelColors[i]; 8949 8950 ctx.fillStyle = textColor; 8951 helpers$1.each(bodyItem.before, fillLineOfText); 8952 8953 lines = bodyItem.lines; 8954 for (j = 0, jlen = lines.length; j < jlen; ++j) { 8955 // Draw Legend-like boxes if needed 8956 if (drawColorBoxes) { 8957 var rtlColorX = rtlHelper.x(colorX); 8958 8959 // Fill a white rect so that colours merge nicely if the opacity is < 1 8960 ctx.fillStyle = vm.legendColorBackground; 8961 ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize); 8962 8963 // Border 8964 ctx.lineWidth = 1; 8965 ctx.strokeStyle = labelColors.borderColor; 8966 ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize); 8967 8968 // Inner square 8969 ctx.fillStyle = labelColors.backgroundColor; 8970 ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); 8971 ctx.fillStyle = textColor; 8972 } 8973 8974 fillLineOfText(lines[j]); 8975 } 8976 8977 helpers$1.each(bodyItem.after, fillLineOfText); 8978 } 8979 8980 // Reset back to 0 for after body 8981 xLinePadding = 0; 8982 8983 // After body lines 8984 helpers$1.each(vm.afterBody, fillLineOfText); 8985 pt.y -= bodySpacing; // Remove last body spacing 8986 }, 8987 8988 drawFooter: function(pt, vm, ctx) { 8989 var footer = vm.footer; 8990 var length = footer.length; 8991 var footerFontSize, i; 8992 8993 if (length) { 8994 var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); 8995 8996 pt.x = getAlignedX(vm, vm._footerAlign); 8997 pt.y += vm.footerMarginTop; 8998 8999 ctx.textAlign = rtlHelper.textAlign(vm._footerAlign); 9000 ctx.textBaseline = 'middle'; 9001 9002 footerFontSize = vm.footerFontSize; 9003 9004 ctx.fillStyle = vm.footerFontColor; 9005 ctx.font = helpers$1.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily); 9006 9007 for (i = 0; i < length; ++i) { 9008 ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFontSize / 2); 9009 pt.y += footerFontSize + vm.footerSpacing; 9010 } 9011 } 9012 }, 9013 9014 drawBackground: function(pt, vm, ctx, tooltipSize) { 9015 ctx.fillStyle = vm.backgroundColor; 9016 ctx.strokeStyle = vm.borderColor; 9017 ctx.lineWidth = vm.borderWidth; 9018 var xAlign = vm.xAlign; 9019 var yAlign = vm.yAlign; 9020 var x = pt.x; 9021 var y = pt.y; 9022 var width = tooltipSize.width; 9023 var height = tooltipSize.height; 9024 var radius = vm.cornerRadius; 9025 9026 ctx.beginPath(); 9027 ctx.moveTo(x + radius, y); 9028 if (yAlign === 'top') { 9029 this.drawCaret(pt, tooltipSize); 9030 } 9031 ctx.lineTo(x + width - radius, y); 9032 ctx.quadraticCurveTo(x + width, y, x + width, y + radius); 9033 if (yAlign === 'center' && xAlign === 'right') { 9034 this.drawCaret(pt, tooltipSize); 9035 } 9036 ctx.lineTo(x + width, y + height - radius); 9037 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); 9038 if (yAlign === 'bottom') { 9039 this.drawCaret(pt, tooltipSize); 9040 } 9041 ctx.lineTo(x + radius, y + height); 9042 ctx.quadraticCurveTo(x, y + height, x, y + height - radius); 9043 if (yAlign === 'center' && xAlign === 'left') { 9044 this.drawCaret(pt, tooltipSize); 9045 } 9046 ctx.lineTo(x, y + radius); 9047 ctx.quadraticCurveTo(x, y, x + radius, y); 9048 ctx.closePath(); 9049 9050 ctx.fill(); 9051 9052 if (vm.borderWidth > 0) { 9053 ctx.stroke(); 9054 } 9055 }, 9056 9057 draw: function() { 9058 var ctx = this._chart.ctx; 9059 var vm = this._view; 9060 9061 if (vm.opacity === 0) { 9062 return; 9063 } 9064 9065 var tooltipSize = { 9066 width: vm.width, 9067 height: vm.height 9068 }; 9069 var pt = { 9070 x: vm.x, 9071 y: vm.y 9072 }; 9073 9074 // IE11/Edge does not like very small opacities, so snap to 0 9075 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; 9076 9077 // Truthy/falsey value for empty tooltip 9078 var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; 9079 9080 if (this._options.enabled && hasTooltipContent) { 9081 ctx.save(); 9082 ctx.globalAlpha = opacity; 9083 9084 // Draw Background 9085 this.drawBackground(pt, vm, ctx, tooltipSize); 9086 9087 // Draw Title, Body, and Footer 9088 pt.y += vm.yPadding; 9089 9090 helpers$1.rtl.overrideTextDirection(ctx, vm.textDirection); 9091 9092 // Titles 9093 this.drawTitle(pt, vm, ctx); 9094 9095 // Body 9096 this.drawBody(pt, vm, ctx); 9097 9098 // Footer 9099 this.drawFooter(pt, vm, ctx); 9100 9101 helpers$1.rtl.restoreTextDirection(ctx, vm.textDirection); 9102 9103 ctx.restore(); 9104 } 9105 }, 9106 9107 /** 9108 * Handle an event 9109 * @private 9110 * @param {IEvent} event - The event to handle 9111 * @returns {boolean} true if the tooltip changed 9112 */ 9113 handleEvent: function(e) { 9114 var me = this; 9115 var options = me._options; 9116 var changed = false; 9117 9118 me._lastActive = me._lastActive || []; 9119 9120 // Find Active Elements for tooltips 9121 if (e.type === 'mouseout') { 9122 me._active = []; 9123 } else { 9124 me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); 9125 if (options.reverse) { 9126 me._active.reverse(); 9127 } 9128 } 9129 9130 // Remember Last Actives 9131 changed = !helpers$1.arrayEquals(me._active, me._lastActive); 9132 9133 // Only handle target event on tooltip change 9134 if (changed) { 9135 me._lastActive = me._active; 9136 9137 if (options.enabled || options.custom) { 9138 me._eventPosition = { 9139 x: e.x, 9140 y: e.y 9141 }; 9142 9143 me.update(true); 9144 me.pivot(); 9145 } 9146 } 9147 9148 return changed; 9149 } 9150}); 9151 9152/** 9153 * @namespace Chart.Tooltip.positioners 9154 */ 9155var positioners_1 = positioners; 9156 9157var core_tooltip = exports$4; 9158core_tooltip.positioners = positioners_1; 9159 9160var valueOrDefault$9 = helpers$1.valueOrDefault; 9161 9162core_defaults._set('global', { 9163 elements: {}, 9164 events: [ 9165 'mousemove', 9166 'mouseout', 9167 'click', 9168 'touchstart', 9169 'touchmove' 9170 ], 9171 hover: { 9172 onHover: null, 9173 mode: 'nearest', 9174 intersect: true, 9175 animationDuration: 400 9176 }, 9177 onClick: null, 9178 maintainAspectRatio: true, 9179 responsive: true, 9180 responsiveAnimationDuration: 0 9181}); 9182 9183/** 9184 * Recursively merge the given config objects representing the `scales` option 9185 * by incorporating scale defaults in `xAxes` and `yAxes` array items, then 9186 * returns a deep copy of the result, thus doesn't alter inputs. 9187 */ 9188function mergeScaleConfig(/* config objects ... */) { 9189 return helpers$1.merge(Object.create(null), [].slice.call(arguments), { 9190 merger: function(key, target, source, options) { 9191 if (key === 'xAxes' || key === 'yAxes') { 9192 var slen = source[key].length; 9193 var i, type, scale; 9194 9195 if (!target[key]) { 9196 target[key] = []; 9197 } 9198 9199 for (i = 0; i < slen; ++i) { 9200 scale = source[key][i]; 9201 type = valueOrDefault$9(scale.type, key === 'xAxes' ? 'category' : 'linear'); 9202 9203 if (i >= target[key].length) { 9204 target[key].push({}); 9205 } 9206 9207 if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { 9208 // new/untyped scale or type changed: let's apply the new defaults 9209 // then merge source scale to correctly overwrite the defaults. 9210 helpers$1.merge(target[key][i], [core_scaleService.getScaleDefaults(type), scale]); 9211 } else { 9212 // scales type are the same 9213 helpers$1.merge(target[key][i], scale); 9214 } 9215 } 9216 } else { 9217 helpers$1._merger(key, target, source, options); 9218 } 9219 } 9220 }); 9221} 9222 9223/** 9224 * Recursively merge the given config objects as the root options by handling 9225 * default scale options for the `scales` and `scale` properties, then returns 9226 * a deep copy of the result, thus doesn't alter inputs. 9227 */ 9228function mergeConfig(/* config objects ... */) { 9229 return helpers$1.merge(Object.create(null), [].slice.call(arguments), { 9230 merger: function(key, target, source, options) { 9231 var tval = target[key] || Object.create(null); 9232 var sval = source[key]; 9233 9234 if (key === 'scales') { 9235 // scale config merging is complex. Add our own function here for that 9236 target[key] = mergeScaleConfig(tval, sval); 9237 } else if (key === 'scale') { 9238 // used in polar area & radar charts since there is only one scale 9239 target[key] = helpers$1.merge(tval, [core_scaleService.getScaleDefaults(sval.type), sval]); 9240 } else { 9241 helpers$1._merger(key, target, source, options); 9242 } 9243 } 9244 }); 9245} 9246 9247function initConfig(config) { 9248 config = config || Object.create(null); 9249 9250 // Do NOT use mergeConfig for the data object because this method merges arrays 9251 // and so would change references to labels and datasets, preventing data updates. 9252 var data = config.data = config.data || {}; 9253 data.datasets = data.datasets || []; 9254 data.labels = data.labels || []; 9255 9256 config.options = mergeConfig( 9257 core_defaults.global, 9258 core_defaults[config.type], 9259 config.options || {}); 9260 9261 return config; 9262} 9263 9264function updateConfig(chart) { 9265 var newOptions = chart.options; 9266 9267 helpers$1.each(chart.scales, function(scale) { 9268 core_layouts.removeBox(chart, scale); 9269 }); 9270 9271 newOptions = mergeConfig( 9272 core_defaults.global, 9273 core_defaults[chart.config.type], 9274 newOptions); 9275 9276 chart.options = chart.config.options = newOptions; 9277 chart.ensureScalesHaveIDs(); 9278 chart.buildOrUpdateScales(); 9279 9280 // Tooltip 9281 chart.tooltip._options = newOptions.tooltips; 9282 chart.tooltip.initialize(); 9283} 9284 9285function nextAvailableScaleId(axesOpts, prefix, index) { 9286 var id; 9287 var hasId = function(obj) { 9288 return obj.id === id; 9289 }; 9290 9291 do { 9292 id = prefix + index++; 9293 } while (helpers$1.findIndex(axesOpts, hasId) >= 0); 9294 9295 return id; 9296} 9297 9298function positionIsHorizontal(position) { 9299 return position === 'top' || position === 'bottom'; 9300} 9301 9302function compare2Level(l1, l2) { 9303 return function(a, b) { 9304 return a[l1] === b[l1] 9305 ? a[l2] - b[l2] 9306 : a[l1] - b[l1]; 9307 }; 9308} 9309 9310var Chart = function(item, config) { 9311 this.construct(item, config); 9312 return this; 9313}; 9314 9315helpers$1.extend(Chart.prototype, /** @lends Chart */ { 9316 /** 9317 * @private 9318 */ 9319 construct: function(item, config) { 9320 var me = this; 9321 9322 config = initConfig(config); 9323 9324 var context = platform.acquireContext(item, config); 9325 var canvas = context && context.canvas; 9326 var height = canvas && canvas.height; 9327 var width = canvas && canvas.width; 9328 9329 me.id = helpers$1.uid(); 9330 me.ctx = context; 9331 me.canvas = canvas; 9332 me.config = config; 9333 me.width = width; 9334 me.height = height; 9335 me.aspectRatio = height ? width / height : null; 9336 me.options = config.options; 9337 me._bufferedRender = false; 9338 me._layers = []; 9339 9340 /** 9341 * Provided for backward compatibility, Chart and Chart.Controller have been merged, 9342 * the "instance" still need to be defined since it might be called from plugins. 9343 * @prop Chart#chart 9344 * @deprecated since version 2.6.0 9345 * @todo remove at version 3 9346 * @private 9347 */ 9348 me.chart = me; 9349 me.controller = me; // chart.chart.controller #inception 9350 9351 // Add the chart instance to the global namespace 9352 Chart.instances[me.id] = me; 9353 9354 // Define alias to the config data: `chart.data === chart.config.data` 9355 Object.defineProperty(me, 'data', { 9356 get: function() { 9357 return me.config.data; 9358 }, 9359 set: function(value) { 9360 me.config.data = value; 9361 } 9362 }); 9363 9364 if (!context || !canvas) { 9365 // The given item is not a compatible context2d element, let's return before finalizing 9366 // the chart initialization but after setting basic chart / controller properties that 9367 // can help to figure out that the chart is not valid (e.g chart.canvas !== null); 9368 // https://github.com/chartjs/Chart.js/issues/2807 9369 console.error("Failed to create chart: can't acquire context from the given item"); 9370 return; 9371 } 9372 9373 me.initialize(); 9374 me.update(); 9375 }, 9376 9377 /** 9378 * @private 9379 */ 9380 initialize: function() { 9381 var me = this; 9382 9383 // Before init plugin notification 9384 core_plugins.notify(me, 'beforeInit'); 9385 9386 helpers$1.retinaScale(me, me.options.devicePixelRatio); 9387 9388 me.bindEvents(); 9389 9390 if (me.options.responsive) { 9391 // Initial resize before chart draws (must be silent to preserve initial animations). 9392 me.resize(true); 9393 } 9394 9395 me.initToolTip(); 9396 9397 // After init plugin notification 9398 core_plugins.notify(me, 'afterInit'); 9399 9400 return me; 9401 }, 9402 9403 clear: function() { 9404 helpers$1.canvas.clear(this); 9405 return this; 9406 }, 9407 9408 stop: function() { 9409 // Stops any current animation loop occurring 9410 core_animations.cancelAnimation(this); 9411 return this; 9412 }, 9413 9414 resize: function(silent) { 9415 var me = this; 9416 var options = me.options; 9417 var canvas = me.canvas; 9418 var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; 9419 9420 // the canvas render width and height will be casted to integers so make sure that 9421 // the canvas display style uses the same integer values to avoid blurring effect. 9422 9423 // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed 9424 var newWidth = Math.max(0, Math.floor(helpers$1.getMaximumWidth(canvas))); 9425 var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers$1.getMaximumHeight(canvas))); 9426 9427 if (me.width === newWidth && me.height === newHeight) { 9428 return; 9429 } 9430 9431 canvas.width = me.width = newWidth; 9432 canvas.height = me.height = newHeight; 9433 canvas.style.width = newWidth + 'px'; 9434 canvas.style.height = newHeight + 'px'; 9435 9436 helpers$1.retinaScale(me, options.devicePixelRatio); 9437 9438 if (!silent) { 9439 // Notify any plugins about the resize 9440 var newSize = {width: newWidth, height: newHeight}; 9441 core_plugins.notify(me, 'resize', [newSize]); 9442 9443 // Notify of resize 9444 if (options.onResize) { 9445 options.onResize(me, newSize); 9446 } 9447 9448 me.stop(); 9449 me.update({ 9450 duration: options.responsiveAnimationDuration 9451 }); 9452 } 9453 }, 9454 9455 ensureScalesHaveIDs: function() { 9456 var options = this.options; 9457 var scalesOptions = options.scales || {}; 9458 var scaleOptions = options.scale; 9459 9460 helpers$1.each(scalesOptions.xAxes, function(xAxisOptions, index) { 9461 if (!xAxisOptions.id) { 9462 xAxisOptions.id = nextAvailableScaleId(scalesOptions.xAxes, 'x-axis-', index); 9463 } 9464 }); 9465 9466 helpers$1.each(scalesOptions.yAxes, function(yAxisOptions, index) { 9467 if (!yAxisOptions.id) { 9468 yAxisOptions.id = nextAvailableScaleId(scalesOptions.yAxes, 'y-axis-', index); 9469 } 9470 }); 9471 9472 if (scaleOptions) { 9473 scaleOptions.id = scaleOptions.id || 'scale'; 9474 } 9475 }, 9476 9477 /** 9478 * Builds a map of scale ID to scale object for future lookup. 9479 */ 9480 buildOrUpdateScales: function() { 9481 var me = this; 9482 var options = me.options; 9483 var scales = me.scales || {}; 9484 var items = []; 9485 var updated = Object.keys(scales).reduce(function(obj, id) { 9486 obj[id] = false; 9487 return obj; 9488 }, {}); 9489 9490 if (options.scales) { 9491 items = items.concat( 9492 (options.scales.xAxes || []).map(function(xAxisOptions) { 9493 return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'}; 9494 }), 9495 (options.scales.yAxes || []).map(function(yAxisOptions) { 9496 return {options: yAxisOptions, dtype: 'linear', dposition: 'left'}; 9497 }) 9498 ); 9499 } 9500 9501 if (options.scale) { 9502 items.push({ 9503 options: options.scale, 9504 dtype: 'radialLinear', 9505 isDefault: true, 9506 dposition: 'chartArea' 9507 }); 9508 } 9509 9510 helpers$1.each(items, function(item) { 9511 var scaleOptions = item.options; 9512 var id = scaleOptions.id; 9513 var scaleType = valueOrDefault$9(scaleOptions.type, item.dtype); 9514 9515 if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { 9516 scaleOptions.position = item.dposition; 9517 } 9518 9519 updated[id] = true; 9520 var scale = null; 9521 if (id in scales && scales[id].type === scaleType) { 9522 scale = scales[id]; 9523 scale.options = scaleOptions; 9524 scale.ctx = me.ctx; 9525 scale.chart = me; 9526 } else { 9527 var scaleClass = core_scaleService.getScaleConstructor(scaleType); 9528 if (!scaleClass) { 9529 return; 9530 } 9531 scale = new scaleClass({ 9532 id: id, 9533 type: scaleType, 9534 options: scaleOptions, 9535 ctx: me.ctx, 9536 chart: me 9537 }); 9538 scales[scale.id] = scale; 9539 } 9540 9541 scale.mergeTicksOptions(); 9542 9543 // TODO(SB): I think we should be able to remove this custom case (options.scale) 9544 // and consider it as a regular scale part of the "scales"" map only! This would 9545 // make the logic easier and remove some useless? custom code. 9546 if (item.isDefault) { 9547 me.scale = scale; 9548 } 9549 }); 9550 // clear up discarded scales 9551 helpers$1.each(updated, function(hasUpdated, id) { 9552 if (!hasUpdated) { 9553 delete scales[id]; 9554 } 9555 }); 9556 9557 me.scales = scales; 9558 9559 core_scaleService.addScalesToLayout(this); 9560 }, 9561 9562 buildOrUpdateControllers: function() { 9563 var me = this; 9564 var newControllers = []; 9565 var datasets = me.data.datasets; 9566 var i, ilen; 9567 9568 for (i = 0, ilen = datasets.length; i < ilen; i++) { 9569 var dataset = datasets[i]; 9570 var meta = me.getDatasetMeta(i); 9571 var type = dataset.type || me.config.type; 9572 9573 if (meta.type && meta.type !== type) { 9574 me.destroyDatasetMeta(i); 9575 meta = me.getDatasetMeta(i); 9576 } 9577 meta.type = type; 9578 meta.order = dataset.order || 0; 9579 meta.index = i; 9580 9581 if (meta.controller) { 9582 meta.controller.updateIndex(i); 9583 meta.controller.linkScales(); 9584 } else { 9585 var ControllerClass = controllers[meta.type]; 9586 if (ControllerClass === undefined) { 9587 throw new Error('"' + meta.type + '" is not a chart type.'); 9588 } 9589 9590 meta.controller = new ControllerClass(me, i); 9591 newControllers.push(meta.controller); 9592 } 9593 } 9594 9595 return newControllers; 9596 }, 9597 9598 /** 9599 * Reset the elements of all datasets 9600 * @private 9601 */ 9602 resetElements: function() { 9603 var me = this; 9604 helpers$1.each(me.data.datasets, function(dataset, datasetIndex) { 9605 me.getDatasetMeta(datasetIndex).controller.reset(); 9606 }, me); 9607 }, 9608 9609 /** 9610 * Resets the chart back to it's state before the initial animation 9611 */ 9612 reset: function() { 9613 this.resetElements(); 9614 this.tooltip.initialize(); 9615 }, 9616 9617 update: function(config) { 9618 var me = this; 9619 var i, ilen; 9620 9621 if (!config || typeof config !== 'object') { 9622 // backwards compatibility 9623 config = { 9624 duration: config, 9625 lazy: arguments[1] 9626 }; 9627 } 9628 9629 updateConfig(me); 9630 9631 // plugins options references might have change, let's invalidate the cache 9632 // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 9633 core_plugins._invalidate(me); 9634 9635 if (core_plugins.notify(me, 'beforeUpdate') === false) { 9636 return; 9637 } 9638 9639 // In case the entire data object changed 9640 me.tooltip._data = me.data; 9641 9642 // Make sure dataset controllers are updated and new controllers are reset 9643 var newControllers = me.buildOrUpdateControllers(); 9644 9645 // Make sure all dataset controllers have correct meta data counts 9646 for (i = 0, ilen = me.data.datasets.length; i < ilen; i++) { 9647 me.getDatasetMeta(i).controller.buildOrUpdateElements(); 9648 } 9649 9650 me.updateLayout(); 9651 9652 // Can only reset the new controllers after the scales have been updated 9653 if (me.options.animation && me.options.animation.duration) { 9654 helpers$1.each(newControllers, function(controller) { 9655 controller.reset(); 9656 }); 9657 } 9658 9659 me.updateDatasets(); 9660 9661 // Need to reset tooltip in case it is displayed with elements that are removed 9662 // after update. 9663 me.tooltip.initialize(); 9664 9665 // Last active contains items that were previously in the tooltip. 9666 // When we reset the tooltip, we need to clear it 9667 me.lastActive = []; 9668 9669 // Do this before render so that any plugins that need final scale updates can use it 9670 core_plugins.notify(me, 'afterUpdate'); 9671 9672 me._layers.sort(compare2Level('z', '_idx')); 9673 9674 if (me._bufferedRender) { 9675 me._bufferedRequest = { 9676 duration: config.duration, 9677 easing: config.easing, 9678 lazy: config.lazy 9679 }; 9680 } else { 9681 me.render(config); 9682 } 9683 }, 9684 9685 /** 9686 * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` 9687 * hook, in which case, plugins will not be called on `afterLayout`. 9688 * @private 9689 */ 9690 updateLayout: function() { 9691 var me = this; 9692 9693 if (core_plugins.notify(me, 'beforeLayout') === false) { 9694 return; 9695 } 9696 9697 core_layouts.update(this, this.width, this.height); 9698 9699 me._layers = []; 9700 helpers$1.each(me.boxes, function(box) { 9701 // _configure is called twice, once in core.scale.update and once here. 9702 // Here the boxes are fully updated and at their final positions. 9703 if (box._configure) { 9704 box._configure(); 9705 } 9706 me._layers.push.apply(me._layers, box._layers()); 9707 }, me); 9708 9709 me._layers.forEach(function(item, index) { 9710 item._idx = index; 9711 }); 9712 9713 /** 9714 * Provided for backward compatibility, use `afterLayout` instead. 9715 * @method IPlugin#afterScaleUpdate 9716 * @deprecated since version 2.5.0 9717 * @todo remove at version 3 9718 * @private 9719 */ 9720 core_plugins.notify(me, 'afterScaleUpdate'); 9721 core_plugins.notify(me, 'afterLayout'); 9722 }, 9723 9724 /** 9725 * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` 9726 * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. 9727 * @private 9728 */ 9729 updateDatasets: function() { 9730 var me = this; 9731 9732 if (core_plugins.notify(me, 'beforeDatasetsUpdate') === false) { 9733 return; 9734 } 9735 9736 for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { 9737 me.updateDataset(i); 9738 } 9739 9740 core_plugins.notify(me, 'afterDatasetsUpdate'); 9741 }, 9742 9743 /** 9744 * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` 9745 * hook, in which case, plugins will not be called on `afterDatasetUpdate`. 9746 * @private 9747 */ 9748 updateDataset: function(index) { 9749 var me = this; 9750 var meta = me.getDatasetMeta(index); 9751 var args = { 9752 meta: meta, 9753 index: index 9754 }; 9755 9756 if (core_plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { 9757 return; 9758 } 9759 9760 meta.controller._update(); 9761 9762 core_plugins.notify(me, 'afterDatasetUpdate', [args]); 9763 }, 9764 9765 render: function(config) { 9766 var me = this; 9767 9768 if (!config || typeof config !== 'object') { 9769 // backwards compatibility 9770 config = { 9771 duration: config, 9772 lazy: arguments[1] 9773 }; 9774 } 9775 9776 var animationOptions = me.options.animation; 9777 var duration = valueOrDefault$9(config.duration, animationOptions && animationOptions.duration); 9778 var lazy = config.lazy; 9779 9780 if (core_plugins.notify(me, 'beforeRender') === false) { 9781 return; 9782 } 9783 9784 var onComplete = function(animation) { 9785 core_plugins.notify(me, 'afterRender'); 9786 helpers$1.callback(animationOptions && animationOptions.onComplete, [animation], me); 9787 }; 9788 9789 if (animationOptions && duration) { 9790 var animation = new core_animation({ 9791 numSteps: duration / 16.66, // 60 fps 9792 easing: config.easing || animationOptions.easing, 9793 9794 render: function(chart, animationObject) { 9795 var easingFunction = helpers$1.easing.effects[animationObject.easing]; 9796 var currentStep = animationObject.currentStep; 9797 var stepDecimal = currentStep / animationObject.numSteps; 9798 9799 chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); 9800 }, 9801 9802 onAnimationProgress: animationOptions.onProgress, 9803 onAnimationComplete: onComplete 9804 }); 9805 9806 core_animations.addAnimation(me, animation, duration, lazy); 9807 } else { 9808 me.draw(); 9809 9810 // See https://github.com/chartjs/Chart.js/issues/3781 9811 onComplete(new core_animation({numSteps: 0, chart: me})); 9812 } 9813 9814 return me; 9815 }, 9816 9817 draw: function(easingValue) { 9818 var me = this; 9819 var i, layers; 9820 9821 me.clear(); 9822 9823 if (helpers$1.isNullOrUndef(easingValue)) { 9824 easingValue = 1; 9825 } 9826 9827 me.transition(easingValue); 9828 9829 if (me.width <= 0 || me.height <= 0) { 9830 return; 9831 } 9832 9833 if (core_plugins.notify(me, 'beforeDraw', [easingValue]) === false) { 9834 return; 9835 } 9836 9837 // Because of plugin hooks (before/afterDatasetsDraw), datasets can't 9838 // currently be part of layers. Instead, we draw 9839 // layers <= 0 before(default, backward compat), and the rest after 9840 layers = me._layers; 9841 for (i = 0; i < layers.length && layers[i].z <= 0; ++i) { 9842 layers[i].draw(me.chartArea); 9843 } 9844 9845 me.drawDatasets(easingValue); 9846 9847 // Rest of layers 9848 for (; i < layers.length; ++i) { 9849 layers[i].draw(me.chartArea); 9850 } 9851 9852 me._drawTooltip(easingValue); 9853 9854 core_plugins.notify(me, 'afterDraw', [easingValue]); 9855 }, 9856 9857 /** 9858 * @private 9859 */ 9860 transition: function(easingValue) { 9861 var me = this; 9862 9863 for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { 9864 if (me.isDatasetVisible(i)) { 9865 me.getDatasetMeta(i).controller.transition(easingValue); 9866 } 9867 } 9868 9869 me.tooltip.transition(easingValue); 9870 }, 9871 9872 /** 9873 * @private 9874 */ 9875 _getSortedDatasetMetas: function(filterVisible) { 9876 var me = this; 9877 var datasets = me.data.datasets || []; 9878 var result = []; 9879 var i, ilen; 9880 9881 for (i = 0, ilen = datasets.length; i < ilen; ++i) { 9882 if (!filterVisible || me.isDatasetVisible(i)) { 9883 result.push(me.getDatasetMeta(i)); 9884 } 9885 } 9886 9887 result.sort(compare2Level('order', 'index')); 9888 9889 return result; 9890 }, 9891 9892 /** 9893 * @private 9894 */ 9895 _getSortedVisibleDatasetMetas: function() { 9896 return this._getSortedDatasetMetas(true); 9897 }, 9898 9899 /** 9900 * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` 9901 * hook, in which case, plugins will not be called on `afterDatasetsDraw`. 9902 * @private 9903 */ 9904 drawDatasets: function(easingValue) { 9905 var me = this; 9906 var metasets, i; 9907 9908 if (core_plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { 9909 return; 9910 } 9911 9912 metasets = me._getSortedVisibleDatasetMetas(); 9913 for (i = metasets.length - 1; i >= 0; --i) { 9914 me.drawDataset(metasets[i], easingValue); 9915 } 9916 9917 core_plugins.notify(me, 'afterDatasetsDraw', [easingValue]); 9918 }, 9919 9920 /** 9921 * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` 9922 * hook, in which case, plugins will not be called on `afterDatasetDraw`. 9923 * @private 9924 */ 9925 drawDataset: function(meta, easingValue) { 9926 var me = this; 9927 var args = { 9928 meta: meta, 9929 index: meta.index, 9930 easingValue: easingValue 9931 }; 9932 9933 if (core_plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { 9934 return; 9935 } 9936 9937 meta.controller.draw(easingValue); 9938 9939 core_plugins.notify(me, 'afterDatasetDraw', [args]); 9940 }, 9941 9942 /** 9943 * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` 9944 * hook, in which case, plugins will not be called on `afterTooltipDraw`. 9945 * @private 9946 */ 9947 _drawTooltip: function(easingValue) { 9948 var me = this; 9949 var tooltip = me.tooltip; 9950 var args = { 9951 tooltip: tooltip, 9952 easingValue: easingValue 9953 }; 9954 9955 if (core_plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { 9956 return; 9957 } 9958 9959 tooltip.draw(); 9960 9961 core_plugins.notify(me, 'afterTooltipDraw', [args]); 9962 }, 9963 9964 /** 9965 * Get the single element that was clicked on 9966 * @return An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw 9967 */ 9968 getElementAtEvent: function(e) { 9969 return core_interaction.modes.single(this, e); 9970 }, 9971 9972 getElementsAtEvent: function(e) { 9973 return core_interaction.modes.label(this, e, {intersect: true}); 9974 }, 9975 9976 getElementsAtXAxis: function(e) { 9977 return core_interaction.modes['x-axis'](this, e, {intersect: true}); 9978 }, 9979 9980 getElementsAtEventForMode: function(e, mode, options) { 9981 var method = core_interaction.modes[mode]; 9982 if (typeof method === 'function') { 9983 return method(this, e, options); 9984 } 9985 9986 return []; 9987 }, 9988 9989 getDatasetAtEvent: function(e) { 9990 return core_interaction.modes.dataset(this, e, {intersect: true}); 9991 }, 9992 9993 getDatasetMeta: function(datasetIndex) { 9994 var me = this; 9995 var dataset = me.data.datasets[datasetIndex]; 9996 if (!dataset._meta) { 9997 dataset._meta = {}; 9998 } 9999 10000 var meta = dataset._meta[me.id]; 10001 if (!meta) { 10002 meta = dataset._meta[me.id] = { 10003 type: null, 10004 data: [], 10005 dataset: null, 10006 controller: null, 10007 hidden: null, // See isDatasetVisible() comment 10008 xAxisID: null, 10009 yAxisID: null, 10010 order: dataset.order || 0, 10011 index: datasetIndex 10012 }; 10013 } 10014 10015 return meta; 10016 }, 10017 10018 getVisibleDatasetCount: function() { 10019 var count = 0; 10020 for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { 10021 if (this.isDatasetVisible(i)) { 10022 count++; 10023 } 10024 } 10025 return count; 10026 }, 10027 10028 isDatasetVisible: function(datasetIndex) { 10029 var meta = this.getDatasetMeta(datasetIndex); 10030 10031 // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, 10032 // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. 10033 return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; 10034 }, 10035 10036 generateLegend: function() { 10037 return this.options.legendCallback(this); 10038 }, 10039 10040 /** 10041 * @private 10042 */ 10043 destroyDatasetMeta: function(datasetIndex) { 10044 var id = this.id; 10045 var dataset = this.data.datasets[datasetIndex]; 10046 var meta = dataset._meta && dataset._meta[id]; 10047 10048 if (meta) { 10049 meta.controller.destroy(); 10050 delete dataset._meta[id]; 10051 } 10052 }, 10053 10054 destroy: function() { 10055 var me = this; 10056 var canvas = me.canvas; 10057 var i, ilen; 10058 10059 me.stop(); 10060 10061 // dataset controllers need to cleanup associated data 10062 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { 10063 me.destroyDatasetMeta(i); 10064 } 10065 10066 if (canvas) { 10067 me.unbindEvents(); 10068 helpers$1.canvas.clear(me); 10069 platform.releaseContext(me.ctx); 10070 me.canvas = null; 10071 me.ctx = null; 10072 } 10073 10074 core_plugins.notify(me, 'destroy'); 10075 10076 delete Chart.instances[me.id]; 10077 }, 10078 10079 toBase64Image: function() { 10080 return this.canvas.toDataURL.apply(this.canvas, arguments); 10081 }, 10082 10083 initToolTip: function() { 10084 var me = this; 10085 me.tooltip = new core_tooltip({ 10086 _chart: me, 10087 _chartInstance: me, // deprecated, backward compatibility 10088 _data: me.data, 10089 _options: me.options.tooltips 10090 }, me); 10091 }, 10092 10093 /** 10094 * @private 10095 */ 10096 bindEvents: function() { 10097 var me = this; 10098 var listeners = me._listeners = {}; 10099 var listener = function() { 10100 me.eventHandler.apply(me, arguments); 10101 }; 10102 10103 helpers$1.each(me.options.events, function(type) { 10104 platform.addEventListener(me, type, listener); 10105 listeners[type] = listener; 10106 }); 10107 10108 // Elements used to detect size change should not be injected for non responsive charts. 10109 // See https://github.com/chartjs/Chart.js/issues/2210 10110 if (me.options.responsive) { 10111 listener = function() { 10112 me.resize(); 10113 }; 10114 10115 platform.addEventListener(me, 'resize', listener); 10116 listeners.resize = listener; 10117 } 10118 }, 10119 10120 /** 10121 * @private 10122 */ 10123 unbindEvents: function() { 10124 var me = this; 10125 var listeners = me._listeners; 10126 if (!listeners) { 10127 return; 10128 } 10129 10130 delete me._listeners; 10131 helpers$1.each(listeners, function(listener, type) { 10132 platform.removeEventListener(me, type, listener); 10133 }); 10134 }, 10135 10136 updateHoverStyle: function(elements, mode, enabled) { 10137 var prefix = enabled ? 'set' : 'remove'; 10138 var element, i, ilen; 10139 10140 for (i = 0, ilen = elements.length; i < ilen; ++i) { 10141 element = elements[i]; 10142 if (element) { 10143 this.getDatasetMeta(element._datasetIndex).controller[prefix + 'HoverStyle'](element); 10144 } 10145 } 10146 10147 if (mode === 'dataset') { 10148 this.getDatasetMeta(elements[0]._datasetIndex).controller['_' + prefix + 'DatasetHoverStyle'](); 10149 } 10150 }, 10151 10152 /** 10153 * @private 10154 */ 10155 eventHandler: function(e) { 10156 var me = this; 10157 var tooltip = me.tooltip; 10158 10159 if (core_plugins.notify(me, 'beforeEvent', [e]) === false) { 10160 return; 10161 } 10162 10163 // Buffer any update calls so that renders do not occur 10164 me._bufferedRender = true; 10165 me._bufferedRequest = null; 10166 10167 var changed = me.handleEvent(e); 10168 // for smooth tooltip animations issue #4989 10169 // the tooltip should be the source of change 10170 // Animation check workaround: 10171 // tooltip._start will be null when tooltip isn't animating 10172 if (tooltip) { 10173 changed = tooltip._start 10174 ? tooltip.handleEvent(e) 10175 : changed | tooltip.handleEvent(e); 10176 } 10177 10178 core_plugins.notify(me, 'afterEvent', [e]); 10179 10180 var bufferedRequest = me._bufferedRequest; 10181 if (bufferedRequest) { 10182 // If we have an update that was triggered, we need to do a normal render 10183 me.render(bufferedRequest); 10184 } else if (changed && !me.animating) { 10185 // If entering, leaving, or changing elements, animate the change via pivot 10186 me.stop(); 10187 10188 // We only need to render at this point. Updating will cause scales to be 10189 // recomputed generating flicker & using more memory than necessary. 10190 me.render({ 10191 duration: me.options.hover.animationDuration, 10192 lazy: true 10193 }); 10194 } 10195 10196 me._bufferedRender = false; 10197 me._bufferedRequest = null; 10198 10199 return me; 10200 }, 10201 10202 /** 10203 * Handle an event 10204 * @private 10205 * @param {IEvent} event the event to handle 10206 * @return {boolean} true if the chart needs to re-render 10207 */ 10208 handleEvent: function(e) { 10209 var me = this; 10210 var options = me.options || {}; 10211 var hoverOptions = options.hover; 10212 var changed = false; 10213 10214 me.lastActive = me.lastActive || []; 10215 10216 // Find Active Elements for hover and tooltips 10217 if (e.type === 'mouseout') { 10218 me.active = []; 10219 } else { 10220 me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); 10221 } 10222 10223 // Invoke onHover hook 10224 // Need to call with native event here to not break backwards compatibility 10225 helpers$1.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); 10226 10227 if (e.type === 'mouseup' || e.type === 'click') { 10228 if (options.onClick) { 10229 // Use e.native here for backwards compatibility 10230 options.onClick.call(me, e.native, me.active); 10231 } 10232 } 10233 10234 // Remove styling for last active (even if it may still be active) 10235 if (me.lastActive.length) { 10236 me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); 10237 } 10238 10239 // Built in hover styling 10240 if (me.active.length && hoverOptions.mode) { 10241 me.updateHoverStyle(me.active, hoverOptions.mode, true); 10242 } 10243 10244 changed = !helpers$1.arrayEquals(me.active, me.lastActive); 10245 10246 // Remember Last Actives 10247 me.lastActive = me.active; 10248 10249 return changed; 10250 } 10251}); 10252 10253/** 10254 * NOTE(SB) We actually don't use this container anymore but we need to keep it 10255 * for backward compatibility. Though, it can still be useful for plugins that 10256 * would need to work on multiple charts?! 10257 */ 10258Chart.instances = {}; 10259 10260var core_controller = Chart; 10261 10262// DEPRECATIONS 10263 10264/** 10265 * Provided for backward compatibility, use Chart instead. 10266 * @class Chart.Controller 10267 * @deprecated since version 2.6 10268 * @todo remove at version 3 10269 * @private 10270 */ 10271Chart.Controller = Chart; 10272 10273/** 10274 * Provided for backward compatibility, not available anymore. 10275 * @namespace Chart 10276 * @deprecated since version 2.8 10277 * @todo remove at version 3 10278 * @private 10279 */ 10280Chart.types = {}; 10281 10282/** 10283 * Provided for backward compatibility, not available anymore. 10284 * @namespace Chart.helpers.configMerge 10285 * @deprecated since version 2.8.0 10286 * @todo remove at version 3 10287 * @private 10288 */ 10289helpers$1.configMerge = mergeConfig; 10290 10291/** 10292 * Provided for backward compatibility, not available anymore. 10293 * @namespace Chart.helpers.scaleMerge 10294 * @deprecated since version 2.8.0 10295 * @todo remove at version 3 10296 * @private 10297 */ 10298helpers$1.scaleMerge = mergeScaleConfig; 10299 10300var core_helpers = function() { 10301 10302 // -- Basic js utility methods 10303 10304 helpers$1.where = function(collection, filterCallback) { 10305 if (helpers$1.isArray(collection) && Array.prototype.filter) { 10306 return collection.filter(filterCallback); 10307 } 10308 var filtered = []; 10309 10310 helpers$1.each(collection, function(item) { 10311 if (filterCallback(item)) { 10312 filtered.push(item); 10313 } 10314 }); 10315 10316 return filtered; 10317 }; 10318 helpers$1.findIndex = Array.prototype.findIndex ? 10319 function(array, callback, scope) { 10320 return array.findIndex(callback, scope); 10321 } : 10322 function(array, callback, scope) { 10323 scope = scope === undefined ? array : scope; 10324 for (var i = 0, ilen = array.length; i < ilen; ++i) { 10325 if (callback.call(scope, array[i], i, array)) { 10326 return i; 10327 } 10328 } 10329 return -1; 10330 }; 10331 helpers$1.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { 10332 // Default to start of the array 10333 if (helpers$1.isNullOrUndef(startIndex)) { 10334 startIndex = -1; 10335 } 10336 for (var i = startIndex + 1; i < arrayToSearch.length; i++) { 10337 var currentItem = arrayToSearch[i]; 10338 if (filterCallback(currentItem)) { 10339 return currentItem; 10340 } 10341 } 10342 }; 10343 helpers$1.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { 10344 // Default to end of the array 10345 if (helpers$1.isNullOrUndef(startIndex)) { 10346 startIndex = arrayToSearch.length; 10347 } 10348 for (var i = startIndex - 1; i >= 0; i--) { 10349 var currentItem = arrayToSearch[i]; 10350 if (filterCallback(currentItem)) { 10351 return currentItem; 10352 } 10353 } 10354 }; 10355 10356 // -- Math methods 10357 helpers$1.isNumber = function(n) { 10358 return !isNaN(parseFloat(n)) && isFinite(n); 10359 }; 10360 helpers$1.almostEquals = function(x, y, epsilon) { 10361 return Math.abs(x - y) < epsilon; 10362 }; 10363 helpers$1.almostWhole = function(x, epsilon) { 10364 var rounded = Math.round(x); 10365 return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x); 10366 }; 10367 helpers$1.max = function(array) { 10368 return array.reduce(function(max, value) { 10369 if (!isNaN(value)) { 10370 return Math.max(max, value); 10371 } 10372 return max; 10373 }, Number.NEGATIVE_INFINITY); 10374 }; 10375 helpers$1.min = function(array) { 10376 return array.reduce(function(min, value) { 10377 if (!isNaN(value)) { 10378 return Math.min(min, value); 10379 } 10380 return min; 10381 }, Number.POSITIVE_INFINITY); 10382 }; 10383 helpers$1.sign = Math.sign ? 10384 function(x) { 10385 return Math.sign(x); 10386 } : 10387 function(x) { 10388 x = +x; // convert to a number 10389 if (x === 0 || isNaN(x)) { 10390 return x; 10391 } 10392 return x > 0 ? 1 : -1; 10393 }; 10394 helpers$1.toRadians = function(degrees) { 10395 return degrees * (Math.PI / 180); 10396 }; 10397 helpers$1.toDegrees = function(radians) { 10398 return radians * (180 / Math.PI); 10399 }; 10400 10401 /** 10402 * Returns the number of decimal places 10403 * i.e. the number of digits after the decimal point, of the value of this Number. 10404 * @param {number} x - A number. 10405 * @returns {number} The number of decimal places. 10406 * @private 10407 */ 10408 helpers$1._decimalPlaces = function(x) { 10409 if (!helpers$1.isFinite(x)) { 10410 return; 10411 } 10412 var e = 1; 10413 var p = 0; 10414 while (Math.round(x * e) / e !== x) { 10415 e *= 10; 10416 p++; 10417 } 10418 return p; 10419 }; 10420 10421 // Gets the angle from vertical upright to the point about a centre. 10422 helpers$1.getAngleFromPoint = function(centrePoint, anglePoint) { 10423 var distanceFromXCenter = anglePoint.x - centrePoint.x; 10424 var distanceFromYCenter = anglePoint.y - centrePoint.y; 10425 var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); 10426 10427 var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); 10428 10429 if (angle < (-0.5 * Math.PI)) { 10430 angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] 10431 } 10432 10433 return { 10434 angle: angle, 10435 distance: radialDistanceFromCenter 10436 }; 10437 }; 10438 helpers$1.distanceBetweenPoints = function(pt1, pt2) { 10439 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); 10440 }; 10441 10442 /** 10443 * Provided for backward compatibility, not available anymore 10444 * @function Chart.helpers.aliasPixel 10445 * @deprecated since version 2.8.0 10446 * @todo remove at version 3 10447 */ 10448 helpers$1.aliasPixel = function(pixelWidth) { 10449 return (pixelWidth % 2 === 0) ? 0 : 0.5; 10450 }; 10451 10452 /** 10453 * Returns the aligned pixel value to avoid anti-aliasing blur 10454 * @param {Chart} chart - The chart instance. 10455 * @param {number} pixel - A pixel value. 10456 * @param {number} width - The width of the element. 10457 * @returns {number} The aligned pixel value. 10458 * @private 10459 */ 10460 helpers$1._alignPixel = function(chart, pixel, width) { 10461 var devicePixelRatio = chart.currentDevicePixelRatio; 10462 var halfWidth = width / 2; 10463 return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth; 10464 }; 10465 10466 helpers$1.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { 10467 // Props to Rob Spencer at scaled innovation for his post on splining between points 10468 // http://scaledinnovation.com/analytics/splines/aboutSplines.html 10469 10470 // This function must also respect "skipped" points 10471 10472 var previous = firstPoint.skip ? middlePoint : firstPoint; 10473 var current = middlePoint; 10474 var next = afterPoint.skip ? middlePoint : afterPoint; 10475 10476 var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); 10477 var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); 10478 10479 var s01 = d01 / (d01 + d12); 10480 var s12 = d12 / (d01 + d12); 10481 10482 // If all points are the same, s01 & s02 will be inf 10483 s01 = isNaN(s01) ? 0 : s01; 10484 s12 = isNaN(s12) ? 0 : s12; 10485 10486 var fa = t * s01; // scaling factor for triangle Ta 10487 var fb = t * s12; 10488 10489 return { 10490 previous: { 10491 x: current.x - fa * (next.x - previous.x), 10492 y: current.y - fa * (next.y - previous.y) 10493 }, 10494 next: { 10495 x: current.x + fb * (next.x - previous.x), 10496 y: current.y + fb * (next.y - previous.y) 10497 } 10498 }; 10499 }; 10500 helpers$1.EPSILON = Number.EPSILON || 1e-14; 10501 helpers$1.splineCurveMonotone = function(points) { 10502 // This function calculates Bézier control points in a similar way than |splineCurve|, 10503 // but preserves monotonicity of the provided data and ensures no local extremums are added 10504 // between the dataset discrete points due to the interpolation. 10505 // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation 10506 10507 var pointsWithTangents = (points || []).map(function(point) { 10508 return { 10509 model: point._model, 10510 deltaK: 0, 10511 mK: 0 10512 }; 10513 }); 10514 10515 // Calculate slopes (deltaK) and initialize tangents (mK) 10516 var pointsLen = pointsWithTangents.length; 10517 var i, pointBefore, pointCurrent, pointAfter; 10518 for (i = 0; i < pointsLen; ++i) { 10519 pointCurrent = pointsWithTangents[i]; 10520 if (pointCurrent.model.skip) { 10521 continue; 10522 } 10523 10524 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; 10525 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; 10526 if (pointAfter && !pointAfter.model.skip) { 10527 var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); 10528 10529 // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 10530 pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; 10531 } 10532 10533 if (!pointBefore || pointBefore.model.skip) { 10534 pointCurrent.mK = pointCurrent.deltaK; 10535 } else if (!pointAfter || pointAfter.model.skip) { 10536 pointCurrent.mK = pointBefore.deltaK; 10537 } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { 10538 pointCurrent.mK = 0; 10539 } else { 10540 pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; 10541 } 10542 } 10543 10544 // Adjust tangents to ensure monotonic properties 10545 var alphaK, betaK, tauK, squaredMagnitude; 10546 for (i = 0; i < pointsLen - 1; ++i) { 10547 pointCurrent = pointsWithTangents[i]; 10548 pointAfter = pointsWithTangents[i + 1]; 10549 if (pointCurrent.model.skip || pointAfter.model.skip) { 10550 continue; 10551 } 10552 10553 if (helpers$1.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { 10554 pointCurrent.mK = pointAfter.mK = 0; 10555 continue; 10556 } 10557 10558 alphaK = pointCurrent.mK / pointCurrent.deltaK; 10559 betaK = pointAfter.mK / pointCurrent.deltaK; 10560 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); 10561 if (squaredMagnitude <= 9) { 10562 continue; 10563 } 10564 10565 tauK = 3 / Math.sqrt(squaredMagnitude); 10566 pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; 10567 pointAfter.mK = betaK * tauK * pointCurrent.deltaK; 10568 } 10569 10570 // Compute control points 10571 var deltaX; 10572 for (i = 0; i < pointsLen; ++i) { 10573 pointCurrent = pointsWithTangents[i]; 10574 if (pointCurrent.model.skip) { 10575 continue; 10576 } 10577 10578 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; 10579 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; 10580 if (pointBefore && !pointBefore.model.skip) { 10581 deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; 10582 pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; 10583 pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; 10584 } 10585 if (pointAfter && !pointAfter.model.skip) { 10586 deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; 10587 pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; 10588 pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; 10589 } 10590 } 10591 }; 10592 helpers$1.nextItem = function(collection, index, loop) { 10593 if (loop) { 10594 return index >= collection.length - 1 ? collection[0] : collection[index + 1]; 10595 } 10596 return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; 10597 }; 10598 helpers$1.previousItem = function(collection, index, loop) { 10599 if (loop) { 10600 return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; 10601 } 10602 return index <= 0 ? collection[0] : collection[index - 1]; 10603 }; 10604 // Implementation of the nice number algorithm used in determining where axis labels will go 10605 helpers$1.niceNum = function(range, round) { 10606 var exponent = Math.floor(helpers$1.log10(range)); 10607 var fraction = range / Math.pow(10, exponent); 10608 var niceFraction; 10609 10610 if (round) { 10611 if (fraction < 1.5) { 10612 niceFraction = 1; 10613 } else if (fraction < 3) { 10614 niceFraction = 2; 10615 } else if (fraction < 7) { 10616 niceFraction = 5; 10617 } else { 10618 niceFraction = 10; 10619 } 10620 } else if (fraction <= 1.0) { 10621 niceFraction = 1; 10622 } else if (fraction <= 2) { 10623 niceFraction = 2; 10624 } else if (fraction <= 5) { 10625 niceFraction = 5; 10626 } else { 10627 niceFraction = 10; 10628 } 10629 10630 return niceFraction * Math.pow(10, exponent); 10631 }; 10632 // Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ 10633 helpers$1.requestAnimFrame = (function() { 10634 if (typeof window === 'undefined') { 10635 return function(callback) { 10636 callback(); 10637 }; 10638 } 10639 return window.requestAnimationFrame || 10640 window.webkitRequestAnimationFrame || 10641 window.mozRequestAnimationFrame || 10642 window.oRequestAnimationFrame || 10643 window.msRequestAnimationFrame || 10644 function(callback) { 10645 return window.setTimeout(callback, 1000 / 60); 10646 }; 10647 }()); 10648 // -- DOM methods 10649 helpers$1.getRelativePosition = function(evt, chart) { 10650 var mouseX, mouseY; 10651 var e = evt.originalEvent || evt; 10652 var canvas = evt.target || evt.srcElement; 10653 var boundingRect = canvas.getBoundingClientRect(); 10654 10655 var touches = e.touches; 10656 if (touches && touches.length > 0) { 10657 mouseX = touches[0].clientX; 10658 mouseY = touches[0].clientY; 10659 10660 } else { 10661 mouseX = e.clientX; 10662 mouseY = e.clientY; 10663 } 10664 10665 // Scale mouse coordinates into canvas coordinates 10666 // by following the pattern laid out by 'jerryj' in the comments of 10667 // https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ 10668 var paddingLeft = parseFloat(helpers$1.getStyle(canvas, 'padding-left')); 10669 var paddingTop = parseFloat(helpers$1.getStyle(canvas, 'padding-top')); 10670 var paddingRight = parseFloat(helpers$1.getStyle(canvas, 'padding-right')); 10671 var paddingBottom = parseFloat(helpers$1.getStyle(canvas, 'padding-bottom')); 10672 var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; 10673 var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; 10674 10675 // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However 10676 // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here 10677 mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); 10678 mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); 10679 10680 return { 10681 x: mouseX, 10682 y: mouseY 10683 }; 10684 10685 }; 10686 10687 // Private helper function to convert max-width/max-height values that may be percentages into a number 10688 function parseMaxStyle(styleValue, node, parentProperty) { 10689 var valueInPixels; 10690 if (typeof styleValue === 'string') { 10691 valueInPixels = parseInt(styleValue, 10); 10692 10693 if (styleValue.indexOf('%') !== -1) { 10694 // percentage * size in dimension 10695 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; 10696 } 10697 } else { 10698 valueInPixels = styleValue; 10699 } 10700 10701 return valueInPixels; 10702 } 10703 10704 /** 10705 * Returns if the given value contains an effective constraint. 10706 * @private 10707 */ 10708 function isConstrainedValue(value) { 10709 return value !== undefined && value !== null && value !== 'none'; 10710 } 10711 10712 /** 10713 * Returns the max width or height of the given DOM node in a cross-browser compatible fashion 10714 * @param {HTMLElement} domNode - the node to check the constraint on 10715 * @param {string} maxStyle - the style that defines the maximum for the direction we are using ('max-width' / 'max-height') 10716 * @param {string} percentageProperty - property of parent to use when calculating width as a percentage 10717 * @see {@link https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser} 10718 */ 10719 function getConstraintDimension(domNode, maxStyle, percentageProperty) { 10720 var view = document.defaultView; 10721 var parentNode = helpers$1._getParentNode(domNode); 10722 var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; 10723 var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; 10724 var hasCNode = isConstrainedValue(constrainedNode); 10725 var hasCContainer = isConstrainedValue(constrainedContainer); 10726 var infinity = Number.POSITIVE_INFINITY; 10727 10728 if (hasCNode || hasCContainer) { 10729 return Math.min( 10730 hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, 10731 hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); 10732 } 10733 10734 return 'none'; 10735 } 10736 // returns Number or undefined if no constraint 10737 helpers$1.getConstraintWidth = function(domNode) { 10738 return getConstraintDimension(domNode, 'max-width', 'clientWidth'); 10739 }; 10740 // returns Number or undefined if no constraint 10741 helpers$1.getConstraintHeight = function(domNode) { 10742 return getConstraintDimension(domNode, 'max-height', 'clientHeight'); 10743 }; 10744 /** 10745 * @private 10746 */ 10747 helpers$1._calculatePadding = function(container, padding, parentDimension) { 10748 padding = helpers$1.getStyle(container, padding); 10749 10750 return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10); 10751 }; 10752 /** 10753 * @private 10754 */ 10755 helpers$1._getParentNode = function(domNode) { 10756 var parent = domNode.parentNode; 10757 if (parent && parent.toString() === '[object ShadowRoot]') { 10758 parent = parent.host; 10759 } 10760 return parent; 10761 }; 10762 helpers$1.getMaximumWidth = function(domNode) { 10763 var container = helpers$1._getParentNode(domNode); 10764 if (!container) { 10765 return domNode.clientWidth; 10766 } 10767 10768 var clientWidth = container.clientWidth; 10769 var paddingLeft = helpers$1._calculatePadding(container, 'padding-left', clientWidth); 10770 var paddingRight = helpers$1._calculatePadding(container, 'padding-right', clientWidth); 10771 10772 var w = clientWidth - paddingLeft - paddingRight; 10773 var cw = helpers$1.getConstraintWidth(domNode); 10774 return isNaN(cw) ? w : Math.min(w, cw); 10775 }; 10776 helpers$1.getMaximumHeight = function(domNode) { 10777 var container = helpers$1._getParentNode(domNode); 10778 if (!container) { 10779 return domNode.clientHeight; 10780 } 10781 10782 var clientHeight = container.clientHeight; 10783 var paddingTop = helpers$1._calculatePadding(container, 'padding-top', clientHeight); 10784 var paddingBottom = helpers$1._calculatePadding(container, 'padding-bottom', clientHeight); 10785 10786 var h = clientHeight - paddingTop - paddingBottom; 10787 var ch = helpers$1.getConstraintHeight(domNode); 10788 return isNaN(ch) ? h : Math.min(h, ch); 10789 }; 10790 helpers$1.getStyle = function(el, property) { 10791 return el.currentStyle ? 10792 el.currentStyle[property] : 10793 document.defaultView.getComputedStyle(el, null).getPropertyValue(property); 10794 }; 10795 helpers$1.retinaScale = function(chart, forceRatio) { 10796 var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1; 10797 if (pixelRatio === 1) { 10798 return; 10799 } 10800 10801 var canvas = chart.canvas; 10802 var height = chart.height; 10803 var width = chart.width; 10804 10805 canvas.height = height * pixelRatio; 10806 canvas.width = width * pixelRatio; 10807 chart.ctx.scale(pixelRatio, pixelRatio); 10808 10809 // If no style has been set on the canvas, the render size is used as display size, 10810 // making the chart visually bigger, so let's enforce it to the "correct" values. 10811 // See https://github.com/chartjs/Chart.js/issues/3575 10812 if (!canvas.style.height && !canvas.style.width) { 10813 canvas.style.height = height + 'px'; 10814 canvas.style.width = width + 'px'; 10815 } 10816 }; 10817 // -- Canvas methods 10818 helpers$1.fontString = function(pixelSize, fontStyle, fontFamily) { 10819 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; 10820 }; 10821 helpers$1.longestText = function(ctx, font, arrayOfThings, cache) { 10822 cache = cache || {}; 10823 var data = cache.data = cache.data || {}; 10824 var gc = cache.garbageCollect = cache.garbageCollect || []; 10825 10826 if (cache.font !== font) { 10827 data = cache.data = {}; 10828 gc = cache.garbageCollect = []; 10829 cache.font = font; 10830 } 10831 10832 ctx.font = font; 10833 var longest = 0; 10834 var ilen = arrayOfThings.length; 10835 var i, j, jlen, thing, nestedThing; 10836 for (i = 0; i < ilen; i++) { 10837 thing = arrayOfThings[i]; 10838 10839 // Undefined strings and arrays should not be measured 10840 if (thing !== undefined && thing !== null && helpers$1.isArray(thing) !== true) { 10841 longest = helpers$1.measureText(ctx, data, gc, longest, thing); 10842 } else if (helpers$1.isArray(thing)) { 10843 // if it is an array lets measure each element 10844 // to do maybe simplify this function a bit so we can do this more recursively? 10845 for (j = 0, jlen = thing.length; j < jlen; j++) { 10846 nestedThing = thing[j]; 10847 // Undefined strings and arrays should not be measured 10848 if (nestedThing !== undefined && nestedThing !== null && !helpers$1.isArray(nestedThing)) { 10849 longest = helpers$1.measureText(ctx, data, gc, longest, nestedThing); 10850 } 10851 } 10852 } 10853 } 10854 10855 var gcLen = gc.length / 2; 10856 if (gcLen > arrayOfThings.length) { 10857 for (i = 0; i < gcLen; i++) { 10858 delete data[gc[i]]; 10859 } 10860 gc.splice(0, gcLen); 10861 } 10862 return longest; 10863 }; 10864 helpers$1.measureText = function(ctx, data, gc, longest, string) { 10865 var textWidth = data[string]; 10866 if (!textWidth) { 10867 textWidth = data[string] = ctx.measureText(string).width; 10868 gc.push(string); 10869 } 10870 if (textWidth > longest) { 10871 longest = textWidth; 10872 } 10873 return longest; 10874 }; 10875 10876 /** 10877 * @deprecated 10878 */ 10879 helpers$1.numberOfLabelLines = function(arrayOfThings) { 10880 var numberOfLines = 1; 10881 helpers$1.each(arrayOfThings, function(thing) { 10882 if (helpers$1.isArray(thing)) { 10883 if (thing.length > numberOfLines) { 10884 numberOfLines = thing.length; 10885 } 10886 } 10887 }); 10888 return numberOfLines; 10889 }; 10890 10891 helpers$1.color = !chartjsColor ? 10892 function(value) { 10893 console.error('Color.js not found!'); 10894 return value; 10895 } : 10896 function(value) { 10897 /* global CanvasGradient */ 10898 if (value instanceof CanvasGradient) { 10899 value = core_defaults.global.defaultColor; 10900 } 10901 10902 return chartjsColor(value); 10903 }; 10904 10905 helpers$1.getHoverColor = function(colorValue) { 10906 /* global CanvasPattern */ 10907 return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ? 10908 colorValue : 10909 helpers$1.color(colorValue).saturate(0.5).darken(0.1).rgbString(); 10910 }; 10911}; 10912 10913function abstract() { 10914 throw new Error( 10915 'This method is not implemented: either no adapter can ' + 10916 'be found or an incomplete integration was provided.' 10917 ); 10918} 10919 10920/** 10921 * Date adapter (current used by the time scale) 10922 * @namespace Chart._adapters._date 10923 * @memberof Chart._adapters 10924 * @private 10925 */ 10926 10927/** 10928 * Currently supported unit string values. 10929 * @typedef {('millisecond'|'second'|'minute'|'hour'|'day'|'week'|'month'|'quarter'|'year')} 10930 * @memberof Chart._adapters._date 10931 * @name Unit 10932 */ 10933 10934/** 10935 * @class 10936 */ 10937function DateAdapter(options) { 10938 this.options = options || {}; 10939} 10940 10941helpers$1.extend(DateAdapter.prototype, /** @lends DateAdapter */ { 10942 /** 10943 * Returns a map of time formats for the supported formatting units defined 10944 * in Unit as well as 'datetime' representing a detailed date/time string. 10945 * @returns {{string: string}} 10946 */ 10947 formats: abstract, 10948 10949 /** 10950 * Parses the given `value` and return the associated timestamp. 10951 * @param {any} value - the value to parse (usually comes from the data) 10952 * @param {string} [format] - the expected data format 10953 * @returns {(number|null)} 10954 * @function 10955 */ 10956 parse: abstract, 10957 10958 /** 10959 * Returns the formatted date in the specified `format` for a given `timestamp`. 10960 * @param {number} timestamp - the timestamp to format 10961 * @param {string} format - the date/time token 10962 * @return {string} 10963 * @function 10964 */ 10965 format: abstract, 10966 10967 /** 10968 * Adds the specified `amount` of `unit` to the given `timestamp`. 10969 * @param {number} timestamp - the input timestamp 10970 * @param {number} amount - the amount to add 10971 * @param {Unit} unit - the unit as string 10972 * @return {number} 10973 * @function 10974 */ 10975 add: abstract, 10976 10977 /** 10978 * Returns the number of `unit` between the given timestamps. 10979 * @param {number} max - the input timestamp (reference) 10980 * @param {number} min - the timestamp to substract 10981 * @param {Unit} unit - the unit as string 10982 * @return {number} 10983 * @function 10984 */ 10985 diff: abstract, 10986 10987 /** 10988 * Returns start of `unit` for the given `timestamp`. 10989 * @param {number} timestamp - the input timestamp 10990 * @param {Unit} unit - the unit as string 10991 * @param {number} [weekday] - the ISO day of the week with 1 being Monday 10992 * and 7 being Sunday (only needed if param *unit* is `isoWeek`). 10993 * @function 10994 */ 10995 startOf: abstract, 10996 10997 /** 10998 * Returns end of `unit` for the given `timestamp`. 10999 * @param {number} timestamp - the input timestamp 11000 * @param {Unit} unit - the unit as string 11001 * @function 11002 */ 11003 endOf: abstract, 11004 11005 // DEPRECATIONS 11006 11007 /** 11008 * Provided for backward compatibility for scale.getValueForPixel(), 11009 * this method should be overridden only by the moment adapter. 11010 * @deprecated since version 2.8.0 11011 * @todo remove at version 3 11012 * @private 11013 */ 11014 _create: function(value) { 11015 return value; 11016 } 11017}); 11018 11019DateAdapter.override = function(members) { 11020 helpers$1.extend(DateAdapter.prototype, members); 11021}; 11022 11023var _date = DateAdapter; 11024 11025var core_adapters = { 11026 _date: _date 11027}; 11028 11029/** 11030 * Namespace to hold static tick generation functions 11031 * @namespace Chart.Ticks 11032 */ 11033var core_ticks = { 11034 /** 11035 * Namespace to hold formatters for different types of ticks 11036 * @namespace Chart.Ticks.formatters 11037 */ 11038 formatters: { 11039 /** 11040 * Formatter for value labels 11041 * @method Chart.Ticks.formatters.values 11042 * @param value the value to display 11043 * @return {string|string[]} the label to display 11044 */ 11045 values: function(value) { 11046 return helpers$1.isArray(value) ? value : '' + value; 11047 }, 11048 11049 /** 11050 * Formatter for linear numeric ticks 11051 * @method Chart.Ticks.formatters.linear 11052 * @param tickValue {number} the value to be formatted 11053 * @param index {number} the position of the tickValue parameter in the ticks array 11054 * @param ticks {number[]} the list of ticks being converted 11055 * @return {string} string representation of the tickValue parameter 11056 */ 11057 linear: function(tickValue, index, ticks) { 11058 // If we have lots of ticks, don't use the ones 11059 var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; 11060 11061 // If we have a number like 2.5 as the delta, figure out how many decimal places we need 11062 if (Math.abs(delta) > 1) { 11063 if (tickValue !== Math.floor(tickValue)) { 11064 // not an integer 11065 delta = tickValue - Math.floor(tickValue); 11066 } 11067 } 11068 11069 var logDelta = helpers$1.log10(Math.abs(delta)); 11070 var tickString = ''; 11071 11072 if (tickValue !== 0) { 11073 var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1])); 11074 if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation 11075 var logTick = helpers$1.log10(Math.abs(tickValue)); 11076 var numExponential = Math.floor(logTick) - Math.floor(logDelta); 11077 numExponential = Math.max(Math.min(numExponential, 20), 0); 11078 tickString = tickValue.toExponential(numExponential); 11079 } else { 11080 var numDecimal = -1 * Math.floor(logDelta); 11081 numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places 11082 tickString = tickValue.toFixed(numDecimal); 11083 } 11084 } else { 11085 tickString = '0'; // never show decimal places for 0 11086 } 11087 11088 return tickString; 11089 }, 11090 11091 logarithmic: function(tickValue, index, ticks) { 11092 var remain = tickValue / (Math.pow(10, Math.floor(helpers$1.log10(tickValue)))); 11093 11094 if (tickValue === 0) { 11095 return '0'; 11096 } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { 11097 return tickValue.toExponential(); 11098 } 11099 return ''; 11100 } 11101 } 11102}; 11103 11104var isArray = helpers$1.isArray; 11105var isNullOrUndef = helpers$1.isNullOrUndef; 11106var valueOrDefault$a = helpers$1.valueOrDefault; 11107var valueAtIndexOrDefault = helpers$1.valueAtIndexOrDefault; 11108 11109core_defaults._set('scale', { 11110 display: true, 11111 position: 'left', 11112 offset: false, 11113 11114 // grid line settings 11115 gridLines: { 11116 display: true, 11117 color: 'rgba(0,0,0,0.1)', 11118 lineWidth: 1, 11119 drawBorder: true, 11120 drawOnChartArea: true, 11121 drawTicks: true, 11122 tickMarkLength: 10, 11123 zeroLineWidth: 1, 11124 zeroLineColor: 'rgba(0,0,0,0.25)', 11125 zeroLineBorderDash: [], 11126 zeroLineBorderDashOffset: 0.0, 11127 offsetGridLines: false, 11128 borderDash: [], 11129 borderDashOffset: 0.0 11130 }, 11131 11132 // scale label 11133 scaleLabel: { 11134 // display property 11135 display: false, 11136 11137 // actual label 11138 labelString: '', 11139 11140 // top/bottom padding 11141 padding: { 11142 top: 4, 11143 bottom: 4 11144 } 11145 }, 11146 11147 // label settings 11148 ticks: { 11149 beginAtZero: false, 11150 minRotation: 0, 11151 maxRotation: 50, 11152 mirror: false, 11153 padding: 0, 11154 reverse: false, 11155 display: true, 11156 autoSkip: true, 11157 autoSkipPadding: 0, 11158 labelOffset: 0, 11159 // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. 11160 callback: core_ticks.formatters.values, 11161 minor: {}, 11162 major: {} 11163 } 11164}); 11165 11166/** Returns a new array containing numItems from arr */ 11167function sample(arr, numItems) { 11168 var result = []; 11169 var increment = arr.length / numItems; 11170 var i = 0; 11171 var len = arr.length; 11172 11173 for (; i < len; i += increment) { 11174 result.push(arr[Math.floor(i)]); 11175 } 11176 return result; 11177} 11178 11179function getPixelForGridLine(scale, index, offsetGridLines) { 11180 var length = scale.getTicks().length; 11181 var validIndex = Math.min(index, length - 1); 11182 var lineValue = scale.getPixelForTick(validIndex); 11183 var start = scale._startPixel; 11184 var end = scale._endPixel; 11185 var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. 11186 var offset; 11187 11188 if (offsetGridLines) { 11189 if (length === 1) { 11190 offset = Math.max(lineValue - start, end - lineValue); 11191 } else if (index === 0) { 11192 offset = (scale.getPixelForTick(1) - lineValue) / 2; 11193 } else { 11194 offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2; 11195 } 11196 lineValue += validIndex < index ? offset : -offset; 11197 11198 // Return undefined if the pixel is out of the range 11199 if (lineValue < start - epsilon || lineValue > end + epsilon) { 11200 return; 11201 } 11202 } 11203 return lineValue; 11204} 11205 11206function garbageCollect(caches, length) { 11207 helpers$1.each(caches, function(cache) { 11208 var gc = cache.gc; 11209 var gcLen = gc.length / 2; 11210 var i; 11211 if (gcLen > length) { 11212 for (i = 0; i < gcLen; ++i) { 11213 delete cache.data[gc[i]]; 11214 } 11215 gc.splice(0, gcLen); 11216 } 11217 }); 11218} 11219 11220/** 11221 * Returns {width, height, offset} objects for the first, last, widest, highest tick 11222 * labels where offset indicates the anchor point offset from the top in pixels. 11223 */ 11224function computeLabelSizes(ctx, tickFonts, ticks, caches) { 11225 var length = ticks.length; 11226 var widths = []; 11227 var heights = []; 11228 var offsets = []; 11229 var widestLabelSize = 0; 11230 var highestLabelSize = 0; 11231 var i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel, widest, highest; 11232 11233 for (i = 0; i < length; ++i) { 11234 label = ticks[i].label; 11235 tickFont = ticks[i].major ? tickFonts.major : tickFonts.minor; 11236 ctx.font = fontString = tickFont.string; 11237 cache = caches[fontString] = caches[fontString] || {data: {}, gc: []}; 11238 lineHeight = tickFont.lineHeight; 11239 width = height = 0; 11240 // Undefined labels and arrays should not be measured 11241 if (!isNullOrUndef(label) && !isArray(label)) { 11242 width = helpers$1.measureText(ctx, cache.data, cache.gc, width, label); 11243 height = lineHeight; 11244 } else if (isArray(label)) { 11245 // if it is an array let's measure each element 11246 for (j = 0, jlen = label.length; j < jlen; ++j) { 11247 nestedLabel = label[j]; 11248 // Undefined labels and arrays should not be measured 11249 if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) { 11250 width = helpers$1.measureText(ctx, cache.data, cache.gc, width, nestedLabel); 11251 height += lineHeight; 11252 } 11253 } 11254 } 11255 widths.push(width); 11256 heights.push(height); 11257 offsets.push(lineHeight / 2); 11258 widestLabelSize = Math.max(width, widestLabelSize); 11259 highestLabelSize = Math.max(height, highestLabelSize); 11260 } 11261 garbageCollect(caches, length); 11262 11263 widest = widths.indexOf(widestLabelSize); 11264 highest = heights.indexOf(highestLabelSize); 11265 11266 function valueAt(idx) { 11267 return { 11268 width: widths[idx] || 0, 11269 height: heights[idx] || 0, 11270 offset: offsets[idx] || 0 11271 }; 11272 } 11273 11274 return { 11275 first: valueAt(0), 11276 last: valueAt(length - 1), 11277 widest: valueAt(widest), 11278 highest: valueAt(highest) 11279 }; 11280} 11281 11282function getTickMarkLength(options) { 11283 return options.drawTicks ? options.tickMarkLength : 0; 11284} 11285 11286function getScaleLabelHeight(options) { 11287 var font, padding; 11288 11289 if (!options.display) { 11290 return 0; 11291 } 11292 11293 font = helpers$1.options._parseFont(options); 11294 padding = helpers$1.options.toPadding(options.padding); 11295 11296 return font.lineHeight + padding.height; 11297} 11298 11299function parseFontOptions(options, nestedOpts) { 11300 return helpers$1.extend(helpers$1.options._parseFont({ 11301 fontFamily: valueOrDefault$a(nestedOpts.fontFamily, options.fontFamily), 11302 fontSize: valueOrDefault$a(nestedOpts.fontSize, options.fontSize), 11303 fontStyle: valueOrDefault$a(nestedOpts.fontStyle, options.fontStyle), 11304 lineHeight: valueOrDefault$a(nestedOpts.lineHeight, options.lineHeight) 11305 }), { 11306 color: helpers$1.options.resolve([nestedOpts.fontColor, options.fontColor, core_defaults.global.defaultFontColor]) 11307 }); 11308} 11309 11310function parseTickFontOptions(options) { 11311 var minor = parseFontOptions(options, options.minor); 11312 var major = options.major.enabled ? parseFontOptions(options, options.major) : minor; 11313 11314 return {minor: minor, major: major}; 11315} 11316 11317function nonSkipped(ticksToFilter) { 11318 var filtered = []; 11319 var item, index, len; 11320 for (index = 0, len = ticksToFilter.length; index < len; ++index) { 11321 item = ticksToFilter[index]; 11322 if (typeof item._index !== 'undefined') { 11323 filtered.push(item); 11324 } 11325 } 11326 return filtered; 11327} 11328 11329function getEvenSpacing(arr) { 11330 var len = arr.length; 11331 var i, diff; 11332 11333 if (len < 2) { 11334 return false; 11335 } 11336 11337 for (diff = arr[0], i = 1; i < len; ++i) { 11338 if (arr[i] - arr[i - 1] !== diff) { 11339 return false; 11340 } 11341 } 11342 return diff; 11343} 11344 11345function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) { 11346 var evenMajorSpacing = getEvenSpacing(majorIndices); 11347 var spacing = (ticks.length - 1) / ticksLimit; 11348 var factors, factor, i, ilen; 11349 11350 // If the major ticks are evenly spaced apart, place the minor ticks 11351 // so that they divide the major ticks into even chunks 11352 if (!evenMajorSpacing) { 11353 return Math.max(spacing, 1); 11354 } 11355 11356 factors = helpers$1.math._factorize(evenMajorSpacing); 11357 for (i = 0, ilen = factors.length - 1; i < ilen; i++) { 11358 factor = factors[i]; 11359 if (factor > spacing) { 11360 return factor; 11361 } 11362 } 11363 return Math.max(spacing, 1); 11364} 11365 11366function getMajorIndices(ticks) { 11367 var result = []; 11368 var i, ilen; 11369 for (i = 0, ilen = ticks.length; i < ilen; i++) { 11370 if (ticks[i].major) { 11371 result.push(i); 11372 } 11373 } 11374 return result; 11375} 11376 11377function skipMajors(ticks, majorIndices, spacing) { 11378 var count = 0; 11379 var next = majorIndices[0]; 11380 var i, tick; 11381 11382 spacing = Math.ceil(spacing); 11383 for (i = 0; i < ticks.length; i++) { 11384 tick = ticks[i]; 11385 if (i === next) { 11386 tick._index = i; 11387 count++; 11388 next = majorIndices[count * spacing]; 11389 } else { 11390 delete tick.label; 11391 } 11392 } 11393} 11394 11395function skip(ticks, spacing, majorStart, majorEnd) { 11396 var start = valueOrDefault$a(majorStart, 0); 11397 var end = Math.min(valueOrDefault$a(majorEnd, ticks.length), ticks.length); 11398 var count = 0; 11399 var length, i, tick, next; 11400 11401 spacing = Math.ceil(spacing); 11402 if (majorEnd) { 11403 length = majorEnd - majorStart; 11404 spacing = length / Math.floor(length / spacing); 11405 } 11406 11407 next = start; 11408 11409 while (next < 0) { 11410 count++; 11411 next = Math.round(start + count * spacing); 11412 } 11413 11414 for (i = Math.max(start, 0); i < end; i++) { 11415 tick = ticks[i]; 11416 if (i === next) { 11417 tick._index = i; 11418 count++; 11419 next = Math.round(start + count * spacing); 11420 } else { 11421 delete tick.label; 11422 } 11423 } 11424} 11425 11426var Scale = core_element.extend({ 11427 11428 zeroLineIndex: 0, 11429 11430 /** 11431 * Get the padding needed for the scale 11432 * @method getPadding 11433 * @private 11434 * @returns {Padding} the necessary padding 11435 */ 11436 getPadding: function() { 11437 var me = this; 11438 return { 11439 left: me.paddingLeft || 0, 11440 top: me.paddingTop || 0, 11441 right: me.paddingRight || 0, 11442 bottom: me.paddingBottom || 0 11443 }; 11444 }, 11445 11446 /** 11447 * Returns the scale tick objects ({label, major}) 11448 * @since 2.7 11449 */ 11450 getTicks: function() { 11451 return this._ticks; 11452 }, 11453 11454 /** 11455 * @private 11456 */ 11457 _getLabels: function() { 11458 var data = this.chart.data; 11459 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || []; 11460 }, 11461 11462 // These methods are ordered by lifecyle. Utilities then follow. 11463 // Any function defined here is inherited by all scale types. 11464 // Any function can be extended by the scale type 11465 11466 /** 11467 * Provided for backward compatibility, not available anymore 11468 * @function Chart.Scale.mergeTicksOptions 11469 * @deprecated since version 2.8.0 11470 * @todo remove at version 3 11471 */ 11472 mergeTicksOptions: function() { 11473 // noop 11474 }, 11475 11476 beforeUpdate: function() { 11477 helpers$1.callback(this.options.beforeUpdate, [this]); 11478 }, 11479 11480 /** 11481 * @param {number} maxWidth - the max width in pixels 11482 * @param {number} maxHeight - the max height in pixels 11483 * @param {object} margins - the space between the edge of the other scales and edge of the chart 11484 * This space comes from two sources: 11485 * - padding - space that's required to show the labels at the edges of the scale 11486 * - thickness of scales or legends in another orientation 11487 */ 11488 update: function(maxWidth, maxHeight, margins) { 11489 var me = this; 11490 var tickOpts = me.options.ticks; 11491 var sampleSize = tickOpts.sampleSize; 11492 var i, ilen, labels, ticks, samplingEnabled; 11493 11494 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 11495 me.beforeUpdate(); 11496 11497 // Absorb the master measurements 11498 me.maxWidth = maxWidth; 11499 me.maxHeight = maxHeight; 11500 me.margins = helpers$1.extend({ 11501 left: 0, 11502 right: 0, 11503 top: 0, 11504 bottom: 0 11505 }, margins); 11506 11507 me._ticks = null; 11508 me.ticks = null; 11509 me._labelSizes = null; 11510 me._maxLabelLines = 0; 11511 me.longestLabelWidth = 0; 11512 me.longestTextCache = me.longestTextCache || {}; 11513 me._gridLineItems = null; 11514 me._labelItems = null; 11515 11516 // Dimensions 11517 me.beforeSetDimensions(); 11518 me.setDimensions(); 11519 me.afterSetDimensions(); 11520 11521 // Data min/max 11522 me.beforeDataLimits(); 11523 me.determineDataLimits(); 11524 me.afterDataLimits(); 11525 11526 // Ticks - `this.ticks` is now DEPRECATED! 11527 // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member 11528 // and must not be accessed directly from outside this class. `this.ticks` being 11529 // around for long time and not marked as private, we can't change its structure 11530 // without unexpected breaking changes. If you need to access the scale ticks, 11531 // use scale.getTicks() instead. 11532 11533 me.beforeBuildTicks(); 11534 11535 // New implementations should return an array of objects but for BACKWARD COMPAT, 11536 // we still support no return (`this.ticks` internally set by calling this method). 11537 ticks = me.buildTicks() || []; 11538 11539 // Allow modification of ticks in callback. 11540 ticks = me.afterBuildTicks(ticks) || ticks; 11541 11542 // Ensure ticks contains ticks in new tick format 11543 if ((!ticks || !ticks.length) && me.ticks) { 11544 ticks = []; 11545 for (i = 0, ilen = me.ticks.length; i < ilen; ++i) { 11546 ticks.push({ 11547 value: me.ticks[i], 11548 major: false 11549 }); 11550 } 11551 } 11552 11553 me._ticks = ticks; 11554 11555 // Compute tick rotation and fit using a sampled subset of labels 11556 // We generally don't need to compute the size of every single label for determining scale size 11557 samplingEnabled = sampleSize < ticks.length; 11558 labels = me._convertTicksToLabels(samplingEnabled ? sample(ticks, sampleSize) : ticks); 11559 11560 // _configure is called twice, once here, once from core.controller.updateLayout. 11561 // Here we haven't been positioned yet, but dimensions are correct. 11562 // Variables set in _configure are needed for calculateTickRotation, and 11563 // it's ok that coordinates are not correct there, only dimensions matter. 11564 me._configure(); 11565 11566 // Tick Rotation 11567 me.beforeCalculateTickRotation(); 11568 me.calculateTickRotation(); 11569 me.afterCalculateTickRotation(); 11570 11571 me.beforeFit(); 11572 me.fit(); 11573 me.afterFit(); 11574 11575 // Auto-skip 11576 me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks; 11577 11578 if (samplingEnabled) { 11579 // Generate labels using all non-skipped ticks 11580 labels = me._convertTicksToLabels(me._ticksToDraw); 11581 } 11582 11583 me.ticks = labels; // BACKWARD COMPATIBILITY 11584 11585 // IMPORTANT: after this point, we consider that `this.ticks` will NEVER change! 11586 11587 me.afterUpdate(); 11588 11589 // TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused 11590 // make maxWidth and maxHeight private 11591 return me.minSize; 11592 }, 11593 11594 /** 11595 * @private 11596 */ 11597 _configure: function() { 11598 var me = this; 11599 var reversePixels = me.options.ticks.reverse; 11600 var startPixel, endPixel; 11601 11602 if (me.isHorizontal()) { 11603 startPixel = me.left; 11604 endPixel = me.right; 11605 } else { 11606 startPixel = me.top; 11607 endPixel = me.bottom; 11608 // by default vertical scales are from bottom to top, so pixels are reversed 11609 reversePixels = !reversePixels; 11610 } 11611 me._startPixel = startPixel; 11612 me._endPixel = endPixel; 11613 me._reversePixels = reversePixels; 11614 me._length = endPixel - startPixel; 11615 }, 11616 11617 afterUpdate: function() { 11618 helpers$1.callback(this.options.afterUpdate, [this]); 11619 }, 11620 11621 // 11622 11623 beforeSetDimensions: function() { 11624 helpers$1.callback(this.options.beforeSetDimensions, [this]); 11625 }, 11626 setDimensions: function() { 11627 var me = this; 11628 // Set the unconstrained dimension before label rotation 11629 if (me.isHorizontal()) { 11630 // Reset position before calculating rotation 11631 me.width = me.maxWidth; 11632 me.left = 0; 11633 me.right = me.width; 11634 } else { 11635 me.height = me.maxHeight; 11636 11637 // Reset position before calculating rotation 11638 me.top = 0; 11639 me.bottom = me.height; 11640 } 11641 11642 // Reset padding 11643 me.paddingLeft = 0; 11644 me.paddingTop = 0; 11645 me.paddingRight = 0; 11646 me.paddingBottom = 0; 11647 }, 11648 afterSetDimensions: function() { 11649 helpers$1.callback(this.options.afterSetDimensions, [this]); 11650 }, 11651 11652 // Data limits 11653 beforeDataLimits: function() { 11654 helpers$1.callback(this.options.beforeDataLimits, [this]); 11655 }, 11656 determineDataLimits: helpers$1.noop, 11657 afterDataLimits: function() { 11658 helpers$1.callback(this.options.afterDataLimits, [this]); 11659 }, 11660 11661 // 11662 beforeBuildTicks: function() { 11663 helpers$1.callback(this.options.beforeBuildTicks, [this]); 11664 }, 11665 buildTicks: helpers$1.noop, 11666 afterBuildTicks: function(ticks) { 11667 var me = this; 11668 // ticks is empty for old axis implementations here 11669 if (isArray(ticks) && ticks.length) { 11670 return helpers$1.callback(me.options.afterBuildTicks, [me, ticks]); 11671 } 11672 // Support old implementations (that modified `this.ticks` directly in buildTicks) 11673 me.ticks = helpers$1.callback(me.options.afterBuildTicks, [me, me.ticks]) || me.ticks; 11674 return ticks; 11675 }, 11676 11677 beforeTickToLabelConversion: function() { 11678 helpers$1.callback(this.options.beforeTickToLabelConversion, [this]); 11679 }, 11680 convertTicksToLabels: function() { 11681 var me = this; 11682 // Convert ticks to strings 11683 var tickOpts = me.options.ticks; 11684 me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); 11685 }, 11686 afterTickToLabelConversion: function() { 11687 helpers$1.callback(this.options.afterTickToLabelConversion, [this]); 11688 }, 11689 11690 // 11691 11692 beforeCalculateTickRotation: function() { 11693 helpers$1.callback(this.options.beforeCalculateTickRotation, [this]); 11694 }, 11695 calculateTickRotation: function() { 11696 var me = this; 11697 var options = me.options; 11698 var tickOpts = options.ticks; 11699 var numTicks = me.getTicks().length; 11700 var minRotation = tickOpts.minRotation || 0; 11701 var maxRotation = tickOpts.maxRotation; 11702 var labelRotation = minRotation; 11703 var labelSizes, maxLabelWidth, maxLabelHeight, maxWidth, tickWidth, maxHeight, maxLabelDiagonal; 11704 11705 if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) { 11706 me.labelRotation = minRotation; 11707 return; 11708 } 11709 11710 labelSizes = me._getLabelSizes(); 11711 maxLabelWidth = labelSizes.widest.width; 11712 maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset; 11713 11714 // Estimate the width of each grid based on the canvas width, the maximum 11715 // label width and the number of tick intervals 11716 maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth); 11717 tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1); 11718 11719 // Allow 3 pixels x2 padding either side for label readability 11720 if (maxLabelWidth + 6 > tickWidth) { 11721 tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1)); 11722 maxHeight = me.maxHeight - getTickMarkLength(options.gridLines) 11723 - tickOpts.padding - getScaleLabelHeight(options.scaleLabel); 11724 maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight); 11725 labelRotation = helpers$1.toDegrees(Math.min( 11726 Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)), 11727 Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal) 11728 )); 11729 labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation)); 11730 } 11731 11732 me.labelRotation = labelRotation; 11733 }, 11734 afterCalculateTickRotation: function() { 11735 helpers$1.callback(this.options.afterCalculateTickRotation, [this]); 11736 }, 11737 11738 // 11739 11740 beforeFit: function() { 11741 helpers$1.callback(this.options.beforeFit, [this]); 11742 }, 11743 fit: function() { 11744 var me = this; 11745 // Reset 11746 var minSize = me.minSize = { 11747 width: 0, 11748 height: 0 11749 }; 11750 11751 var chart = me.chart; 11752 var opts = me.options; 11753 var tickOpts = opts.ticks; 11754 var scaleLabelOpts = opts.scaleLabel; 11755 var gridLineOpts = opts.gridLines; 11756 var display = me._isVisible(); 11757 var isBottom = opts.position === 'bottom'; 11758 var isHorizontal = me.isHorizontal(); 11759 11760 // Width 11761 if (isHorizontal) { 11762 minSize.width = me.maxWidth; 11763 } else if (display) { 11764 minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); 11765 } 11766 11767 // height 11768 if (!isHorizontal) { 11769 minSize.height = me.maxHeight; // fill all the height 11770 } else if (display) { 11771 minSize.height = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); 11772 } 11773 11774 // Don't bother fitting the ticks if we are not showing the labels 11775 if (tickOpts.display && display) { 11776 var tickFonts = parseTickFontOptions(tickOpts); 11777 var labelSizes = me._getLabelSizes(); 11778 var firstLabelSize = labelSizes.first; 11779 var lastLabelSize = labelSizes.last; 11780 var widestLabelSize = labelSizes.widest; 11781 var highestLabelSize = labelSizes.highest; 11782 var lineSpace = tickFonts.minor.lineHeight * 0.4; 11783 var tickPadding = tickOpts.padding; 11784 11785 if (isHorizontal) { 11786 // A horizontal axis is more constrained by the height. 11787 var isRotated = me.labelRotation !== 0; 11788 var angleRadians = helpers$1.toRadians(me.labelRotation); 11789 var cosRotation = Math.cos(angleRadians); 11790 var sinRotation = Math.sin(angleRadians); 11791 11792 var labelHeight = sinRotation * widestLabelSize.width 11793 + cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0)) 11794 + (isRotated ? 0 : lineSpace); // padding 11795 11796 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); 11797 11798 var offsetLeft = me.getPixelForTick(0) - me.left; 11799 var offsetRight = me.right - me.getPixelForTick(me.getTicks().length - 1); 11800 var paddingLeft, paddingRight; 11801 11802 // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned 11803 // which means that the right padding is dominated by the font height 11804 if (isRotated) { 11805 paddingLeft = isBottom ? 11806 cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset : 11807 sinRotation * (firstLabelSize.height - firstLabelSize.offset); 11808 paddingRight = isBottom ? 11809 sinRotation * (lastLabelSize.height - lastLabelSize.offset) : 11810 cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset; 11811 } else { 11812 paddingLeft = firstLabelSize.width / 2; 11813 paddingRight = lastLabelSize.width / 2; 11814 } 11815 11816 // Adjust padding taking into account changes in offsets 11817 // and add 3 px to move away from canvas edges 11818 me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3; 11819 me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3; 11820 } else { 11821 // A vertical axis is more constrained by the width. Labels are the 11822 // dominant factor here, so get that length first and account for padding 11823 var labelWidth = tickOpts.mirror ? 0 : 11824 // use lineSpace for consistency with horizontal axis 11825 // tickPadding is not implemented for horizontal 11826 widestLabelSize.width + tickPadding + lineSpace; 11827 11828 minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth); 11829 11830 me.paddingTop = firstLabelSize.height / 2; 11831 me.paddingBottom = lastLabelSize.height / 2; 11832 } 11833 } 11834 11835 me.handleMargins(); 11836 11837 if (isHorizontal) { 11838 me.width = me._length = chart.width - me.margins.left - me.margins.right; 11839 me.height = minSize.height; 11840 } else { 11841 me.width = minSize.width; 11842 me.height = me._length = chart.height - me.margins.top - me.margins.bottom; 11843 } 11844 }, 11845 11846 /** 11847 * Handle margins and padding interactions 11848 * @private 11849 */ 11850 handleMargins: function() { 11851 var me = this; 11852 if (me.margins) { 11853 me.margins.left = Math.max(me.paddingLeft, me.margins.left); 11854 me.margins.top = Math.max(me.paddingTop, me.margins.top); 11855 me.margins.right = Math.max(me.paddingRight, me.margins.right); 11856 me.margins.bottom = Math.max(me.paddingBottom, me.margins.bottom); 11857 } 11858 }, 11859 11860 afterFit: function() { 11861 helpers$1.callback(this.options.afterFit, [this]); 11862 }, 11863 11864 // Shared Methods 11865 isHorizontal: function() { 11866 var pos = this.options.position; 11867 return pos === 'top' || pos === 'bottom'; 11868 }, 11869 isFullWidth: function() { 11870 return this.options.fullWidth; 11871 }, 11872 11873 // 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 11874 getRightValue: function(rawValue) { 11875 // Null and undefined values first 11876 if (isNullOrUndef(rawValue)) { 11877 return NaN; 11878 } 11879 // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values 11880 if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) { 11881 return NaN; 11882 } 11883 11884 // If it is in fact an object, dive in one more level 11885 if (rawValue) { 11886 if (this.isHorizontal()) { 11887 if (rawValue.x !== undefined) { 11888 return this.getRightValue(rawValue.x); 11889 } 11890 } else if (rawValue.y !== undefined) { 11891 return this.getRightValue(rawValue.y); 11892 } 11893 } 11894 11895 // Value is good, return it 11896 return rawValue; 11897 }, 11898 11899 _convertTicksToLabels: function(ticks) { 11900 var me = this; 11901 var labels, i, ilen; 11902 11903 me.ticks = ticks.map(function(tick) { 11904 return tick.value; 11905 }); 11906 11907 me.beforeTickToLabelConversion(); 11908 11909 // New implementations should return the formatted tick labels but for BACKWARD 11910 // COMPAT, we still support no return (`this.ticks` internally changed by calling 11911 // this method and supposed to contain only string values). 11912 labels = me.convertTicksToLabels(ticks) || me.ticks; 11913 11914 me.afterTickToLabelConversion(); 11915 11916 // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) 11917 for (i = 0, ilen = ticks.length; i < ilen; ++i) { 11918 ticks[i].label = labels[i]; 11919 } 11920 11921 return labels; 11922 }, 11923 11924 /** 11925 * @private 11926 */ 11927 _getLabelSizes: function() { 11928 var me = this; 11929 var labelSizes = me._labelSizes; 11930 11931 if (!labelSizes) { 11932 me._labelSizes = labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(me.options.ticks), me.getTicks(), me.longestTextCache); 11933 me.longestLabelWidth = labelSizes.widest.width; 11934 } 11935 11936 return labelSizes; 11937 }, 11938 11939 /** 11940 * @private 11941 */ 11942 _parseValue: function(value) { 11943 var start, end, min, max; 11944 11945 if (isArray(value)) { 11946 start = +this.getRightValue(value[0]); 11947 end = +this.getRightValue(value[1]); 11948 min = Math.min(start, end); 11949 max = Math.max(start, end); 11950 } else { 11951 value = +this.getRightValue(value); 11952 start = undefined; 11953 end = value; 11954 min = value; 11955 max = value; 11956 } 11957 11958 return { 11959 min: min, 11960 max: max, 11961 start: start, 11962 end: end 11963 }; 11964 }, 11965 11966 /** 11967 * @private 11968 */ 11969 _getScaleLabel: function(rawValue) { 11970 var v = this._parseValue(rawValue); 11971 if (v.start !== undefined) { 11972 return '[' + v.start + ', ' + v.end + ']'; 11973 } 11974 11975 return +this.getRightValue(rawValue); 11976 }, 11977 11978 /** 11979 * Used to get the value to display in the tooltip for the data at the given index 11980 * @param index 11981 * @param datasetIndex 11982 */ 11983 getLabelForIndex: helpers$1.noop, 11984 11985 /** 11986 * Returns the location of the given data point. Value can either be an index or a numerical value 11987 * The coordinate (0, 0) is at the upper-left corner of the canvas 11988 * @param value 11989 * @param index 11990 * @param datasetIndex 11991 */ 11992 getPixelForValue: helpers$1.noop, 11993 11994 /** 11995 * Used to get the data value from a given pixel. This is the inverse of getPixelForValue 11996 * The coordinate (0, 0) is at the upper-left corner of the canvas 11997 * @param pixel 11998 */ 11999 getValueForPixel: helpers$1.noop, 12000 12001 /** 12002 * Returns the location of the tick at the given index 12003 * The coordinate (0, 0) is at the upper-left corner of the canvas 12004 */ 12005 getPixelForTick: function(index) { 12006 var me = this; 12007 var offset = me.options.offset; 12008 var numTicks = me._ticks.length; 12009 var tickWidth = 1 / Math.max(numTicks - (offset ? 0 : 1), 1); 12010 12011 return index < 0 || index > numTicks - 1 12012 ? null 12013 : me.getPixelForDecimal(index * tickWidth + (offset ? tickWidth / 2 : 0)); 12014 }, 12015 12016 /** 12017 * Utility for getting the pixel location of a percentage of scale 12018 * The coordinate (0, 0) is at the upper-left corner of the canvas 12019 */ 12020 getPixelForDecimal: function(decimal) { 12021 var me = this; 12022 12023 if (me._reversePixels) { 12024 decimal = 1 - decimal; 12025 } 12026 12027 return me._startPixel + decimal * me._length; 12028 }, 12029 12030 getDecimalForPixel: function(pixel) { 12031 var decimal = (pixel - this._startPixel) / this._length; 12032 return this._reversePixels ? 1 - decimal : decimal; 12033 }, 12034 12035 /** 12036 * Returns the pixel for the minimum chart value 12037 * The coordinate (0, 0) is at the upper-left corner of the canvas 12038 */ 12039 getBasePixel: function() { 12040 return this.getPixelForValue(this.getBaseValue()); 12041 }, 12042 12043 getBaseValue: function() { 12044 var me = this; 12045 var min = me.min; 12046 var max = me.max; 12047 12048 return me.beginAtZero ? 0 : 12049 min < 0 && max < 0 ? max : 12050 min > 0 && max > 0 ? min : 12051 0; 12052 }, 12053 12054 /** 12055 * Returns a subset of ticks to be plotted to avoid overlapping labels. 12056 * @private 12057 */ 12058 _autoSkip: function(ticks) { 12059 var me = this; 12060 var tickOpts = me.options.ticks; 12061 var axisLength = me._length; 12062 var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1; 12063 var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : []; 12064 var numMajorIndices = majorIndices.length; 12065 var first = majorIndices[0]; 12066 var last = majorIndices[numMajorIndices - 1]; 12067 var i, ilen, spacing, avgMajorSpacing; 12068 12069 // If there are too many major ticks to display them all 12070 if (numMajorIndices > ticksLimit) { 12071 skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit); 12072 return nonSkipped(ticks); 12073 } 12074 12075 spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit); 12076 12077 if (numMajorIndices > 0) { 12078 for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) { 12079 skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]); 12080 } 12081 avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null; 12082 skip(ticks, spacing, helpers$1.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first); 12083 skip(ticks, spacing, last, helpers$1.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing); 12084 return nonSkipped(ticks); 12085 } 12086 skip(ticks, spacing); 12087 return nonSkipped(ticks); 12088 }, 12089 12090 /** 12091 * @private 12092 */ 12093 _tickSize: function() { 12094 var me = this; 12095 var optionTicks = me.options.ticks; 12096 12097 // Calculate space needed by label in axis direction. 12098 var rot = helpers$1.toRadians(me.labelRotation); 12099 var cos = Math.abs(Math.cos(rot)); 12100 var sin = Math.abs(Math.sin(rot)); 12101 12102 var labelSizes = me._getLabelSizes(); 12103 var padding = optionTicks.autoSkipPadding || 0; 12104 var w = labelSizes ? labelSizes.widest.width + padding : 0; 12105 var h = labelSizes ? labelSizes.highest.height + padding : 0; 12106 12107 // Calculate space needed for 1 tick in axis direction. 12108 return me.isHorizontal() 12109 ? h * cos > w * sin ? w / cos : h / sin 12110 : h * sin < w * cos ? h / cos : w / sin; 12111 }, 12112 12113 /** 12114 * @private 12115 */ 12116 _isVisible: function() { 12117 var me = this; 12118 var chart = me.chart; 12119 var display = me.options.display; 12120 var i, ilen, meta; 12121 12122 if (display !== 'auto') { 12123 return !!display; 12124 } 12125 12126 // When 'auto', the scale is visible if at least one associated dataset is visible. 12127 for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { 12128 if (chart.isDatasetVisible(i)) { 12129 meta = chart.getDatasetMeta(i); 12130 if (meta.xAxisID === me.id || meta.yAxisID === me.id) { 12131 return true; 12132 } 12133 } 12134 } 12135 12136 return false; 12137 }, 12138 12139 /** 12140 * @private 12141 */ 12142 _computeGridLineItems: function(chartArea) { 12143 var me = this; 12144 var chart = me.chart; 12145 var options = me.options; 12146 var gridLines = options.gridLines; 12147 var position = options.position; 12148 var offsetGridLines = gridLines.offsetGridLines; 12149 var isHorizontal = me.isHorizontal(); 12150 var ticks = me._ticksToDraw; 12151 var ticksLength = ticks.length + (offsetGridLines ? 1 : 0); 12152 12153 var tl = getTickMarkLength(gridLines); 12154 var items = []; 12155 var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0; 12156 var axisHalfWidth = axisWidth / 2; 12157 var alignPixel = helpers$1._alignPixel; 12158 var alignBorderValue = function(pixel) { 12159 return alignPixel(chart, pixel, axisWidth); 12160 }; 12161 var borderValue, i, tick, lineValue, alignedLineValue; 12162 var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset; 12163 12164 if (position === 'top') { 12165 borderValue = alignBorderValue(me.bottom); 12166 ty1 = me.bottom - tl; 12167 ty2 = borderValue - axisHalfWidth; 12168 y1 = alignBorderValue(chartArea.top) + axisHalfWidth; 12169 y2 = chartArea.bottom; 12170 } else if (position === 'bottom') { 12171 borderValue = alignBorderValue(me.top); 12172 y1 = chartArea.top; 12173 y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth; 12174 ty1 = borderValue + axisHalfWidth; 12175 ty2 = me.top + tl; 12176 } else if (position === 'left') { 12177 borderValue = alignBorderValue(me.right); 12178 tx1 = me.right - tl; 12179 tx2 = borderValue - axisHalfWidth; 12180 x1 = alignBorderValue(chartArea.left) + axisHalfWidth; 12181 x2 = chartArea.right; 12182 } else { 12183 borderValue = alignBorderValue(me.left); 12184 x1 = chartArea.left; 12185 x2 = alignBorderValue(chartArea.right) - axisHalfWidth; 12186 tx1 = borderValue + axisHalfWidth; 12187 tx2 = me.left + tl; 12188 } 12189 12190 for (i = 0; i < ticksLength; ++i) { 12191 tick = ticks[i] || {}; 12192 12193 // autoskipper skipped this tick (#4635) 12194 if (isNullOrUndef(tick.label) && i < ticks.length) { 12195 continue; 12196 } 12197 12198 if (i === me.zeroLineIndex && options.offset === offsetGridLines) { 12199 // Draw the first index specially 12200 lineWidth = gridLines.zeroLineWidth; 12201 lineColor = gridLines.zeroLineColor; 12202 borderDash = gridLines.zeroLineBorderDash || []; 12203 borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0; 12204 } else { 12205 lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, i, 1); 12206 lineColor = valueAtIndexOrDefault(gridLines.color, i, 'rgba(0,0,0,0.1)'); 12207 borderDash = gridLines.borderDash || []; 12208 borderDashOffset = gridLines.borderDashOffset || 0.0; 12209 } 12210 12211 lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines); 12212 12213 // Skip if the pixel is out of the range 12214 if (lineValue === undefined) { 12215 continue; 12216 } 12217 12218 alignedLineValue = alignPixel(chart, lineValue, lineWidth); 12219 12220 if (isHorizontal) { 12221 tx1 = tx2 = x1 = x2 = alignedLineValue; 12222 } else { 12223 ty1 = ty2 = y1 = y2 = alignedLineValue; 12224 } 12225 12226 items.push({ 12227 tx1: tx1, 12228 ty1: ty1, 12229 tx2: tx2, 12230 ty2: ty2, 12231 x1: x1, 12232 y1: y1, 12233 x2: x2, 12234 y2: y2, 12235 width: lineWidth, 12236 color: lineColor, 12237 borderDash: borderDash, 12238 borderDashOffset: borderDashOffset, 12239 }); 12240 } 12241 12242 items.ticksLength = ticksLength; 12243 items.borderValue = borderValue; 12244 12245 return items; 12246 }, 12247 12248 /** 12249 * @private 12250 */ 12251 _computeLabelItems: function() { 12252 var me = this; 12253 var options = me.options; 12254 var optionTicks = options.ticks; 12255 var position = options.position; 12256 var isMirrored = optionTicks.mirror; 12257 var isHorizontal = me.isHorizontal(); 12258 var ticks = me._ticksToDraw; 12259 var fonts = parseTickFontOptions(optionTicks); 12260 var tickPadding = optionTicks.padding; 12261 var tl = getTickMarkLength(options.gridLines); 12262 var rotation = -helpers$1.toRadians(me.labelRotation); 12263 var items = []; 12264 var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; 12265 12266 if (position === 'top') { 12267 y = me.bottom - tl - tickPadding; 12268 textAlign = !rotation ? 'center' : 'left'; 12269 } else if (position === 'bottom') { 12270 y = me.top + tl + tickPadding; 12271 textAlign = !rotation ? 'center' : 'right'; 12272 } else if (position === 'left') { 12273 x = me.right - (isMirrored ? 0 : tl) - tickPadding; 12274 textAlign = isMirrored ? 'left' : 'right'; 12275 } else { 12276 x = me.left + (isMirrored ? 0 : tl) + tickPadding; 12277 textAlign = isMirrored ? 'right' : 'left'; 12278 } 12279 12280 for (i = 0, ilen = ticks.length; i < ilen; ++i) { 12281 tick = ticks[i]; 12282 label = tick.label; 12283 12284 // autoskipper skipped this tick (#4635) 12285 if (isNullOrUndef(label)) { 12286 continue; 12287 } 12288 12289 pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset; 12290 font = tick.major ? fonts.major : fonts.minor; 12291 lineHeight = font.lineHeight; 12292 lineCount = isArray(label) ? label.length : 1; 12293 12294 if (isHorizontal) { 12295 x = pixel; 12296 textOffset = position === 'top' 12297 ? ((!rotation ? 0.5 : 1) - lineCount) * lineHeight 12298 : (!rotation ? 0.5 : 0) * lineHeight; 12299 } else { 12300 y = pixel; 12301 textOffset = (1 - lineCount) * lineHeight / 2; 12302 } 12303 12304 items.push({ 12305 x: x, 12306 y: y, 12307 rotation: rotation, 12308 label: label, 12309 font: font, 12310 textOffset: textOffset, 12311 textAlign: textAlign 12312 }); 12313 } 12314 12315 return items; 12316 }, 12317 12318 /** 12319 * @private 12320 */ 12321 _drawGrid: function(chartArea) { 12322 var me = this; 12323 var gridLines = me.options.gridLines; 12324 12325 if (!gridLines.display) { 12326 return; 12327 } 12328 12329 var ctx = me.ctx; 12330 var chart = me.chart; 12331 var alignPixel = helpers$1._alignPixel; 12332 var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0; 12333 var items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea)); 12334 var width, color, i, ilen, item; 12335 12336 for (i = 0, ilen = items.length; i < ilen; ++i) { 12337 item = items[i]; 12338 width = item.width; 12339 color = item.color; 12340 12341 if (width && color) { 12342 ctx.save(); 12343 ctx.lineWidth = width; 12344 ctx.strokeStyle = color; 12345 if (ctx.setLineDash) { 12346 ctx.setLineDash(item.borderDash); 12347 ctx.lineDashOffset = item.borderDashOffset; 12348 } 12349 12350 ctx.beginPath(); 12351 12352 if (gridLines.drawTicks) { 12353 ctx.moveTo(item.tx1, item.ty1); 12354 ctx.lineTo(item.tx2, item.ty2); 12355 } 12356 12357 if (gridLines.drawOnChartArea) { 12358 ctx.moveTo(item.x1, item.y1); 12359 ctx.lineTo(item.x2, item.y2); 12360 } 12361 12362 ctx.stroke(); 12363 ctx.restore(); 12364 } 12365 } 12366 12367 if (axisWidth) { 12368 // Draw the line at the edge of the axis 12369 var firstLineWidth = axisWidth; 12370 var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, items.ticksLength - 1, 1); 12371 var borderValue = items.borderValue; 12372 var x1, x2, y1, y2; 12373 12374 if (me.isHorizontal()) { 12375 x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2; 12376 x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2; 12377 y1 = y2 = borderValue; 12378 } else { 12379 y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2; 12380 y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2; 12381 x1 = x2 = borderValue; 12382 } 12383 12384 ctx.lineWidth = axisWidth; 12385 ctx.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0); 12386 ctx.beginPath(); 12387 ctx.moveTo(x1, y1); 12388 ctx.lineTo(x2, y2); 12389 ctx.stroke(); 12390 } 12391 }, 12392 12393 /** 12394 * @private 12395 */ 12396 _drawLabels: function() { 12397 var me = this; 12398 var optionTicks = me.options.ticks; 12399 12400 if (!optionTicks.display) { 12401 return; 12402 } 12403 12404 var ctx = me.ctx; 12405 var items = me._labelItems || (me._labelItems = me._computeLabelItems()); 12406 var i, j, ilen, jlen, item, tickFont, label, y; 12407 12408 for (i = 0, ilen = items.length; i < ilen; ++i) { 12409 item = items[i]; 12410 tickFont = item.font; 12411 12412 // Make sure we draw text in the correct color and font 12413 ctx.save(); 12414 ctx.translate(item.x, item.y); 12415 ctx.rotate(item.rotation); 12416 ctx.font = tickFont.string; 12417 ctx.fillStyle = tickFont.color; 12418 ctx.textBaseline = 'middle'; 12419 ctx.textAlign = item.textAlign; 12420 12421 label = item.label; 12422 y = item.textOffset; 12423 if (isArray(label)) { 12424 for (j = 0, jlen = label.length; j < jlen; ++j) { 12425 // We just make sure the multiline element is a string here.. 12426 ctx.fillText('' + label[j], 0, y); 12427 y += tickFont.lineHeight; 12428 } 12429 } else { 12430 ctx.fillText(label, 0, y); 12431 } 12432 ctx.restore(); 12433 } 12434 }, 12435 12436 /** 12437 * @private 12438 */ 12439 _drawTitle: function() { 12440 var me = this; 12441 var ctx = me.ctx; 12442 var options = me.options; 12443 var scaleLabel = options.scaleLabel; 12444 12445 if (!scaleLabel.display) { 12446 return; 12447 } 12448 12449 var scaleLabelFontColor = valueOrDefault$a(scaleLabel.fontColor, core_defaults.global.defaultFontColor); 12450 var scaleLabelFont = helpers$1.options._parseFont(scaleLabel); 12451 var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding); 12452 var halfLineHeight = scaleLabelFont.lineHeight / 2; 12453 var position = options.position; 12454 var rotation = 0; 12455 var scaleLabelX, scaleLabelY; 12456 12457 if (me.isHorizontal()) { 12458 scaleLabelX = me.left + me.width / 2; // midpoint of the width 12459 scaleLabelY = position === 'bottom' 12460 ? me.bottom - halfLineHeight - scaleLabelPadding.bottom 12461 : me.top + halfLineHeight + scaleLabelPadding.top; 12462 } else { 12463 var isLeft = position === 'left'; 12464 scaleLabelX = isLeft 12465 ? me.left + halfLineHeight + scaleLabelPadding.top 12466 : me.right - halfLineHeight - scaleLabelPadding.top; 12467 scaleLabelY = me.top + me.height / 2; 12468 rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; 12469 } 12470 12471 ctx.save(); 12472 ctx.translate(scaleLabelX, scaleLabelY); 12473 ctx.rotate(rotation); 12474 ctx.textAlign = 'center'; 12475 ctx.textBaseline = 'middle'; 12476 ctx.fillStyle = scaleLabelFontColor; // render in correct colour 12477 ctx.font = scaleLabelFont.string; 12478 ctx.fillText(scaleLabel.labelString, 0, 0); 12479 ctx.restore(); 12480 }, 12481 12482 draw: function(chartArea) { 12483 var me = this; 12484 12485 if (!me._isVisible()) { 12486 return; 12487 } 12488 12489 me._drawGrid(chartArea); 12490 me._drawTitle(); 12491 me._drawLabels(); 12492 }, 12493 12494 /** 12495 * @private 12496 */ 12497 _layers: function() { 12498 var me = this; 12499 var opts = me.options; 12500 var tz = opts.ticks && opts.ticks.z || 0; 12501 var gz = opts.gridLines && opts.gridLines.z || 0; 12502 12503 if (!me._isVisible() || tz === gz || me.draw !== me._draw) { 12504 // backward compatibility: draw has been overridden by custom scale 12505 return [{ 12506 z: tz, 12507 draw: function() { 12508 me.draw.apply(me, arguments); 12509 } 12510 }]; 12511 } 12512 12513 return [{ 12514 z: gz, 12515 draw: function() { 12516 me._drawGrid.apply(me, arguments); 12517 me._drawTitle.apply(me, arguments); 12518 } 12519 }, { 12520 z: tz, 12521 draw: function() { 12522 me._drawLabels.apply(me, arguments); 12523 } 12524 }]; 12525 }, 12526 12527 /** 12528 * @private 12529 */ 12530 _getMatchingVisibleMetas: function(type) { 12531 var me = this; 12532 var isHorizontal = me.isHorizontal(); 12533 return me.chart._getSortedVisibleDatasetMetas() 12534 .filter(function(meta) { 12535 return (!type || meta.type === type) 12536 && (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id); 12537 }); 12538 } 12539}); 12540 12541Scale.prototype._draw = Scale.prototype.draw; 12542 12543var core_scale = Scale; 12544 12545var isNullOrUndef$1 = helpers$1.isNullOrUndef; 12546 12547var defaultConfig = { 12548 position: 'bottom' 12549}; 12550 12551var scale_category = core_scale.extend({ 12552 determineDataLimits: function() { 12553 var me = this; 12554 var labels = me._getLabels(); 12555 var ticksOpts = me.options.ticks; 12556 var min = ticksOpts.min; 12557 var max = ticksOpts.max; 12558 var minIndex = 0; 12559 var maxIndex = labels.length - 1; 12560 var findIndex; 12561 12562 if (min !== undefined) { 12563 // user specified min value 12564 findIndex = labels.indexOf(min); 12565 if (findIndex >= 0) { 12566 minIndex = findIndex; 12567 } 12568 } 12569 12570 if (max !== undefined) { 12571 // user specified max value 12572 findIndex = labels.indexOf(max); 12573 if (findIndex >= 0) { 12574 maxIndex = findIndex; 12575 } 12576 } 12577 12578 me.minIndex = minIndex; 12579 me.maxIndex = maxIndex; 12580 me.min = labels[minIndex]; 12581 me.max = labels[maxIndex]; 12582 }, 12583 12584 buildTicks: function() { 12585 var me = this; 12586 var labels = me._getLabels(); 12587 var minIndex = me.minIndex; 12588 var maxIndex = me.maxIndex; 12589 12590 // If we are viewing some subset of labels, slice the original array 12591 me.ticks = (minIndex === 0 && maxIndex === labels.length - 1) ? labels : labels.slice(minIndex, maxIndex + 1); 12592 }, 12593 12594 getLabelForIndex: function(index, datasetIndex) { 12595 var me = this; 12596 var chart = me.chart; 12597 12598 if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) { 12599 return me.getRightValue(chart.data.datasets[datasetIndex].data[index]); 12600 } 12601 12602 return me._getLabels()[index]; 12603 }, 12604 12605 _configure: function() { 12606 var me = this; 12607 var offset = me.options.offset; 12608 var ticks = me.ticks; 12609 12610 core_scale.prototype._configure.call(me); 12611 12612 if (!me.isHorizontal()) { 12613 // For backward compatibility, vertical category scale reverse is inverted. 12614 me._reversePixels = !me._reversePixels; 12615 } 12616 12617 if (!ticks) { 12618 return; 12619 } 12620 12621 me._startValue = me.minIndex - (offset ? 0.5 : 0); 12622 me._valueRange = Math.max(ticks.length - (offset ? 0 : 1), 1); 12623 }, 12624 12625 // Used to get data value locations. Value can either be an index or a numerical value 12626 getPixelForValue: function(value, index, datasetIndex) { 12627 var me = this; 12628 var valueCategory, labels, idx; 12629 12630 if (!isNullOrUndef$1(index) && !isNullOrUndef$1(datasetIndex)) { 12631 value = me.chart.data.datasets[datasetIndex].data[index]; 12632 } 12633 12634 // If value is a data object, then index is the index in the data array, 12635 // not the index of the scale. We need to change that. 12636 if (!isNullOrUndef$1(value)) { 12637 valueCategory = me.isHorizontal() ? value.x : value.y; 12638 } 12639 if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { 12640 labels = me._getLabels(); 12641 value = helpers$1.valueOrDefault(valueCategory, value); 12642 idx = labels.indexOf(value); 12643 index = idx !== -1 ? idx : index; 12644 if (isNaN(index)) { 12645 index = value; 12646 } 12647 } 12648 return me.getPixelForDecimal((index - me._startValue) / me._valueRange); 12649 }, 12650 12651 getPixelForTick: function(index) { 12652 var ticks = this.ticks; 12653 return index < 0 || index > ticks.length - 1 12654 ? null 12655 : this.getPixelForValue(ticks[index], index + this.minIndex); 12656 }, 12657 12658 getValueForPixel: function(pixel) { 12659 var me = this; 12660 var value = Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange); 12661 return Math.min(Math.max(value, 0), me.ticks.length - 1); 12662 }, 12663 12664 getBasePixel: function() { 12665 return this.bottom; 12666 } 12667}); 12668 12669// INTERNAL: static default options, registered in src/index.js 12670var _defaults = defaultConfig; 12671scale_category._defaults = _defaults; 12672 12673var noop = helpers$1.noop; 12674var isNullOrUndef$2 = helpers$1.isNullOrUndef; 12675 12676/** 12677 * Generate a set of linear ticks 12678 * @param generationOptions the options used to generate the ticks 12679 * @param dataRange the range of the data 12680 * @returns {number[]} array of tick values 12681 */ 12682function generateTicks(generationOptions, dataRange) { 12683 var ticks = []; 12684 // To get a "nice" value for the tick spacing, we will use the appropriately named 12685 // "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks 12686 // for details. 12687 12688 var MIN_SPACING = 1e-14; 12689 var stepSize = generationOptions.stepSize; 12690 var unit = stepSize || 1; 12691 var maxNumSpaces = generationOptions.maxTicks - 1; 12692 var min = generationOptions.min; 12693 var max = generationOptions.max; 12694 var precision = generationOptions.precision; 12695 var rmin = dataRange.min; 12696 var rmax = dataRange.max; 12697 var spacing = helpers$1.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit; 12698 var factor, niceMin, niceMax, numSpaces; 12699 12700 // Beyond MIN_SPACING floating point numbers being to lose precision 12701 // such that we can't do the math necessary to generate ticks 12702 if (spacing < MIN_SPACING && isNullOrUndef$2(min) && isNullOrUndef$2(max)) { 12703 return [rmin, rmax]; 12704 } 12705 12706 numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing); 12707 if (numSpaces > maxNumSpaces) { 12708 // If the calculated num of spaces exceeds maxNumSpaces, recalculate it 12709 spacing = helpers$1.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit; 12710 } 12711 12712 if (stepSize || isNullOrUndef$2(precision)) { 12713 // If a precision is not specified, calculate factor based on spacing 12714 factor = Math.pow(10, helpers$1._decimalPlaces(spacing)); 12715 } else { 12716 // If the user specified a precision, round to that number of decimal places 12717 factor = Math.pow(10, precision); 12718 spacing = Math.ceil(spacing * factor) / factor; 12719 } 12720 12721 niceMin = Math.floor(rmin / spacing) * spacing; 12722 niceMax = Math.ceil(rmax / spacing) * spacing; 12723 12724 // If min, max and stepSize is set and they make an evenly spaced scale use it. 12725 if (stepSize) { 12726 // If very close to our whole number, use it. 12727 if (!isNullOrUndef$2(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) { 12728 niceMin = min; 12729 } 12730 if (!isNullOrUndef$2(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) { 12731 niceMax = max; 12732 } 12733 } 12734 12735 numSpaces = (niceMax - niceMin) / spacing; 12736 // If very close to our rounded value, use it. 12737 if (helpers$1.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { 12738 numSpaces = Math.round(numSpaces); 12739 } else { 12740 numSpaces = Math.ceil(numSpaces); 12741 } 12742 12743 niceMin = Math.round(niceMin * factor) / factor; 12744 niceMax = Math.round(niceMax * factor) / factor; 12745 ticks.push(isNullOrUndef$2(min) ? niceMin : min); 12746 for (var j = 1; j < numSpaces; ++j) { 12747 ticks.push(Math.round((niceMin + j * spacing) * factor) / factor); 12748 } 12749 ticks.push(isNullOrUndef$2(max) ? niceMax : max); 12750 12751 return ticks; 12752} 12753 12754var scale_linearbase = core_scale.extend({ 12755 getRightValue: function(value) { 12756 if (typeof value === 'string') { 12757 return +value; 12758 } 12759 return core_scale.prototype.getRightValue.call(this, value); 12760 }, 12761 12762 handleTickRangeOptions: function() { 12763 var me = this; 12764 var opts = me.options; 12765 var tickOpts = opts.ticks; 12766 12767 // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, 12768 // do nothing since that would make the chart weird. If the user really wants a weird chart 12769 // axis, they can manually override it 12770 if (tickOpts.beginAtZero) { 12771 var minSign = helpers$1.sign(me.min); 12772 var maxSign = helpers$1.sign(me.max); 12773 12774 if (minSign < 0 && maxSign < 0) { 12775 // move the top up to 0 12776 me.max = 0; 12777 } else if (minSign > 0 && maxSign > 0) { 12778 // move the bottom down to 0 12779 me.min = 0; 12780 } 12781 } 12782 12783 var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; 12784 var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; 12785 12786 if (tickOpts.min !== undefined) { 12787 me.min = tickOpts.min; 12788 } else if (tickOpts.suggestedMin !== undefined) { 12789 if (me.min === null) { 12790 me.min = tickOpts.suggestedMin; 12791 } else { 12792 me.min = Math.min(me.min, tickOpts.suggestedMin); 12793 } 12794 } 12795 12796 if (tickOpts.max !== undefined) { 12797 me.max = tickOpts.max; 12798 } else if (tickOpts.suggestedMax !== undefined) { 12799 if (me.max === null) { 12800 me.max = tickOpts.suggestedMax; 12801 } else { 12802 me.max = Math.max(me.max, tickOpts.suggestedMax); 12803 } 12804 } 12805 12806 if (setMin !== setMax) { 12807 // We set the min or the max but not both. 12808 // So ensure that our range is good 12809 // Inverted or 0 length range can happen when 12810 // ticks.min is set, and no datasets are visible 12811 if (me.min >= me.max) { 12812 if (setMin) { 12813 me.max = me.min + 1; 12814 } else { 12815 me.min = me.max - 1; 12816 } 12817 } 12818 } 12819 12820 if (me.min === me.max) { 12821 me.max++; 12822 12823 if (!tickOpts.beginAtZero) { 12824 me.min--; 12825 } 12826 } 12827 }, 12828 12829 getTickLimit: function() { 12830 var me = this; 12831 var tickOpts = me.options.ticks; 12832 var stepSize = tickOpts.stepSize; 12833 var maxTicksLimit = tickOpts.maxTicksLimit; 12834 var maxTicks; 12835 12836 if (stepSize) { 12837 maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1; 12838 } else { 12839 maxTicks = me._computeTickLimit(); 12840 maxTicksLimit = maxTicksLimit || 11; 12841 } 12842 12843 if (maxTicksLimit) { 12844 maxTicks = Math.min(maxTicksLimit, maxTicks); 12845 } 12846 12847 return maxTicks; 12848 }, 12849 12850 _computeTickLimit: function() { 12851 return Number.POSITIVE_INFINITY; 12852 }, 12853 12854 handleDirectionalChanges: noop, 12855 12856 buildTicks: function() { 12857 var me = this; 12858 var opts = me.options; 12859 var tickOpts = opts.ticks; 12860 12861 // Figure out what the max number of ticks we can support it is based on the size of 12862 // the axis area. For now, we say that the minimum tick spacing in pixels must be 40 12863 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on 12864 // the graph. Make sure we always have at least 2 ticks 12865 var maxTicks = me.getTickLimit(); 12866 maxTicks = Math.max(2, maxTicks); 12867 12868 var numericGeneratorOptions = { 12869 maxTicks: maxTicks, 12870 min: tickOpts.min, 12871 max: tickOpts.max, 12872 precision: tickOpts.precision, 12873 stepSize: helpers$1.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) 12874 }; 12875 var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); 12876 12877 me.handleDirectionalChanges(); 12878 12879 // At this point, we need to update our max and min given the tick values since we have expanded the 12880 // range of the scale 12881 me.max = helpers$1.max(ticks); 12882 me.min = helpers$1.min(ticks); 12883 12884 if (tickOpts.reverse) { 12885 ticks.reverse(); 12886 12887 me.start = me.max; 12888 me.end = me.min; 12889 } else { 12890 me.start = me.min; 12891 me.end = me.max; 12892 } 12893 }, 12894 12895 convertTicksToLabels: function() { 12896 var me = this; 12897 me.ticksAsNumbers = me.ticks.slice(); 12898 me.zeroLineIndex = me.ticks.indexOf(0); 12899 12900 core_scale.prototype.convertTicksToLabels.call(me); 12901 }, 12902 12903 _configure: function() { 12904 var me = this; 12905 var ticks = me.getTicks(); 12906 var start = me.min; 12907 var end = me.max; 12908 var offset; 12909 12910 core_scale.prototype._configure.call(me); 12911 12912 if (me.options.offset && ticks.length) { 12913 offset = (end - start) / Math.max(ticks.length - 1, 1) / 2; 12914 start -= offset; 12915 end += offset; 12916 } 12917 me._startValue = start; 12918 me._endValue = end; 12919 me._valueRange = end - start; 12920 } 12921}); 12922 12923var defaultConfig$1 = { 12924 position: 'left', 12925 ticks: { 12926 callback: core_ticks.formatters.linear 12927 } 12928}; 12929 12930var DEFAULT_MIN = 0; 12931var DEFAULT_MAX = 1; 12932 12933function getOrCreateStack(stacks, stacked, meta) { 12934 var key = [ 12935 meta.type, 12936 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined 12937 stacked === undefined && meta.stack === undefined ? meta.index : '', 12938 meta.stack 12939 ].join('.'); 12940 12941 if (stacks[key] === undefined) { 12942 stacks[key] = { 12943 pos: [], 12944 neg: [] 12945 }; 12946 } 12947 12948 return stacks[key]; 12949} 12950 12951function stackData(scale, stacks, meta, data) { 12952 var opts = scale.options; 12953 var stacked = opts.stacked; 12954 var stack = getOrCreateStack(stacks, stacked, meta); 12955 var pos = stack.pos; 12956 var neg = stack.neg; 12957 var ilen = data.length; 12958 var i, value; 12959 12960 for (i = 0; i < ilen; ++i) { 12961 value = scale._parseValue(data[i]); 12962 if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { 12963 continue; 12964 } 12965 12966 pos[i] = pos[i] || 0; 12967 neg[i] = neg[i] || 0; 12968 12969 if (opts.relativePoints) { 12970 pos[i] = 100; 12971 } else if (value.min < 0 || value.max < 0) { 12972 neg[i] += value.min; 12973 } else { 12974 pos[i] += value.max; 12975 } 12976 } 12977} 12978 12979function updateMinMax(scale, meta, data) { 12980 var ilen = data.length; 12981 var i, value; 12982 12983 for (i = 0; i < ilen; ++i) { 12984 value = scale._parseValue(data[i]); 12985 if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { 12986 continue; 12987 } 12988 12989 scale.min = Math.min(scale.min, value.min); 12990 scale.max = Math.max(scale.max, value.max); 12991 } 12992} 12993 12994var scale_linear = scale_linearbase.extend({ 12995 determineDataLimits: function() { 12996 var me = this; 12997 var opts = me.options; 12998 var chart = me.chart; 12999 var datasets = chart.data.datasets; 13000 var metasets = me._getMatchingVisibleMetas(); 13001 var hasStacks = opts.stacked; 13002 var stacks = {}; 13003 var ilen = metasets.length; 13004 var i, meta, data, values; 13005 13006 me.min = Number.POSITIVE_INFINITY; 13007 me.max = Number.NEGATIVE_INFINITY; 13008 13009 if (hasStacks === undefined) { 13010 for (i = 0; !hasStacks && i < ilen; ++i) { 13011 meta = metasets[i]; 13012 hasStacks = meta.stack !== undefined; 13013 } 13014 } 13015 13016 for (i = 0; i < ilen; ++i) { 13017 meta = metasets[i]; 13018 data = datasets[meta.index].data; 13019 if (hasStacks) { 13020 stackData(me, stacks, meta, data); 13021 } else { 13022 updateMinMax(me, meta, data); 13023 } 13024 } 13025 13026 helpers$1.each(stacks, function(stackValues) { 13027 values = stackValues.pos.concat(stackValues.neg); 13028 me.min = Math.min(me.min, helpers$1.min(values)); 13029 me.max = Math.max(me.max, helpers$1.max(values)); 13030 }); 13031 13032 me.min = helpers$1.isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; 13033 me.max = helpers$1.isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; 13034 13035 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero 13036 me.handleTickRangeOptions(); 13037 }, 13038 13039 // Returns the maximum number of ticks based on the scale dimension 13040 _computeTickLimit: function() { 13041 var me = this; 13042 var tickFont; 13043 13044 if (me.isHorizontal()) { 13045 return Math.ceil(me.width / 40); 13046 } 13047 tickFont = helpers$1.options._parseFont(me.options.ticks); 13048 return Math.ceil(me.height / tickFont.lineHeight); 13049 }, 13050 13051 // Called after the ticks are built. We need 13052 handleDirectionalChanges: function() { 13053 if (!this.isHorizontal()) { 13054 // We are in a vertical orientation. The top value is the highest. So reverse the array 13055 this.ticks.reverse(); 13056 } 13057 }, 13058 13059 getLabelForIndex: function(index, datasetIndex) { 13060 return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]); 13061 }, 13062 13063 // Utils 13064 getPixelForValue: function(value) { 13065 var me = this; 13066 return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange); 13067 }, 13068 13069 getValueForPixel: function(pixel) { 13070 return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange; 13071 }, 13072 13073 getPixelForTick: function(index) { 13074 var ticks = this.ticksAsNumbers; 13075 if (index < 0 || index > ticks.length - 1) { 13076 return null; 13077 } 13078 return this.getPixelForValue(ticks[index]); 13079 } 13080}); 13081 13082// INTERNAL: static default options, registered in src/index.js 13083var _defaults$1 = defaultConfig$1; 13084scale_linear._defaults = _defaults$1; 13085 13086var valueOrDefault$b = helpers$1.valueOrDefault; 13087var log10 = helpers$1.math.log10; 13088 13089/** 13090 * Generate a set of logarithmic ticks 13091 * @param generationOptions the options used to generate the ticks 13092 * @param dataRange the range of the data 13093 * @returns {number[]} array of tick values 13094 */ 13095function generateTicks$1(generationOptions, dataRange) { 13096 var ticks = []; 13097 13098 var tickVal = valueOrDefault$b(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min)))); 13099 13100 var endExp = Math.floor(log10(dataRange.max)); 13101 var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); 13102 var exp, significand; 13103 13104 if (tickVal === 0) { 13105 exp = Math.floor(log10(dataRange.minNotZero)); 13106 significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); 13107 13108 ticks.push(tickVal); 13109 tickVal = significand * Math.pow(10, exp); 13110 } else { 13111 exp = Math.floor(log10(tickVal)); 13112 significand = Math.floor(tickVal / Math.pow(10, exp)); 13113 } 13114 var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; 13115 13116 do { 13117 ticks.push(tickVal); 13118 13119 ++significand; 13120 if (significand === 10) { 13121 significand = 1; 13122 ++exp; 13123 precision = exp >= 0 ? 1 : precision; 13124 } 13125 13126 tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; 13127 } while (exp < endExp || (exp === endExp && significand < endSignificand)); 13128 13129 var lastTick = valueOrDefault$b(generationOptions.max, tickVal); 13130 ticks.push(lastTick); 13131 13132 return ticks; 13133} 13134 13135var defaultConfig$2 = { 13136 position: 'left', 13137 13138 // label settings 13139 ticks: { 13140 callback: core_ticks.formatters.logarithmic 13141 } 13142}; 13143 13144// TODO(v3): change this to positiveOrDefault 13145function nonNegativeOrDefault(value, defaultValue) { 13146 return helpers$1.isFinite(value) && value >= 0 ? value : defaultValue; 13147} 13148 13149var scale_logarithmic = core_scale.extend({ 13150 determineDataLimits: function() { 13151 var me = this; 13152 var opts = me.options; 13153 var chart = me.chart; 13154 var datasets = chart.data.datasets; 13155 var isHorizontal = me.isHorizontal(); 13156 function IDMatches(meta) { 13157 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; 13158 } 13159 var datasetIndex, meta, value, data, i, ilen; 13160 13161 // Calculate Range 13162 me.min = Number.POSITIVE_INFINITY; 13163 me.max = Number.NEGATIVE_INFINITY; 13164 me.minNotZero = Number.POSITIVE_INFINITY; 13165 13166 var hasStacks = opts.stacked; 13167 if (hasStacks === undefined) { 13168 for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { 13169 meta = chart.getDatasetMeta(datasetIndex); 13170 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && 13171 meta.stack !== undefined) { 13172 hasStacks = true; 13173 break; 13174 } 13175 } 13176 } 13177 13178 if (opts.stacked || hasStacks) { 13179 var valuesPerStack = {}; 13180 13181 for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { 13182 meta = chart.getDatasetMeta(datasetIndex); 13183 var key = [ 13184 meta.type, 13185 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined 13186 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), 13187 meta.stack 13188 ].join('.'); 13189 13190 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 13191 if (valuesPerStack[key] === undefined) { 13192 valuesPerStack[key] = []; 13193 } 13194 13195 data = datasets[datasetIndex].data; 13196 for (i = 0, ilen = data.length; i < ilen; i++) { 13197 var values = valuesPerStack[key]; 13198 value = me._parseValue(data[i]); 13199 // invalid, hidden and negative values are ignored 13200 if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) { 13201 continue; 13202 } 13203 values[i] = values[i] || 0; 13204 values[i] += value.max; 13205 } 13206 } 13207 } 13208 13209 helpers$1.each(valuesPerStack, function(valuesForType) { 13210 if (valuesForType.length > 0) { 13211 var minVal = helpers$1.min(valuesForType); 13212 var maxVal = helpers$1.max(valuesForType); 13213 me.min = Math.min(me.min, minVal); 13214 me.max = Math.max(me.max, maxVal); 13215 } 13216 }); 13217 13218 } else { 13219 for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { 13220 meta = chart.getDatasetMeta(datasetIndex); 13221 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 13222 data = datasets[datasetIndex].data; 13223 for (i = 0, ilen = data.length; i < ilen; i++) { 13224 value = me._parseValue(data[i]); 13225 // invalid, hidden and negative values are ignored 13226 if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) { 13227 continue; 13228 } 13229 13230 me.min = Math.min(value.min, me.min); 13231 me.max = Math.max(value.max, me.max); 13232 13233 if (value.min !== 0) { 13234 me.minNotZero = Math.min(value.min, me.minNotZero); 13235 } 13236 } 13237 } 13238 } 13239 } 13240 13241 me.min = helpers$1.isFinite(me.min) ? me.min : null; 13242 me.max = helpers$1.isFinite(me.max) ? me.max : null; 13243 me.minNotZero = helpers$1.isFinite(me.minNotZero) ? me.minNotZero : null; 13244 13245 // Common base implementation to handle ticks.min, ticks.max 13246 this.handleTickRangeOptions(); 13247 }, 13248 13249 handleTickRangeOptions: function() { 13250 var me = this; 13251 var tickOpts = me.options.ticks; 13252 var DEFAULT_MIN = 1; 13253 var DEFAULT_MAX = 10; 13254 13255 me.min = nonNegativeOrDefault(tickOpts.min, me.min); 13256 me.max = nonNegativeOrDefault(tickOpts.max, me.max); 13257 13258 if (me.min === me.max) { 13259 if (me.min !== 0 && me.min !== null) { 13260 me.min = Math.pow(10, Math.floor(log10(me.min)) - 1); 13261 me.max = Math.pow(10, Math.floor(log10(me.max)) + 1); 13262 } else { 13263 me.min = DEFAULT_MIN; 13264 me.max = DEFAULT_MAX; 13265 } 13266 } 13267 if (me.min === null) { 13268 me.min = Math.pow(10, Math.floor(log10(me.max)) - 1); 13269 } 13270 if (me.max === null) { 13271 me.max = me.min !== 0 13272 ? Math.pow(10, Math.floor(log10(me.min)) + 1) 13273 : DEFAULT_MAX; 13274 } 13275 if (me.minNotZero === null) { 13276 if (me.min > 0) { 13277 me.minNotZero = me.min; 13278 } else if (me.max < 1) { 13279 me.minNotZero = Math.pow(10, Math.floor(log10(me.max))); 13280 } else { 13281 me.minNotZero = DEFAULT_MIN; 13282 } 13283 } 13284 }, 13285 13286 buildTicks: function() { 13287 var me = this; 13288 var tickOpts = me.options.ticks; 13289 var reverse = !me.isHorizontal(); 13290 13291 var generationOptions = { 13292 min: nonNegativeOrDefault(tickOpts.min), 13293 max: nonNegativeOrDefault(tickOpts.max) 13294 }; 13295 var ticks = me.ticks = generateTicks$1(generationOptions, me); 13296 13297 // At this point, we need to update our max and min given the tick values since we have expanded the 13298 // range of the scale 13299 me.max = helpers$1.max(ticks); 13300 me.min = helpers$1.min(ticks); 13301 13302 if (tickOpts.reverse) { 13303 reverse = !reverse; 13304 me.start = me.max; 13305 me.end = me.min; 13306 } else { 13307 me.start = me.min; 13308 me.end = me.max; 13309 } 13310 if (reverse) { 13311 ticks.reverse(); 13312 } 13313 }, 13314 13315 convertTicksToLabels: function() { 13316 this.tickValues = this.ticks.slice(); 13317 13318 core_scale.prototype.convertTicksToLabels.call(this); 13319 }, 13320 13321 // Get the correct tooltip label 13322 getLabelForIndex: function(index, datasetIndex) { 13323 return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]); 13324 }, 13325 13326 getPixelForTick: function(index) { 13327 var ticks = this.tickValues; 13328 if (index < 0 || index > ticks.length - 1) { 13329 return null; 13330 } 13331 return this.getPixelForValue(ticks[index]); 13332 }, 13333 13334 /** 13335 * Returns the value of the first tick. 13336 * @param {number} value - The minimum not zero value. 13337 * @return {number} The first tick value. 13338 * @private 13339 */ 13340 _getFirstTickValue: function(value) { 13341 var exp = Math.floor(log10(value)); 13342 var significand = Math.floor(value / Math.pow(10, exp)); 13343 13344 return significand * Math.pow(10, exp); 13345 }, 13346 13347 _configure: function() { 13348 var me = this; 13349 var start = me.min; 13350 var offset = 0; 13351 13352 core_scale.prototype._configure.call(me); 13353 13354 if (start === 0) { 13355 start = me._getFirstTickValue(me.minNotZero); 13356 offset = valueOrDefault$b(me.options.ticks.fontSize, core_defaults.global.defaultFontSize) / me._length; 13357 } 13358 13359 me._startValue = log10(start); 13360 me._valueOffset = offset; 13361 me._valueRange = (log10(me.max) - log10(start)) / (1 - offset); 13362 }, 13363 13364 getPixelForValue: function(value) { 13365 var me = this; 13366 var decimal = 0; 13367 13368 value = +me.getRightValue(value); 13369 13370 if (value > me.min && value > 0) { 13371 decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset; 13372 } 13373 return me.getPixelForDecimal(decimal); 13374 }, 13375 13376 getValueForPixel: function(pixel) { 13377 var me = this; 13378 var decimal = me.getDecimalForPixel(pixel); 13379 return decimal === 0 && me.min === 0 13380 ? 0 13381 : Math.pow(10, me._startValue + (decimal - me._valueOffset) * me._valueRange); 13382 } 13383}); 13384 13385// INTERNAL: static default options, registered in src/index.js 13386var _defaults$2 = defaultConfig$2; 13387scale_logarithmic._defaults = _defaults$2; 13388 13389var valueOrDefault$c = helpers$1.valueOrDefault; 13390var valueAtIndexOrDefault$1 = helpers$1.valueAtIndexOrDefault; 13391var resolve$4 = helpers$1.options.resolve; 13392 13393var defaultConfig$3 = { 13394 display: true, 13395 13396 // Boolean - Whether to animate scaling the chart from the centre 13397 animate: true, 13398 position: 'chartArea', 13399 13400 angleLines: { 13401 display: true, 13402 color: 'rgba(0,0,0,0.1)', 13403 lineWidth: 1, 13404 borderDash: [], 13405 borderDashOffset: 0.0 13406 }, 13407 13408 gridLines: { 13409 circular: false 13410 }, 13411 13412 // label settings 13413 ticks: { 13414 // Boolean - Show a backdrop to the scale label 13415 showLabelBackdrop: true, 13416 13417 // String - The colour of the label backdrop 13418 backdropColor: 'rgba(255,255,255,0.75)', 13419 13420 // Number - The backdrop padding above & below the label in pixels 13421 backdropPaddingY: 2, 13422 13423 // Number - The backdrop padding to the side of the label in pixels 13424 backdropPaddingX: 2, 13425 13426 callback: core_ticks.formatters.linear 13427 }, 13428 13429 pointLabels: { 13430 // Boolean - if true, show point labels 13431 display: true, 13432 13433 // Number - Point label font size in pixels 13434 fontSize: 10, 13435 13436 // Function - Used to convert point labels 13437 callback: function(label) { 13438 return label; 13439 } 13440 } 13441}; 13442 13443function getTickBackdropHeight(opts) { 13444 var tickOpts = opts.ticks; 13445 13446 if (tickOpts.display && opts.display) { 13447 return valueOrDefault$c(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2; 13448 } 13449 return 0; 13450} 13451 13452function measureLabelSize(ctx, lineHeight, label) { 13453 if (helpers$1.isArray(label)) { 13454 return { 13455 w: helpers$1.longestText(ctx, ctx.font, label), 13456 h: label.length * lineHeight 13457 }; 13458 } 13459 13460 return { 13461 w: ctx.measureText(label).width, 13462 h: lineHeight 13463 }; 13464} 13465 13466function determineLimits(angle, pos, size, min, max) { 13467 if (angle === min || angle === max) { 13468 return { 13469 start: pos - (size / 2), 13470 end: pos + (size / 2) 13471 }; 13472 } else if (angle < min || angle > max) { 13473 return { 13474 start: pos - size, 13475 end: pos 13476 }; 13477 } 13478 13479 return { 13480 start: pos, 13481 end: pos + size 13482 }; 13483} 13484 13485/** 13486 * Helper function to fit a radial linear scale with point labels 13487 */ 13488function fitWithPointLabels(scale) { 13489 13490 // Right, this is really confusing and there is a lot of maths going on here 13491 // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 13492 // 13493 // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif 13494 // 13495 // Solution: 13496 // 13497 // We assume the radius of the polygon is half the size of the canvas at first 13498 // at each index we check if the text overlaps. 13499 // 13500 // Where it does, we store that angle and that index. 13501 // 13502 // After finding the largest index and angle we calculate how much we need to remove 13503 // from the shape radius to move the point inwards by that x. 13504 // 13505 // We average the left and right distances to get the maximum shape radius that can fit in the box 13506 // along with labels. 13507 // 13508 // Once we have that, we can find the centre point for the chart, by taking the x text protrusion 13509 // on each side, removing that from the size, halving it and adding the left x protrusion width. 13510 // 13511 // This will mean we have a shape fitted to the canvas, as large as it can be with the labels 13512 // and position it in the most space efficient manner 13513 // 13514 // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif 13515 13516 var plFont = helpers$1.options._parseFont(scale.options.pointLabels); 13517 13518 // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. 13519 // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points 13520 var furthestLimits = { 13521 l: 0, 13522 r: scale.width, 13523 t: 0, 13524 b: scale.height - scale.paddingTop 13525 }; 13526 var furthestAngles = {}; 13527 var i, textSize, pointPosition; 13528 13529 scale.ctx.font = plFont.string; 13530 scale._pointLabelSizes = []; 13531 13532 var valueCount = scale.chart.data.labels.length; 13533 for (i = 0; i < valueCount; i++) { 13534 pointPosition = scale.getPointPosition(i, scale.drawingArea + 5); 13535 textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]); 13536 scale._pointLabelSizes[i] = textSize; 13537 13538 // Add quarter circle to make degree 0 mean top of circle 13539 var angleRadians = scale.getIndexAngle(i); 13540 var angle = helpers$1.toDegrees(angleRadians) % 360; 13541 var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); 13542 var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); 13543 13544 if (hLimits.start < furthestLimits.l) { 13545 furthestLimits.l = hLimits.start; 13546 furthestAngles.l = angleRadians; 13547 } 13548 13549 if (hLimits.end > furthestLimits.r) { 13550 furthestLimits.r = hLimits.end; 13551 furthestAngles.r = angleRadians; 13552 } 13553 13554 if (vLimits.start < furthestLimits.t) { 13555 furthestLimits.t = vLimits.start; 13556 furthestAngles.t = angleRadians; 13557 } 13558 13559 if (vLimits.end > furthestLimits.b) { 13560 furthestLimits.b = vLimits.end; 13561 furthestAngles.b = angleRadians; 13562 } 13563 } 13564 13565 scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles); 13566} 13567 13568function getTextAlignForAngle(angle) { 13569 if (angle === 0 || angle === 180) { 13570 return 'center'; 13571 } else if (angle < 180) { 13572 return 'left'; 13573 } 13574 13575 return 'right'; 13576} 13577 13578function fillText(ctx, text, position, lineHeight) { 13579 var y = position.y + lineHeight / 2; 13580 var i, ilen; 13581 13582 if (helpers$1.isArray(text)) { 13583 for (i = 0, ilen = text.length; i < ilen; ++i) { 13584 ctx.fillText(text[i], position.x, y); 13585 y += lineHeight; 13586 } 13587 } else { 13588 ctx.fillText(text, position.x, y); 13589 } 13590} 13591 13592function adjustPointPositionForLabelHeight(angle, textSize, position) { 13593 if (angle === 90 || angle === 270) { 13594 position.y -= (textSize.h / 2); 13595 } else if (angle > 270 || angle < 90) { 13596 position.y -= textSize.h; 13597 } 13598} 13599 13600function drawPointLabels(scale) { 13601 var ctx = scale.ctx; 13602 var opts = scale.options; 13603 var pointLabelOpts = opts.pointLabels; 13604 var tickBackdropHeight = getTickBackdropHeight(opts); 13605 var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); 13606 var plFont = helpers$1.options._parseFont(pointLabelOpts); 13607 13608 ctx.save(); 13609 13610 ctx.font = plFont.string; 13611 ctx.textBaseline = 'middle'; 13612 13613 for (var i = scale.chart.data.labels.length - 1; i >= 0; i--) { 13614 // Extra pixels out for some label spacing 13615 var extra = (i === 0 ? tickBackdropHeight / 2 : 0); 13616 var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5); 13617 13618 // Keep this in loop since we may support array properties here 13619 var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor); 13620 ctx.fillStyle = pointLabelFontColor; 13621 13622 var angleRadians = scale.getIndexAngle(i); 13623 var angle = helpers$1.toDegrees(angleRadians); 13624 ctx.textAlign = getTextAlignForAngle(angle); 13625 adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); 13626 fillText(ctx, scale.pointLabels[i], pointLabelPosition, plFont.lineHeight); 13627 } 13628 ctx.restore(); 13629} 13630 13631function drawRadiusLine(scale, gridLineOpts, radius, index) { 13632 var ctx = scale.ctx; 13633 var circular = gridLineOpts.circular; 13634 var valueCount = scale.chart.data.labels.length; 13635 var lineColor = valueAtIndexOrDefault$1(gridLineOpts.color, index - 1); 13636 var lineWidth = valueAtIndexOrDefault$1(gridLineOpts.lineWidth, index - 1); 13637 var pointPosition; 13638 13639 if ((!circular && !valueCount) || !lineColor || !lineWidth) { 13640 return; 13641 } 13642 13643 ctx.save(); 13644 ctx.strokeStyle = lineColor; 13645 ctx.lineWidth = lineWidth; 13646 if (ctx.setLineDash) { 13647 ctx.setLineDash(gridLineOpts.borderDash || []); 13648 ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0; 13649 } 13650 13651 ctx.beginPath(); 13652 if (circular) { 13653 // Draw circular arcs between the points 13654 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); 13655 } else { 13656 // Draw straight lines connecting each index 13657 pointPosition = scale.getPointPosition(0, radius); 13658 ctx.moveTo(pointPosition.x, pointPosition.y); 13659 13660 for (var i = 1; i < valueCount; i++) { 13661 pointPosition = scale.getPointPosition(i, radius); 13662 ctx.lineTo(pointPosition.x, pointPosition.y); 13663 } 13664 } 13665 ctx.closePath(); 13666 ctx.stroke(); 13667 ctx.restore(); 13668} 13669 13670function numberOrZero(param) { 13671 return helpers$1.isNumber(param) ? param : 0; 13672} 13673 13674var scale_radialLinear = scale_linearbase.extend({ 13675 setDimensions: function() { 13676 var me = this; 13677 13678 // Set the unconstrained dimension before label rotation 13679 me.width = me.maxWidth; 13680 me.height = me.maxHeight; 13681 me.paddingTop = getTickBackdropHeight(me.options) / 2; 13682 me.xCenter = Math.floor(me.width / 2); 13683 me.yCenter = Math.floor((me.height - me.paddingTop) / 2); 13684 me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2; 13685 }, 13686 13687 determineDataLimits: function() { 13688 var me = this; 13689 var chart = me.chart; 13690 var min = Number.POSITIVE_INFINITY; 13691 var max = Number.NEGATIVE_INFINITY; 13692 13693 helpers$1.each(chart.data.datasets, function(dataset, datasetIndex) { 13694 if (chart.isDatasetVisible(datasetIndex)) { 13695 var meta = chart.getDatasetMeta(datasetIndex); 13696 13697 helpers$1.each(dataset.data, function(rawValue, index) { 13698 var value = +me.getRightValue(rawValue); 13699 if (isNaN(value) || meta.data[index].hidden) { 13700 return; 13701 } 13702 13703 min = Math.min(value, min); 13704 max = Math.max(value, max); 13705 }); 13706 } 13707 }); 13708 13709 me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); 13710 me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); 13711 13712 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero 13713 me.handleTickRangeOptions(); 13714 }, 13715 13716 // Returns the maximum number of ticks based on the scale dimension 13717 _computeTickLimit: function() { 13718 return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); 13719 }, 13720 13721 convertTicksToLabels: function() { 13722 var me = this; 13723 13724 scale_linearbase.prototype.convertTicksToLabels.call(me); 13725 13726 // Point labels 13727 me.pointLabels = me.chart.data.labels.map(function() { 13728 var label = helpers$1.callback(me.options.pointLabels.callback, arguments, me); 13729 return label || label === 0 ? label : ''; 13730 }); 13731 }, 13732 13733 getLabelForIndex: function(index, datasetIndex) { 13734 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); 13735 }, 13736 13737 fit: function() { 13738 var me = this; 13739 var opts = me.options; 13740 13741 if (opts.display && opts.pointLabels.display) { 13742 fitWithPointLabels(me); 13743 } else { 13744 me.setCenterPoint(0, 0, 0, 0); 13745 } 13746 }, 13747 13748 /** 13749 * Set radius reductions and determine new radius and center point 13750 * @private 13751 */ 13752 setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { 13753 var me = this; 13754 var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); 13755 var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); 13756 var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); 13757 var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b); 13758 13759 radiusReductionLeft = numberOrZero(radiusReductionLeft); 13760 radiusReductionRight = numberOrZero(radiusReductionRight); 13761 radiusReductionTop = numberOrZero(radiusReductionTop); 13762 radiusReductionBottom = numberOrZero(radiusReductionBottom); 13763 13764 me.drawingArea = Math.min( 13765 Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), 13766 Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); 13767 me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); 13768 }, 13769 13770 setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { 13771 var me = this; 13772 var maxRight = me.width - rightMovement - me.drawingArea; 13773 var maxLeft = leftMovement + me.drawingArea; 13774 var maxTop = topMovement + me.drawingArea; 13775 var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea; 13776 13777 me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); 13778 me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop); 13779 }, 13780 13781 getIndexAngle: function(index) { 13782 var chart = this.chart; 13783 var angleMultiplier = 360 / chart.data.labels.length; 13784 var options = chart.options || {}; 13785 var startAngle = options.startAngle || 0; 13786 13787 // Start from the top instead of right, so remove a quarter of the circle 13788 var angle = (index * angleMultiplier + startAngle) % 360; 13789 13790 return (angle < 0 ? angle + 360 : angle) * Math.PI * 2 / 360; 13791 }, 13792 13793 getDistanceFromCenterForValue: function(value) { 13794 var me = this; 13795 13796 if (helpers$1.isNullOrUndef(value)) { 13797 return NaN; 13798 } 13799 13800 // Take into account half font size + the yPadding of the top value 13801 var scalingFactor = me.drawingArea / (me.max - me.min); 13802 if (me.options.ticks.reverse) { 13803 return (me.max - value) * scalingFactor; 13804 } 13805 return (value - me.min) * scalingFactor; 13806 }, 13807 13808 getPointPosition: function(index, distanceFromCenter) { 13809 var me = this; 13810 var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); 13811 return { 13812 x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter, 13813 y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter 13814 }; 13815 }, 13816 13817 getPointPositionForValue: function(index, value) { 13818 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); 13819 }, 13820 13821 getBasePosition: function(index) { 13822 var me = this; 13823 var min = me.min; 13824 var max = me.max; 13825 13826 return me.getPointPositionForValue(index || 0, 13827 me.beginAtZero ? 0 : 13828 min < 0 && max < 0 ? max : 13829 min > 0 && max > 0 ? min : 13830 0); 13831 }, 13832 13833 /** 13834 * @private 13835 */ 13836 _drawGrid: function() { 13837 var me = this; 13838 var ctx = me.ctx; 13839 var opts = me.options; 13840 var gridLineOpts = opts.gridLines; 13841 var angleLineOpts = opts.angleLines; 13842 var lineWidth = valueOrDefault$c(angleLineOpts.lineWidth, gridLineOpts.lineWidth); 13843 var lineColor = valueOrDefault$c(angleLineOpts.color, gridLineOpts.color); 13844 var i, offset, position; 13845 13846 if (opts.pointLabels.display) { 13847 drawPointLabels(me); 13848 } 13849 13850 if (gridLineOpts.display) { 13851 helpers$1.each(me.ticks, function(label, index) { 13852 if (index !== 0) { 13853 offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); 13854 drawRadiusLine(me, gridLineOpts, offset, index); 13855 } 13856 }); 13857 } 13858 13859 if (angleLineOpts.display && lineWidth && lineColor) { 13860 ctx.save(); 13861 ctx.lineWidth = lineWidth; 13862 ctx.strokeStyle = lineColor; 13863 if (ctx.setLineDash) { 13864 ctx.setLineDash(resolve$4([angleLineOpts.borderDash, gridLineOpts.borderDash, []])); 13865 ctx.lineDashOffset = resolve$4([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]); 13866 } 13867 13868 for (i = me.chart.data.labels.length - 1; i >= 0; i--) { 13869 offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max); 13870 position = me.getPointPosition(i, offset); 13871 ctx.beginPath(); 13872 ctx.moveTo(me.xCenter, me.yCenter); 13873 ctx.lineTo(position.x, position.y); 13874 ctx.stroke(); 13875 } 13876 13877 ctx.restore(); 13878 } 13879 }, 13880 13881 /** 13882 * @private 13883 */ 13884 _drawLabels: function() { 13885 var me = this; 13886 var ctx = me.ctx; 13887 var opts = me.options; 13888 var tickOpts = opts.ticks; 13889 13890 if (!tickOpts.display) { 13891 return; 13892 } 13893 13894 var startAngle = me.getIndexAngle(0); 13895 var tickFont = helpers$1.options._parseFont(tickOpts); 13896 var tickFontColor = valueOrDefault$c(tickOpts.fontColor, core_defaults.global.defaultFontColor); 13897 var offset, width; 13898 13899 ctx.save(); 13900 ctx.font = tickFont.string; 13901 ctx.translate(me.xCenter, me.yCenter); 13902 ctx.rotate(startAngle); 13903 ctx.textAlign = 'center'; 13904 ctx.textBaseline = 'middle'; 13905 13906 helpers$1.each(me.ticks, function(label, index) { 13907 if (index === 0 && !tickOpts.reverse) { 13908 return; 13909 } 13910 13911 offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); 13912 13913 if (tickOpts.showLabelBackdrop) { 13914 width = ctx.measureText(label).width; 13915 ctx.fillStyle = tickOpts.backdropColor; 13916 13917 ctx.fillRect( 13918 -width / 2 - tickOpts.backdropPaddingX, 13919 -offset - tickFont.size / 2 - tickOpts.backdropPaddingY, 13920 width + tickOpts.backdropPaddingX * 2, 13921 tickFont.size + tickOpts.backdropPaddingY * 2 13922 ); 13923 } 13924 13925 ctx.fillStyle = tickFontColor; 13926 ctx.fillText(label, 0, -offset); 13927 }); 13928 13929 ctx.restore(); 13930 }, 13931 13932 /** 13933 * @private 13934 */ 13935 _drawTitle: helpers$1.noop 13936}); 13937 13938// INTERNAL: static default options, registered in src/index.js 13939var _defaults$3 = defaultConfig$3; 13940scale_radialLinear._defaults = _defaults$3; 13941 13942var deprecated$1 = helpers$1._deprecated; 13943var resolve$5 = helpers$1.options.resolve; 13944var valueOrDefault$d = helpers$1.valueOrDefault; 13945 13946// Integer constants are from the ES6 spec. 13947var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; 13948var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; 13949 13950var INTERVALS = { 13951 millisecond: { 13952 common: true, 13953 size: 1, 13954 steps: 1000 13955 }, 13956 second: { 13957 common: true, 13958 size: 1000, 13959 steps: 60 13960 }, 13961 minute: { 13962 common: true, 13963 size: 60000, 13964 steps: 60 13965 }, 13966 hour: { 13967 common: true, 13968 size: 3600000, 13969 steps: 24 13970 }, 13971 day: { 13972 common: true, 13973 size: 86400000, 13974 steps: 30 13975 }, 13976 week: { 13977 common: false, 13978 size: 604800000, 13979 steps: 4 13980 }, 13981 month: { 13982 common: true, 13983 size: 2.628e9, 13984 steps: 12 13985 }, 13986 quarter: { 13987 common: false, 13988 size: 7.884e9, 13989 steps: 4 13990 }, 13991 year: { 13992 common: true, 13993 size: 3.154e10 13994 } 13995}; 13996 13997var UNITS = Object.keys(INTERVALS); 13998 13999function sorter(a, b) { 14000 return a - b; 14001} 14002 14003function arrayUnique(items) { 14004 var hash = {}; 14005 var out = []; 14006 var i, ilen, item; 14007 14008 for (i = 0, ilen = items.length; i < ilen; ++i) { 14009 item = items[i]; 14010 if (!hash[item]) { 14011 hash[item] = true; 14012 out.push(item); 14013 } 14014 } 14015 14016 return out; 14017} 14018 14019function getMin(options) { 14020 return helpers$1.valueOrDefault(options.time.min, options.ticks.min); 14021} 14022 14023function getMax(options) { 14024 return helpers$1.valueOrDefault(options.time.max, options.ticks.max); 14025} 14026 14027/** 14028 * Returns an array of {time, pos} objects used to interpolate a specific `time` or position 14029 * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is 14030 * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other 14031 * extremity (left + width or top + height). Note that it would be more optimized to directly 14032 * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need 14033 * to create the lookup table. The table ALWAYS contains at least two items: min and max. 14034 * 14035 * @param {number[]} timestamps - timestamps sorted from lowest to highest. 14036 * @param {string} distribution - If 'linear', timestamps will be spread linearly along the min 14037 * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. 14038 * If 'series', timestamps will be positioned at the same distance from each other. In this 14039 * case, only timestamps that break the time linearity are registered, meaning that in the 14040 * best case, all timestamps are linear, the table contains only min and max. 14041 */ 14042function buildLookupTable(timestamps, min, max, distribution) { 14043 if (distribution === 'linear' || !timestamps.length) { 14044 return [ 14045 {time: min, pos: 0}, 14046 {time: max, pos: 1} 14047 ]; 14048 } 14049 14050 var table = []; 14051 var items = [min]; 14052 var i, ilen, prev, curr, next; 14053 14054 for (i = 0, ilen = timestamps.length; i < ilen; ++i) { 14055 curr = timestamps[i]; 14056 if (curr > min && curr < max) { 14057 items.push(curr); 14058 } 14059 } 14060 14061 items.push(max); 14062 14063 for (i = 0, ilen = items.length; i < ilen; ++i) { 14064 next = items[i + 1]; 14065 prev = items[i - 1]; 14066 curr = items[i]; 14067 14068 // only add points that breaks the scale linearity 14069 if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { 14070 table.push({time: curr, pos: i / (ilen - 1)}); 14071 } 14072 } 14073 14074 return table; 14075} 14076 14077// @see adapted from https://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ 14078function lookup(table, key, value) { 14079 var lo = 0; 14080 var hi = table.length - 1; 14081 var mid, i0, i1; 14082 14083 while (lo >= 0 && lo <= hi) { 14084 mid = (lo + hi) >> 1; 14085 i0 = table[mid - 1] || null; 14086 i1 = table[mid]; 14087 14088 if (!i0) { 14089 // given value is outside table (before first item) 14090 return {lo: null, hi: i1}; 14091 } else if (i1[key] < value) { 14092 lo = mid + 1; 14093 } else if (i0[key] > value) { 14094 hi = mid - 1; 14095 } else { 14096 return {lo: i0, hi: i1}; 14097 } 14098 } 14099 14100 // given value is outside table (after last item) 14101 return {lo: i1, hi: null}; 14102} 14103 14104/** 14105 * Linearly interpolates the given source `value` using the table items `skey` values and 14106 * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') 14107 * returns the position for a timestamp equal to 42. If value is out of bounds, values at 14108 * index [0, 1] or [n - 1, n] are used for the interpolation. 14109 */ 14110function interpolate$1(table, skey, sval, tkey) { 14111 var range = lookup(table, skey, sval); 14112 14113 // Note: the lookup table ALWAYS contains at least 2 items (min and max) 14114 var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; 14115 var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; 14116 14117 var span = next[skey] - prev[skey]; 14118 var ratio = span ? (sval - prev[skey]) / span : 0; 14119 var offset = (next[tkey] - prev[tkey]) * ratio; 14120 14121 return prev[tkey] + offset; 14122} 14123 14124function toTimestamp(scale, input) { 14125 var adapter = scale._adapter; 14126 var options = scale.options.time; 14127 var parser = options.parser; 14128 var format = parser || options.format; 14129 var value = input; 14130 14131 if (typeof parser === 'function') { 14132 value = parser(value); 14133 } 14134 14135 // Only parse if its not a timestamp already 14136 if (!helpers$1.isFinite(value)) { 14137 value = typeof format === 'string' 14138 ? adapter.parse(value, format) 14139 : adapter.parse(value); 14140 } 14141 14142 if (value !== null) { 14143 return +value; 14144 } 14145 14146 // Labels are in an incompatible format and no `parser` has been provided. 14147 // The user might still use the deprecated `format` option for parsing. 14148 if (!parser && typeof format === 'function') { 14149 value = format(input); 14150 14151 // `format` could return something else than a timestamp, if so, parse it 14152 if (!helpers$1.isFinite(value)) { 14153 value = adapter.parse(value); 14154 } 14155 } 14156 14157 return value; 14158} 14159 14160function parse(scale, input) { 14161 if (helpers$1.isNullOrUndef(input)) { 14162 return null; 14163 } 14164 14165 var options = scale.options.time; 14166 var value = toTimestamp(scale, scale.getRightValue(input)); 14167 if (value === null) { 14168 return value; 14169 } 14170 14171 if (options.round) { 14172 value = +scale._adapter.startOf(value, options.round); 14173 } 14174 14175 return value; 14176} 14177 14178/** 14179 * Figures out what unit results in an appropriate number of auto-generated ticks 14180 */ 14181function determineUnitForAutoTicks(minUnit, min, max, capacity) { 14182 var ilen = UNITS.length; 14183 var i, interval, factor; 14184 14185 for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { 14186 interval = INTERVALS[UNITS[i]]; 14187 factor = interval.steps ? interval.steps : MAX_INTEGER; 14188 14189 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { 14190 return UNITS[i]; 14191 } 14192 } 14193 14194 return UNITS[ilen - 1]; 14195} 14196 14197/** 14198 * Figures out what unit to format a set of ticks with 14199 */ 14200function determineUnitForFormatting(scale, numTicks, minUnit, min, max) { 14201 var i, unit; 14202 14203 for (i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) { 14204 unit = UNITS[i]; 14205 if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) { 14206 return unit; 14207 } 14208 } 14209 14210 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; 14211} 14212 14213function determineMajorUnit(unit) { 14214 for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { 14215 if (INTERVALS[UNITS[i]].common) { 14216 return UNITS[i]; 14217 } 14218 } 14219} 14220 14221/** 14222 * Generates a maximum of `capacity` timestamps between min and max, rounded to the 14223 * `minor` unit using the given scale time `options`. 14224 * Important: this method can return ticks outside the min and max range, it's the 14225 * responsibility of the calling code to clamp values if needed. 14226 */ 14227function generate(scale, min, max, capacity) { 14228 var adapter = scale._adapter; 14229 var options = scale.options; 14230 var timeOpts = options.time; 14231 var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); 14232 var stepSize = resolve$5([timeOpts.stepSize, timeOpts.unitStepSize, 1]); 14233 var weekday = minor === 'week' ? timeOpts.isoWeekday : false; 14234 var first = min; 14235 var ticks = []; 14236 var time; 14237 14238 // For 'week' unit, handle the first day of week option 14239 if (weekday) { 14240 first = +adapter.startOf(first, 'isoWeek', weekday); 14241 } 14242 14243 // Align first ticks on unit 14244 first = +adapter.startOf(first, weekday ? 'day' : minor); 14245 14246 // Prevent browser from freezing in case user options request millions of milliseconds 14247 if (adapter.diff(max, min, minor) > 100000 * stepSize) { 14248 throw min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor; 14249 } 14250 14251 for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) { 14252 ticks.push(time); 14253 } 14254 14255 if (time === max || options.bounds === 'ticks') { 14256 ticks.push(time); 14257 } 14258 14259 return ticks; 14260} 14261 14262/** 14263 * Returns the start and end offsets from edges in the form of {start, end} 14264 * where each value is a relative width to the scale and ranges between 0 and 1. 14265 * They add extra margins on the both sides by scaling down the original scale. 14266 * Offsets are added when the `offset` option is true. 14267 */ 14268function computeOffsets(table, ticks, min, max, options) { 14269 var start = 0; 14270 var end = 0; 14271 var first, last; 14272 14273 if (options.offset && ticks.length) { 14274 first = interpolate$1(table, 'time', ticks[0], 'pos'); 14275 if (ticks.length === 1) { 14276 start = 1 - first; 14277 } else { 14278 start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2; 14279 } 14280 last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos'); 14281 if (ticks.length === 1) { 14282 end = last; 14283 } else { 14284 end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2; 14285 } 14286 } 14287 14288 return {start: start, end: end, factor: 1 / (start + 1 + end)}; 14289} 14290 14291function setMajorTicks(scale, ticks, map, majorUnit) { 14292 var adapter = scale._adapter; 14293 var first = +adapter.startOf(ticks[0].value, majorUnit); 14294 var last = ticks[ticks.length - 1].value; 14295 var major, index; 14296 14297 for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) { 14298 index = map[major]; 14299 if (index >= 0) { 14300 ticks[index].major = true; 14301 } 14302 } 14303 return ticks; 14304} 14305 14306function ticksFromTimestamps(scale, values, majorUnit) { 14307 var ticks = []; 14308 var map = {}; 14309 var ilen = values.length; 14310 var i, value; 14311 14312 for (i = 0; i < ilen; ++i) { 14313 value = values[i]; 14314 map[value] = i; 14315 14316 ticks.push({ 14317 value: value, 14318 major: false 14319 }); 14320 } 14321 14322 // We set the major ticks separately from the above loop because calling startOf for every tick 14323 // is expensive when there is a large number of ticks 14324 return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit); 14325} 14326 14327var defaultConfig$4 = { 14328 position: 'bottom', 14329 14330 /** 14331 * Data distribution along the scale: 14332 * - 'linear': data are spread according to their time (distances can vary), 14333 * - 'series': data are spread at the same distance from each other. 14334 * @see https://github.com/chartjs/Chart.js/pull/4507 14335 * @since 2.7.0 14336 */ 14337 distribution: 'linear', 14338 14339 /** 14340 * Scale boundary strategy (bypassed by min/max time options) 14341 * - `data`: make sure data are fully visible, ticks outside are removed 14342 * - `ticks`: make sure ticks are fully visible, data outside are truncated 14343 * @see https://github.com/chartjs/Chart.js/pull/4556 14344 * @since 2.7.0 14345 */ 14346 bounds: 'data', 14347 14348 adapters: {}, 14349 time: { 14350 parser: false, // false == a pattern string from https://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment 14351 unit: false, // false == automatic or override with week, month, year, etc. 14352 round: false, // none, or override with week, month, year, etc. 14353 displayFormat: false, // DEPRECATED 14354 isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/ 14355 minUnit: 'millisecond', 14356 displayFormats: {} 14357 }, 14358 ticks: { 14359 autoSkip: false, 14360 14361 /** 14362 * Ticks generation input values: 14363 * - 'auto': generates "optimal" ticks based on scale size and time options. 14364 * - 'data': generates ticks from data (including labels from data {t|x|y} objects). 14365 * - 'labels': generates ticks from user given `data.labels` values ONLY. 14366 * @see https://github.com/chartjs/Chart.js/pull/4507 14367 * @since 2.7.0 14368 */ 14369 source: 'auto', 14370 14371 major: { 14372 enabled: false 14373 } 14374 } 14375}; 14376 14377var scale_time = core_scale.extend({ 14378 initialize: function() { 14379 this.mergeTicksOptions(); 14380 core_scale.prototype.initialize.call(this); 14381 }, 14382 14383 update: function() { 14384 var me = this; 14385 var options = me.options; 14386 var time = options.time || (options.time = {}); 14387 var adapter = me._adapter = new core_adapters._date(options.adapters.date); 14388 14389 // DEPRECATIONS: output a message only one time per update 14390 deprecated$1('time scale', time.format, 'time.format', 'time.parser'); 14391 deprecated$1('time scale', time.min, 'time.min', 'ticks.min'); 14392 deprecated$1('time scale', time.max, 'time.max', 'ticks.max'); 14393 14394 // Backward compatibility: before introducing adapter, `displayFormats` was 14395 // supposed to contain *all* unit/string pairs but this can't be resolved 14396 // when loading the scale (adapters are loaded afterward), so let's populate 14397 // missing formats on update 14398 helpers$1.mergeIf(time.displayFormats, adapter.formats()); 14399 14400 return core_scale.prototype.update.apply(me, arguments); 14401 }, 14402 14403 /** 14404 * Allows data to be referenced via 't' attribute 14405 */ 14406 getRightValue: function(rawValue) { 14407 if (rawValue && rawValue.t !== undefined) { 14408 rawValue = rawValue.t; 14409 } 14410 return core_scale.prototype.getRightValue.call(this, rawValue); 14411 }, 14412 14413 determineDataLimits: function() { 14414 var me = this; 14415 var chart = me.chart; 14416 var adapter = me._adapter; 14417 var options = me.options; 14418 var unit = options.time.unit || 'day'; 14419 var min = MAX_INTEGER; 14420 var max = MIN_INTEGER; 14421 var timestamps = []; 14422 var datasets = []; 14423 var labels = []; 14424 var i, j, ilen, jlen, data, timestamp, labelsAdded; 14425 var dataLabels = me._getLabels(); 14426 14427 for (i = 0, ilen = dataLabels.length; i < ilen; ++i) { 14428 labels.push(parse(me, dataLabels[i])); 14429 } 14430 14431 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { 14432 if (chart.isDatasetVisible(i)) { 14433 data = chart.data.datasets[i].data; 14434 14435 // Let's consider that all data have the same format. 14436 if (helpers$1.isObject(data[0])) { 14437 datasets[i] = []; 14438 14439 for (j = 0, jlen = data.length; j < jlen; ++j) { 14440 timestamp = parse(me, data[j]); 14441 timestamps.push(timestamp); 14442 datasets[i][j] = timestamp; 14443 } 14444 } else { 14445 datasets[i] = labels.slice(0); 14446 if (!labelsAdded) { 14447 timestamps = timestamps.concat(labels); 14448 labelsAdded = true; 14449 } 14450 } 14451 } else { 14452 datasets[i] = []; 14453 } 14454 } 14455 14456 if (labels.length) { 14457 min = Math.min(min, labels[0]); 14458 max = Math.max(max, labels[labels.length - 1]); 14459 } 14460 14461 if (timestamps.length) { 14462 timestamps = ilen > 1 ? arrayUnique(timestamps).sort(sorter) : timestamps.sort(sorter); 14463 min = Math.min(min, timestamps[0]); 14464 max = Math.max(max, timestamps[timestamps.length - 1]); 14465 } 14466 14467 min = parse(me, getMin(options)) || min; 14468 max = parse(me, getMax(options)) || max; 14469 14470 // In case there is no valid min/max, set limits based on unit time option 14471 min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min; 14472 max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), unit) + 1 : max; 14473 14474 // Make sure that max is strictly higher than min (required by the lookup table) 14475 me.min = Math.min(min, max); 14476 me.max = Math.max(min + 1, max); 14477 14478 // PRIVATE 14479 me._table = []; 14480 me._timestamps = { 14481 data: timestamps, 14482 datasets: datasets, 14483 labels: labels 14484 }; 14485 }, 14486 14487 buildTicks: function() { 14488 var me = this; 14489 var min = me.min; 14490 var max = me.max; 14491 var options = me.options; 14492 var tickOpts = options.ticks; 14493 var timeOpts = options.time; 14494 var timestamps = me._timestamps; 14495 var ticks = []; 14496 var capacity = me.getLabelCapacity(min); 14497 var source = tickOpts.source; 14498 var distribution = options.distribution; 14499 var i, ilen, timestamp; 14500 14501 if (source === 'data' || (source === 'auto' && distribution === 'series')) { 14502 timestamps = timestamps.data; 14503 } else if (source === 'labels') { 14504 timestamps = timestamps.labels; 14505 } else { 14506 timestamps = generate(me, min, max, capacity); 14507 } 14508 14509 if (options.bounds === 'ticks' && timestamps.length) { 14510 min = timestamps[0]; 14511 max = timestamps[timestamps.length - 1]; 14512 } 14513 14514 // Enforce limits with user min/max options 14515 min = parse(me, getMin(options)) || min; 14516 max = parse(me, getMax(options)) || max; 14517 14518 // Remove ticks outside the min/max range 14519 for (i = 0, ilen = timestamps.length; i < ilen; ++i) { 14520 timestamp = timestamps[i]; 14521 if (timestamp >= min && timestamp <= max) { 14522 ticks.push(timestamp); 14523 } 14524 } 14525 14526 me.min = min; 14527 me.max = max; 14528 14529 // PRIVATE 14530 // determineUnitForFormatting relies on the number of ticks so we don't use it when 14531 // autoSkip is enabled because we don't yet know what the final number of ticks will be 14532 me._unit = timeOpts.unit || (tickOpts.autoSkip 14533 ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, capacity) 14534 : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max)); 14535 me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined 14536 : determineMajorUnit(me._unit); 14537 me._table = buildLookupTable(me._timestamps.data, min, max, distribution); 14538 me._offsets = computeOffsets(me._table, ticks, min, max, options); 14539 14540 if (tickOpts.reverse) { 14541 ticks.reverse(); 14542 } 14543 14544 return ticksFromTimestamps(me, ticks, me._majorUnit); 14545 }, 14546 14547 getLabelForIndex: function(index, datasetIndex) { 14548 var me = this; 14549 var adapter = me._adapter; 14550 var data = me.chart.data; 14551 var timeOpts = me.options.time; 14552 var label = data.labels && index < data.labels.length ? data.labels[index] : ''; 14553 var value = data.datasets[datasetIndex].data[index]; 14554 14555 if (helpers$1.isObject(value)) { 14556 label = me.getRightValue(value); 14557 } 14558 if (timeOpts.tooltipFormat) { 14559 return adapter.format(toTimestamp(me, label), timeOpts.tooltipFormat); 14560 } 14561 if (typeof label === 'string') { 14562 return label; 14563 } 14564 return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime); 14565 }, 14566 14567 /** 14568 * Function to format an individual tick mark 14569 * @private 14570 */ 14571 tickFormatFunction: function(time, index, ticks, format) { 14572 var me = this; 14573 var adapter = me._adapter; 14574 var options = me.options; 14575 var formats = options.time.displayFormats; 14576 var minorFormat = formats[me._unit]; 14577 var majorUnit = me._majorUnit; 14578 var majorFormat = formats[majorUnit]; 14579 var tick = ticks[index]; 14580 var tickOpts = options.ticks; 14581 var major = majorUnit && majorFormat && tick && tick.major; 14582 var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat); 14583 var nestedTickOpts = major ? tickOpts.major : tickOpts.minor; 14584 var formatter = resolve$5([ 14585 nestedTickOpts.callback, 14586 nestedTickOpts.userCallback, 14587 tickOpts.callback, 14588 tickOpts.userCallback 14589 ]); 14590 14591 return formatter ? formatter(label, index, ticks) : label; 14592 }, 14593 14594 convertTicksToLabels: function(ticks) { 14595 var labels = []; 14596 var i, ilen; 14597 14598 for (i = 0, ilen = ticks.length; i < ilen; ++i) { 14599 labels.push(this.tickFormatFunction(ticks[i].value, i, ticks)); 14600 } 14601 14602 return labels; 14603 }, 14604 14605 /** 14606 * @private 14607 */ 14608 getPixelForOffset: function(time) { 14609 var me = this; 14610 var offsets = me._offsets; 14611 var pos = interpolate$1(me._table, 'time', time, 'pos'); 14612 return me.getPixelForDecimal((offsets.start + pos) * offsets.factor); 14613 }, 14614 14615 getPixelForValue: function(value, index, datasetIndex) { 14616 var me = this; 14617 var time = null; 14618 14619 if (index !== undefined && datasetIndex !== undefined) { 14620 time = me._timestamps.datasets[datasetIndex][index]; 14621 } 14622 14623 if (time === null) { 14624 time = parse(me, value); 14625 } 14626 14627 if (time !== null) { 14628 return me.getPixelForOffset(time); 14629 } 14630 }, 14631 14632 getPixelForTick: function(index) { 14633 var ticks = this.getTicks(); 14634 return index >= 0 && index < ticks.length ? 14635 this.getPixelForOffset(ticks[index].value) : 14636 null; 14637 }, 14638 14639 getValueForPixel: function(pixel) { 14640 var me = this; 14641 var offsets = me._offsets; 14642 var pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end; 14643 var time = interpolate$1(me._table, 'pos', pos, 'time'); 14644 14645 // DEPRECATION, we should return time directly 14646 return me._adapter._create(time); 14647 }, 14648 14649 /** 14650 * @private 14651 */ 14652 _getLabelSize: function(label) { 14653 var me = this; 14654 var ticksOpts = me.options.ticks; 14655 var tickLabelWidth = me.ctx.measureText(label).width; 14656 var angle = helpers$1.toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation); 14657 var cosRotation = Math.cos(angle); 14658 var sinRotation = Math.sin(angle); 14659 var tickFontSize = valueOrDefault$d(ticksOpts.fontSize, core_defaults.global.defaultFontSize); 14660 14661 return { 14662 w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation), 14663 h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation) 14664 }; 14665 }, 14666 14667 /** 14668 * Crude approximation of what the label width might be 14669 * @private 14670 */ 14671 getLabelWidth: function(label) { 14672 return this._getLabelSize(label).w; 14673 }, 14674 14675 /** 14676 * @private 14677 */ 14678 getLabelCapacity: function(exampleTime) { 14679 var me = this; 14680 var timeOpts = me.options.time; 14681 var displayFormats = timeOpts.displayFormats; 14682 14683 // pick the longest format (milliseconds) for guestimation 14684 var format = displayFormats[timeOpts.unit] || displayFormats.millisecond; 14685 var exampleLabel = me.tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format); 14686 var size = me._getLabelSize(exampleLabel); 14687 var capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h); 14688 14689 if (me.options.offset) { 14690 capacity--; 14691 } 14692 14693 return capacity > 0 ? capacity : 1; 14694 } 14695}); 14696 14697// INTERNAL: static default options, registered in src/index.js 14698var _defaults$4 = defaultConfig$4; 14699scale_time._defaults = _defaults$4; 14700 14701var scales = { 14702 category: scale_category, 14703 linear: scale_linear, 14704 logarithmic: scale_logarithmic, 14705 radialLinear: scale_radialLinear, 14706 time: scale_time 14707}; 14708 14709var FORMATS = { 14710 datetime: 'MMM D, YYYY, h:mm:ss a', 14711 millisecond: 'h:mm:ss.SSS a', 14712 second: 'h:mm:ss a', 14713 minute: 'h:mm a', 14714 hour: 'hA', 14715 day: 'MMM D', 14716 week: 'll', 14717 month: 'MMM YYYY', 14718 quarter: '[Q]Q - YYYY', 14719 year: 'YYYY' 14720}; 14721 14722core_adapters._date.override(typeof moment === 'function' ? { 14723 _id: 'moment', // DEBUG ONLY 14724 14725 formats: function() { 14726 return FORMATS; 14727 }, 14728 14729 parse: function(value, format) { 14730 if (typeof value === 'string' && typeof format === 'string') { 14731 value = moment(value, format); 14732 } else if (!(value instanceof moment)) { 14733 value = moment(value); 14734 } 14735 return value.isValid() ? value.valueOf() : null; 14736 }, 14737 14738 format: function(time, format) { 14739 return moment(time).format(format); 14740 }, 14741 14742 add: function(time, amount, unit) { 14743 return moment(time).add(amount, unit).valueOf(); 14744 }, 14745 14746 diff: function(max, min, unit) { 14747 return moment(max).diff(moment(min), unit); 14748 }, 14749 14750 startOf: function(time, unit, weekday) { 14751 time = moment(time); 14752 if (unit === 'isoWeek') { 14753 return time.isoWeekday(weekday).valueOf(); 14754 } 14755 return time.startOf(unit).valueOf(); 14756 }, 14757 14758 endOf: function(time, unit) { 14759 return moment(time).endOf(unit).valueOf(); 14760 }, 14761 14762 // DEPRECATIONS 14763 14764 /** 14765 * Provided for backward compatibility with scale.getValueForPixel(). 14766 * @deprecated since version 2.8.0 14767 * @todo remove at version 3 14768 * @private 14769 */ 14770 _create: function(time) { 14771 return moment(time); 14772 }, 14773} : {}); 14774 14775core_defaults._set('global', { 14776 plugins: { 14777 filler: { 14778 propagate: true 14779 } 14780 } 14781}); 14782 14783var mappers = { 14784 dataset: function(source) { 14785 var index = source.fill; 14786 var chart = source.chart; 14787 var meta = chart.getDatasetMeta(index); 14788 var visible = meta && chart.isDatasetVisible(index); 14789 var points = (visible && meta.dataset._children) || []; 14790 var length = points.length || 0; 14791 14792 return !length ? null : function(point, i) { 14793 return (i < length && points[i]._view) || null; 14794 }; 14795 }, 14796 14797 boundary: function(source) { 14798 var boundary = source.boundary; 14799 var x = boundary ? boundary.x : null; 14800 var y = boundary ? boundary.y : null; 14801 14802 if (helpers$1.isArray(boundary)) { 14803 return function(point, i) { 14804 return boundary[i]; 14805 }; 14806 } 14807 14808 return function(point) { 14809 return { 14810 x: x === null ? point.x : x, 14811 y: y === null ? point.y : y, 14812 }; 14813 }; 14814 } 14815}; 14816 14817// @todo if (fill[0] === '#') 14818function decodeFill(el, index, count) { 14819 var model = el._model || {}; 14820 var fill = model.fill; 14821 var target; 14822 14823 if (fill === undefined) { 14824 fill = !!model.backgroundColor; 14825 } 14826 14827 if (fill === false || fill === null) { 14828 return false; 14829 } 14830 14831 if (fill === true) { 14832 return 'origin'; 14833 } 14834 14835 target = parseFloat(fill, 10); 14836 if (isFinite(target) && Math.floor(target) === target) { 14837 if (fill[0] === '-' || fill[0] === '+') { 14838 target = index + target; 14839 } 14840 14841 if (target === index || target < 0 || target >= count) { 14842 return false; 14843 } 14844 14845 return target; 14846 } 14847 14848 switch (fill) { 14849 // compatibility 14850 case 'bottom': 14851 return 'start'; 14852 case 'top': 14853 return 'end'; 14854 case 'zero': 14855 return 'origin'; 14856 // supported boundaries 14857 case 'origin': 14858 case 'start': 14859 case 'end': 14860 return fill; 14861 // invalid fill values 14862 default: 14863 return false; 14864 } 14865} 14866 14867function computeLinearBoundary(source) { 14868 var model = source.el._model || {}; 14869 var scale = source.el._scale || {}; 14870 var fill = source.fill; 14871 var target = null; 14872 var horizontal; 14873 14874 if (isFinite(fill)) { 14875 return null; 14876 } 14877 14878 // Backward compatibility: until v3, we still need to support boundary values set on 14879 // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and 14880 // controllers might still use it (e.g. the Smith chart). 14881 14882 if (fill === 'start') { 14883 target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; 14884 } else if (fill === 'end') { 14885 target = model.scaleTop === undefined ? scale.top : model.scaleTop; 14886 } else if (model.scaleZero !== undefined) { 14887 target = model.scaleZero; 14888 } else if (scale.getBasePixel) { 14889 target = scale.getBasePixel(); 14890 } 14891 14892 if (target !== undefined && target !== null) { 14893 if (target.x !== undefined && target.y !== undefined) { 14894 return target; 14895 } 14896 14897 if (helpers$1.isFinite(target)) { 14898 horizontal = scale.isHorizontal(); 14899 return { 14900 x: horizontal ? target : null, 14901 y: horizontal ? null : target 14902 }; 14903 } 14904 } 14905 14906 return null; 14907} 14908 14909function computeCircularBoundary(source) { 14910 var scale = source.el._scale; 14911 var options = scale.options; 14912 var length = scale.chart.data.labels.length; 14913 var fill = source.fill; 14914 var target = []; 14915 var start, end, center, i, point; 14916 14917 if (!length) { 14918 return null; 14919 } 14920 14921 start = options.ticks.reverse ? scale.max : scale.min; 14922 end = options.ticks.reverse ? scale.min : scale.max; 14923 center = scale.getPointPositionForValue(0, start); 14924 for (i = 0; i < length; ++i) { 14925 point = fill === 'start' || fill === 'end' 14926 ? scale.getPointPositionForValue(i, fill === 'start' ? start : end) 14927 : scale.getBasePosition(i); 14928 if (options.gridLines.circular) { 14929 point.cx = center.x; 14930 point.cy = center.y; 14931 point.angle = scale.getIndexAngle(i) - Math.PI / 2; 14932 } 14933 target.push(point); 14934 } 14935 return target; 14936} 14937 14938function computeBoundary(source) { 14939 var scale = source.el._scale || {}; 14940 14941 if (scale.getPointPositionForValue) { 14942 return computeCircularBoundary(source); 14943 } 14944 return computeLinearBoundary(source); 14945} 14946 14947function resolveTarget(sources, index, propagate) { 14948 var source = sources[index]; 14949 var fill = source.fill; 14950 var visited = [index]; 14951 var target; 14952 14953 if (!propagate) { 14954 return fill; 14955 } 14956 14957 while (fill !== false && visited.indexOf(fill) === -1) { 14958 if (!isFinite(fill)) { 14959 return fill; 14960 } 14961 14962 target = sources[fill]; 14963 if (!target) { 14964 return false; 14965 } 14966 14967 if (target.visible) { 14968 return fill; 14969 } 14970 14971 visited.push(fill); 14972 fill = target.fill; 14973 } 14974 14975 return false; 14976} 14977 14978function createMapper(source) { 14979 var fill = source.fill; 14980 var type = 'dataset'; 14981 14982 if (fill === false) { 14983 return null; 14984 } 14985 14986 if (!isFinite(fill)) { 14987 type = 'boundary'; 14988 } 14989 14990 return mappers[type](source); 14991} 14992 14993function isDrawable(point) { 14994 return point && !point.skip; 14995} 14996 14997function drawArea(ctx, curve0, curve1, len0, len1) { 14998 var i, cx, cy, r; 14999 15000 if (!len0 || !len1) { 15001 return; 15002 } 15003 15004 // building first area curve (normal) 15005 ctx.moveTo(curve0[0].x, curve0[0].y); 15006 for (i = 1; i < len0; ++i) { 15007 helpers$1.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); 15008 } 15009 15010 if (curve1[0].angle !== undefined) { 15011 cx = curve1[0].cx; 15012 cy = curve1[0].cy; 15013 r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2)); 15014 for (i = len1 - 1; i > 0; --i) { 15015 ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true); 15016 } 15017 return; 15018 } 15019 15020 // joining the two area curves 15021 ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); 15022 15023 // building opposite area curve (reverse) 15024 for (i = len1 - 1; i > 0; --i) { 15025 helpers$1.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); 15026 } 15027} 15028 15029function doFill(ctx, points, mapper, view, color, loop) { 15030 var count = points.length; 15031 var span = view.spanGaps; 15032 var curve0 = []; 15033 var curve1 = []; 15034 var len0 = 0; 15035 var len1 = 0; 15036 var i, ilen, index, p0, p1, d0, d1, loopOffset; 15037 15038 ctx.beginPath(); 15039 15040 for (i = 0, ilen = count; i < ilen; ++i) { 15041 index = i % count; 15042 p0 = points[index]._view; 15043 p1 = mapper(p0, index, view); 15044 d0 = isDrawable(p0); 15045 d1 = isDrawable(p1); 15046 15047 if (loop && loopOffset === undefined && d0) { 15048 loopOffset = i + 1; 15049 ilen = count + loopOffset; 15050 } 15051 15052 if (d0 && d1) { 15053 len0 = curve0.push(p0); 15054 len1 = curve1.push(p1); 15055 } else if (len0 && len1) { 15056 if (!span) { 15057 drawArea(ctx, curve0, curve1, len0, len1); 15058 len0 = len1 = 0; 15059 curve0 = []; 15060 curve1 = []; 15061 } else { 15062 if (d0) { 15063 curve0.push(p0); 15064 } 15065 if (d1) { 15066 curve1.push(p1); 15067 } 15068 } 15069 } 15070 } 15071 15072 drawArea(ctx, curve0, curve1, len0, len1); 15073 15074 ctx.closePath(); 15075 ctx.fillStyle = color; 15076 ctx.fill(); 15077} 15078 15079var plugin_filler = { 15080 id: 'filler', 15081 15082 afterDatasetsUpdate: function(chart, options) { 15083 var count = (chart.data.datasets || []).length; 15084 var propagate = options.propagate; 15085 var sources = []; 15086 var meta, i, el, source; 15087 15088 for (i = 0; i < count; ++i) { 15089 meta = chart.getDatasetMeta(i); 15090 el = meta.dataset; 15091 source = null; 15092 15093 if (el && el._model && el instanceof elements.Line) { 15094 source = { 15095 visible: chart.isDatasetVisible(i), 15096 fill: decodeFill(el, i, count), 15097 chart: chart, 15098 el: el 15099 }; 15100 } 15101 15102 meta.$filler = source; 15103 sources.push(source); 15104 } 15105 15106 for (i = 0; i < count; ++i) { 15107 source = sources[i]; 15108 if (!source) { 15109 continue; 15110 } 15111 15112 source.fill = resolveTarget(sources, i, propagate); 15113 source.boundary = computeBoundary(source); 15114 source.mapper = createMapper(source); 15115 } 15116 }, 15117 15118 beforeDatasetsDraw: function(chart) { 15119 var metasets = chart._getSortedVisibleDatasetMetas(); 15120 var ctx = chart.ctx; 15121 var meta, i, el, view, points, mapper, color; 15122 15123 for (i = metasets.length - 1; i >= 0; --i) { 15124 meta = metasets[i].$filler; 15125 15126 if (!meta || !meta.visible) { 15127 continue; 15128 } 15129 15130 el = meta.el; 15131 view = el._view; 15132 points = el._children || []; 15133 mapper = meta.mapper; 15134 color = view.backgroundColor || core_defaults.global.defaultColor; 15135 15136 if (mapper && color && points.length) { 15137 helpers$1.canvas.clipArea(ctx, chart.chartArea); 15138 doFill(ctx, points, mapper, view, color, el._loop); 15139 helpers$1.canvas.unclipArea(ctx); 15140 } 15141 } 15142 } 15143}; 15144 15145var getRtlHelper$1 = helpers$1.rtl.getRtlAdapter; 15146var noop$1 = helpers$1.noop; 15147var valueOrDefault$e = helpers$1.valueOrDefault; 15148 15149core_defaults._set('global', { 15150 legend: { 15151 display: true, 15152 position: 'top', 15153 align: 'center', 15154 fullWidth: true, 15155 reverse: false, 15156 weight: 1000, 15157 15158 // a callback that will handle 15159 onClick: function(e, legendItem) { 15160 var index = legendItem.datasetIndex; 15161 var ci = this.chart; 15162 var meta = ci.getDatasetMeta(index); 15163 15164 // See controller.isDatasetVisible comment 15165 meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; 15166 15167 // We hid a dataset ... rerender the chart 15168 ci.update(); 15169 }, 15170 15171 onHover: null, 15172 onLeave: null, 15173 15174 labels: { 15175 boxWidth: 40, 15176 padding: 10, 15177 // Generates labels shown in the legend 15178 // Valid properties to return: 15179 // text : text to display 15180 // fillStyle : fill of coloured box 15181 // strokeStyle: stroke of coloured box 15182 // hidden : if this legend item refers to a hidden item 15183 // lineCap : cap style for line 15184 // lineDash 15185 // lineDashOffset : 15186 // lineJoin : 15187 // lineWidth : 15188 generateLabels: function(chart) { 15189 var datasets = chart.data.datasets; 15190 var options = chart.options.legend || {}; 15191 var usePointStyle = options.labels && options.labels.usePointStyle; 15192 15193 return chart._getSortedDatasetMetas().map(function(meta) { 15194 var style = meta.controller.getStyle(usePointStyle ? 0 : undefined); 15195 15196 return { 15197 text: datasets[meta.index].label, 15198 fillStyle: style.backgroundColor, 15199 hidden: !chart.isDatasetVisible(meta.index), 15200 lineCap: style.borderCapStyle, 15201 lineDash: style.borderDash, 15202 lineDashOffset: style.borderDashOffset, 15203 lineJoin: style.borderJoinStyle, 15204 lineWidth: style.borderWidth, 15205 strokeStyle: style.borderColor, 15206 pointStyle: style.pointStyle, 15207 rotation: style.rotation, 15208 15209 // Below is extra data used for toggling the datasets 15210 datasetIndex: meta.index 15211 }; 15212 }, this); 15213 } 15214 } 15215 }, 15216 15217 legendCallback: function(chart) { 15218 var list = document.createElement('ul'); 15219 var datasets = chart.data.datasets; 15220 var i, ilen, listItem, listItemSpan; 15221 15222 list.setAttribute('class', chart.id + '-legend'); 15223 15224 for (i = 0, ilen = datasets.length; i < ilen; i++) { 15225 listItem = list.appendChild(document.createElement('li')); 15226 listItemSpan = listItem.appendChild(document.createElement('span')); 15227 listItemSpan.style.backgroundColor = datasets[i].backgroundColor; 15228 if (datasets[i].label) { 15229 listItem.appendChild(document.createTextNode(datasets[i].label)); 15230 } 15231 } 15232 15233 return list.outerHTML; 15234 } 15235}); 15236 15237/** 15238 * Helper function to get the box width based on the usePointStyle option 15239 * @param {object} labelopts - the label options on the legend 15240 * @param {number} fontSize - the label font size 15241 * @return {number} width of the color box area 15242 */ 15243function getBoxWidth(labelOpts, fontSize) { 15244 return labelOpts.usePointStyle && labelOpts.boxWidth > fontSize ? 15245 fontSize : 15246 labelOpts.boxWidth; 15247} 15248 15249/** 15250 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! 15251 */ 15252var Legend = core_element.extend({ 15253 15254 initialize: function(config) { 15255 var me = this; 15256 helpers$1.extend(me, config); 15257 15258 // Contains hit boxes for each dataset (in dataset order) 15259 me.legendHitBoxes = []; 15260 15261 /** 15262 * @private 15263 */ 15264 me._hoveredItem = null; 15265 15266 // Are we in doughnut mode which has a different data type 15267 me.doughnutMode = false; 15268 }, 15269 15270 // These methods are ordered by lifecycle. Utilities then follow. 15271 // Any function defined here is inherited by all legend types. 15272 // Any function can be extended by the legend type 15273 15274 beforeUpdate: noop$1, 15275 update: function(maxWidth, maxHeight, margins) { 15276 var me = this; 15277 15278 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 15279 me.beforeUpdate(); 15280 15281 // Absorb the master measurements 15282 me.maxWidth = maxWidth; 15283 me.maxHeight = maxHeight; 15284 me.margins = margins; 15285 15286 // Dimensions 15287 me.beforeSetDimensions(); 15288 me.setDimensions(); 15289 me.afterSetDimensions(); 15290 // Labels 15291 me.beforeBuildLabels(); 15292 me.buildLabels(); 15293 me.afterBuildLabels(); 15294 15295 // Fit 15296 me.beforeFit(); 15297 me.fit(); 15298 me.afterFit(); 15299 // 15300 me.afterUpdate(); 15301 15302 return me.minSize; 15303 }, 15304 afterUpdate: noop$1, 15305 15306 // 15307 15308 beforeSetDimensions: noop$1, 15309 setDimensions: function() { 15310 var me = this; 15311 // Set the unconstrained dimension before label rotation 15312 if (me.isHorizontal()) { 15313 // Reset position before calculating rotation 15314 me.width = me.maxWidth; 15315 me.left = 0; 15316 me.right = me.width; 15317 } else { 15318 me.height = me.maxHeight; 15319 15320 // Reset position before calculating rotation 15321 me.top = 0; 15322 me.bottom = me.height; 15323 } 15324 15325 // Reset padding 15326 me.paddingLeft = 0; 15327 me.paddingTop = 0; 15328 me.paddingRight = 0; 15329 me.paddingBottom = 0; 15330 15331 // Reset minSize 15332 me.minSize = { 15333 width: 0, 15334 height: 0 15335 }; 15336 }, 15337 afterSetDimensions: noop$1, 15338 15339 // 15340 15341 beforeBuildLabels: noop$1, 15342 buildLabels: function() { 15343 var me = this; 15344 var labelOpts = me.options.labels || {}; 15345 var legendItems = helpers$1.callback(labelOpts.generateLabels, [me.chart], me) || []; 15346 15347 if (labelOpts.filter) { 15348 legendItems = legendItems.filter(function(item) { 15349 return labelOpts.filter(item, me.chart.data); 15350 }); 15351 } 15352 15353 if (me.options.reverse) { 15354 legendItems.reverse(); 15355 } 15356 15357 me.legendItems = legendItems; 15358 }, 15359 afterBuildLabels: noop$1, 15360 15361 // 15362 15363 beforeFit: noop$1, 15364 fit: function() { 15365 var me = this; 15366 var opts = me.options; 15367 var labelOpts = opts.labels; 15368 var display = opts.display; 15369 15370 var ctx = me.ctx; 15371 15372 var labelFont = helpers$1.options._parseFont(labelOpts); 15373 var fontSize = labelFont.size; 15374 15375 // Reset hit boxes 15376 var hitboxes = me.legendHitBoxes = []; 15377 15378 var minSize = me.minSize; 15379 var isHorizontal = me.isHorizontal(); 15380 15381 if (isHorizontal) { 15382 minSize.width = me.maxWidth; // fill all the width 15383 minSize.height = display ? 10 : 0; 15384 } else { 15385 minSize.width = display ? 10 : 0; 15386 minSize.height = me.maxHeight; // fill all the height 15387 } 15388 15389 // Increase sizes here 15390 if (!display) { 15391 me.width = minSize.width = me.height = minSize.height = 0; 15392 return; 15393 } 15394 ctx.font = labelFont.string; 15395 15396 if (isHorizontal) { 15397 // Labels 15398 15399 // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one 15400 var lineWidths = me.lineWidths = [0]; 15401 var totalHeight = 0; 15402 15403 ctx.textAlign = 'left'; 15404 ctx.textBaseline = 'middle'; 15405 15406 helpers$1.each(me.legendItems, function(legendItem, i) { 15407 var boxWidth = getBoxWidth(labelOpts, fontSize); 15408 var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; 15409 15410 if (i === 0 || lineWidths[lineWidths.length - 1] + width + 2 * labelOpts.padding > minSize.width) { 15411 totalHeight += fontSize + labelOpts.padding; 15412 lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0; 15413 } 15414 15415 // Store the hitbox width and height here. Final position will be updated in `draw` 15416 hitboxes[i] = { 15417 left: 0, 15418 top: 0, 15419 width: width, 15420 height: fontSize 15421 }; 15422 15423 lineWidths[lineWidths.length - 1] += width + labelOpts.padding; 15424 }); 15425 15426 minSize.height += totalHeight; 15427 15428 } else { 15429 var vPadding = labelOpts.padding; 15430 var columnWidths = me.columnWidths = []; 15431 var columnHeights = me.columnHeights = []; 15432 var totalWidth = labelOpts.padding; 15433 var currentColWidth = 0; 15434 var currentColHeight = 0; 15435 15436 helpers$1.each(me.legendItems, function(legendItem, i) { 15437 var boxWidth = getBoxWidth(labelOpts, fontSize); 15438 var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; 15439 15440 // If too tall, go to new column 15441 if (i > 0 && currentColHeight + fontSize + 2 * vPadding > minSize.height) { 15442 totalWidth += currentColWidth + labelOpts.padding; 15443 columnWidths.push(currentColWidth); // previous column width 15444 columnHeights.push(currentColHeight); 15445 currentColWidth = 0; 15446 currentColHeight = 0; 15447 } 15448 15449 // Get max width 15450 currentColWidth = Math.max(currentColWidth, itemWidth); 15451 currentColHeight += fontSize + vPadding; 15452 15453 // Store the hitbox width and height here. Final position will be updated in `draw` 15454 hitboxes[i] = { 15455 left: 0, 15456 top: 0, 15457 width: itemWidth, 15458 height: fontSize 15459 }; 15460 }); 15461 15462 totalWidth += currentColWidth; 15463 columnWidths.push(currentColWidth); 15464 columnHeights.push(currentColHeight); 15465 minSize.width += totalWidth; 15466 } 15467 15468 me.width = minSize.width; 15469 me.height = minSize.height; 15470 }, 15471 afterFit: noop$1, 15472 15473 // Shared Methods 15474 isHorizontal: function() { 15475 return this.options.position === 'top' || this.options.position === 'bottom'; 15476 }, 15477 15478 // Actually draw the legend on the canvas 15479 draw: function() { 15480 var me = this; 15481 var opts = me.options; 15482 var labelOpts = opts.labels; 15483 var globalDefaults = core_defaults.global; 15484 var defaultColor = globalDefaults.defaultColor; 15485 var lineDefault = globalDefaults.elements.line; 15486 var legendHeight = me.height; 15487 var columnHeights = me.columnHeights; 15488 var legendWidth = me.width; 15489 var lineWidths = me.lineWidths; 15490 15491 if (!opts.display) { 15492 return; 15493 } 15494 15495 var rtlHelper = getRtlHelper$1(opts.rtl, me.left, me.minSize.width); 15496 var ctx = me.ctx; 15497 var fontColor = valueOrDefault$e(labelOpts.fontColor, globalDefaults.defaultFontColor); 15498 var labelFont = helpers$1.options._parseFont(labelOpts); 15499 var fontSize = labelFont.size; 15500 var cursor; 15501 15502 // Canvas setup 15503 ctx.textAlign = rtlHelper.textAlign('left'); 15504 ctx.textBaseline = 'middle'; 15505 ctx.lineWidth = 0.5; 15506 ctx.strokeStyle = fontColor; // for strikethrough effect 15507 ctx.fillStyle = fontColor; // render in correct colour 15508 ctx.font = labelFont.string; 15509 15510 var boxWidth = getBoxWidth(labelOpts, fontSize); 15511 var hitboxes = me.legendHitBoxes; 15512 15513 // current position 15514 var drawLegendBox = function(x, y, legendItem) { 15515 if (isNaN(boxWidth) || boxWidth <= 0) { 15516 return; 15517 } 15518 15519 // Set the ctx for the box 15520 ctx.save(); 15521 15522 var lineWidth = valueOrDefault$e(legendItem.lineWidth, lineDefault.borderWidth); 15523 ctx.fillStyle = valueOrDefault$e(legendItem.fillStyle, defaultColor); 15524 ctx.lineCap = valueOrDefault$e(legendItem.lineCap, lineDefault.borderCapStyle); 15525 ctx.lineDashOffset = valueOrDefault$e(legendItem.lineDashOffset, lineDefault.borderDashOffset); 15526 ctx.lineJoin = valueOrDefault$e(legendItem.lineJoin, lineDefault.borderJoinStyle); 15527 ctx.lineWidth = lineWidth; 15528 ctx.strokeStyle = valueOrDefault$e(legendItem.strokeStyle, defaultColor); 15529 15530 if (ctx.setLineDash) { 15531 // IE 9 and 10 do not support line dash 15532 ctx.setLineDash(valueOrDefault$e(legendItem.lineDash, lineDefault.borderDash)); 15533 } 15534 15535 if (labelOpts && labelOpts.usePointStyle) { 15536 // Recalculate x and y for drawPoint() because its expecting 15537 // x and y to be center of figure (instead of top left) 15538 var radius = boxWidth * Math.SQRT2 / 2; 15539 var centerX = rtlHelper.xPlus(x, boxWidth / 2); 15540 var centerY = y + fontSize / 2; 15541 15542 // Draw pointStyle as legend symbol 15543 helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY, legendItem.rotation); 15544 } else { 15545 // Draw box as legend symbol 15546 ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize); 15547 if (lineWidth !== 0) { 15548 ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize); 15549 } 15550 } 15551 15552 ctx.restore(); 15553 }; 15554 15555 var fillText = function(x, y, legendItem, textWidth) { 15556 var halfFontSize = fontSize / 2; 15557 var xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize); 15558 var yMiddle = y + halfFontSize; 15559 15560 ctx.fillText(legendItem.text, xLeft, yMiddle); 15561 15562 if (legendItem.hidden) { 15563 // Strikethrough the text if hidden 15564 ctx.beginPath(); 15565 ctx.lineWidth = 2; 15566 ctx.moveTo(xLeft, yMiddle); 15567 ctx.lineTo(rtlHelper.xPlus(xLeft, textWidth), yMiddle); 15568 ctx.stroke(); 15569 } 15570 }; 15571 15572 var alignmentOffset = function(dimension, blockSize) { 15573 switch (opts.align) { 15574 case 'start': 15575 return labelOpts.padding; 15576 case 'end': 15577 return dimension - blockSize; 15578 default: // center 15579 return (dimension - blockSize + labelOpts.padding) / 2; 15580 } 15581 }; 15582 15583 // Horizontal 15584 var isHorizontal = me.isHorizontal(); 15585 if (isHorizontal) { 15586 cursor = { 15587 x: me.left + alignmentOffset(legendWidth, lineWidths[0]), 15588 y: me.top + labelOpts.padding, 15589 line: 0 15590 }; 15591 } else { 15592 cursor = { 15593 x: me.left + labelOpts.padding, 15594 y: me.top + alignmentOffset(legendHeight, columnHeights[0]), 15595 line: 0 15596 }; 15597 } 15598 15599 helpers$1.rtl.overrideTextDirection(me.ctx, opts.textDirection); 15600 15601 var itemHeight = fontSize + labelOpts.padding; 15602 helpers$1.each(me.legendItems, function(legendItem, i) { 15603 var textWidth = ctx.measureText(legendItem.text).width; 15604 var width = boxWidth + (fontSize / 2) + textWidth; 15605 var x = cursor.x; 15606 var y = cursor.y; 15607 15608 rtlHelper.setWidth(me.minSize.width); 15609 15610 // Use (me.left + me.minSize.width) and (me.top + me.minSize.height) 15611 // instead of me.right and me.bottom because me.width and me.height 15612 // may have been changed since me.minSize was calculated 15613 if (isHorizontal) { 15614 if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) { 15615 y = cursor.y += itemHeight; 15616 cursor.line++; 15617 x = cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]); 15618 } 15619 } else if (i > 0 && y + itemHeight > me.top + me.minSize.height) { 15620 x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; 15621 cursor.line++; 15622 y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]); 15623 } 15624 15625 var realX = rtlHelper.x(x); 15626 15627 drawLegendBox(realX, y, legendItem); 15628 15629 hitboxes[i].left = rtlHelper.leftForLtr(realX, hitboxes[i].width); 15630 hitboxes[i].top = y; 15631 15632 // Fill the actual label 15633 fillText(realX, y, legendItem, textWidth); 15634 15635 if (isHorizontal) { 15636 cursor.x += width + labelOpts.padding; 15637 } else { 15638 cursor.y += itemHeight; 15639 } 15640 }); 15641 15642 helpers$1.rtl.restoreTextDirection(me.ctx, opts.textDirection); 15643 }, 15644 15645 /** 15646 * @private 15647 */ 15648 _getLegendItemAt: function(x, y) { 15649 var me = this; 15650 var i, hitBox, lh; 15651 15652 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { 15653 // See if we are touching one of the dataset boxes 15654 lh = me.legendHitBoxes; 15655 for (i = 0; i < lh.length; ++i) { 15656 hitBox = lh[i]; 15657 15658 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { 15659 // Touching an element 15660 return me.legendItems[i]; 15661 } 15662 } 15663 } 15664 15665 return null; 15666 }, 15667 15668 /** 15669 * Handle an event 15670 * @private 15671 * @param {IEvent} event - The event to handle 15672 */ 15673 handleEvent: function(e) { 15674 var me = this; 15675 var opts = me.options; 15676 var type = e.type === 'mouseup' ? 'click' : e.type; 15677 var hoveredItem; 15678 15679 if (type === 'mousemove') { 15680 if (!opts.onHover && !opts.onLeave) { 15681 return; 15682 } 15683 } else if (type === 'click') { 15684 if (!opts.onClick) { 15685 return; 15686 } 15687 } else { 15688 return; 15689 } 15690 15691 // Chart event already has relative position in it 15692 hoveredItem = me._getLegendItemAt(e.x, e.y); 15693 15694 if (type === 'click') { 15695 if (hoveredItem && opts.onClick) { 15696 // use e.native for backwards compatibility 15697 opts.onClick.call(me, e.native, hoveredItem); 15698 } 15699 } else { 15700 if (opts.onLeave && hoveredItem !== me._hoveredItem) { 15701 if (me._hoveredItem) { 15702 opts.onLeave.call(me, e.native, me._hoveredItem); 15703 } 15704 me._hoveredItem = hoveredItem; 15705 } 15706 15707 if (opts.onHover && hoveredItem) { 15708 // use e.native for backwards compatibility 15709 opts.onHover.call(me, e.native, hoveredItem); 15710 } 15711 } 15712 } 15713}); 15714 15715function createNewLegendAndAttach(chart, legendOpts) { 15716 var legend = new Legend({ 15717 ctx: chart.ctx, 15718 options: legendOpts, 15719 chart: chart 15720 }); 15721 15722 core_layouts.configure(chart, legend, legendOpts); 15723 core_layouts.addBox(chart, legend); 15724 chart.legend = legend; 15725} 15726 15727var plugin_legend = { 15728 id: 'legend', 15729 15730 /** 15731 * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making 15732 * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of 15733 * the plugin, which one will be re-exposed in the chart.js file. 15734 * https://github.com/chartjs/Chart.js/pull/2640 15735 * @private 15736 */ 15737 _element: Legend, 15738 15739 beforeInit: function(chart) { 15740 var legendOpts = chart.options.legend; 15741 15742 if (legendOpts) { 15743 createNewLegendAndAttach(chart, legendOpts); 15744 } 15745 }, 15746 15747 beforeUpdate: function(chart) { 15748 var legendOpts = chart.options.legend; 15749 var legend = chart.legend; 15750 15751 if (legendOpts) { 15752 helpers$1.mergeIf(legendOpts, core_defaults.global.legend); 15753 15754 if (legend) { 15755 core_layouts.configure(chart, legend, legendOpts); 15756 legend.options = legendOpts; 15757 } else { 15758 createNewLegendAndAttach(chart, legendOpts); 15759 } 15760 } else if (legend) { 15761 core_layouts.removeBox(chart, legend); 15762 delete chart.legend; 15763 } 15764 }, 15765 15766 afterEvent: function(chart, e) { 15767 var legend = chart.legend; 15768 if (legend) { 15769 legend.handleEvent(e); 15770 } 15771 } 15772}; 15773 15774var noop$2 = helpers$1.noop; 15775 15776core_defaults._set('global', { 15777 title: { 15778 display: false, 15779 fontStyle: 'bold', 15780 fullWidth: true, 15781 padding: 10, 15782 position: 'top', 15783 text: '', 15784 weight: 2000 // by default greater than legend (1000) to be above 15785 } 15786}); 15787 15788/** 15789 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! 15790 */ 15791var Title = core_element.extend({ 15792 initialize: function(config) { 15793 var me = this; 15794 helpers$1.extend(me, config); 15795 15796 // Contains hit boxes for each dataset (in dataset order) 15797 me.legendHitBoxes = []; 15798 }, 15799 15800 // These methods are ordered by lifecycle. Utilities then follow. 15801 15802 beforeUpdate: noop$2, 15803 update: function(maxWidth, maxHeight, margins) { 15804 var me = this; 15805 15806 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 15807 me.beforeUpdate(); 15808 15809 // Absorb the master measurements 15810 me.maxWidth = maxWidth; 15811 me.maxHeight = maxHeight; 15812 me.margins = margins; 15813 15814 // Dimensions 15815 me.beforeSetDimensions(); 15816 me.setDimensions(); 15817 me.afterSetDimensions(); 15818 // Labels 15819 me.beforeBuildLabels(); 15820 me.buildLabels(); 15821 me.afterBuildLabels(); 15822 15823 // Fit 15824 me.beforeFit(); 15825 me.fit(); 15826 me.afterFit(); 15827 // 15828 me.afterUpdate(); 15829 15830 return me.minSize; 15831 15832 }, 15833 afterUpdate: noop$2, 15834 15835 // 15836 15837 beforeSetDimensions: noop$2, 15838 setDimensions: function() { 15839 var me = this; 15840 // Set the unconstrained dimension before label rotation 15841 if (me.isHorizontal()) { 15842 // Reset position before calculating rotation 15843 me.width = me.maxWidth; 15844 me.left = 0; 15845 me.right = me.width; 15846 } else { 15847 me.height = me.maxHeight; 15848 15849 // Reset position before calculating rotation 15850 me.top = 0; 15851 me.bottom = me.height; 15852 } 15853 15854 // Reset padding 15855 me.paddingLeft = 0; 15856 me.paddingTop = 0; 15857 me.paddingRight = 0; 15858 me.paddingBottom = 0; 15859 15860 // Reset minSize 15861 me.minSize = { 15862 width: 0, 15863 height: 0 15864 }; 15865 }, 15866 afterSetDimensions: noop$2, 15867 15868 // 15869 15870 beforeBuildLabels: noop$2, 15871 buildLabels: noop$2, 15872 afterBuildLabels: noop$2, 15873 15874 // 15875 15876 beforeFit: noop$2, 15877 fit: function() { 15878 var me = this; 15879 var opts = me.options; 15880 var minSize = me.minSize = {}; 15881 var isHorizontal = me.isHorizontal(); 15882 var lineCount, textSize; 15883 15884 if (!opts.display) { 15885 me.width = minSize.width = me.height = minSize.height = 0; 15886 return; 15887 } 15888 15889 lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1; 15890 textSize = lineCount * helpers$1.options._parseFont(opts).lineHeight + opts.padding * 2; 15891 15892 me.width = minSize.width = isHorizontal ? me.maxWidth : textSize; 15893 me.height = minSize.height = isHorizontal ? textSize : me.maxHeight; 15894 }, 15895 afterFit: noop$2, 15896 15897 // Shared Methods 15898 isHorizontal: function() { 15899 var pos = this.options.position; 15900 return pos === 'top' || pos === 'bottom'; 15901 }, 15902 15903 // Actually draw the title block on the canvas 15904 draw: function() { 15905 var me = this; 15906 var ctx = me.ctx; 15907 var opts = me.options; 15908 15909 if (!opts.display) { 15910 return; 15911 } 15912 15913 var fontOpts = helpers$1.options._parseFont(opts); 15914 var lineHeight = fontOpts.lineHeight; 15915 var offset = lineHeight / 2 + opts.padding; 15916 var rotation = 0; 15917 var top = me.top; 15918 var left = me.left; 15919 var bottom = me.bottom; 15920 var right = me.right; 15921 var maxWidth, titleX, titleY; 15922 15923 ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour 15924 ctx.font = fontOpts.string; 15925 15926 // Horizontal 15927 if (me.isHorizontal()) { 15928 titleX = left + ((right - left) / 2); // midpoint of the width 15929 titleY = top + offset; 15930 maxWidth = right - left; 15931 } else { 15932 titleX = opts.position === 'left' ? left + offset : right - offset; 15933 titleY = top + ((bottom - top) / 2); 15934 maxWidth = bottom - top; 15935 rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); 15936 } 15937 15938 ctx.save(); 15939 ctx.translate(titleX, titleY); 15940 ctx.rotate(rotation); 15941 ctx.textAlign = 'center'; 15942 ctx.textBaseline = 'middle'; 15943 15944 var text = opts.text; 15945 if (helpers$1.isArray(text)) { 15946 var y = 0; 15947 for (var i = 0; i < text.length; ++i) { 15948 ctx.fillText(text[i], 0, y, maxWidth); 15949 y += lineHeight; 15950 } 15951 } else { 15952 ctx.fillText(text, 0, 0, maxWidth); 15953 } 15954 15955 ctx.restore(); 15956 } 15957}); 15958 15959function createNewTitleBlockAndAttach(chart, titleOpts) { 15960 var title = new Title({ 15961 ctx: chart.ctx, 15962 options: titleOpts, 15963 chart: chart 15964 }); 15965 15966 core_layouts.configure(chart, title, titleOpts); 15967 core_layouts.addBox(chart, title); 15968 chart.titleBlock = title; 15969} 15970 15971var plugin_title = { 15972 id: 'title', 15973 15974 /** 15975 * Backward compatibility: since 2.1.5, the title is registered as a plugin, making 15976 * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of 15977 * the plugin, which one will be re-exposed in the chart.js file. 15978 * https://github.com/chartjs/Chart.js/pull/2640 15979 * @private 15980 */ 15981 _element: Title, 15982 15983 beforeInit: function(chart) { 15984 var titleOpts = chart.options.title; 15985 15986 if (titleOpts) { 15987 createNewTitleBlockAndAttach(chart, titleOpts); 15988 } 15989 }, 15990 15991 beforeUpdate: function(chart) { 15992 var titleOpts = chart.options.title; 15993 var titleBlock = chart.titleBlock; 15994 15995 if (titleOpts) { 15996 helpers$1.mergeIf(titleOpts, core_defaults.global.title); 15997 15998 if (titleBlock) { 15999 core_layouts.configure(chart, titleBlock, titleOpts); 16000 titleBlock.options = titleOpts; 16001 } else { 16002 createNewTitleBlockAndAttach(chart, titleOpts); 16003 } 16004 } else if (titleBlock) { 16005 core_layouts.removeBox(chart, titleBlock); 16006 delete chart.titleBlock; 16007 } 16008 } 16009}; 16010 16011var plugins = {}; 16012var filler = plugin_filler; 16013var legend = plugin_legend; 16014var title = plugin_title; 16015plugins.filler = filler; 16016plugins.legend = legend; 16017plugins.title = title; 16018 16019/** 16020 * @namespace Chart 16021 */ 16022 16023 16024core_controller.helpers = helpers$1; 16025 16026// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! 16027core_helpers(); 16028 16029core_controller._adapters = core_adapters; 16030core_controller.Animation = core_animation; 16031core_controller.animationService = core_animations; 16032core_controller.controllers = controllers; 16033core_controller.DatasetController = core_datasetController; 16034core_controller.defaults = core_defaults; 16035core_controller.Element = core_element; 16036core_controller.elements = elements; 16037core_controller.Interaction = core_interaction; 16038core_controller.layouts = core_layouts; 16039core_controller.platform = platform; 16040core_controller.plugins = core_plugins; 16041core_controller.Scale = core_scale; 16042core_controller.scaleService = core_scaleService; 16043core_controller.Ticks = core_ticks; 16044core_controller.Tooltip = core_tooltip; 16045 16046// Register built-in scales 16047 16048core_controller.helpers.each(scales, function(scale, type) { 16049 core_controller.scaleService.registerScaleType(type, scale, scale._defaults); 16050}); 16051 16052// Load to register built-in adapters (as side effects) 16053 16054 16055// Loading built-in plugins 16056 16057for (var k in plugins) { 16058 if (plugins.hasOwnProperty(k)) { 16059 core_controller.plugins.register(plugins[k]); 16060 } 16061} 16062 16063core_controller.platform.initialize(); 16064 16065var src = core_controller; 16066if (typeof window !== 'undefined') { 16067 window.Chart = core_controller; 16068} 16069 16070// DEPRECATIONS 16071 16072/** 16073 * Provided for backward compatibility, not available anymore 16074 * @namespace Chart.Chart 16075 * @deprecated since version 2.8.0 16076 * @todo remove at version 3 16077 * @private 16078 */ 16079core_controller.Chart = core_controller; 16080 16081/** 16082 * Provided for backward compatibility, not available anymore 16083 * @namespace Chart.Legend 16084 * @deprecated since version 2.1.5 16085 * @todo remove at version 3 16086 * @private 16087 */ 16088core_controller.Legend = plugins.legend._element; 16089 16090/** 16091 * Provided for backward compatibility, not available anymore 16092 * @namespace Chart.Title 16093 * @deprecated since version 2.1.5 16094 * @todo remove at version 3 16095 * @private 16096 */ 16097core_controller.Title = plugins.title._element; 16098 16099/** 16100 * Provided for backward compatibility, use Chart.plugins instead 16101 * @namespace Chart.pluginService 16102 * @deprecated since version 2.1.5 16103 * @todo remove at version 3 16104 * @private 16105 */ 16106core_controller.pluginService = core_controller.plugins; 16107 16108/** 16109 * Provided for backward compatibility, inheriting from Chart.PlugingBase has no 16110 * effect, instead simply create/register plugins via plain JavaScript objects. 16111 * @interface Chart.PluginBase 16112 * @deprecated since version 2.5.0 16113 * @todo remove at version 3 16114 * @private 16115 */ 16116core_controller.PluginBase = core_controller.Element.extend({}); 16117 16118/** 16119 * Provided for backward compatibility, use Chart.helpers.canvas instead. 16120 * @namespace Chart.canvasHelpers 16121 * @deprecated since version 2.6.0 16122 * @todo remove at version 3 16123 * @private 16124 */ 16125core_controller.canvasHelpers = core_controller.helpers.canvas; 16126 16127/** 16128 * Provided for backward compatibility, use Chart.layouts instead. 16129 * @namespace Chart.layoutService 16130 * @deprecated since version 2.7.3 16131 * @todo remove at version 3 16132 * @private 16133 */ 16134core_controller.layoutService = core_controller.layouts; 16135 16136/** 16137 * Provided for backward compatibility, not available anymore. 16138 * @namespace Chart.LinearScaleBase 16139 * @deprecated since version 2.8 16140 * @todo remove at version 3 16141 * @private 16142 */ 16143core_controller.LinearScaleBase = scale_linearbase; 16144 16145/** 16146 * Provided for backward compatibility, instead we should create a new Chart 16147 * by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`). 16148 * @deprecated since version 2.8.0 16149 * @todo remove at version 3 16150 */ 16151core_controller.helpers.each( 16152 [ 16153 'Bar', 16154 'Bubble', 16155 'Doughnut', 16156 'Line', 16157 'PolarArea', 16158 'Radar', 16159 'Scatter' 16160 ], 16161 function(klass) { 16162 core_controller[klass] = function(ctx, cfg) { 16163 return new core_controller(ctx, core_controller.helpers.merge(cfg || {}, { 16164 type: klass.charAt(0).toLowerCase() + klass.slice(1) 16165 })); 16166 }; 16167 } 16168); 16169 16170return src; 16171 16172})));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.