1/*! 2 * Chart.js v2.8.0 3 * https://www.chartjs.org 4 * (c) 2019 Chart.js Contributors 5 * Released under the MIT License 6 */ 7(function (global, factory) { 8 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(function () { 9 try { 10 return require('moment'); 11 } catch (e) { 12 } 13 }()) : 14 typeof define === 'function' && define.amd ? define(['require'], function (require) { 15 return factory(function () { 16 try { 17 return require('moment'); 18 } catch (e) { 19 } 20 }()); 21 }) : 22 (global.Chart = factory(global.moment)); 23}(this, (function (moment) { 24 'use strict'; 25 26 moment = moment && moment.hasOwnProperty('default') ? moment['default'] : moment; 27 28 /* MIT license */ 29 30 var conversions = { 31 rgb2hsl: rgb2hsl, 32 rgb2hsv: rgb2hsv, 33 rgb2hwb: rgb2hwb, 34 rgb2cmyk: rgb2cmyk, 35 rgb2keyword: rgb2keyword, 36 rgb2xyz: rgb2xyz, 37 rgb2lab: rgb2lab, 38 rgb2lch: rgb2lch, 39 40 hsl2rgb: hsl2rgb, 41 hsl2hsv: hsl2hsv, 42 hsl2hwb: hsl2hwb, 43 hsl2cmyk: hsl2cmyk, 44 hsl2keyword: hsl2keyword, 45 46 hsv2rgb: hsv2rgb, 47 hsv2hsl: hsv2hsl, 48 hsv2hwb: hsv2hwb, 49 hsv2cmyk: hsv2cmyk, 50 hsv2keyword: hsv2keyword, 51 52 hwb2rgb: hwb2rgb, 53 hwb2hsl: hwb2hsl, 54 hwb2hsv: hwb2hsv, 55 hwb2cmyk: hwb2cmyk, 56 hwb2keyword: hwb2keyword, 57 58 cmyk2rgb: cmyk2rgb, 59 cmyk2hsl: cmyk2hsl, 60 cmyk2hsv: cmyk2hsv, 61 cmyk2hwb: cmyk2hwb, 62 cmyk2keyword: cmyk2keyword, 63 64 keyword2rgb: keyword2rgb, 65 keyword2hsl: keyword2hsl, 66 keyword2hsv: keyword2hsv, 67 keyword2hwb: keyword2hwb, 68 keyword2cmyk: keyword2cmyk, 69 keyword2lab: keyword2lab, 70 keyword2xyz: keyword2xyz, 71 72 xyz2rgb: xyz2rgb, 73 xyz2lab: xyz2lab, 74 xyz2lch: xyz2lch, 75 76 lab2xyz: lab2xyz, 77 lab2rgb: lab2rgb, 78 lab2lch: lab2lch, 79 80 lch2lab: lch2lab, 81 lch2xyz: lch2xyz, 82 lch2rgb: lch2rgb 83 }; 84 85 86 function rgb2hsl(rgb) { 87 var r = rgb[0] / 255, 88 g = rgb[1] / 255, 89 b = rgb[2] / 255, 90 min = Math.min(r, g, b), 91 max = Math.max(r, g, b), 92 delta = max - min, 93 h, s, l; 94 95 if (max == min) 96 h = 0; 97 else if (r == max) 98 h = (g - b) / delta; 99 else if (g == max) 100 h = 2 + (b - r) / delta; 101 else if (b == max) 102 h = 4 + (r - g) / delta; 103 104 h = Math.min(h * 60, 360); 105 106 if (h < 0) 107 h += 360; 108 109 l = (min + max) / 2; 110 111 if (max == min) 112 s = 0; 113 else if (l <= 0.5) 114 s = delta / (max + min); 115 else 116 s = delta / (2 - max - min); 117 118 return [h, s * 100, l * 100]; 119 } 120 121 function rgb2hsv(rgb) { 122 var r = rgb[0], 123 g = rgb[1], 124 b = rgb[2], 125 min = Math.min(r, g, b), 126 max = Math.max(r, g, b), 127 delta = max - min, 128 h, s, v; 129 130 if (max == 0) 131 s = 0; 132 else 133 s = (delta / max * 1000) / 10; 134 135 if (max == min) 136 h = 0; 137 else if (r == max) 138 h = (g - b) / delta; 139 else if (g == max) 140 h = 2 + (b - r) / delta; 141 else if (b == max) 142 h = 4 + (r - g) / delta; 143 144 h = Math.min(h * 60, 360); 145 146 if (h < 0) 147 h += 360; 148 149 v = ((max / 255) * 1000) / 10; 150 151 return [h, s, v]; 152 } 153 154 function rgb2hwb(rgb) { 155 var r = rgb[0], 156 g = rgb[1], 157 b = rgb[2], 158 h = rgb2hsl(rgb)[0], 159 w = 1 / 255 * Math.min(r, Math.min(g, b)), 160 b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); 161 162 return [h, w * 100, b * 100]; 163 } 164 165 function rgb2cmyk(rgb) { 166 var r = rgb[0] / 255, 167 g = rgb[1] / 255, 168 b = rgb[2] / 255, 169 c, m, y, k; 170 171 k = Math.min(1 - r, 1 - g, 1 - b); 172 c = (1 - r - k) / (1 - k) || 0; 173 m = (1 - g - k) / (1 - k) || 0; 174 y = (1 - b - k) / (1 - k) || 0; 175 return [c * 100, m * 100, y * 100, k * 100]; 176 } 177 178 function rgb2keyword(rgb) { 179 return reverseKeywords[JSON.stringify(rgb)]; 180 } 181 182 function rgb2xyz(rgb) {
183 var r = rgb[0] / 255, 184 g = rgb[1] / 255, 185 b = rgb[2] / 255; 186 187 // assume sRGB 188 r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); 189 g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); 190 b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); 191 192 var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); 193 var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); 194 var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); 195 196 return [x * 100, y * 100, z * 100]; 197 } 198 199 function rgb2lab(rgb) { 200 var xyz = rgb2xyz(rgb), 201 x = xyz[0], 202 y = xyz[1], 203 z = xyz[2], 204 l, a, b; 205 206 x /= 95.047; 207 y /= 100; 208 z /= 108.883; 209 210 x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); 211 y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); 212 z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); 213 214 l = (116 * y) - 16; 215 a = 500 * (x - y); 216 b = 200 * (y - z); 217 218 return [l, a, b]; 219 } 220 221 function rgb2lch(args) { 222 return lab2lch(rgb2lab(args)); 223 } 224 225 function hsl2rgb(hsl) { 226 var h = hsl[0] / 360, 227 s = hsl[1] / 100, 228 l = hsl[2] / 100, 229 t1, t2, t3, rgb, val; 230 231 if (s == 0) { 232 val = l * 255; 233 return [val, val, val]; 234 } 235 236 if (l < 0.5) 237 t2 = l * (1 + s); 238 else 239 t2 = l + s - l * s; 240 t1 = 2 * l - t2; 241 242 rgb = [0, 0, 0]; 243 for (var i = 0; i < 3; i++) { 244 t3 = h + 1 / 3 * -(i - 1); 245 t3 < 0 && t3++; 246 t3 > 1 && t3--; 247 248 if (6 * t3 < 1) 249 val = t1 + (t2 - t1) * 6 * t3; 250 else if (2 * t3 < 1) 251 val = t2; 252 else if (3 * t3 < 2) 253 val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; 254 else 255 val = t1; 256 257 rgb[i] = val * 255; 258 } 259 260 return rgb; 261 } 262 263 function hsl2hsv(hsl) { 264 var h = hsl[0], 265 s = hsl[1] / 100, 266 l = hsl[2] / 100, 267 sv, v; 268 269 if (l === 0) { 270 // no need to do calc on black 271 // also avoids divide by 0 error 272 return [0, 0, 0]; 273 } 274 275 l *= 2; 276 s *= (l <= 1) ? l : 2 - l; 277 v = (l + s) / 2; 278 sv = (2 * s) / (l + s); 279 return [h, sv * 100, v * 100]; 280 } 281 282 function hsl2hwb(args) { 283 return rgb2hwb(hsl2rgb(args)); 284 } 285 286 function hsl2cmyk(args) { 287 return rgb2cmyk(hsl2rgb(args)); 288 } 289 290 function hsl2keyword(args) { 291 return rgb2keyword(hsl2rgb(args)); 292 } 293 294 295 function hsv2rgb(hsv) { 296 var h = hsv[0] / 60, 297 s = hsv[1] / 100, 298 v = hsv[2] / 100, 299 hi = Math.floor(h) % 6; 300 301 var f = h - Math.floor(h), 302 p = 255 * v * (1 - s), 303 q = 255 * v * (1 - (s * f)), 304 t = 255 * v * (1 - (s * (1 - f))), 305 v = 255 * v; 306 307 switch (hi) { 308 case 0: 309 return [v, t, p]; 310 case 1: 311 return [q, v, p]; 312 case 2: 313 return [p, v, t]; 314 case 3: 315 return [p, q, v]; 316 case 4: 317 return [t, p, v]; 318 case 5: 319 return [v, p, q]; 320 } 321 } 322 323 function hsv2hsl(hsv) { 324 var h = hsv[0], 325 s = hsv[1] / 100, 326 v = hsv[2] / 100, 327 sl, l; 328 329 l = (2 - s) * v; 330 sl = s * v; 331 sl /= (l <= 1) ? l : 2 - l; 332 sl = sl || 0; 333 l /= 2; 334 return [h, sl * 100, l * 100]; 335 } 336 337 function hsv2hwb(args) { 338 return rgb2hwb(hsv2rgb(args)) 339 } 340 341 function hsv2cmyk(args) { 342 return rgb2cmyk(hsv2rgb(args)); 343 } 344 345 function hsv2keyword(args) { 346 return rgb2keyword(hsv2rgb(args)); 347 } 348 349// http://dev.w3.org/csswg/css-color/#hwb-to-rgb 350 function hwb2rgb(hwb) { 351 var h = hwb[0] / 360, 352 wh = hwb[1] / 100, 353 bl = hwb[2] / 100, 354 ratio = wh + bl, 355 i, v, f, n; 356 357 // wh + bl cant be > 1 358 if (ratio > 1) { 359 wh /= ratio; 360 bl /= ratio; 361 } 362 363 i = Math.floor(6 * h); 364 v = 1 - bl; 365 f = 6 * h - i; 366 if ((i & 0x01) != 0) { 367 f = 1 - f; 368 } 369 n = wh + f * (v - wh); // linear interpolation 370 371 switch (i) { 372 default: 373 case 6: 374 case 0: 375 r = v; 376 g = n; 377 b = wh; 378 break; 379 case 1: 380 r = n; 381 g = v; 382 b = wh; 383 break; 384 case 2: 385 r = wh; 386 g = v; 387 b = n; 388 break; 389 case 3: 390 r = wh; 391 g = n; 392 b = v; 393 break; 394 case 4: 395 r = n; 396 g = wh; 397 b = v; 398 break; 399 case 5: 400 r = v; 401 g = wh; 402 b = n; 403 break; 404 } 405 406 return [r * 255, g * 255, b * 255]; 407 } 408 409 function hwb2hsl(args) { 410 return rgb2hsl(hwb2rgb(args)); 411 } 412 413 function hwb2hsv(args) { 414 return rgb2hsv(hwb2rgb(args)); 415 } 416 417 function hwb2cmyk(args) { 418 return rgb2cmyk(hwb2rgb(args)); 419 } 420 421 function hwb2keyword(args) { 422 return rgb2keyword(hwb2rgb(args)); 423 } 424 425 function cmyk2rgb(cmyk) { 426 var c = cmyk[0] / 100, 427 m = cmyk[1] / 100, 428 y = cmyk[2] / 100, 429 k = cmyk[3] / 100, 430 r, g, b; 431 432 r = 1 - Math.min(1, c * (1 - k) + k); 433 g = 1 - Math.min(1, m * (1 - k) + k); 434 b = 1 - Math.min(1, y * (1 - k) + k); 435 return [r * 255, g * 255, b * 255]; 436 } 437 438 function cmyk2hsl(args) { 439 return rgb2hsl(cmyk2rgb(args)); 440 } 441 442 function cmyk2hsv(args) { 443 return rgb2hsv(cmyk2rgb(args)); 444 } 445 446 function cmyk2hwb(args) { 447 return rgb2hwb(cmyk2rgb(args)); 448 } 449 450 function cmyk2keyword(args) { 451 return rgb2keyword(cmyk2rgb(args)); 452 } 453 454 455 function xyz2rgb(xyz) { 456 var x = xyz[0] / 100, 457 y = xyz[1] / 100, 458 z = xyz[2] / 100, 459 r, g, b; 460 461 r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); 462 g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); 463 b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); 464 465 // assume sRGB 466 r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) 467 : r = (r * 12.92); 468 469 g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) 470 : g = (g * 12.92); 471 472 b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) 473 : b = (b * 12.92); 474 475 r = Math.min(Math.max(0, r), 1); 476 g = Math.min(Math.max(0, g), 1); 477 b = Math.min(Math.max(0, b), 1); 478 479 return [r * 255, g * 255, b * 255]; 480 } 481 482 function xyz2lab(xyz) { 483 var x = xyz[0], 484 y = xyz[1], 485 z = xyz[2], 486 l, a, b; 487 488 x /= 95.047; 489 y /= 100; 490 z /= 108.883; 491 492 x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); 493 y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); 494 z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); 495 496 l = (116 * y) - 16; 497 a = 500 * (x - y); 498 b = 200 * (y - z); 499 500 return [l, a, b]; 501 } 502 503 function xyz2lch(args) { 504 return lab2lch(xyz2lab(args)); 505 } 506 507 function lab2xyz(lab) { 508 var l = lab[0], 509 a = lab[1], 510 b = lab[2], 511 x, y, z, y2; 512 513 if (l <= 8) { 514 y = (l * 100) / 903.3; 515 y2 = (7.787 * (y / 100)) + (16 / 116); 516 } else { 517 y = 100 * Math.pow((l + 16) / 116, 3); 518 y2 = Math.pow(y / 100, 1 / 3); 519 } 520 521 x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3); 522 523 z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3); 524 525 return [x, y, z]; 526 } 527 528 function lab2lch(lab) { 529 var l = lab[0], 530 a = lab[1], 531 b = lab[2], 532 hr, h, c; 533 534 hr = Math.atan2(b, a);
535 h = hr * 360 / 2 / Math.PI; 536 if (h < 0) { 537 h += 360; 538 } 539 c = Math.sqrt(a * a + b * b); 540 return [l, c, h]; 541 } 542 543 function lab2rgb(args) { 544 return xyz2rgb(lab2xyz(args)); 545 } 546 547 function lch2lab(lch) { 548 var l = lch[0], 549 c = lch[1], 550 h = lch[2], 551 a, b, hr; 552 553 hr = h / 360 * 2 * Math.PI; 554 a = c * Math.cos(hr); 555 b = c * Math.sin(hr); 556 return [l, a, b]; 557 } 558 559 function lch2xyz(args) { 560 return lab2xyz(lch2lab(args)); 561 } 562 563 function lch2rgb(args) { 564 return lab2rgb(lch2lab(args)); 565 } 566 567 function keyword2rgb(keyword) { 568 return cssKeywords[keyword]; 569 } 570 571 function keyword2hsl(args) { 572 return rgb2hsl(keyword2rgb(args)); 573 } 574 575 function keyword2hsv(args) { 576 return rgb2hsv(keyword2rgb(args)); 577 } 578 579 function keyword2hwb(args) { 580 return rgb2hwb(keyword2rgb(args)); 581 } 582 583 function keyword2cmyk(args) { 584 return rgb2cmyk(keyword2rgb(args)); 585 } 586 587 function keyword2lab(args) { 588 return rgb2lab(keyword2rgb(args)); 589 } 590 591 function keyword2xyz(args) { 592 return rgb2xyz(keyword2rgb(args)); 593 } 594 595 var cssKeywords = { 596 aliceblue: [240, 248, 255], 597 antiquewhite: [250, 235, 215], 598 aqua: [0, 255, 255], 599 aquamarine: [127, 255, 212], 600 azure: [240, 255, 255], 601 beige: [245, 245, 220], 602 bisque: [255, 228, 196], 603 black: [0, 0, 0], 604 blanchedalmond: [255, 235, 205], 605 blue: [0, 0, 255], 606 blueviolet: [138, 43, 226], 607 brown: [165, 42, 42], 608 burlywood: [222, 184, 135], 609 cadetblue: [95, 158, 160], 610 chartreuse: [127, 255, 0], 611 chocolate: [210, 105, 30], 612 coral: [255, 127, 80], 613 cornflowerblue: [100, 149, 237], 614 cornsilk: [255, 248, 220], 615 crimson: [220, 20, 60], 616 cyan: [0, 255, 255], 617 darkblue: [0, 0, 139], 618 darkcyan: [0, 139, 139], 619 darkgoldenrod: [184, 134, 11], 620 darkgray: [169, 169, 169], 621 darkgreen: [0, 100, 0], 622 darkgrey: [169, 169, 169], 623 darkkhaki: [189, 183, 107], 624 darkmagenta: [139, 0, 139], 625 darkolivegreen: [85, 107, 47], 626 darkorange: [255, 140, 0], 627 darkorchid: [153, 50, 204], 628 darkred: [139, 0, 0], 629 darksalmon: [233, 150, 122], 630 darkseagreen: [143, 188, 143], 631 darkslateblue: [72, 61, 139], 632 darkslategray: [47, 79, 79], 633 darkslategrey: [47, 79, 79], 634 darkturquoise: [0, 206, 209], 635 darkviolet: [148, 0, 211], 636 deeppink: [255, 20, 147], 637 deepskyblue: [0, 191, 255], 638 dimgray: [105, 105, 105], 639 dimgrey: [105, 105, 105], 640 dodgerblue: [30, 144, 255], 641 firebrick: [178, 34, 34], 642 floralwhite: [255, 250, 240], 643 forestgreen: [34, 139, 34], 644 fuchsia: [255, 0, 255], 645 gainsboro: [220, 220, 220], 646 ghostwhite: [248, 248, 255], 647 gold: [255, 215, 0], 648 goldenrod: [218, 165, 32], 649 gray: [128, 128, 128], 650 green: [0, 128, 0], 651 greenyellow: [173, 255, 47], 652 grey: [128, 128, 128], 653 honeydew: [240, 255, 240], 654 hotpink: [255, 105, 180], 655 indianred: [205, 92, 92], 656 indigo: [75, 0, 130], 657 ivory: [255, 255, 240], 658 khaki: [240, 230, 140], 659 lavender: [230, 230, 250], 660 lavenderblush: [255, 240, 245], 661 lawngreen: [124, 252, 0], 662 lemonchiffon: [255, 250, 205], 663 lightblue: [173, 216, 230], 664 lightcoral: [240, 128, 128], 665 lightcyan: [224, 255, 255], 666 lightgoldenrodyellow: [250, 250, 210], 667 lightgray: [211, 211, 211], 668 lightgreen: [144, 238, 144], 669 lightgrey: [211, 211, 211], 670 lightpink: [255, 182, 193], 671 lightsalmon: [255, 160, 122], 672 lightseagreen: [32, 178, 170], 673 lightskyblue: [135, 206, 250], 674 lightslategray: [119, 136, 153], 675 lightslategrey: [119, 136, 153], 676 lightsteelblue: [176, 196, 222], 677 lightyellow: [255, 255, 224], 678 lime: [0, 255, 0], 679 limegreen: [50, 205, 50], 680 linen: [250, 240, 230], 681 magenta: [255, 0, 255], 682 maroon: [128, 0, 0], 683 mediumaquamarine: [102, 205, 170], 684 mediumblue: [0, 0, 205], 685 mediumorchid: [186, 85, 211], 686 mediumpurple: [147, 112, 219], 687 mediumseagreen: [60, 179, 113], 688 mediumslateblue: [123, 104, 238], 689 mediumspringgreen: [0, 250, 154], 690 mediumturquoise: [72, 209, 204], 691 mediumvioletred: [199, 21, 133], 692 midnightblue: [25, 25, 112], 693 mintcream: [245, 255, 250], 694 mistyrose: [255, 228, 225], 695 moccasin: [255, 228, 181], 696 navajowhite: [255, 222, 173], 697 navy: [0, 0, 128], 698 oldlace: [253, 245, 230], 699 olive: [128, 128, 0], 700 olivedrab: [107, 142, 35], 701 orange: [255, 165, 0], 702 orangered: [255, 69, 0], 703 orchid: [218, 112, 214], 704 palegoldenrod: [238, 232, 170], 705 palegreen: [152, 251, 152], 706 paleturquoise: [175, 238, 238], 707 palevioletred: [219, 112, 147], 708 papayawhip: [255, 239, 213], 709 peachpuff: [255, 218, 185], 710 peru: [205, 133, 63], 711 pink: [255, 192, 203], 712 plum: [221, 160, 221], 713 powderblue: [176, 224, 230], 714 purple: [128, 0, 128], 715 rebeccapurple: [102, 51, 153], 716 red: [255, 0, 0], 717 rosybrown: [188, 143, 143], 718 royalblue: [65, 105, 225], 719 saddlebrown: [139, 69, 19], 720 salmon: [250, 128, 114], 721 sandybrown: [244, 164, 96], 722 seagreen: [46, 139, 87], 723 seashell: [255, 245, 238], 724 sienna: [160, 82, 45], 725 silver: [192, 192, 192], 726 skyblue: [135, 206, 235], 727 slateblue: [106, 90, 205], 728 slategray: [112, 128, 144], 729 slategrey: [112, 128, 144], 730 snow: [255, 250, 250], 731 springgreen: [0, 255, 127], 732 steelblue: [70, 130, 180], 733 tan: [210, 180, 140], 734 teal: [0, 128, 128], 735 thistle: [216, 191, 216], 736 tomato: [255, 99, 71], 737 turquoise: [64, 224, 208], 738 violet: [238, 130, 238], 739 wheat: [245, 222, 179], 740 white: [255, 255, 255], 741 whitesmoke: [245, 245, 245], 742 yellow: [255, 255, 0], 743 yellowgreen: [154, 205, 50] 744 }; 745 746 var reverseKeywords = {}; 747 for (var key in cssKeywords) { 748 reverseKeywords[JSON.stringify(cssKeywords[key])] = key; 749 } 750 751 var convert = function () { 752 return new Converter(); 753 }; 754 755 for (var func in conversions) {
756 // export Raw versions 757 convert[func + "Raw"] = (function (func) { 758 // accept array or plain args 759 return function (arg) { 760 if (typeof arg == "number") 761 arg = Array.prototype.slice.call(arguments); 762 return conversions[func](arg); 763 } 764 })(func); 765 766 var pair = /(\w+)2(\w+)/.exec(func), 767 from = pair[1], 768 to = pair[2]; 769 770 // export rgb2hsl and ["rgb"]["hsl"] 771 convert[from] = convert[from] || {}; 772 773 convert[from][to] = convert[func] = (function (func) { 774 return function (arg) { 775 if (typeof arg == "number") 776 arg = Array.prototype.slice.call(arguments); 777 778 var val = conversions[func](arg); 779 if (typeof val == "string" || val === undefined) 780 return val; // keyword 781 782 for (var i = 0; i < val.length; i++) 783 val[i] = Math.round(val[i]); 784 return val; 785 } 786 })(func); 787 } 788 789 790 /* Converter does lazy conversion and caching */ 791 var Converter = function () { 792 this.convs = {}; 793 }; 794 795 /* Either get the values for a space or 796 set the values for a space, depending on args */ 797 Converter.prototype.routeSpace = function (space, args) { 798 var values = args[0]; 799 if (values === undefined) { 800 // color.rgb() 801 return this.getValues(space); 802 } 803 // color.rgb(10, 10, 10) 804 if (typeof values == "number") { 805 values = Array.prototype.slice.call(args); 806 } 807 808 return this.setValues(space, values); 809 }; 810 811 /* Set the values for a space, invalidating cache */ 812 Converter.prototype.setValues = function (space, values) { 813 this.space = space; 814 this.convs = {}; 815 this.convs[space] = values; 816 return this; 817 }; 818 819 /* Get the values for a space. If there's already 820 a conversion for the space, fetch it, otherwise 821 compute it */ 822 Converter.prototype.getValues = function (space) { 823 var vals = this.convs[space]; 824 if (!vals) { 825 var fspace = this.space, 826 from = this.convs[fspace]; 827 vals = convert[fspace][space](from); 828 829 this.convs[space] = vals; 830 } 831 return vals; 832 }; 833 834 ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function (space) { 835 Converter.prototype[space] = function (vals) { 836 return this.routeSpace(space, arguments); 837 }; 838 }); 839 840 var colorConvert = convert; 841 842 var colorName = { 843 "aliceblue": [240, 248, 255], 844 "antiquewhite": [250, 235, 215], 845 "aqua": [0, 255, 255], 846 "aquamarine": [127, 255, 212], 847 "azure": [240, 255, 255], 848 "beige": [245, 245, 220], 849 "bisque": [255, 228, 196], 850 "black": [0, 0, 0], 851 "blanchedalmond": [255, 235, 205], 852 "blue": [0, 0, 255], 853 "blueviolet": [138, 43, 226], 854 "brown": [165, 42, 42], 855 "burlywood": [222, 184, 135], 856 "cadetblue": [95, 158, 160], 857 "chartreuse": [127, 255, 0], 858 "chocolate": [210, 105, 30], 859 "coral": [255, 127, 80], 860 "cornflowerblue": [100, 149, 237], 861 "cornsilk": [255, 248, 220], 862 "crimson": [220, 20, 60], 863 "cyan": [0, 255, 255], 864 "darkblue": [0, 0, 139], 865 "darkcyan": [0, 139, 139], 866 "darkgoldenrod": [184, 134, 11], 867 "darkgray": [169, 169, 169], 868 "darkgreen": [0, 100, 0], 869 "darkgrey": [169, 169, 169], 870 "darkkhaki": [189, 183, 107], 871 "darkmagenta": [139, 0, 139], 872 "darkolivegreen": [85, 107, 47], 873 "darkorange": [255, 140, 0], 874 "darkorchid": [153, 50, 204], 875 "darkred": [139, 0, 0], 876 "darksalmon": [233, 150, 122], 877 "darkseagreen": [143, 188, 143], 878 "darkslateblue": [72, 61, 139], 879 "darkslategray": [47, 79, 79], 880 "darkslategrey": [47, 79, 79], 881 "darkturquoise": [0, 206, 209], 882 "darkviolet": [148, 0, 211], 883 "deeppink": [255, 20, 147], 884 "deepskyblue": [0, 191, 255], 885 "dimgray": [105, 105, 105], 886 "dimgrey": [105, 105, 105], 887 "dodgerblue": [30, 144, 255], 888 "firebrick": [178, 34, 34], 889 "floralwhite": [255, 250, 240], 890 "forestgreen": [34, 139, 34], 891 "fuchsia": [255, 0, 255], 892 "gainsboro": [220, 220, 220], 893 "ghostwhite": [248, 248, 255], 894 "gold": [255, 215, 0], 895 "goldenrod": [218, 165, 32], 896 "gray": [128, 128, 128], 897 "green": [0, 128, 0], 898 "greenyellow": [173, 255, 47], 899 "grey": [128, 128, 128], 900 "honeydew": [240, 255, 240], 901 "hotpink": [255, 105, 180], 902 "indianred": [205, 92, 92], 903 "indigo": [75, 0, 130], 904 "ivory": [255, 255, 240], 905 "khaki": [240, 230, 140], 906 "lavender": [230, 230, 250], 907 "lavenderblush": [255, 240, 245], 908 "lawngreen": [124, 252, 0], 909 "lemonchiffon": [255, 250, 205], 910 "lightblue": [173, 216, 230], 911 "lightcoral": [240, 128, 128], 912 "lightcyan": [224, 255, 255], 913 "lightgoldenrodyellow": [250, 250, 210], 914 "lightgray": [211, 211, 211], 915 "lightgreen": [144, 238, 144], 916 "lightgrey": [211, 211, 211], 917 "lightpink": [255, 182, 193], 918 "lightsalmon": [255, 160, 122], 919 "lightseagreen": [32, 178, 170], 920 "lightskyblue": [135, 206, 250], 921 "lightslategray": [119, 136, 153], 922 "lightslategrey": [119, 136, 153], 923 "lightsteelblue": [176, 196, 222], 924 "lightyellow": [255, 255, 224], 925 "lime": [0, 255, 0], 926 "limegreen": [50, 205, 50], 927 "linen": [250, 240, 230], 928 "magenta": [255, 0, 255], 929 "maroon": [128, 0, 0], 930 "mediumaquamarine": [102, 205, 170], 931 "mediumblue": [0, 0, 205], 932 "mediumorchid": [186, 85, 211], 933 "mediumpurple": [147, 112, 219], 934 "mediumseagreen": [60, 179, 113], 935 "mediumslateblue": [123, 104, 238], 936 "mediumspringgreen": [0, 250, 154],
vendor: 79,923 bytes, lines 937-3384
937 "mediumturquoise": [72, 209, 204], 938 "mediumvioletred": [199, 21, 133], 939 "midnightblue": [25, 25, 112], 940 "mintcream": [245, 255, 250], 941 "mistyrose": [255, 228, 225], 942 "moccasin": [255, 228, 181], 943 "navajowhite": [255, 222, 173], 944 "navy": [0, 0, 128], 945 "oldlace": [253, 245, 230], 946 "olive": [128, 128, 0], 947 "olivedrab": [107, 142, 35], 948 "orange": [255, 165, 0], 949 "orangered": [255, 69, 0], 950 "orchid": [218, 112, 214], 951 "palegoldenrod": [238, 232, 170], 952 "palegreen": [152, 251, 152], 953 "paleturquoise": [175, 238, 238], 954 "palevioletred": [219, 112, 147], 955 "papayawhip": [255, 239, 213], 956 "peachpuff": [255, 218, 185], 957 "peru": [205, 133, 63], 958 "pink": [255, 192, 203], 959 "plum": [221, 160, 221], 960 "powderblue": [176, 224, 230], 961 "purple": [128, 0, 128], 962 "rebeccapurple": [102, 51, 153], 963 "red": [255, 0, 0], 964 "rosybrown": [188, 143, 143], 965 "royalblue": [65, 105, 225], 966 "saddlebrown": [139, 69, 19], 967 "salmon": [250, 128, 114], 968 "sandybrown": [244, 164, 96], 969 "seagreen": [46, 139, 87], 970 "seashell": [255, 245, 238], 971 "sienna": [160, 82, 45], 972 "silver": [192, 192, 192], 973 "skyblue": [135, 206, 235], 974 "slateblue": [106, 90, 205], 975 "slategray": [112, 128, 144], 976 "slategrey": [112, 128, 144], 977 "snow": [255, 250, 250], 978 "springgreen": [0, 255, 127], 979 "steelblue": [70, 130, 180], 980 "tan": [210, 180, 140], 981 "teal": [0, 128, 128], 982 "thistle": [216, 191, 216], 983 "tomato": [255, 99, 71], 984 "turquoise": [64, 224, 208], 985 "violet": [238, 130, 238], 986 "wheat": [245, 222, 179], 987 "white": [255, 255, 255], 988 "whitesmoke": [245, 245, 245], 989 "yellow": [255, 255, 0], 990 "yellowgreen": [154, 205, 50] 991 }; 992 993 /* MIT license */ 994 995 996 var colorString = { 997 getRgba: getRgba, 998 getHsla: getHsla, 999 getRgb: getRgb, 1000 getHsl: getHsl, 1001 getHwb: getHwb, 1002 getAlpha: getAlpha, 1003 1004 hexString: hexString, 1005 rgbString: rgbString, 1006 rgbaString: rgbaString, 1007 percentString: percentString, 1008 percentaString: percentaString, 1009 hslString: hslString, 1010 hslaString: hslaString, 1011 hwbString: hwbString, 1012 keyword: keyword 1013 }; 1014 1015 function getRgba(string) { 1016 if (!string) { 1017 return; 1018 } 1019 var abbr = /^#([a-fA-F0-9]{3,4})$/i, 1020 hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i, 1021 rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, 1022 per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, 1023 keyword = /(\w+)/; 1024 1025 var rgb = [0, 0, 0], 1026 a = 1, 1027 match = string.match(abbr), 1028 hexAlpha = ""; 1029 if (match) { 1030 match = match[1]; 1031 hexAlpha = match[3]; 1032 for (var i = 0; i < rgb.length; i++) { 1033 rgb[i] = parseInt(match[i] + match[i], 16); 1034 } 1035 if (hexAlpha) { 1036 a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100; 1037 } 1038 } 1039 else if (match = string.match(hex)) { 1040 hexAlpha = match[2]; 1041 match = match[1]; 1042 for (var i = 0; i < rgb.length; i++) { 1043 rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); 1044 } 1045 if (hexAlpha) { 1046 a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100; 1047 } 1048 } 1049 else if (match = string.match(rgba)) { 1050 for (var i = 0; i < rgb.length; i++) { 1051 rgb[i] = parseInt(match[i + 1]); 1052 } 1053 a = parseFloat(match[4]); 1054 } 1055 else if (match = string.match(per)) { 1056 for (var i = 0; i < rgb.length; i++) { 1057 rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); 1058 } 1059 a = parseFloat(match[4]); 1060 } 1061 else if (match = string.match(keyword)) { 1062 if (match[1] == "transparent") { 1063 return [0, 0, 0, 0]; 1064 } 1065 rgb = colorName[match[1]]; 1066 if (!rgb) { 1067 return; 1068 } 1069 } 1070 1071 for (var i = 0; i < rgb.length; i++) { 1072 rgb[i] = scale(rgb[i], 0, 255); 1073 } 1074 if (!a && a != 0) { 1075 a = 1; 1076 } 1077 else { 1078 a = scale(a, 0, 1); 1079 } 1080 rgb[3] = a; 1081 return rgb; 1082 } 1083 1084 function getHsla(string) { 1085 if (!string) { 1086 return; 1087 } 1088 var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; 1089 var match = string.match(hsl); 1090 if (match) { 1091 var alpha = parseFloat(match[4]); 1092 var h = scale(parseInt(match[1]), 0, 360), 1093 s = scale(parseFloat(match[2]), 0, 100), 1094 l = scale(parseFloat(match[3]), 0, 100), 1095 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); 1096 return [h, s, l, a]; 1097 } 1098 } 1099 1100 function getHwb(string) { 1101 if (!string) { 1102 return; 1103 } 1104 var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; 1105 var match = string.match(hwb); 1106 if (match) { 1107 var alpha = parseFloat(match[4]); 1108 var h = scale(parseInt(match[1]), 0, 360), 1109 w = scale(parseFloat(match[2]), 0, 100), 1110 b = scale(parseFloat(match[3]), 0, 100), 1111 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); 1112 return [h, w, b, a]; 1113 } 1114 } 1115 1116 function getRgb(string) { 1117 var rgba = getRgba(string); 1118 return rgba && rgba.slice(0, 3); 1119 } 1120 1121 function getHsl(string) { 1122 var hsla = getHsla(string); 1123 return hsla && hsla.slice(0, 3); 1124 } 1125 1126 function getAlpha(string) { 1127 var vals = getRgba(string); 1128 if (vals) { 1129 return vals[3]; 1130 } 1131 else if (vals = getHsla(string)) { 1132 return vals[3]; 1133 } 1134 else if (vals = getHwb(string)) { 1135 return vals[3]; 1136 } 1137 } 1138 1139// generators 1140 function hexString(rgba, a) { 1141 var a = (a !== undefined && rgba.length === 3) ? a : rgba[3]; 1142 return "#" + hexDouble(rgba[0]) 1143 + hexDouble(rgba[1]) 1144 + hexDouble(rgba[2]) 1145 + ( 1146 (a >= 0 && a < 1) 1147 ? hexDouble(Math.round(a * 255)) 1148 : "" 1149 ); 1150 } 1151 1152 function rgbString(rgba, alpha) { 1153 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { 1154 return rgbaString(rgba, alpha); 1155 } 1156 return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; 1157 } 1158 1159 function rgbaString(rgba, alpha) { 1160 if (alpha === undefined) { 1161 alpha = (rgba[3] !== undefined ? rgba[3] : 1); 1162 } 1163 return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] 1164 + ", " + alpha + ")"; 1165 } 1166 1167 function percentString(rgba, alpha) { 1168 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { 1169 return percentaString(rgba, alpha); 1170 } 1171 var r = Math.round(rgba[0] / 255 * 100), 1172 g = Math.round(rgba[1] / 255 * 100), 1173 b = Math.round(rgba[2] / 255 * 100); 1174 1175 return "rgb(" + r + "%, " + g + "%, " + b + "%)"; 1176 } 1177 1178 function percentaString(rgba, alpha) { 1179 var r = Math.round(rgba[0] / 255 * 100), 1180 g = Math.round(rgba[1] / 255 * 100), 1181 b = Math.round(rgba[2] / 255 * 100); 1182 return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; 1183 } 1184 1185 function hslString(hsla, alpha) { 1186 if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { 1187 return hslaString(hsla, alpha); 1188 } 1189 return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; 1190 } 1191 1192 function hslaString(hsla, alpha) { 1193 if (alpha === undefined) { 1194 alpha = (hsla[3] !== undefined ? hsla[3] : 1); 1195 } 1196 return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " 1197 + alpha + ")"; 1198 } 1199 1200// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax 1201// (hwb have alpha optional & 1 is default value) 1202 function hwbString(hwb, alpha) { 1203 if (alpha === undefined) { 1204 alpha = (hwb[3] !== undefined ? hwb[3] : 1); 1205 } 1206 return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" 1207 + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; 1208 } 1209 1210 function keyword(rgb) { 1211 return reverseNames[rgb.slice(0, 3)]; 1212 } 1213 1214// helpers 1215 function scale(num, min, max) { 1216 return Math.min(Math.max(min, num), max); 1217 } 1218 1219 function hexDouble(num) { 1220 var str = num.toString(16).toUpperCase(); 1221 return (str.length < 2) ? "0" + str : str; 1222 } 1223 1224 1225//create a list of reverse color names 1226 var reverseNames = {}; 1227 for (var name in colorName) { 1228 reverseNames[colorName[name]] = name; 1229 } 1230 1231 /* MIT license */ 1232 1233 1234 var Color = function (obj) { 1235 if (obj instanceof Color) { 1236 return obj; 1237 } 1238 if (!(this instanceof Color)) { 1239 return new Color(obj); 1240 } 1241 1242 this.valid = false; 1243 this.values = { 1244 rgb: [0, 0, 0], 1245 hsl: [0, 0, 0], 1246 hsv: [0, 0, 0], 1247 hwb: [0, 0, 0], 1248 cmyk: [0, 0, 0, 0], 1249 alpha: 1 1250 }; 1251 1252 // parse Color() argument 1253 var vals; 1254 if (typeof obj === 'string') { 1255 vals = colorString.getRgba(obj); 1256 if (vals) { 1257 this.setValues('rgb', vals); 1258 } else if (vals = colorString.getHsla(obj)) { 1259 this.setValues('hsl', vals); 1260 } else if (vals = colorString.getHwb(obj)) { 1261 this.setValues('hwb', vals); 1262 } 1263 } else if (typeof obj === 'object') { 1264 vals = obj; 1265 if (vals.r !== undefined || vals.red !== undefined) { 1266 this.setValues('rgb', vals); 1267 } else if (vals.l !== undefined || vals.lightness !== undefined) { 1268 this.setValues('hsl', vals); 1269 } else if (vals.v !== undefined || vals.value !== undefined) { 1270 this.setValues('hsv', vals); 1271 } else if (vals.w !== undefined || vals.whiteness !== undefined) { 1272 this.setValues('hwb', vals); 1273 } else if (vals.c !== undefined || vals.cyan !== undefined) { 1274 this.setValues('cmyk', vals); 1275 } 1276 } 1277 }; 1278 1279 Color.prototype = { 1280 isValid: function () { 1281 return this.valid; 1282 }, 1283 rgb: function () { 1284 return this.setSpace('rgb', arguments); 1285 }, 1286 hsl: function () { 1287 return this.setSpace('hsl', arguments); 1288 }, 1289 hsv: function () { 1290 return this.setSpace('hsv', arguments); 1291 }, 1292 hwb: function () { 1293 return this.setSpace('hwb', arguments); 1294 }, 1295 cmyk: function () { 1296 return this.setSpace('cmyk', arguments); 1297 }, 1298 1299 rgbArray: function () { 1300 return this.values.rgb; 1301 }, 1302 hslArray: function () { 1303 return this.values.hsl; 1304 }, 1305 hsvArray: function () { 1306 return this.values.hsv; 1307 }, 1308 hwbArray: function () { 1309 var values = this.values; 1310 if (values.alpha !== 1) { 1311 return values.hwb.concat([values.alpha]); 1312 } 1313 return values.hwb; 1314 }, 1315 cmykArray: function () { 1316 return this.values.cmyk; 1317 }, 1318 rgbaArray: function () { 1319 var values = this.values; 1320 return values.rgb.concat([values.alpha]); 1321 }, 1322 hslaArray: function () { 1323 var values = this.values; 1324 return values.hsl.concat([values.alpha]); 1325 }, 1326 alpha: function (val) { 1327 if (val === undefined) { 1328 return this.values.alpha; 1329 } 1330 this.setValues('alpha', val); 1331 return this; 1332 }, 1333 1334 red: function (val) { 1335 return this.setChannel('rgb', 0, val); 1336 }, 1337 green: function (val) { 1338 return this.setChannel('rgb', 1, val); 1339 }, 1340 blue: function (val) { 1341 return this.setChannel('rgb', 2, val); 1342 }, 1343 hue: function (val) { 1344 if (val) { 1345 val %= 360; 1346 val = val < 0 ? 360 + val : val; 1347 } 1348 return this.setChannel('hsl', 0, val); 1349 }, 1350 saturation: function (val) { 1351 return this.setChannel('hsl', 1, val); 1352 }, 1353 lightness: function (val) { 1354 return this.setChannel('hsl', 2, val); 1355 }, 1356 saturationv: function (val) { 1357 return this.setChannel('hsv', 1, val); 1358 }, 1359 whiteness: function (val) { 1360 return this.setChannel('hwb', 1, val); 1361 }, 1362 blackness: function (val) { 1363 return this.setChannel('hwb', 2, val); 1364 }, 1365 value: function (val) { 1366 return this.setChannel('hsv', 2, val); 1367 }, 1368 cyan: function (val) { 1369 return this.setChannel('cmyk', 0, val); 1370 }, 1371 magenta: function (val) { 1372 return this.setChannel('cmyk', 1, val); 1373 }, 1374 yellow: function (val) { 1375 return this.setChannel('cmyk', 2, val); 1376 }, 1377 black: function (val) { 1378 return this.setChannel('cmyk', 3, val); 1379 }, 1380 1381 hexString: function () { 1382 return colorString.hexString(this.values.rgb); 1383 }, 1384 rgbString: function () { 1385 return colorString.rgbString(this.values.rgb, this.values.alpha); 1386 }, 1387 rgbaString: function () { 1388 return colorString.rgbaString(this.values.rgb, this.values.alpha); 1389 }, 1390 percentString: function () { 1391 return colorString.percentString(this.values.rgb, this.values.alpha); 1392 }, 1393 hslString: function () { 1394 return colorString.hslString(this.values.hsl, this.values.alpha); 1395 }, 1396 hslaString: function () { 1397 return colorString.hslaString(this.values.hsl, this.values.alpha); 1398 }, 1399 hwbString: function () { 1400 return colorString.hwbString(this.values.hwb, this.values.alpha); 1401 }, 1402 keyword: function () { 1403 return colorString.keyword(this.values.rgb, this.values.alpha); 1404 }, 1405 1406 rgbNumber: function () { 1407 var rgb = this.values.rgb; 1408 return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; 1409 }, 1410 1411 luminosity: function () { 1412 // http://www.w3.org/TR/WCAG20/#relativeluminancedef 1413 var rgb = this.values.rgb; 1414 var lum = []; 1415 for (var i = 0; i < rgb.length; i++) { 1416 var chan = rgb[i] / 255; 1417 lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); 1418 } 1419 return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; 1420 }, 1421 1422 contrast: function (color2) { 1423 // http://www.w3.org/TR/WCAG20/#contrast-ratiodef 1424 var lum1 = this.luminosity(); 1425 var lum2 = color2.luminosity(); 1426 if (lum1 > lum2) { 1427 return (lum1 + 0.05) / (lum2 + 0.05); 1428 } 1429 return (lum2 + 0.05) / (lum1 + 0.05); 1430 }, 1431 1432 level: function (color2) { 1433 var contrastRatio = this.contrast(color2); 1434 if (contrastRatio >= 7.1) { 1435 return 'AAA'; 1436 } 1437 1438 return (contrastRatio >= 4.5) ? 'AA' : ''; 1439 }, 1440 1441 dark: function () { 1442 // YIQ equation from http://24ways.org/2010/calculating-color-contrast 1443 var rgb = this.values.rgb; 1444 var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; 1445 return yiq < 128; 1446 }, 1447 1448 light: function () { 1449 return !this.dark(); 1450 }, 1451 1452 negate: function () { 1453 var rgb = []; 1454 for (var i = 0; i < 3; i++) { 1455 rgb[i] = 255 - this.values.rgb[i]; 1456 } 1457 this.setValues('rgb', rgb); 1458 return this; 1459 }, 1460 1461 lighten: function (ratio) { 1462 var hsl = this.values.hsl; 1463 hsl[2] += hsl[2] * ratio; 1464 this.setValues('hsl', hsl); 1465 return this; 1466 }, 1467 1468 darken: function (ratio) { 1469 var hsl = this.values.hsl; 1470 hsl[2] -= hsl[2] * ratio; 1471 this.setValues('hsl', hsl); 1472 return this; 1473 }, 1474 1475 saturate: function (ratio) { 1476 var hsl = this.values.hsl; 1477 hsl[1] += hsl[1] * ratio; 1478 this.setValues('hsl', hsl); 1479 return this; 1480 }, 1481 1482 desaturate: function (ratio) { 1483 var hsl = this.values.hsl; 1484 hsl[1] -= hsl[1] * ratio; 1485 this.setValues('hsl', hsl); 1486 return this; 1487 }, 1488 1489 whiten: function (ratio) { 1490 var hwb = this.values.hwb; 1491 hwb[1] += hwb[1] * ratio; 1492 this.setValues('hwb', hwb); 1493 return this; 1494 }, 1495 1496 blacken: function (ratio) { 1497 var hwb = this.values.hwb; 1498 hwb[2] += hwb[2] * ratio; 1499 this.setValues('hwb', hwb); 1500 return this; 1501 }, 1502 1503 greyscale: function () { 1504 var rgb = this.values.rgb; 1505 // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale 1506 var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; 1507 this.setValues('rgb', [val, val, val]); 1508 return this; 1509 }, 1510 1511 clearer: function (ratio) { 1512 var alpha = this.values.alpha; 1513 this.setValues('alpha', alpha - (alpha * ratio)); 1514 return this; 1515 }, 1516 1517 opaquer: function (ratio) { 1518 var alpha = this.values.alpha; 1519 this.setValues('alpha', alpha + (alpha * ratio)); 1520 return this; 1521 }, 1522 1523 rotate: function (degrees) { 1524 var hsl = this.values.hsl; 1525 var hue = (hsl[0] + degrees) % 360; 1526 hsl[0] = hue < 0 ? 360 + hue : hue; 1527 this.setValues('hsl', hsl); 1528 return this; 1529 }, 1530 1531 /** 1532 * Ported from sass implementation in C 1533 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 1534 */ 1535 mix: function (mixinColor, weight) { 1536 var color1 = this; 1537 var color2 = mixinColor; 1538 var p = weight === undefined ? 0.5 : weight; 1539 1540 var w = 2 * p - 1; 1541 var a = color1.alpha() - color2.alpha(); 1542 1543 var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; 1544 var w2 = 1 - w1; 1545 1546 return this 1547 .rgb( 1548 w1 * color1.red() + w2 * color2.red(), 1549 w1 * color1.green() + w2 * color2.green(), 1550 w1 * color1.blue() + w2 * color2.blue() 1551 ) 1552 .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); 1553 }, 1554 1555 toJSON: function () { 1556 return this.rgb(); 1557 }, 1558 1559 clone: function () { 1560 // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, 1561 // making the final build way to big to embed in Chart.js. So let's do it manually, 1562 // assuming that values to clone are 1 dimension arrays containing only numbers, 1563 // except 'alpha' which is a number. 1564 var result = new Color(); 1565 var source = this.values; 1566 var target = result.values; 1567 var value, type; 1568 1569 for (var prop in source) { 1570 if (source.hasOwnProperty(prop)) { 1571 value = source[prop]; 1572 type = ({}).toString.call(value); 1573 if (type === '[object Array]') { 1574 target[prop] = value.slice(0); 1575 } else if (type === '[object Number]') { 1576 target[prop] = value; 1577 } else { 1578 console.error('unexpected color value:', value); 1579 } 1580 } 1581 } 1582 1583 return result; 1584 } 1585 }; 1586 1587 Color.prototype.spaces = { 1588 rgb: ['red', 'green', 'blue'], 1589 hsl: ['hue', 'saturation', 'lightness'], 1590 hsv: ['hue', 'saturation', 'value'], 1591 hwb: ['hue', 'whiteness', 'blackness'], 1592 cmyk: ['cyan', 'magenta', 'yellow', 'black'] 1593 }; 1594 1595 Color.prototype.maxes = { 1596 rgb: [255, 255, 255], 1597 hsl: [360, 100, 100], 1598 hsv: [360, 100, 100], 1599 hwb: [360, 100, 100], 1600 cmyk: [100, 100, 100, 100] 1601 }; 1602 1603 Color.prototype.getValues = function (space) { 1604 var values = this.values; 1605 var vals = {}; 1606 1607 for (var i = 0; i < space.length; i++) { 1608 vals[space.charAt(i)] = values[space][i]; 1609 } 1610 1611 if (values.alpha !== 1) { 1612 vals.a = values.alpha; 1613 } 1614 1615 // {r: 255, g: 255, b: 255, a: 0.4} 1616 return vals; 1617 }; 1618 1619 Color.prototype.setValues = function (space, vals) { 1620 var values = this.values; 1621 var spaces = this.spaces; 1622 var maxes = this.maxes; 1623 var alpha = 1; 1624 var i; 1625 1626 this.valid = true; 1627 1628 if (space === 'alpha') { 1629 alpha = vals; 1630 } else if (vals.length) { 1631 // [10, 10, 10] 1632 values[space] = vals.slice(0, space.length); 1633 alpha = vals[space.length]; 1634 } else if (vals[space.charAt(0)] !== undefined) { 1635 // {r: 10, g: 10, b: 10} 1636 for (i = 0; i < space.length; i++) { 1637 values[space][i] = vals[space.charAt(i)]; 1638 } 1639 1640 alpha = vals.a; 1641 } else if (vals[spaces[space][0]] !== undefined) { 1642 // {red: 10, green: 10, blue: 10} 1643 var chans = spaces[space]; 1644 1645 for (i = 0; i < space.length; i++) { 1646 values[space][i] = vals[chans[i]]; 1647 } 1648 1649 alpha = vals.alpha; 1650 } 1651 1652 values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); 1653 1654 if (space === 'alpha') { 1655 return false; 1656 } 1657 1658 var capped; 1659 1660 // cap values of the space prior converting all values 1661 for (i = 0; i < space.length; i++) { 1662 capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); 1663 values[space][i] = Math.round(capped); 1664 } 1665 1666 // convert to all the other color spaces 1667 for (var sname in spaces) { 1668 if (sname !== space) { 1669 values[sname] = colorConvert[space][sname](values[space]); 1670 } 1671 } 1672 1673 return true; 1674 }; 1675 1676 Color.prototype.setSpace = function (space, args) { 1677 var vals = args[0]; 1678 1679 if (vals === undefined) { 1680 // color.rgb() 1681 return this.getValues(space); 1682 } 1683 1684 // color.rgb(10, 10, 10) 1685 if (typeof vals === 'number') { 1686 vals = Array.prototype.slice.call(args); 1687 } 1688 1689 this.setValues(space, vals); 1690 return this; 1691 }; 1692 1693 Color.prototype.setChannel = function (space, index, val) { 1694 var svalues = this.values[space]; 1695 if (val === undefined) { 1696 // color.red() 1697 return svalues[index]; 1698 } else if (val === svalues[index]) { 1699 // color.red(color.red()) 1700 return this; 1701 } 1702 1703 // color.red(100) 1704 svalues[index] = val; 1705 this.setValues(space, svalues); 1706 1707 return this; 1708 }; 1709 1710 if (typeof window !== 'undefined') { 1711 window.Chart = window.Chart || {}; 1712 window.Chart.Color = Color; 1713 1714 if (typeof window.Color === 'undefined') { 1715 // maintain backward compatibility ONLY if no other Color lib has been defined 1716 window.Color = Color; 1717 } 1718 } 1719 1720 var chartjsColor = Color; 1721 1722 /** 1723 * @namespace Chart.helpers 1724 */ 1725 var helpers = { 1726 /** 1727 * An empty function that can be used, for example, for optional callback. 1728 */ 1729 noop: function () { 1730 }, 1731 1732 /** 1733 * Returns a unique id, sequentially generated from a global variable. 1734 * @returns {number} 1735 * @function 1736 */ 1737 uid: (function () { 1738 var id = 0; 1739 return function () { 1740 return id++; 1741 }; 1742 }()), 1743 1744 /** 1745 * Returns true if `value` is neither null nor undefined, else returns false. 1746 * @param {*} value - The value to test. 1747 * @returns {boolean} 1748 * @since 2.7.0 1749 */ 1750 isNullOrUndef: function (value) { 1751 return value === null || typeof value === 'undefined'; 1752 }, 1753 1754 /** 1755 * Returns true if `value` is an array (including typed arrays), else returns false. 1756 * @param {*} value - The value to test. 1757 * @returns {boolean} 1758 * @function 1759 */ 1760 isArray: function (value) { 1761 if (Array.isArray && Array.isArray(value)) { 1762 return true; 1763 } 1764 var type = Object.prototype.toString.call(value); 1765 if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { 1766 return true; 1767 } 1768 return false; 1769 }, 1770 1771 /** 1772 * Returns true if `value` is an object (excluding null), else returns false. 1773 * @param {*} value - The value to test. 1774 * @returns {boolean} 1775 * @since 2.7.0 1776 */ 1777 isObject: function (value) { 1778 return value !== null && Object.prototype.toString.call(value) === '[object Object]'; 1779 }, 1780 1781 /** 1782 * Returns true if `value` is a finite number, else returns false 1783 * @param {*} value - The value to test. 1784 * @returns {boolean} 1785 */ 1786 isFinite: function (value) { 1787 return (typeof value === 'number' || value instanceof Number) && isFinite(value); 1788 }, 1789 1790 /** 1791 * Returns `value` if defined, else returns `defaultValue`. 1792 * @param {*} value - The value to return if defined. 1793 * @param {*} defaultValue - The value to return if `value` is undefined. 1794 * @returns {*} 1795 */ 1796 valueOrDefault: function (value, defaultValue) { 1797 return typeof value === 'undefined' ? defaultValue : value; 1798 }, 1799 1800 /** 1801 * Returns value at the given `index` in array if defined, else returns `defaultValue`. 1802 * @param {Array} value - The array to lookup for value at `index`. 1803 * @param {number} index - The index in `value` to lookup for value. 1804 * @param {*} defaultValue - The value to return if `value[index]` is undefined. 1805 * @returns {*} 1806 */ 1807 valueAtIndexOrDefault: function (value, index, defaultValue) { 1808 return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); 1809 }, 1810 1811 /** 1812 * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the 1813 * value returned by `fn`. If `fn` is not a function, this method returns undefined. 1814 * @param {function} fn - The function to call. 1815 * @param {Array|undefined|null} args - The arguments with which `fn` should be called. 1816 * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. 1817 * @returns {*} 1818 */ 1819 callback: function (fn, args, thisArg) { 1820 if (fn && typeof fn.call === 'function') { 1821 return fn.apply(thisArg, args); 1822 } 1823 }, 1824 1825 /** 1826 * Note(SB) for performance sake, this method should only be used when loopable type 1827 * is unknown or in none intensive code (not called often and small loopable). Else 1828 * it's preferable to use a regular for() loop and save extra function calls. 1829 * @param {object|Array} loopable - The object or array to be iterated. 1830 * @param {function} fn - The function to call for each item. 1831 * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. 1832 * @param {boolean} [reverse] - If true, iterates backward on the loopable. 1833 */ 1834 each: function (loopable, fn, thisArg, reverse) { 1835 var i, len, keys; 1836 if (helpers.isArray(loopable)) { 1837 len = loopable.length; 1838 if (reverse) { 1839 for (i = len - 1; i >= 0; i--) { 1840 fn.call(thisArg, loopable[i], i); 1841 } 1842 } else { 1843 for (i = 0; i < len; i++) { 1844 fn.call(thisArg, loopable[i], i); 1845 } 1846 } 1847 } else if (helpers.isObject(loopable)) { 1848 keys = Object.keys(loopable); 1849 len = keys.length; 1850 for (i = 0; i < len; i++) { 1851 fn.call(thisArg, loopable[keys[i]], keys[i]); 1852 } 1853 } 1854 }, 1855 1856 /** 1857 * Returns true if the `a0` and `a1` arrays have the same content, else returns false. 1858 * @see https://stackoverflow.com/a/14853974 1859 * @param {Array} a0 - The array to compare 1860 * @param {Array} a1 - The array to compare 1861 * @returns {boolean} 1862 */ 1863 arrayEquals: function (a0, a1) { 1864 var i, ilen, v0, v1; 1865 1866 if (!a0 || !a1 || a0.length !== a1.length) { 1867 return false; 1868 } 1869 1870 for (i = 0, ilen = a0.length; i < ilen; ++i) { 1871 v0 = a0[i]; 1872 v1 = a1[i]; 1873 1874 if (v0 instanceof Array && v1 instanceof Array) { 1875 if (!helpers.arrayEquals(v0, v1)) { 1876 return false; 1877 } 1878 } else if (v0 !== v1) { 1879 // NOTE: two different object instances will never be equal: {x:20} != {x:20} 1880 return false; 1881 } 1882 } 1883 1884 return true; 1885 }, 1886 1887 /** 1888 * Returns a deep copy of `source` without keeping references on objects and arrays. 1889 * @param {*} source - The value to clone. 1890 * @returns {*} 1891 */ 1892 clone: function (source) { 1893 if (helpers.isArray(source)) { 1894 return source.map(helpers.clone); 1895 } 1896 1897 if (helpers.isObject(source)) { 1898 var target = {}; 1899 var keys = Object.keys(source); 1900 var klen = keys.length; 1901 var k = 0; 1902 1903 for (; k < klen; ++k) { 1904 target[keys[k]] = helpers.clone(source[keys[k]]); 1905 } 1906 1907 return target; 1908 } 1909 1910 return source; 1911 }, 1912 1913 /** 1914 * The default merger when Chart.helpers.merge is called without merger option. 1915 * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback. 1916 * @private 1917 */ 1918 _merger: function (key, target, source, options) { 1919 var tval = target[key]; 1920 var sval = source[key]; 1921 1922 if (helpers.isObject(tval) && helpers.isObject(sval)) { 1923 helpers.merge(tval, sval, options); 1924 } else { 1925 target[key] = helpers.clone(sval); 1926 } 1927 }, 1928 1929 /** 1930 * Merges source[key] in target[key] only if target[key] is undefined. 1931 * @private 1932 */ 1933 _mergerIf: function (key, target, source) { 1934 var tval = target[key]; 1935 var sval = source[key]; 1936 1937 if (helpers.isObject(tval) && helpers.isObject(sval)) { 1938 helpers.mergeIf(tval, sval); 1939 } else if (!target.hasOwnProperty(key)) { 1940 target[key] = helpers.clone(sval); 1941 } 1942 }, 1943 1944 /** 1945 * Recursively deep copies `source` properties into `target` with the given `options`. 1946 * IMPORTANT: `target` is not cloned and will be updated with `source` properties. 1947 * @param {object} target - The target object in which all sources are merged into. 1948 * @param {object|object[]} source - Object(s) to merge into `target`. 1949 * @param {object} [options] - Merging options: 1950 * @param {function} [options.merger] - The merge method (key, target, source, options) 1951 * @returns {object} The `target` object. 1952 */ 1953 merge: function (target, source, options) { 1954 var sources = helpers.isArray(source) ? source : [source]; 1955 var ilen = sources.length; 1956 var merge, i, keys, klen, k; 1957 1958 if (!helpers.isObject(target)) { 1959 return target; 1960 } 1961 1962 options = options || {}; 1963 merge = options.merger || helpers._merger; 1964 1965 for (i = 0; i < ilen; ++i) { 1966 source = sources[i]; 1967 if (!helpers.isObject(source)) { 1968 continue; 1969 } 1970 1971 keys = Object.keys(source); 1972 for (k = 0, klen = keys.length; k < klen; ++k) { 1973 merge(keys[k], target, source, options); 1974 } 1975 } 1976 1977 return target; 1978 }, 1979 1980 /** 1981 * Recursively deep copies `source` properties into `target` *only* if not defined in target. 1982 * IMPORTANT: `target` is not cloned and will be updated with `source` properties. 1983 * @param {object} target - The target object in which all sources are merged into. 1984 * @param {object|object[]} source - Object(s) to merge into `target`. 1985 * @returns {object} The `target` object. 1986 */ 1987 mergeIf: function (target, source) { 1988 return helpers.merge(target, source, {merger: helpers._mergerIf}); 1989 }, 1990 1991 /** 1992 * Applies the contents of two or more objects together into the first object. 1993 * @param {object} target - The target object in which all objects are merged into. 1994 * @param {object} arg1 - Object containing additional properties to merge in target. 1995 * @param {object} argN - Additional objects containing properties to merge in target. 1996 * @returns {object} The `target` object. 1997 */ 1998 extend: function (target) { 1999 var setFn = function (value, key) { 2000 target[key] = value; 2001 }; 2002 for (var i = 1, ilen = arguments.length; i < ilen; ++i) { 2003 helpers.each(arguments[i], setFn); 2004 } 2005 return target; 2006 }, 2007 2008 /** 2009 * Basic javascript inheritance based on the model created in Backbone.js 2010 */ 2011 inherits: function (extensions) { 2012 var me = this; 2013 var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function () { 2014 return me.apply(this, arguments); 2015 }; 2016 2017 var Surrogate = function () { 2018 this.constructor = ChartElement; 2019 }; 2020 2021 Surrogate.prototype = me.prototype; 2022 ChartElement.prototype = new Surrogate(); 2023 ChartElement.extend = helpers.inherits; 2024 2025 if (extensions) { 2026 helpers.extend(ChartElement.prototype, extensions); 2027 } 2028 2029 ChartElement.__super__ = me.prototype; 2030 return ChartElement; 2031 } 2032 }; 2033 2034 var helpers_core = helpers; 2035 2036// DEPRECATIONS 2037 2038 /** 2039 * Provided for backward compatibility, use Chart.helpers.callback instead. 2040 * @function Chart.helpers.callCallback 2041 * @deprecated since version 2.6.0 2042 * @todo remove at version 3 2043 * @private 2044 */ 2045 helpers.callCallback = helpers.callback; 2046 2047 /** 2048 * Provided for backward compatibility, use Array.prototype.indexOf instead. 2049 * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ 2050 * @function Chart.helpers.indexOf 2051 * @deprecated since version 2.7.0 2052 * @todo remove at version 3 2053 * @private 2054 */ 2055 helpers.indexOf = function (array, item, fromIndex) { 2056 return Array.prototype.indexOf.call(array, item, fromIndex); 2057 }; 2058 2059 /** 2060 * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. 2061 * @function Chart.helpers.getValueOrDefault 2062 * @deprecated since version 2.7.0 2063 * @todo remove at version 3 2064 * @private 2065 */ 2066 helpers.getValueOrDefault = helpers.valueOrDefault; 2067 2068 /** 2069 * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. 2070 * @function Chart.helpers.getValueAtIndexOrDefault 2071 * @deprecated since version 2.7.0 2072 * @todo remove at version 3 2073 * @private 2074 */ 2075 helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; 2076 2077 /** 2078 * Easing functions adapted from Robert Penner's easing equations. 2079 * @namespace Chart.helpers.easingEffects 2080 * @see http://www.robertpenner.com/easing/ 2081 */ 2082 var effects = { 2083 linear: function (t) { 2084 return t; 2085 }, 2086 2087 easeInQuad: function (t) { 2088 return t * t; 2089 }, 2090 2091 easeOutQuad: function (t) { 2092 return -t * (t - 2); 2093 }, 2094 2095 easeInOutQuad: function (t) { 2096 if ((t /= 0.5) < 1) { 2097 return 0.5 * t * t; 2098 } 2099 return -0.5 * ((--t) * (t - 2) - 1); 2100 }, 2101 2102 easeInCubic: function (t) { 2103 return t * t * t; 2104 }, 2105 2106 easeOutCubic: function (t) { 2107 return (t = t - 1) * t * t + 1; 2108 }, 2109 2110 easeInOutCubic: function (t) { 2111 if ((t /= 0.5) < 1) { 2112 return 0.5 * t * t * t; 2113 } 2114 return 0.5 * ((t -= 2) * t * t + 2); 2115 }, 2116 2117 easeInQuart: function (t) { 2118 return t * t * t * t; 2119 }, 2120 2121 easeOutQuart: function (t) { 2122 return -((t = t - 1) * t * t * t - 1); 2123 }, 2124 2125 easeInOutQuart: function (t) { 2126 if ((t /= 0.5) < 1) { 2127 return 0.5 * t * t * t * t; 2128 } 2129 return -0.5 * ((t -= 2) * t * t * t - 2); 2130 }, 2131 2132 easeInQuint: function (t) { 2133 return t * t * t * t * t; 2134 }, 2135 2136 easeOutQuint: function (t) { 2137 return (t = t - 1) * t * t * t * t + 1; 2138 }, 2139 2140 easeInOutQuint: function (t) { 2141 if ((t /= 0.5) < 1) { 2142 return 0.5 * t * t * t * t * t; 2143 } 2144 return 0.5 * ((t -= 2) * t * t * t * t + 2); 2145 }, 2146 2147 easeInSine: function (t) { 2148 return -Math.cos(t * (Math.PI / 2)) + 1; 2149 }, 2150 2151 easeOutSine: function (t) { 2152 return Math.sin(t * (Math.PI / 2)); 2153 }, 2154 2155 easeInOutSine: function (t) { 2156 return -0.5 * (Math.cos(Math.PI * t) - 1); 2157 }, 2158 2159 easeInExpo: function (t) { 2160 return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); 2161 }, 2162 2163 easeOutExpo: function (t) { 2164 return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; 2165 }, 2166 2167 easeInOutExpo: function (t) { 2168 if (t === 0) { 2169 return 0; 2170 } 2171 if (t === 1) { 2172 return 1; 2173 } 2174 if ((t /= 0.5) < 1) { 2175 return 0.5 * Math.pow(2, 10 * (t - 1)); 2176 } 2177 return 0.5 * (-Math.pow(2, -10 * --t) + 2); 2178 }, 2179 2180 easeInCirc: function (t) { 2181 if (t >= 1) { 2182 return t; 2183 } 2184 return -(Math.sqrt(1 - t * t) - 1); 2185 }, 2186 2187 easeOutCirc: function (t) { 2188 return Math.sqrt(1 - (t = t - 1) * t); 2189 }, 2190 2191 easeInOutCirc: function (t) { 2192 if ((t /= 0.5) < 1) { 2193 return -0.5 * (Math.sqrt(1 - t * t) - 1); 2194 } 2195 return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); 2196 }, 2197 2198 easeInElastic: function (t) { 2199 var s = 1.70158; 2200 var p = 0; 2201 var a = 1; 2202 if (t === 0) { 2203 return 0; 2204 } 2205 if (t === 1) { 2206 return 1; 2207 } 2208 if (!p) { 2209 p = 0.3; 2210 } 2211 if (a < 1) { 2212 a = 1; 2213 s = p / 4; 2214 } else { 2215 s = p / (2 * Math.PI) * Math.asin(1 / a); 2216 } 2217 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); 2218 }, 2219 2220 easeOutElastic: function (t) { 2221 var s = 1.70158; 2222 var p = 0; 2223 var a = 1; 2224 if (t === 0) { 2225 return 0; 2226 } 2227 if (t === 1) { 2228 return 1; 2229 } 2230 if (!p) { 2231 p = 0.3; 2232 } 2233 if (a < 1) { 2234 a = 1; 2235 s = p / 4; 2236 } else { 2237 s = p / (2 * Math.PI) * Math.asin(1 / a); 2238 } 2239 return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; 2240 }, 2241 2242 easeInOutElastic: function (t) { 2243 var s = 1.70158; 2244 var p = 0; 2245 var a = 1; 2246 if (t === 0) { 2247 return 0; 2248 } 2249 if ((t /= 0.5) === 2) { 2250 return 1; 2251 } 2252 if (!p) { 2253 p = 0.45; 2254 } 2255 if (a < 1) { 2256 a = 1; 2257 s = p / 4; 2258 } else { 2259 s = p / (2 * Math.PI) * Math.asin(1 / a); 2260 } 2261 if (t < 1) { 2262 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); 2263 } 2264 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; 2265 }, 2266 easeInBack: function (t) { 2267 var s = 1.70158; 2268 return t * t * ((s + 1) * t - s); 2269 }, 2270 2271 easeOutBack: function (t) { 2272 var s = 1.70158; 2273 return (t = t - 1) * t * ((s + 1) * t + s) + 1; 2274 }, 2275 2276 easeInOutBack: function (t) { 2277 var s = 1.70158; 2278 if ((t /= 0.5) < 1) { 2279 return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); 2280 } 2281 return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); 2282 }, 2283 2284 easeInBounce: function (t) { 2285 return 1 - effects.easeOutBounce(1 - t); 2286 }, 2287 2288 easeOutBounce: function (t) { 2289 if (t < (1 / 2.75)) { 2290 return 7.5625 * t * t; 2291 } 2292 if (t < (2 / 2.75)) { 2293 return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; 2294 } 2295 if (t < (2.5 / 2.75)) { 2296 return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; 2297 } 2298 return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; 2299 }, 2300 2301 easeInOutBounce: function (t) { 2302 if (t < 0.5) { 2303 return effects.easeInBounce(t * 2) * 0.5; 2304 } 2305 return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; 2306 } 2307 }; 2308 2309 var helpers_easing = { 2310 effects: effects 2311 }; 2312 2313// DEPRECATIONS 2314 2315 /** 2316 * Provided for backward compatibility, use Chart.helpers.easing.effects instead. 2317 * @function Chart.helpers.easingEffects 2318 * @deprecated since version 2.7.0 2319 * @todo remove at version 3 2320 * @private 2321 */ 2322 helpers_core.easingEffects = effects; 2323 2324 var PI = Math.PI; 2325 var RAD_PER_DEG = PI / 180; 2326 var DOUBLE_PI = PI * 2; 2327 var HALF_PI = PI / 2; 2328 var QUARTER_PI = PI / 4; 2329 var TWO_THIRDS_PI = PI * 2 / 3; 2330 2331 /** 2332 * @namespace Chart.helpers.canvas 2333 */ 2334 var exports$1 = { 2335 /** 2336 * Clears the entire canvas associated to the given `chart`. 2337 * @param {Chart} chart - The chart for which to clear the canvas. 2338 */ 2339 clear: function (chart) { 2340 chart.ctx.clearRect(0, 0, chart.width, chart.height); 2341 }, 2342 2343 /** 2344 * Creates a "path" for a rectangle with rounded corners at position (x, y) with a 2345 * given size (width, height) and the same `radius` for all corners. 2346 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. 2347 * @param {number} x - The x axis of the coordinate for the rectangle starting point. 2348 * @param {number} y - The y axis of the coordinate for the rectangle starting point. 2349 * @param {number} width - The rectangle's width. 2350 * @param {number} height - The rectangle's height. 2351 * @param {number} radius - The rounded amount (in pixels) for the four corners. 2352 * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? 2353 */ 2354 roundedRect: function (ctx, x, y, width, height, radius) { 2355 if (radius) { 2356 var r = Math.min(radius, height / 2, width / 2); 2357 var left = x + r; 2358 var top = y + r; 2359 var right = x + width - r; 2360 var bottom = y + height - r; 2361 2362 ctx.moveTo(x, top); 2363 if (left < right && top < bottom) { 2364 ctx.arc(left, top, r, -PI, -HALF_PI); 2365 ctx.arc(right, top, r, -HALF_PI, 0); 2366 ctx.arc(right, bottom, r, 0, HALF_PI); 2367 ctx.arc(left, bottom, r, HALF_PI, PI); 2368 } else if (left < right) { 2369 ctx.moveTo(left, y); 2370 ctx.arc(right, top, r, -HALF_PI, HALF_PI); 2371 ctx.arc(left, top, r, HALF_PI, PI + HALF_PI); 2372 } else if (top < bottom) { 2373 ctx.arc(left, top, r, -PI, 0); 2374 ctx.arc(left, bottom, r, 0, PI); 2375 } else { 2376 ctx.arc(left, top, r, -PI, PI); 2377 } 2378 ctx.closePath(); 2379 ctx.moveTo(x, y); 2380 } else { 2381 ctx.rect(x, y, width, height); 2382 } 2383 }, 2384 2385 drawPoint: function (ctx, style, radius, x, y, rotation) { 2386 var type, xOffset, yOffset, size, cornerRadius; 2387 var rad = (rotation || 0) * RAD_PER_DEG; 2388 2389 if (style && typeof style === 'object') { 2390 type = style.toString(); 2391 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { 2392 ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height); 2393 return; 2394 } 2395 } 2396 2397 if (isNaN(radius) || radius <= 0) { 2398 return; 2399 } 2400 2401 ctx.beginPath(); 2402 2403 switch (style) { 2404 // Default includes circle 2405 default: 2406 ctx.arc(x, y, radius, 0, DOUBLE_PI); 2407 ctx.closePath(); 2408 break; 2409 case 'triangle': 2410 ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); 2411 rad += TWO_THIRDS_PI; 2412 ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); 2413 rad += TWO_THIRDS_PI; 2414 ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); 2415 ctx.closePath(); 2416 break; 2417 case 'rectRounded': 2418 // NOTE: the rounded rect implementation changed to use `arc` instead of 2419 // `quadraticCurveTo` since it generates better results when rect is 2420 // almost a circle. 0.516 (instead of 0.5) produces results with visually 2421 // closer proportion to the previous impl and it is inscribed in the 2422 // circle with `radius`. For more details, see the following PRs: 2423 // https://github.com/chartjs/Chart.js/issues/5597 2424 // https://github.com/chartjs/Chart.js/issues/5858 2425 cornerRadius = radius * 0.516; 2426 size = radius - cornerRadius; 2427 xOffset = Math.cos(rad + QUARTER_PI) * size; 2428 yOffset = Math.sin(rad + QUARTER_PI) * size; 2429 ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); 2430 ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); 2431 ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); 2432 ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); 2433 ctx.closePath(); 2434 break; 2435 case 'rect': 2436 if (!rotation) { 2437 size = Math.SQRT1_2 * radius; 2438 ctx.rect(x - size, y - size, 2 * size, 2 * size); 2439 break; 2440 } 2441 rad += QUARTER_PI; 2442 /* falls through */ 2443 case 'rectRot': 2444 xOffset = Math.cos(rad) * radius; 2445 yOffset = Math.sin(rad) * radius; 2446 ctx.moveTo(x - xOffset, y - yOffset); 2447 ctx.lineTo(x + yOffset, y - xOffset); 2448 ctx.lineTo(x + xOffset, y + yOffset); 2449 ctx.lineTo(x - yOffset, y + xOffset); 2450 ctx.closePath(); 2451 break; 2452 case 'crossRot': 2453 rad += QUARTER_PI; 2454 /* falls through */ 2455 case 'cross': 2456 xOffset = Math.cos(rad) * radius; 2457 yOffset = Math.sin(rad) * radius; 2458 ctx.moveTo(x - xOffset, y - yOffset); 2459 ctx.lineTo(x + xOffset, y + yOffset); 2460 ctx.moveTo(x + yOffset, y - xOffset); 2461 ctx.lineTo(x - yOffset, y + xOffset); 2462 break; 2463 case 'star': 2464 xOffset = Math.cos(rad) * radius; 2465 yOffset = Math.sin(rad) * radius; 2466 ctx.moveTo(x - xOffset, y - yOffset); 2467 ctx.lineTo(x + xOffset, y + yOffset); 2468 ctx.moveTo(x + yOffset, y - xOffset); 2469 ctx.lineTo(x - yOffset, y + xOffset); 2470 rad += QUARTER_PI; 2471 xOffset = Math.cos(rad) * radius; 2472 yOffset = Math.sin(rad) * radius; 2473 ctx.moveTo(x - xOffset, y - yOffset); 2474 ctx.lineTo(x + xOffset, y + yOffset); 2475 ctx.moveTo(x + yOffset, y - xOffset); 2476 ctx.lineTo(x - yOffset, y + xOffset); 2477 break; 2478 case 'line': 2479 xOffset = Math.cos(rad) * radius; 2480 yOffset = Math.sin(rad) * radius; 2481 ctx.moveTo(x - xOffset, y - yOffset); 2482 ctx.lineTo(x + xOffset, y + yOffset); 2483 break; 2484 case 'dash': 2485 ctx.moveTo(x, y); 2486 ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); 2487 break; 2488 } 2489 2490 ctx.fill(); 2491 ctx.stroke(); 2492 }, 2493 2494 /** 2495 * Returns true if the point is inside the rectangle 2496 * @param {object} point - The point to test 2497 * @param {object} area - The rectangle 2498 * @returns {boolean} 2499 * @private 2500 */ 2501 _isPointInArea: function (point, area) { 2502 var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. 2503 2504 return point.x > area.left - epsilon && point.x < area.right + epsilon && 2505 point.y > area.top - epsilon && point.y < area.bottom + epsilon; 2506 }, 2507 2508 clipArea: function (ctx, area) { 2509 ctx.save(); 2510 ctx.beginPath(); 2511 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); 2512 ctx.clip(); 2513 }, 2514 2515 unclipArea: function (ctx) { 2516 ctx.restore(); 2517 }, 2518 2519 lineTo: function (ctx, previous, target, flip) { 2520 var stepped = target.steppedLine; 2521 if (stepped) { 2522 if (stepped === 'middle') { 2523 var midpoint = (previous.x + target.x) / 2.0; 2524 ctx.lineTo(midpoint, flip ? target.y : previous.y); 2525 ctx.lineTo(midpoint, flip ? previous.y : target.y); 2526 } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) { 2527 ctx.lineTo(previous.x, target.y); 2528 } else { 2529 ctx.lineTo(target.x, previous.y); 2530 } 2531 ctx.lineTo(target.x, target.y); 2532 return; 2533 } 2534 2535 if (!target.tension) { 2536 ctx.lineTo(target.x, target.y); 2537 return; 2538 } 2539 2540 ctx.bezierCurveTo( 2541 flip ? previous.controlPointPreviousX : previous.controlPointNextX, 2542 flip ? previous.controlPointPreviousY : previous.controlPointNextY, 2543 flip ? target.controlPointNextX : target.controlPointPreviousX, 2544 flip ? target.controlPointNextY : target.controlPointPreviousY, 2545 target.x, 2546 target.y); 2547 } 2548 }; 2549 2550 var helpers_canvas = exports$1; 2551 2552// DEPRECATIONS 2553 2554 /** 2555 * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. 2556 * @namespace Chart.helpers.clear 2557 * @deprecated since version 2.7.0 2558 * @todo remove at version 3 2559 * @private 2560 */ 2561 helpers_core.clear = exports$1.clear; 2562 2563 /** 2564 * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. 2565 * @namespace Chart.helpers.drawRoundedRectangle 2566 * @deprecated since version 2.7.0 2567 * @todo remove at version 3 2568 * @private 2569 */ 2570 helpers_core.drawRoundedRectangle = function (ctx) { 2571 ctx.beginPath(); 2572 exports$1.roundedRect.apply(exports$1, arguments); 2573 }; 2574 2575 var defaults = { 2576 /** 2577 * @private 2578 */ 2579 _set: function (scope, values) { 2580 return helpers_core.merge(this[scope] || (this[scope] = {}), values); 2581 } 2582 }; 2583 2584 defaults._set('global', { 2585 defaultColor: 'rgba(0,0,0,0.1)', 2586 defaultFontColor: '#666', 2587 defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", 2588 defaultFontSize: 12, 2589 defaultFontStyle: 'normal', 2590 defaultLineHeight: 1.2, 2591 showLines: true 2592 }); 2593 2594 var core_defaults = defaults; 2595 2596 var valueOrDefault = helpers_core.valueOrDefault; 2597 2598 /** 2599 * Converts the given font object into a CSS font string. 2600 * @param {object} font - A font object. 2601 * @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font 2602 * @private 2603 */ 2604 function toFontString(font) { 2605 if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) { 2606 return null; 2607 } 2608 2609 return (font.style ? font.style + ' ' : '') 2610 + (font.weight ? font.weight + ' ' : '') 2611 + font.size + 'px ' 2612 + font.family; 2613 } 2614 2615 /** 2616 * @alias Chart.helpers.options 2617 * @namespace 2618 */ 2619 var helpers_options = { 2620 /** 2621 * Converts the given line height `value` in pixels for a specific font `size`. 2622 * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). 2623 * @param {number} size - The font size (in pixels) used to resolve relative `value`. 2624 * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid). 2625 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height 2626 * @since 2.7.0 2627 */ 2628 toLineHeight: function (value, size) { 2629 var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); 2630 if (!matches || matches[1] === 'normal') { 2631 return size * 1.2; 2632 } 2633 2634 value = +matches[2]; 2635 2636 switch (matches[3]) { 2637 case 'px': 2638 return value; 2639 case '%': 2640 value /= 100; 2641 break; 2642 default: 2643 break; 2644 } 2645 2646 return size * value; 2647 }, 2648 2649 /** 2650 * Converts the given value into a padding object with pre-computed width/height. 2651 * @param {number|object} value - If a number, set the value to all TRBL component, 2652 * else, if and object, use defined properties and sets undefined ones to 0. 2653 * @returns {object} The padding values (top, right, bottom, left, width, height) 2654 * @since 2.7.0 2655 */ 2656 toPadding: function (value) { 2657 var t, r, b, l; 2658 2659 if (helpers_core.isObject(value)) { 2660 t = +value.top || 0; 2661 r = +value.right || 0; 2662 b = +value.bottom || 0; 2663 l = +value.left || 0; 2664 } else { 2665 t = r = b = l = +value || 0; 2666 } 2667 2668 return { 2669 top: t, 2670 right: r, 2671 bottom: b, 2672 left: l, 2673 height: t + b, 2674 width: l + r 2675 }; 2676 }, 2677 2678 /** 2679 * Parses font options and returns the font object. 2680 * @param {object} options - A object that contains font options to be parsed. 2681 * @return {object} The font object. 2682 * @todo Support font.* options and renamed to toFont(). 2683 * @private 2684 */ 2685 _parseFont: function (options) { 2686 var globalDefaults = core_defaults.global; 2687 var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); 2688 var font = { 2689 family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), 2690 lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), 2691 size: size, 2692 style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), 2693 weight: null, 2694 string: '' 2695 }; 2696 2697 font.string = toFontString(font); 2698 return font; 2699 }, 2700 2701 /** 2702 * Evaluates the given `inputs` sequentially and returns the first defined value. 2703 * @param {Array} inputs - An array of values, falling back to the last value. 2704 * @param {object} [context] - If defined and the current value is a function, the value 2705 * is called with `context` as first argument and the result becomes the new input. 2706 * @param {number} [index] - If defined and the current value is an array, the value 2707 * at `index` become the new input. 2708 * @since 2.7.0 2709 */ 2710 resolve: function (inputs, context, index) { 2711 var i, ilen, value; 2712 2713 for (i = 0, ilen = inputs.length; i < ilen; ++i) { 2714 value = inputs[i]; 2715 if (value === undefined) { 2716 continue; 2717 } 2718 if (context !== undefined && typeof value === 'function') { 2719 value = value(context); 2720 } 2721 if (index !== undefined && helpers_core.isArray(value)) { 2722 value = value[index]; 2723 } 2724 if (value !== undefined) { 2725 return value; 2726 } 2727 } 2728 } 2729 }; 2730 2731 var helpers$1 = helpers_core; 2732 var easing = helpers_easing; 2733 var canvas = helpers_canvas; 2734 var options = helpers_options; 2735 helpers$1.easing = easing; 2736 helpers$1.canvas = canvas; 2737 helpers$1.options = options; 2738 2739 function interpolate(start, view, model, ease) { 2740 var keys = Object.keys(model); 2741 var i, ilen, key, actual, origin, target, type, c0, c1; 2742 2743 for (i = 0, ilen = keys.length; i < ilen; ++i) { 2744 key = keys[i]; 2745 2746 target = model[key]; 2747 2748 // if a value is added to the model after pivot() has been called, the view 2749 // doesn't contain it, so let's initialize the view to the target value. 2750 if (!view.hasOwnProperty(key)) { 2751 view[key] = target; 2752 } 2753 2754 actual = view[key]; 2755 2756 if (actual === target || key[0] === '_') { 2757 continue; 2758 } 2759 2760 if (!start.hasOwnProperty(key)) { 2761 start[key] = actual; 2762 } 2763 2764 origin = start[key]; 2765 2766 type = typeof target; 2767 2768 if (type === typeof origin) { 2769 if (type === 'string') { 2770 c0 = chartjsColor(origin); 2771 if (c0.valid) { 2772 c1 = chartjsColor(target); 2773 if (c1.valid) { 2774 view[key] = c1.mix(c0, ease).rgbString(); 2775 continue; 2776 } 2777 } 2778 } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) { 2779 view[key] = origin + (target - origin) * ease; 2780 continue; 2781 } 2782 } 2783 2784 view[key] = target; 2785 } 2786 } 2787 2788 var Element = function (configuration) { 2789 helpers$1.extend(this, configuration); 2790 this.initialize.apply(this, arguments); 2791 }; 2792 2793 helpers$1.extend(Element.prototype, { 2794 2795 initialize: function () { 2796 this.hidden = false; 2797 }, 2798 2799 pivot: function () { 2800 var me = this; 2801 if (!me._view) { 2802 me._view = helpers$1.clone(me._model); 2803 } 2804 me._start = {}; 2805 return me; 2806 }, 2807 2808 transition: function (ease) { 2809 var me = this; 2810 var model = me._model; 2811 var start = me._start; 2812 var view = me._view; 2813 2814 // No animation -> No Transition 2815 if (!model || ease === 1) { 2816 me._view = model; 2817 me._start = null; 2818 return me; 2819 } 2820 2821 if (!view) { 2822 view = me._view = {}; 2823 } 2824 2825 if (!start) { 2826 start = me._start = {}; 2827 } 2828 2829 interpolate(start, view, model, ease); 2830 2831 return me; 2832 }, 2833 2834 tooltipPosition: function () { 2835 return { 2836 x: this._model.x, 2837 y: this._model.y 2838 }; 2839 }, 2840 2841 hasValue: function () { 2842 return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y); 2843 } 2844 }); 2845 2846 Element.extend = helpers$1.inherits; 2847 2848 var core_element = Element; 2849 2850 var exports$2 = core_element.extend({ 2851 chart: null, // the animation associated chart instance 2852 currentStep: 0, // the current animation step 2853 numSteps: 60, // default number of steps 2854 easing: '', // the easing to use for this animation 2855 render: null, // render function used by the animation service 2856 2857 onAnimationProgress: null, // user specified callback to fire on each step of the animation 2858 onAnimationComplete: null, // user specified callback to fire when the animation finishes 2859 }); 2860 2861 var core_animation = exports$2; 2862 2863// DEPRECATIONS 2864 2865 /** 2866 * Provided for backward compatibility, use Chart.Animation instead 2867 * @prop Chart.Animation#animationObject 2868 * @deprecated since version 2.6.0 2869 * @todo remove at version 3 2870 */ 2871 Object.defineProperty(exports$2.prototype, 'animationObject', { 2872 get: function () { 2873 return this; 2874 } 2875 }); 2876 2877 /** 2878 * Provided for backward compatibility, use Chart.Animation#chart instead 2879 * @prop Chart.Animation#chartInstance 2880 * @deprecated since version 2.6.0 2881 * @todo remove at version 3 2882 */ 2883 Object.defineProperty(exports$2.prototype, 'chartInstance', { 2884 get: function () { 2885 return this.chart; 2886 }, 2887 set: function (value) { 2888 this.chart = value; 2889 } 2890 }); 2891 2892 core_defaults._set('global', { 2893 animation: { 2894 duration: 1000, 2895 easing: 'easeOutQuart', 2896 onProgress: helpers$1.noop, 2897 onComplete: helpers$1.noop 2898 } 2899 }); 2900 2901 var core_animations = { 2902 animations: [], 2903 request: null, 2904 2905 /** 2906 * @param {Chart} chart - The chart to animate. 2907 * @param {Chart.Animation} animation - The animation that we will animate. 2908 * @param {number} duration - The animation duration in ms. 2909 * @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions 2910 */ 2911 addAnimation: function (chart, animation, duration, lazy) { 2912 var animations = this.animations; 2913 var i, ilen; 2914 2915 animation.chart = chart; 2916 animation.startTime = Date.now(); 2917 animation.duration = duration; 2918 2919 if (!lazy) { 2920 chart.animating = true; 2921 } 2922 2923 for (i = 0, ilen = animations.length; i < ilen; ++i) { 2924 if (animations[i].chart === chart) { 2925 animations[i] = animation; 2926 return; 2927 } 2928 } 2929 2930 animations.push(animation); 2931 2932 // If there are no animations queued, manually kickstart a digest, for lack of a better word 2933 if (animations.length === 1) { 2934 this.requestAnimationFrame(); 2935 } 2936 }, 2937 2938 cancelAnimation: function (chart) { 2939 var index = helpers$1.findIndex(this.animations, function (animation) { 2940 return animation.chart === chart; 2941 }); 2942 2943 if (index !== -1) { 2944 this.animations.splice(index, 1); 2945 chart.animating = false; 2946 } 2947 }, 2948 2949 requestAnimationFrame: function () { 2950 var me = this; 2951 if (me.request === null) { 2952 // Skip animation frame requests until the active one is executed. 2953 // This can happen when processing mouse events, e.g. 'mousemove' 2954 // and 'mouseout' events will trigger multiple renders. 2955 me.request = helpers$1.requestAnimFrame.call(window, function () { 2956 me.request = null; 2957 me.startDigest(); 2958 }); 2959 } 2960 }, 2961 2962 /** 2963 * @private 2964 */ 2965 startDigest: function () { 2966 var me = this; 2967 2968 me.advance(); 2969 2970 // Do we have more stuff to animate? 2971 if (me.animations.length > 0) { 2972 me.requestAnimationFrame(); 2973 } 2974 }, 2975 2976 /** 2977 * @private 2978 */ 2979 advance: function () { 2980 var animations = this.animations; 2981 var animation, chart, numSteps, nextStep; 2982 var i = 0; 2983 2984 // 1 animation per chart, so we are looping charts here 2985 while (i < animations.length) { 2986 animation = animations[i]; 2987 chart = animation.chart; 2988 numSteps = animation.numSteps; 2989 2990 // Make sure that currentStep starts at 1 2991 // https://github.com/chartjs/Chart.js/issues/6104 2992 nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1; 2993 animation.currentStep = Math.min(nextStep, numSteps); 2994 2995 helpers$1.callback(animation.render, [chart, animation], chart); 2996 helpers$1.callback(animation.onAnimationProgress, [animation], chart); 2997 2998 if (animation.currentStep >= numSteps) { 2999 helpers$1.callback(animation.onAnimationComplete, [animation], chart); 3000 chart.animating = false; 3001 animations.splice(i, 1); 3002 } else { 3003 ++i; 3004 } 3005 } 3006 } 3007 }; 3008 3009 var resolve = helpers$1.options.resolve; 3010 3011 var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; 3012 3013 /** 3014 * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', 3015 * 'unshift') and notify the listener AFTER the array has been altered. Listeners are 3016 * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. 3017 */ 3018 function listenArrayEvents(array, listener) { 3019 if (array._chartjs) { 3020 array._chartjs.listeners.push(listener); 3021 return; 3022 } 3023 3024 Object.defineProperty(array, '_chartjs', { 3025 configurable: true, 3026 enumerable: false, 3027 value: { 3028 listeners: [listener] 3029 } 3030 }); 3031 3032 arrayEvents.forEach(function (key) { 3033 var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); 3034 var base = array[key]; 3035 3036 Object.defineProperty(array, key, { 3037 configurable: true, 3038 enumerable: false, 3039 value: function () { 3040 var args = Array.prototype.slice.call(arguments); 3041 var res = base.apply(this, args); 3042 3043 helpers$1.each(array._chartjs.listeners, function (object) { 3044 if (typeof object[method] === 'function') { 3045 object[method].apply(object, args); 3046 } 3047 }); 3048 3049 return res; 3050 } 3051 }); 3052 }); 3053 } 3054 3055 /** 3056 * Removes the given array event listener and cleanup extra attached properties (such as 3057 * the _chartjs stub and overridden methods) if array doesn't have any more listeners. 3058 */ 3059 function unlistenArrayEvents(array, listener) { 3060 var stub = array._chartjs; 3061 if (!stub) { 3062 return; 3063 } 3064 3065 var listeners = stub.listeners; 3066 var index = listeners.indexOf(listener); 3067 if (index !== -1) { 3068 listeners.splice(index, 1); 3069 } 3070 3071 if (listeners.length > 0) { 3072 return; 3073 } 3074 3075 arrayEvents.forEach(function (key) { 3076 delete array[key]; 3077 }); 3078 3079 delete array._chartjs; 3080 } 3081 3082// Base class for all dataset controllers (line, bar, etc) 3083 var DatasetController = function (chart, datasetIndex) { 3084 this.initialize(chart, datasetIndex); 3085 }; 3086 3087 helpers$1.extend(DatasetController.prototype, { 3088 3089 /** 3090 * Element type used to generate a meta dataset (e.g. Chart.element.Line). 3091 * @type {Chart.core.element} 3092 */ 3093 datasetElementType: null, 3094 3095 /** 3096 * Element type used to generate a meta data (e.g. Chart.element.Point). 3097 * @type {Chart.core.element} 3098 */ 3099 dataElementType: null, 3100 3101 initialize: function (chart, datasetIndex) { 3102 var me = this; 3103 me.chart = chart; 3104 me.index = datasetIndex; 3105 me.linkScales(); 3106 me.addElements(); 3107 }, 3108 3109 updateIndex: function (datasetIndex) { 3110 this.index = datasetIndex; 3111 }, 3112 3113 linkScales: function () { 3114 var me = this; 3115 var meta = me.getMeta(); 3116 var dataset = me.getDataset(); 3117 3118 if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) { 3119 meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; 3120 } 3121 if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) { 3122 meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; 3123 } 3124 }, 3125 3126 getDataset: function () { 3127 return this.chart.data.datasets[this.index]; 3128 }, 3129 3130 getMeta: function () { 3131 return this.chart.getDatasetMeta(this.index); 3132 }, 3133 3134 getScaleForId: function (scaleID) { 3135 return this.chart.scales[scaleID]; 3136 }, 3137 3138 /** 3139 * @private 3140 */ 3141 _getValueScaleId: function () { 3142 return this.getMeta().yAxisID; 3143 }, 3144 3145 /** 3146 * @private 3147 */ 3148 _getIndexScaleId: function () { 3149 return this.getMeta().xAxisID; 3150 }, 3151 3152 /** 3153 * @private 3154 */ 3155 _getValueScale: function () { 3156 return this.getScaleForId(this._getValueScaleId()); 3157 }, 3158 3159 /** 3160 * @private 3161 */ 3162 _getIndexScale: function () { 3163 return this.getScaleForId(this._getIndexScaleId()); 3164 }, 3165 3166 reset: function () { 3167 this.update(true); 3168 }, 3169 3170 /** 3171 * @private 3172 */ 3173 destroy: function () { 3174 if (this._data) { 3175 unlistenArrayEvents(this._data, this); 3176 } 3177 }, 3178 3179 createMetaDataset: function () { 3180 var me = this; 3181 var type = me.datasetElementType; 3182 return type && new type({ 3183 _chart: me.chart, 3184 _datasetIndex: me.index 3185 }); 3186 }, 3187 3188 createMetaData: function (index) { 3189 var me = this; 3190 var type = me.dataElementType; 3191 return type && new type({ 3192 _chart: me.chart, 3193 _datasetIndex: me.index, 3194 _index: index 3195 }); 3196 }, 3197 3198 addElements: function () { 3199 var me = this; 3200 var meta = me.getMeta(); 3201 var data = me.getDataset().data || []; 3202 var metaData = meta.data; 3203 var i, ilen; 3204 3205 for (i = 0, ilen = data.length; i < ilen; ++i) { 3206 metaData[i] = metaData[i] || me.createMetaData(i); 3207 } 3208 3209 meta.dataset = meta.dataset || me.createMetaDataset(); 3210 }, 3211 3212 addElementAndReset: function (index) { 3213 var element = this.createMetaData(index); 3214 this.getMeta().data.splice(index, 0, element); 3215 this.updateElement(element, index, true); 3216 }, 3217 3218 buildOrUpdateElements: function () { 3219 var me = this; 3220 var dataset = me.getDataset(); 3221 var data = dataset.data || (dataset.data = []); 3222 3223 // In order to correctly handle data addition/deletion animation (an thus simulate 3224 // real-time charts), we need to monitor these data modifications and synchronize 3225 // the internal meta data accordingly. 3226 if (me._data !== data) { 3227 if (me._data) { 3228 // This case happens when the user replaced the data array instance. 3229 unlistenArrayEvents(me._data, me); 3230 } 3231 3232 if (data && Object.isExtensible(data)) { 3233 listenArrayEvents(data, me); 3234 } 3235 me._data = data; 3236 } 3237 3238 // Re-sync meta data in case the user replaced the data array or if we missed 3239 // any updates and so make sure that we handle number of datapoints changing. 3240 me.resyncElements(); 3241 }, 3242 3243 update: helpers$1.noop, 3244 3245 transition: function (easingValue) { 3246 var meta = this.getMeta(); 3247 var elements = meta.data || []; 3248 var ilen = elements.length; 3249 var i = 0; 3250 3251 for (; i < ilen; ++i) { 3252 elements[i].transition(easingValue); 3253 } 3254 3255 if (meta.dataset) { 3256 meta.dataset.transition(easingValue); 3257 } 3258 }, 3259 3260 draw: function () { 3261 var meta = this.getMeta(); 3262 var elements = meta.data || []; 3263 var ilen = elements.length; 3264 var i = 0; 3265 3266 if (meta.dataset) { 3267 meta.dataset.draw(); 3268 } 3269 3270 for (; i < ilen; ++i) { 3271 elements[i].draw(); 3272 } 3273 }, 3274 3275 removeHoverStyle: function (element) { 3276 helpers$1.merge(element._model, element.$previousStyle || {}); 3277 delete element.$previousStyle; 3278 }, 3279 3280 setHoverStyle: function (element) { 3281 var dataset = this.chart.data.datasets[element._datasetIndex]; 3282 var index = element._index; 3283 var custom = element.custom || {}; 3284 var model = element._model; 3285 var getHoverColor = helpers$1.getHoverColor; 3286 3287 element.$previousStyle = { 3288 backgroundColor: model.backgroundColor, 3289 borderColor: model.borderColor, 3290 borderWidth: model.borderWidth 3291 }; 3292 3293 model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index); 3294 model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index); 3295 model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index); 3296 }, 3297 3298 /** 3299 * @private 3300 */ 3301 resyncElements: function () { 3302 var me = this; 3303 var meta = me.getMeta(); 3304 var data = me.getDataset().data; 3305 var numMeta = meta.data.length; 3306 var numData = data.length; 3307 3308 if (numData < numMeta) { 3309 meta.data.splice(numData, numMeta - numData); 3310 } else if (numData > numMeta) { 3311 me.insertElements(numMeta, numData - numMeta); 3312 } 3313 }, 3314 3315 /** 3316 * @private 3317 */ 3318 insertElements: function (start, count) { 3319 for (var i = 0; i < count; ++i) { 3320 this.addElementAndReset(start + i); 3321 } 3322 }, 3323 3324 /** 3325 * @private 3326 */ 3327 onDataPush: function () { 3328 var count = arguments.length; 3329 this.insertElements(this.getDataset().data.length - count, count); 3330 }, 3331 3332 /** 3333 * @private 3334 */ 3335 onDataPop: function () { 3336 this.getMeta().data.pop(); 3337 }, 3338 3339 /** 3340 * @private 3341 */ 3342 onDataShift: function () { 3343 this.getMeta().data.shift(); 3344 }, 3345 3346 /** 3347 * @private 3348 */ 3349 onDataSplice: function (start, count) { 3350 this.getMeta().data.splice(start, count); 3351 this.insertElements(start, arguments.length - 2); 3352 }, 3353 3354 /** 3355 * @private 3356 */ 3357 onDataUnshift: function () { 3358 this.insertElements(0, arguments.length); 3359 } 3360 }); 3361 3362 DatasetController.extend = helpers$1.inherits; 3363 3364 var core_datasetController = DatasetController; 3365 3366 core_defaults._set('global', { 3367 elements: { 3368 arc: { 3369 backgroundColor: core_defaults.global.defaultColor, 3370 borderColor: '#fff', 3371 borderWidth: 2, 3372 borderAlign: 'center' 3373 } 3374 } 3375 }); 3376 3377 var element_arc = core_element.extend({ 3378 inLabelRange: function (mouseX) { 3379 var vm = this._view; 3380 3381 if (vm) { 3382 return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); 3383 } 3384 return false;
3385 }, 3386 3387 inRange: function (chartX, chartY) { 3388 var vm = this._view; 3389 3390 if (vm) { 3391 var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY}); 3392 var angle = pointRelativePosition.angle; 3393 var distance = pointRelativePosition.distance; 3394 3395 // Sanitise angle range 3396 var startAngle = vm.startAngle; 3397 var endAngle = vm.endAngle; 3398 while (endAngle < startAngle) { 3399 endAngle += 2.0 * Math.PI; 3400 } 3401 while (angle > endAngle) { 3402 angle -= 2.0 * Math.PI; 3403 } 3404 while (angle < startAngle) { 3405 angle += 2.0 * Math.PI; 3406 } 3407 3408 // Check if within the range of the open/close angle 3409 var betweenAngles = (angle >= startAngle && angle <= endAngle); 3410 var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); 3411 3412 return (betweenAngles && withinRadius); 3413 } 3414 return false; 3415 }, 3416 3417 getCenterPoint: function () { 3418 var vm = this._view; 3419 var halfAngle = (vm.startAngle + vm.endAngle) / 2; 3420 var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; 3421 return { 3422 x: vm.x + Math.cos(halfAngle) * halfRadius, 3423 y: vm.y + Math.sin(halfAngle) * halfRadius 3424 }; 3425 }, 3426 3427 getArea: function () { 3428 var vm = this._view; 3429 return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); 3430 }, 3431 3432 tooltipPosition: function () { 3433 var vm = this._view; 3434 var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); 3435 var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; 3436 3437 return { 3438 x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), 3439 y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) 3440 }; 3441 }, 3442 3443 draw: function () { 3444 var ctx = this._chart.ctx; 3445 var vm = this._view; 3446 var sA = vm.startAngle; 3447 var eA = vm.endAngle; 3448 var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0; 3449 var angleMargin; 3450 3451 ctx.save(); 3452 3453 ctx.beginPath(); 3454 ctx.arc(vm.x, vm.y, Math.max(vm.outerRadius - pixelMargin, 0), sA, eA); 3455 ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); 3456 ctx.closePath(); 3457 3458 ctx.fillStyle = vm.backgroundColor; 3459 ctx.fill(); 3460 3461 if (vm.borderWidth) { 3462 if (vm.borderAlign === 'inner') { 3463 // Draw an inner border by cliping the arc and drawing a double-width border 3464 // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders 3465 ctx.beginPath(); 3466 angleMargin = pixelMargin / vm.outerRadius; 3467 ctx.arc(vm.x, vm.y, vm.outerRadius, sA - angleMargin, eA + angleMargin); 3468 if (vm.innerRadius > pixelMargin) { 3469 angleMargin = pixelMargin / vm.innerRadius; 3470 ctx.arc(vm.x, vm.y, vm.innerRadius - pixelMargin, eA + angleMargin, sA - angleMargin, true); 3471 } else { 3472 ctx.arc(vm.x, vm.y, pixelMargin, eA + Math.PI / 2, sA - Math.PI / 2); 3473 } 3474 ctx.closePath(); 3475 ctx.clip(); 3476 3477 ctx.beginPath(); 3478 ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); 3479 ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); 3480 ctx.closePath(); 3481 3482 ctx.lineWidth = vm.borderWidth * 2; 3483 ctx.lineJoin = 'round'; 3484 } else { 3485 ctx.lineWidth = vm.borderWidth; 3486 ctx.lineJoin = 'bevel'; 3487 } 3488 3489 ctx.strokeStyle = vm.borderColor; 3490 ctx.stroke(); 3491 } 3492
vendor: 29,649 bytes, lines 3493-4406
3493 ctx.restore(); 3494 } 3495 }); 3496 3497 var valueOrDefault$1 = helpers$1.valueOrDefault; 3498 3499 var defaultColor = core_defaults.global.defaultColor; 3500 3501 core_defaults._set('global', { 3502 elements: { 3503 line: { 3504 tension: 0.4, 3505 backgroundColor: defaultColor, 3506 borderWidth: 3, 3507 borderColor: defaultColor, 3508 borderCapStyle: 'butt', 3509 borderDash: [], 3510 borderDashOffset: 0.0, 3511 borderJoinStyle: 'miter', 3512 capBezierPoints: true, 3513 fill: true, // do we fill in the area between the line and its base axis 3514 } 3515 } 3516 }); 3517 3518 var element_line = core_element.extend({ 3519 draw: function () { 3520 var me = this; 3521 var vm = me._view; 3522 var ctx = me._chart.ctx; 3523 var spanGaps = vm.spanGaps; 3524 var points = me._children.slice(); // clone array 3525 var globalDefaults = core_defaults.global; 3526 var globalOptionLineElements = globalDefaults.elements.line; 3527 var lastDrawnIndex = -1; 3528 var index, current, previous, currentVM; 3529 3530 // If we are looping, adding the first point again 3531 if (me._loop && points.length) { 3532 points.push(points[0]); 3533 } 3534 3535 ctx.save(); 3536 3537 // Stroke Line Options 3538 ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; 3539 3540 // IE 9 and 10 do not support line dash 3541 if (ctx.setLineDash) { 3542 ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); 3543 } 3544 3545 ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset); 3546 ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; 3547 ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth); 3548 ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; 3549 3550 // Stroke Line 3551 ctx.beginPath(); 3552 lastDrawnIndex = -1; 3553 3554 for (index = 0; index < points.length; ++index) { 3555 current = points[index]; 3556 previous = helpers$1.previousItem(points, index); 3557 currentVM = current._view; 3558 3559 // First point moves to it's starting position no matter what 3560 if (index === 0) { 3561 if (!currentVM.skip) { 3562 ctx.moveTo(currentVM.x, currentVM.y); 3563 lastDrawnIndex = index; 3564 } 3565 } else { 3566 previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; 3567 3568 if (!currentVM.skip) { 3569 if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { 3570 // There was a gap and this is the first point after the gap 3571 ctx.moveTo(currentVM.x, currentVM.y); 3572 } else { 3573 // Line to next point 3574 helpers$1.canvas.lineTo(ctx, previous._view, current._view); 3575 } 3576 lastDrawnIndex = index; 3577 } 3578 } 3579 } 3580 3581 ctx.stroke(); 3582 ctx.restore(); 3583 } 3584 }); 3585 3586 var valueOrDefault$2 = helpers$1.valueOrDefault; 3587 3588 var defaultColor$1 = core_defaults.global.defaultColor; 3589 3590 core_defaults._set('global', { 3591 elements: { 3592 point: { 3593 radius: 3, 3594 pointStyle: 'circle', 3595 backgroundColor: defaultColor$1, 3596 borderColor: defaultColor$1, 3597 borderWidth: 1, 3598 // Hover 3599 hitRadius: 1, 3600 hoverRadius: 4, 3601 hoverBorderWidth: 1 3602 } 3603 } 3604 }); 3605 3606 function xRange(mouseX) { 3607 var vm = this._view; 3608 return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; 3609 } 3610 3611 function yRange(mouseY) { 3612 var vm = this._view; 3613 return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; 3614 } 3615 3616 var element_point = core_element.extend({ 3617 inRange: function (mouseX, mouseY) { 3618 var vm = this._view; 3619 return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; 3620 }, 3621 3622 inLabelRange: xRange, 3623 inXRange: xRange, 3624 inYRange: yRange, 3625 3626 getCenterPoint: function () { 3627 var vm = this._view; 3628 return { 3629 x: vm.x, 3630 y: vm.y 3631 }; 3632 }, 3633 3634 getArea: function () { 3635 return Math.PI * Math.pow(this._view.radius, 2); 3636 }, 3637 3638 tooltipPosition: function () { 3639 var vm = this._view; 3640 return { 3641 x: vm.x, 3642 y: vm.y, 3643 padding: vm.radius + vm.borderWidth 3644 }; 3645 }, 3646 3647 draw: function (chartArea) { 3648 var vm = this._view; 3649 var ctx = this._chart.ctx; 3650 var pointStyle = vm.pointStyle; 3651 var rotation = vm.rotation; 3652 var radius = vm.radius; 3653 var x = vm.x; 3654 var y = vm.y; 3655 var globalDefaults = core_defaults.global; 3656 var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow 3657 3658 if (vm.skip) { 3659 return; 3660 } 3661 3662 // Clipping for Points. 3663 if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) { 3664 ctx.strokeStyle = vm.borderColor || defaultColor; 3665 ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth); 3666 ctx.fillStyle = vm.backgroundColor || defaultColor; 3667 helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); 3668 } 3669 } 3670 }); 3671 3672 var defaultColor$2 = core_defaults.global.defaultColor; 3673 3674 core_defaults._set('global', { 3675 elements: { 3676 rectangle: { 3677 backgroundColor: defaultColor$2, 3678 borderColor: defaultColor$2, 3679 borderSkipped: 'bottom', 3680 borderWidth: 0 3681 } 3682 } 3683 }); 3684 3685 function isVertical(vm) { 3686 return vm && vm.width !== undefined; 3687 } 3688 3689 /** 3690 * Helper function to get the bounds of the bar regardless of the orientation 3691 * @param bar {Chart.Element.Rectangle} the bar 3692 * @return {Bounds} bounds of the bar 3693 * @private 3694 */ 3695 function getBarBounds(vm) { 3696 var x1, x2, y1, y2, half; 3697 3698 if (isVertical(vm)) { 3699 half = vm.width / 2; 3700 x1 = vm.x - half; 3701 x2 = vm.x + half; 3702 y1 = Math.min(vm.y, vm.base); 3703 y2 = Math.max(vm.y, vm.base); 3704 } else { 3705 half = vm.height / 2; 3706 x1 = Math.min(vm.x, vm.base); 3707 x2 = Math.max(vm.x, vm.base); 3708 y1 = vm.y - half; 3709 y2 = vm.y + half; 3710 } 3711 3712 return { 3713 left: x1, 3714 top: y1, 3715 right: x2, 3716 bottom: y2 3717 }; 3718 } 3719 3720 function swap(orig, v1, v2) { 3721 return orig === v1 ? v2 : orig === v2 ? v1 : orig; 3722 } 3723 3724 function parseBorderSkipped(vm) { 3725 var edge = vm.borderSkipped; 3726 var res = {}; 3727 3728 if (!edge) { 3729 return res; 3730 } 3731 3732 if (vm.horizontal) { 3733 if (vm.base > vm.x) { 3734 edge = swap(edge, 'left', 'right'); 3735 } 3736 } else if (vm.base < vm.y) { 3737 edge = swap(edge, 'bottom', 'top'); 3738 } 3739 3740 res[edge] = true; 3741 return res; 3742 } 3743 3744 function parseBorderWidth(vm, maxW, maxH) { 3745 var value = vm.borderWidth; 3746 var skip = parseBorderSkipped(vm); 3747 var t, r, b, l; 3748 3749 if (helpers$1.isObject(value)) { 3750 t = +value.top || 0; 3751 r = +value.right || 0; 3752 b = +value.bottom || 0; 3753 l = +value.left || 0; 3754 } else { 3755 t = r = b = l = +value || 0; 3756 } 3757 3758 return { 3759 t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, 3760 r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, 3761 b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, 3762 l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l 3763 }; 3764 } 3765 3766 function boundingRects(vm) { 3767 var bounds = getBarBounds(vm); 3768 var width = bounds.right - bounds.left; 3769 var height = bounds.bottom - bounds.top; 3770 var border = parseBorderWidth(vm, width / 2, height / 2); 3771 3772 return { 3773 outer: { 3774 x: bounds.left, 3775 y: bounds.top, 3776 w: width, 3777 h: height 3778 }, 3779 inner: { 3780 x: bounds.left + border.l, 3781 y: bounds.top + border.t, 3782 w: width - border.l - border.r, 3783 h: height - border.t - border.b 3784 } 3785 }; 3786 } 3787 3788 function inRange(vm, x, y) { 3789 var skipX = x === null; 3790 var skipY = y === null; 3791 var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm); 3792 3793 return bounds 3794 && (skipX || x >= bounds.left && x <= bounds.right) 3795 && (skipY || y >= bounds.top && y <= bounds.bottom); 3796 } 3797 3798 var element_rectangle = core_element.extend({ 3799 draw: function () { 3800 var ctx = this._chart.ctx; 3801 var vm = this._view; 3802 var rects = boundingRects(vm); 3803 var outer = rects.outer; 3804 var inner = rects.inner; 3805 3806 ctx.fillStyle = vm.backgroundColor; 3807 ctx.fillRect(outer.x, outer.y, outer.w, outer.h); 3808 3809 if (outer.w === inner.w && outer.h === inner.h) { 3810 return; 3811 } 3812 3813 ctx.save(); 3814 ctx.beginPath(); 3815 ctx.rect(outer.x, outer.y, outer.w, outer.h); 3816 ctx.clip(); 3817 ctx.fillStyle = vm.borderColor; 3818 ctx.rect(inner.x, inner.y, inner.w, inner.h); 3819 ctx.fill('evenodd'); 3820 ctx.restore(); 3821 }, 3822 3823 height: function () { 3824 var vm = this._view; 3825 return vm.base - vm.y; 3826 }, 3827 3828 inRange: function (mouseX, mouseY) { 3829 return inRange(this._view, mouseX, mouseY); 3830 }, 3831 3832 inLabelRange: function (mouseX, mouseY) { 3833 var vm = this._view; 3834 return isVertical(vm) 3835 ? inRange(vm, mouseX, null) 3836 : inRange(vm, null, mouseY); 3837 }, 3838 3839 inXRange: function (mouseX) { 3840 return inRange(this._view, mouseX, null); 3841 }, 3842 3843 inYRange: function (mouseY) { 3844 return inRange(this._view, null, mouseY); 3845 }, 3846 3847 getCenterPoint: function () { 3848 var vm = this._view; 3849 var x, y; 3850 if (isVertical(vm)) { 3851 x = vm.x; 3852 y = (vm.y + vm.base) / 2; 3853 } else { 3854 x = (vm.x + vm.base) / 2; 3855 y = vm.y; 3856 } 3857 3858 return {x: x, y: y}; 3859 }, 3860 3861 getArea: function () { 3862 var vm = this._view; 3863 3864 return isVertical(vm) 3865 ? vm.width * Math.abs(vm.y - vm.base) 3866 : vm.height * Math.abs(vm.x - vm.base); 3867 }, 3868 3869 tooltipPosition: function () { 3870 var vm = this._view; 3871 return { 3872 x: vm.x, 3873 y: vm.y 3874 }; 3875 } 3876 }); 3877 3878 var elements = {}; 3879 var Arc = element_arc; 3880 var Line = element_line; 3881 var Point = element_point; 3882 var Rectangle = element_rectangle; 3883 elements.Arc = Arc; 3884 elements.Line = Line; 3885 elements.Point = Point; 3886 elements.Rectangle = Rectangle; 3887 3888 var resolve$1 = helpers$1.options.resolve; 3889 3890 core_defaults._set('bar', { 3891 hover: { 3892 mode: 'label' 3893 }, 3894 3895 scales: { 3896 xAxes: [{ 3897 type: 'category', 3898 categoryPercentage: 0.8, 3899 barPercentage: 0.9, 3900 offset: true, 3901 gridLines: { 3902 offsetGridLines: true 3903 } 3904 }], 3905 3906 yAxes: [{ 3907 type: 'linear' 3908 }] 3909 } 3910 }); 3911 3912 /** 3913 * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. 3914 * @private 3915 */ 3916 function computeMinSampleSize(scale, pixels) { 3917 var min = scale.isHorizontal() ? scale.width : scale.height; 3918 var ticks = scale.getTicks(); 3919 var prev, curr, i, ilen; 3920 3921 for (i = 1, ilen = pixels.length; i < ilen; ++i) { 3922 min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1])); 3923 } 3924 3925 for (i = 0, ilen = ticks.length; i < ilen; ++i) { 3926 curr = scale.getPixelForTick(i); 3927 min = i > 0 ? Math.min(min, curr - prev) : min; 3928 prev = curr; 3929 } 3930 3931 return min; 3932 } 3933 3934 /** 3935 * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, 3936 * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This 3937 * mode currently always generates bars equally sized (until we introduce scriptable options?). 3938 * @private 3939 */ 3940 function computeFitCategoryTraits(index, ruler, options) { 3941 var thickness = options.barThickness; 3942 var count = ruler.stackCount; 3943 var curr = ruler.pixels[index]; 3944 var size, ratio; 3945 3946 if (helpers$1.isNullOrUndef(thickness)) { 3947 size = ruler.min * options.categoryPercentage; 3948 ratio = options.barPercentage; 3949 } else { 3950 // When bar thickness is enforced, category and bar percentages are ignored. 3951 // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') 3952 // and deprecate barPercentage since this value is ignored when thickness is absolute. 3953 size = thickness * count; 3954 ratio = 1; 3955 } 3956 3957 return { 3958 chunk: size / count, 3959 ratio: ratio, 3960 start: curr - (size / 2) 3961 }; 3962 } 3963 3964 /** 3965 * Computes an "optimal" category that globally arranges bars side by side (no gap when 3966 * percentage options are 1), based on the previous and following categories. This mode 3967 * generates bars with different widths when data are not evenly spaced. 3968 * @private 3969 */ 3970 function computeFlexCategoryTraits(index, ruler, options) { 3971 var pixels = ruler.pixels; 3972 var curr = pixels[index]; 3973 var prev = index > 0 ? pixels[index - 1] : null; 3974 var next = index < pixels.length - 1 ? pixels[index + 1] : null; 3975 var percent = options.categoryPercentage; 3976 var start, size; 3977 3978 if (prev === null) { 3979 // first data: its size is double based on the next point or, 3980 // if it's also the last data, we use the scale size. 3981 prev = curr - (next === null ? ruler.end - ruler.start : next - curr); 3982 } 3983 3984 if (next === null) { 3985 // last data: its size is also double based on the previous point. 3986 next = curr + curr - prev; 3987 } 3988 3989 start = curr - (curr - Math.min(prev, next)) / 2 * percent; 3990 size = Math.abs(next - prev) / 2 * percent; 3991 3992 return { 3993 chunk: size / ruler.stackCount, 3994 ratio: options.barPercentage, 3995 start: start 3996 }; 3997 } 3998 3999 var controller_bar = core_datasetController.extend({ 4000 4001 dataElementType: elements.Rectangle, 4002 4003 initialize: function () { 4004 var me = this; 4005 var meta; 4006 4007 core_datasetController.prototype.initialize.apply(me, arguments); 4008 4009 meta = me.getMeta(); 4010 meta.stack = me.getDataset().stack; 4011 meta.bar = true; 4012 }, 4013 4014 update: function (reset) { 4015 var me = this; 4016 var rects = me.getMeta().data; 4017 var i, ilen; 4018 4019 me._ruler = me.getRuler(); 4020 4021 for (i = 0, ilen = rects.length; i < ilen; ++i) { 4022 me.updateElement(rects[i], i, reset); 4023 } 4024 }, 4025 4026 updateElement: function (rectangle, index, reset) { 4027 var me = this; 4028 var meta = me.getMeta(); 4029 var dataset = me.getDataset(); 4030 var options = me._resolveElementOptions(rectangle, index); 4031 4032 rectangle._xScale = me.getScaleForId(meta.xAxisID); 4033 rectangle._yScale = me.getScaleForId(meta.yAxisID); 4034 rectangle._datasetIndex = me.index; 4035 rectangle._index = index; 4036 rectangle._model = { 4037 backgroundColor: options.backgroundColor, 4038 borderColor: options.borderColor, 4039 borderSkipped: options.borderSkipped, 4040 borderWidth: options.borderWidth, 4041 datasetLabel: dataset.label, 4042 label: me.chart.data.labels[index] 4043 }; 4044 4045 me._updateElementGeometry(rectangle, index, reset); 4046 4047 rectangle.pivot(); 4048 }, 4049 4050 /** 4051 * @private 4052 */ 4053 _updateElementGeometry: function (rectangle, index, reset) { 4054 var me = this; 4055 var model = rectangle._model; 4056 var vscale = me._getValueScale(); 4057 var base = vscale.getBasePixel(); 4058 var horizontal = vscale.isHorizontal(); 4059 var ruler = me._ruler || me.getRuler(); 4060 var vpixels = me.calculateBarValuePixels(me.index, index); 4061 var ipixels = me.calculateBarIndexPixels(me.index, index, ruler); 4062 4063 model.horizontal = horizontal; 4064 model.base = reset ? base : vpixels.base; 4065 model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; 4066 model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; 4067 model.height = horizontal ? ipixels.size : undefined; 4068 model.width = horizontal ? undefined : ipixels.size; 4069 }, 4070 4071 /** 4072 * Returns the stacks based on groups and bar visibility. 4073 * @param {number} [last] - The dataset index 4074 * @returns {string[]} The list of stack IDs 4075 * @private 4076 */ 4077 _getStacks: function (last) { 4078 var me = this; 4079 var chart = me.chart; 4080 var scale = me._getIndexScale(); 4081 var stacked = scale.options.stacked; 4082 var ilen = last === undefined ? chart.data.datasets.length : last + 1; 4083 var stacks = []; 4084 var i, meta; 4085 4086 for (i = 0; i < ilen; ++i) { 4087 meta = chart.getDatasetMeta(i); 4088 if (meta.bar && chart.isDatasetVisible(i) && 4089 (stacked === false || 4090 (stacked === true && stacks.indexOf(meta.stack) === -1) || 4091 (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) { 4092 stacks.push(meta.stack); 4093 } 4094 } 4095 4096 return stacks; 4097 }, 4098 4099 /** 4100 * Returns the effective number of stacks based on groups and bar visibility. 4101 * @private 4102 */ 4103 getStackCount: function () { 4104 return this._getStacks().length; 4105 }, 4106 4107 /** 4108 * Returns the stack index for the given dataset based on groups and bar visibility. 4109 * @param {number} [datasetIndex] - The dataset index 4110 * @param {string} [name] - The stack name to find 4111 * @returns {number} The stack index 4112 * @private 4113 */ 4114 getStackIndex: function (datasetIndex, name) { 4115 var stacks = this._getStacks(datasetIndex); 4116 var index = (name !== undefined) 4117 ? stacks.indexOf(name) 4118 : -1; // indexOf returns -1 if element is not present 4119 4120 return (index === -1) 4121 ? stacks.length - 1 4122 : index; 4123 }, 4124 4125 /** 4126 * @private 4127 */ 4128 getRuler: function () { 4129 var me = this; 4130 var scale = me._getIndexScale(); 4131 var stackCount = me.getStackCount(); 4132 var datasetIndex = me.index; 4133 var isHorizontal = scale.isHorizontal(); 4134 var start = isHorizontal ? scale.left : scale.top; 4135 var end = start + (isHorizontal ? scale.width : scale.height); 4136 var pixels = []; 4137 var i, ilen, min; 4138 4139 for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { 4140 pixels.push(scale.getPixelForValue(null, i, datasetIndex)); 4141 } 4142 4143 min = helpers$1.isNullOrUndef(scale.options.barThickness) 4144 ? computeMinSampleSize(scale, pixels) 4145 : -1; 4146 4147 return { 4148 min: min, 4149 pixels: pixels, 4150 start: start, 4151 end: end, 4152 stackCount: stackCount, 4153 scale: scale 4154 }; 4155 }, 4156 4157 /** 4158 * Note: pixel values are not clamped to the scale area. 4159 * @private 4160 */ 4161 calculateBarValuePixels: function (datasetIndex, index) { 4162 var me = this; 4163 var chart = me.chart; 4164 var meta = me.getMeta(); 4165 var scale = me._getValueScale(); 4166 var isHorizontal = scale.isHorizontal(); 4167 var datasets = chart.data.datasets; 4168 var value = +scale.getRightValue(datasets[datasetIndex].data[index]); 4169 var minBarLength = scale.options.minBarLength; 4170 var stacked = scale.options.stacked; 4171 var stack = meta.stack; 4172 var start = 0; 4173 var i, imeta, ivalue, base, head, size; 4174 4175 if (stacked || (stacked === undefined && stack !== undefined)) { 4176 for (i = 0; i < datasetIndex; ++i) { 4177 imeta = chart.getDatasetMeta(i); 4178 4179 if (imeta.bar && 4180 imeta.stack === stack && 4181 imeta.controller._getValueScaleId() === scale.id && 4182 chart.isDatasetVisible(i)) { 4183 4184 ivalue = +scale.getRightValue(datasets[i].data[index]); 4185 if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) { 4186 start += ivalue; 4187 } 4188 } 4189 } 4190 } 4191 4192 base = scale.getPixelForValue(start); 4193 head = scale.getPixelForValue(start + value); 4194 size = head - base; 4195 4196 if (minBarLength !== undefined && Math.abs(size) < minBarLength) { 4197 size = minBarLength; 4198 if (value >= 0 && !isHorizontal || value < 0 && isHorizontal) { 4199 head = base - minBarLength; 4200 } else { 4201 head = base + minBarLength; 4202 } 4203 } 4204 4205 return { 4206 size: size, 4207 base: base, 4208 head: head, 4209 center: head + size / 2 4210 }; 4211 }, 4212 4213 /** 4214 * @private 4215 */ 4216 calculateBarIndexPixels: function (datasetIndex, index, ruler) { 4217 var me = this; 4218 var options = ruler.scale.options; 4219 var range = options.barThickness === 'flex' 4220 ? computeFlexCategoryTraits(index, ruler, options) 4221 : computeFitCategoryTraits(index, ruler, options); 4222 4223 var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); 4224 var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); 4225 var size = Math.min( 4226 helpers$1.valueOrDefault(options.maxBarThickness, Infinity), 4227 range.chunk * range.ratio); 4228 4229 return { 4230 base: center - size / 2, 4231 head: center + size / 2, 4232 center: center, 4233 size: size 4234 }; 4235 }, 4236 4237 draw: function () { 4238 var me = this; 4239 var chart = me.chart; 4240 var scale = me._getValueScale(); 4241 var rects = me.getMeta().data; 4242 var dataset = me.getDataset(); 4243 var ilen = rects.length; 4244 var i = 0; 4245 4246 helpers$1.canvas.clipArea(chart.ctx, chart.chartArea); 4247 4248 for (; i < ilen; ++i) { 4249 if (!isNaN(scale.getRightValue(dataset.data[i]))) { 4250 rects[i].draw(); 4251 } 4252 } 4253 4254 helpers$1.canvas.unclipArea(chart.ctx); 4255 }, 4256 4257 /** 4258 * @private 4259 */ 4260 _resolveElementOptions: function (rectangle, index) { 4261 var me = this; 4262 var chart = me.chart; 4263 var datasets = chart.data.datasets; 4264 var dataset = datasets[me.index]; 4265 var custom = rectangle.custom || {}; 4266 var options = chart.options.elements.rectangle; 4267 var values = {}; 4268 var i, ilen, key; 4269 4270 // Scriptable options 4271 var context = { 4272 chart: chart, 4273 dataIndex: index, 4274 dataset: dataset, 4275 datasetIndex: me.index 4276 }; 4277 4278 var keys = [ 4279 'backgroundColor', 4280 'borderColor', 4281 'borderSkipped', 4282 'borderWidth' 4283 ]; 4284 4285 for (i = 0, ilen = keys.length; i < ilen; ++i) { 4286 key = keys[i]; 4287 values[key] = resolve$1([ 4288 custom[key], 4289 dataset[key], 4290 options[key] 4291 ], context, index); 4292 } 4293 4294 return values; 4295 } 4296 }); 4297 4298 var valueOrDefault$3 = helpers$1.valueOrDefault; 4299 var resolve$2 = helpers$1.options.resolve; 4300 4301 core_defaults._set('bubble', { 4302 hover: { 4303 mode: 'single' 4304 }, 4305 4306 scales: { 4307 xAxes: [{ 4308 type: 'linear', // bubble should probably use a linear scale by default 4309 position: 'bottom', 4310 id: 'x-axis-0' // need an ID so datasets can reference the scale 4311 }], 4312 yAxes: [{ 4313 type: 'linear', 4314 position: 'left', 4315 id: 'y-axis-0' 4316 }] 4317 }, 4318 4319 tooltips: { 4320 callbacks: { 4321 title: function () { 4322 // Title doesn't make sense for scatter since we format the data as a point 4323 return ''; 4324 }, 4325 label: function (item, data) { 4326 var datasetLabel = data.datasets[item.datasetIndex].label || ''; 4327 var dataPoint = data.datasets[item.datasetIndex].data[item.index]; 4328 return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; 4329 } 4330 } 4331 } 4332 }); 4333 4334 var controller_bubble = core_datasetController.extend({ 4335 /** 4336 * @protected 4337 */ 4338 dataElementType: elements.Point, 4339 4340 /** 4341 * @protected 4342 */ 4343 update: function (reset) { 4344 var me = this; 4345 var meta = me.getMeta(); 4346 var points = meta.data; 4347 4348 // Update Points 4349 helpers$1.each(points, function (point, index) { 4350 me.updateElement(point, index, reset); 4351 }); 4352 }, 4353 4354 /** 4355 * @protected 4356 */ 4357 updateElement: function (point, index, reset) { 4358 var me = this; 4359 var meta = me.getMeta(); 4360 var custom = point.custom || {}; 4361 var xScale = me.getScaleForId(meta.xAxisID); 4362 var yScale = me.getScaleForId(meta.yAxisID); 4363 var options = me._resolveElementOptions(point, index); 4364 var data = me.getDataset().data[index]; 4365 var dsIndex = me.index; 4366 4367 var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); 4368 var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); 4369 4370 point._xScale = xScale; 4371 point._yScale = yScale; 4372 point._options = options; 4373 point._datasetIndex = dsIndex; 4374 point._index = index; 4375 point._model = { 4376 backgroundColor: options.backgroundColor, 4377 borderColor: options.borderColor, 4378 borderWidth: options.borderWidth, 4379 hitRadius: options.hitRadius, 4380 pointStyle: options.pointStyle, 4381 rotation: options.rotation, 4382 radius: reset ? 0 : options.radius, 4383 skip: custom.skip || isNaN(x) || isNaN(y), 4384 x: x, 4385 y: y, 4386 }; 4387 4388 point.pivot(); 4389 }, 4390 4391 /** 4392 * @protected 4393 */ 4394 setHoverStyle: function (point) { 4395 var model = point._model; 4396 var options = point._options; 4397 var getHoverColor = helpers$1.getHoverColor; 4398 4399 point.$previousStyle = { 4400 backgroundColor: model.backgroundColor, 4401 borderColor: model.borderColor, 4402 borderWidth: model.borderWidth, 4403 radius: model.radius 4404 }; 4405 4406 model.backgroundColor = valueOrDefault$3(options.hoverBackgroun
4406dColor, getHoverColor(options.backgroundColor)); 4407 model.borderColor = valueOrDefault$3(options.hoverBorderColor, getHoverColor(options.borderColor)); 4408 model.borderWidth = valueOrDefault$3(options.hoverBorderWidth, options.borderWidth); 4409 model.radius = options.radius + options.hoverRadius; 4410 }, 4411 4412 /** 4413 * @private 4414 */ 4415 _resolveElementOptions: function (point, index) { 4416 var me = this; 4417 var chart = me.chart; 4418 var datasets = chart.data.datasets; 4419 var dataset = datasets[me.index]; 4420 var custom = point.custom || {}; 4421 var options = chart.options.elements.point; 4422 var data = dataset.data[index]; 4423 var values = {}; 4424 var i, ilen, key; 4425 4426 // Scriptable options 4427 var context = { 4428 chart: chart, 4429 dataIndex: index, 4430 dataset: dataset, 4431 datasetIndex: me.index 4432 }; 4433 4434 var keys = [ 4435 'backgroundColor', 4436 'borderColor', 4437 'borderWidth', 4438 'hoverBackgroundColor', 4439 'hoverBorderColor', 4440 'hoverBorderWidth', 4441 'hoverRadius', 4442 'hitRadius', 4443 'pointStyle', 4444 'rotation' 4445 ]; 4446 4447 for (i = 0, ilen = keys.length; i < ilen; ++i) { 4448 key = keys[i]; 4449 values[key] = resolve$2([ 4450 custom[key], 4451 dataset[key], 4452 options[key] 4453 ], context, index); 4454 } 4455 4456 // Custom radius resolution 4457 values.radius = resolve$2([ 4458 custom.radius, 4459 data ? data.r : undefined, 4460 dataset.radius, 4461 options.radius 4462 ], context, index); 4463 4464 return values; 4465 } 4466 }); 4467 4468 var resolve$3 = helpers$1.options.resolve; 4469 var valueOrDefault$4 = helpers$1.valueOrDefault; 4470 4471 core_defaults._set('doughnut', { 4472 animation: { 4473 // Boolean - Whether we animate the rotation of the Doughnut 4474 animateRotate: true, 4475 // Boolean - Whether we animate scaling the Doughnut from the centre 4476 animateScale: false 4477 }, 4478 hover: { 4479 mode: 'single' 4480 }, 4481 legendCallback: function (chart) { 4482 var text = []; 4483 text.push('<ul class="' + chart.id + '-legend">'); 4484 4485 var data = chart.data; 4486 var datasets = data.datasets; 4487 var labels = data.labels; 4488 4489 if (datasets.length) { 4490 for (var i = 0; i < datasets[0].data.length; ++i) { 4491 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); 4492 if (labels[i]) { 4493 text.push(labels[i]); 4494 } 4495 text.push('</li>'); 4496 } 4497 } 4498 4499 text.push('</ul>'); 4500 return text.join(''); 4501 }, 4502 legend: { 4503 labels: { 4504 generateLabels: function (chart) { 4505 var data = chart.data; 4506 if (data.labels.length && data.datasets.length) { 4507 return data.labels.map(function (label, i) { 4508 var meta = chart.getDatasetMeta(0); 4509 var ds = data.datasets[0]; 4510 var arc = meta.data[i]; 4511 var custom = arc && arc.custom || {}; 4512 var arcOpts = chart.options.elements.arc; 4513 var fill = resolve$3([custom.backgroundColor, ds.backgroundColor, arcOpts.backgroundColor], undefined, i); 4514 var stroke = resolve$3([custom.borderColor, ds.borderColor, arcOpts.borderColor], undefined, i); 4515 var bw = resolve$3([custom.borderWidth, ds.borderWidth, arcOpts.borderWidth], undefined, i); 4516 4517 return { 4518 text: label, 4519 fillStyle: fill, 4520 strokeStyle: stroke, 4521 lineWidth: bw, 4522 hidden: isNaN(ds.data[i]) || meta.data[i].hidden, 4523 4524 // Extra data used for toggling the correct item 4525 index: i 4526 }; 4527 }); 4528 } 4529 return []; 4530 } 4531 }, 4532 4533 onClick: function (e, legendItem) { 4534 var index = legendItem.index; 4535 var chart = this.chart; 4536 var i, ilen, meta; 4537 4538 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { 4539 meta = chart.getDatasetMeta(i); 4540 // toggle visibility of index if exists 4541 if (meta.data[index]) { 4542 meta.data[index].hidden = !meta.data[index].hidden; 4543 } 4544 } 4545
4546 chart.update(); 4547 } 4548 }, 4549 4550 // The percentage of the chart that we cut out of the middle. 4551 cutoutPercentage: 50, 4552 4553 // The rotation of the chart, where the first data arc begins. 4554 rotation: Math.PI * -0.5, 4555 4556 // The total circumference of the chart. 4557 circumference: Math.PI * 2.0, 4558 4559 // Need to override these to give a nice default 4560 tooltips: { 4561 callbacks: { 4562 title: function () { 4563 return ''; 4564 }, 4565 label: function (tooltipItem, data) { 4566 var dataLabel = data.labels[tooltipItem.index]; 4567 var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; 4568 4569 if (helpers$1.isArray(dataLabel)) { 4570 // show value on first line of multiline label 4571 // need to clone because we are changing the value 4572 dataLabel = dataLabel.slice(); 4573 dataLabel[0] += value; 4574 } else { 4575 dataLabel += value; 4576 } 4577 4578 return dataLabel; 4579 } 4580 } 4581 } 4582 }); 4583 4584 var controller_doughnut = core_datasetController.extend({ 4585 4586 dataElementType: elements.Arc, 4587 4588 linkScales: helpers$1.noop, 4589 4590 // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly 4591 getRingIndex: function (datasetIndex) { 4592 var ringIndex = 0; 4593 4594 for (var j = 0; j < datasetIndex; ++j) { 4595 if (this.chart.isDatasetVisible(j)) { 4596 ++ringIndex; 4597 } 4598 } 4599 4600 return ringIndex; 4601 }, 4602 4603 update: function (reset) { 4604 var me = this; 4605 var chart = me.chart; 4606 var chartArea = chart.chartArea; 4607 var opts = chart.options; 4608 var availableWidth = chartArea.right - chartArea.left; 4609 var availableHeight = chartArea.bottom - chartArea.top; 4610 var minSize = Math.min(availableWidth, availableHeight); 4611 var offset = {x: 0, y: 0}; 4612 var meta = me.getMeta(); 4613 var arcs = meta.data; 4614 var cutoutPercentage = opts.cutoutPercentage; 4615 var circumference = opts.circumference; 4616 var chartWeight = me._getRingWeight(me.index); 4617 var i, ilen; 4618 4619 // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc 4620 if (circumference < Math.PI * 2.0) { 4621 var startAngle = opts.rotation % (Math.PI * 2.0); 4622 startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); 4623 var endAngle = startAngle + circumference; 4624 var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; 4625 var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; 4626 var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); 4627 var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); 4628 var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); 4629 var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); 4630 var cutout = cutoutPercentage / 100.0; 4631 var min = { 4632 x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), 4633 y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout)) 4634 }; 4635 var max = { 4636 x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), 4637 y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout)) 4638 }; 4639 var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; 4640 minSize = Math.min(availableWidth / size.width, availableHeight / size.height); 4641 offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; 4642 } 4643 4644 for (i = 0, ilen = arcs.length; i < ilen; ++i) { 4645 arcs[i]._options = me._resolveElementOptions(arcs[i], i); 4646 } 4647 4648 chart.borderWidth = me.getMaxBorderWidth(); 4649 chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); 4650 chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
vendor: 13,887 bytes, lines 4651-5071
4651 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1); 4652 chart.offsetX = offset.x * chart.outerRadius; 4653 chart.offsetY = offset.y * chart.outerRadius; 4654 4655 meta.total = me.calculateTotal(); 4656 4657 me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index); 4658 me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0); 4659 4660 for (i = 0, ilen = arcs.length; i < ilen; ++i) { 4661 me.updateElement(arcs[i], i, reset); 4662 } 4663 }, 4664 4665 updateElement: function (arc, index, reset) { 4666 var me = this; 4667 var chart = me.chart; 4668 var chartArea = chart.chartArea; 4669 var opts = chart.options; 4670 var animationOpts = opts.animation; 4671 var centerX = (chartArea.left + chartArea.right) / 2; 4672 var centerY = (chartArea.top + chartArea.bottom) / 2; 4673 var startAngle = opts.rotation; // non reset case handled later 4674 var endAngle = opts.rotation; // non reset case handled later 4675 var dataset = me.getDataset(); 4676 var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)); 4677 var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; 4678 var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; 4679 var options = arc._options || {}; 4680 4681 helpers$1.extend(arc, { 4682 // Utility 4683 _datasetIndex: me.index, 4684 _index: index, 4685 4686 // Desired view properties 4687 _model: { 4688 backgroundColor: options.backgroundColor, 4689 borderColor: options.borderColor, 4690 borderWidth: options.borderWidth, 4691 borderAlign: options.borderAlign, 4692 x: centerX + chart.offsetX, 4693 y: centerY + chart.offsetY, 4694 startAngle: startAngle, 4695 endAngle: endAngle, 4696 circumference: circumference, 4697 outerRadius: outerRadius, 4698 innerRadius: innerRadius, 4699 label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) 4700 } 4701 }); 4702 4703 var model = arc._model; 4704 4705 // Set correct angles if not resetting 4706 if (!reset || !animationOpts.animateRotate) { 4707 if (index === 0) { 4708 model.startAngle = opts.rotation; 4709 } else { 4710 model.startAngle = me.getMeta().data[index - 1]._model.endAngle; 4711 } 4712 4713 model.endAngle = model.startAngle + model.circumference; 4714 } 4715 4716 arc.pivot(); 4717 }, 4718 4719 calculateTotal: function () { 4720 var dataset = this.getDataset(); 4721 var meta = this.getMeta(); 4722 var total = 0; 4723 var value; 4724 4725 helpers$1.each(meta.data, function (element, index) { 4726 value = dataset.data[index]; 4727 if (!isNaN(value) && !element.hidden) { 4728 total += Math.abs(value); 4729 } 4730 }); 4731 4732 /* if (total === 0) { 4733 total = NaN; 4734 }*/ 4735 4736 return total; 4737 }, 4738 4739 calculateCircumference: function (value) { 4740 var total = this.getMeta().total; 4741 if (total > 0 && !isNaN(value)) { 4742 return (Math.PI * 2.0) * (Math.abs(value) / total); 4743 } 4744 return 0; 4745 }, 4746 4747 // gets the max border or hover width to properly scale pie charts 4748 getMaxBorderWidth: function (arcs) { 4749 var me = this; 4750 var max = 0; 4751 var chart = me.chart; 4752 var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth; 4753 4754 if (!arcs) { 4755 // Find the outmost visible dataset 4756 for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { 4757 if (chart.isDatasetVisible(i)) { 4758 meta = chart.getDatasetMeta(i); 4759 arcs = meta.data; 4760 if (i !== me.index) { 4761 controller = meta.controller; 4762 } 4763 break; 4764 } 4765 } 4766 } 4767 4768 if (!arcs) { 4769 return 0; 4770 } 4771 4772 for (i = 0, ilen = arcs.length; i < ilen; ++i) { 4773 arc = arcs[i]; 4774 options = controller ? controller._resolveElementOptions(arc, i) : arc._options; 4775 if (options.borderAlign !== 'inner') { 4776 borderWidth = options.borderWidth; 4777 hoverWidth = options.hoverBorderWidth; 4778 4779 max = borderWidth > max ? borderWidth : max; 4780 max = hoverWidth > max ? hoverWidth : max; 4781 } 4782 } 4783 return max; 4784 }, 4785 4786 /** 4787 * @protected 4788 */ 4789 setHoverStyle: function (arc) { 4790 var model = arc._model; 4791 var options = arc._options; 4792 var getHoverColor = helpers$1.getHoverColor; 4793 4794 arc.$previousStyle = { 4795 backgroundColor: model.backgroundColor, 4796 borderColor: model.borderColor, 4797 borderWidth: model.borderWidth, 4798 }; 4799 4800 model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); 4801 model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor)); 4802 model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth); 4803 }, 4804 4805 /** 4806 * @private 4807 */ 4808 _resolveElementOptions: function (arc, index) { 4809 var me = this; 4810 var chart = me.chart; 4811 var dataset = me.getDataset(); 4812 var custom = arc.custom || {}; 4813 var options = chart.options.elements.arc; 4814 var values = {}; 4815 var i, ilen, key; 4816 4817 // Scriptable options 4818 var context = { 4819 chart: chart, 4820 dataIndex: index, 4821 dataset: dataset, 4822 datasetIndex: me.index 4823 }; 4824 4825 var keys = [ 4826 'backgroundColor', 4827 'borderColor', 4828 'borderWidth', 4829 'borderAlign', 4830 'hoverBackgroundColor', 4831 'hoverBorderColor', 4832 'hoverBorderWidth', 4833 ]; 4834 4835 for (i = 0, ilen = keys.length; i < ilen; ++i) { 4836 key = keys[i]; 4837 values[key] = resolve$3([ 4838 custom[key], 4839 dataset[key], 4840 options[key] 4841 ], context, index); 4842 } 4843 4844 return values; 4845 }, 4846 4847 /** 4848 * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly 4849 * @private 4850 */ 4851 _getRingWeightOffset: function (datasetIndex) { 4852 var ringWeightOffset = 0; 4853 4854 for (var i = 0; i < datasetIndex; ++i) { 4855 if (this.chart.isDatasetVisible(i)) { 4856 ringWeightOffset += this._getRingWeight(i); 4857 } 4858 } 4859 4860 return ringWeightOffset; 4861 }, 4862 4863 /** 4864 * @private 4865 */ 4866 _getRingWeight: function (dataSetIndex) { 4867 return Math.max(valueOrDefault$4(this.chart.data.datasets[dataSetIndex].weight, 1), 0); 4868 }, 4869 4870 /** 4871 * Returns the sum of all visibile data set weights. This value can be 0. 4872 * @private 4873 */ 4874 _getVisibleDatasetWeightTotal: function () { 4875 return this._getRingWeightOffset(this.chart.data.datasets.length); 4876 } 4877 }); 4878 4879 core_defaults._set('horizontalBar', { 4880 hover: { 4881 mode: 'index', 4882 axis: 'y' 4883 }, 4884 4885 scales: { 4886 xAxes: [{ 4887 type: 'linear', 4888 position: 'bottom' 4889 }], 4890 4891 yAxes: [{ 4892 type: 'category', 4893 position: 'left', 4894 categoryPercentage: 0.8, 4895 barPercentage: 0.9, 4896 offset: true, 4897 gridLines: { 4898 offsetGridLines: true 4899 } 4900 }] 4901 }, 4902 4903 elements: { 4904 rectangle: { 4905 borderSkipped: 'left' 4906 } 4907 }, 4908 4909 tooltips: { 4910 mode: 'index', 4911 axis: 'y' 4912 } 4913 }); 4914 4915 var controller_horizontalBar = controller_bar.extend({ 4916 /** 4917 * @private 4918 */ 4919 _getValueScaleId: function () { 4920 return this.getMeta().xAxisID; 4921 }, 4922 4923 /** 4924 * @private 4925 */ 4926 _getIndexScaleId: function () { 4927 return this.getMeta().yAxisID; 4928 } 4929 }); 4930 4931 var valueOrDefault$5 = helpers$1.valueOrDefault; 4932 var resolve$4 = helpers$1.options.resolve; 4933 var isPointInArea = helpers$1.canvas._isPointInArea; 4934 4935 core_defaults._set('line', { 4936 showLines: true, 4937 spanGaps: false, 4938 4939 hover: { 4940 mode: 'label' 4941 }, 4942 4943 scales: { 4944 xAxes: [{ 4945 type: 'category', 4946 id: 'x-axis-0' 4947 }], 4948 yAxes: [{ 4949 type: 'linear', 4950 id: 'y-axis-0' 4951 }] 4952 } 4953 }); 4954 4955 function lineEnabled(dataset, options) { 4956 return valueOrDefault$5(dataset.showLine, options.showLines); 4957 } 4958 4959 var controller_line = core_datasetController.extend({ 4960 4961 datasetElementType: elements.Line, 4962 4963 dataElementType: elements.Point, 4964 4965 update: function (reset) { 4966 var me = this; 4967 var meta = me.getMeta(); 4968 var line = meta.dataset; 4969 var points = meta.data || []; 4970 var scale = me.getScaleForId(meta.yAxisID); 4971 var dataset = me.getDataset(); 4972 var showLine = lineEnabled(dataset, me.chart.options); 4973 var i, ilen; 4974 4975 // Update Line 4976 if (showLine) { 4977 // Compatibility: If the properties are defined with only the old name, use those values 4978 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { 4979 dataset.lineTension = dataset.tension; 4980 } 4981 4982 // Utility 4983 line._scale = scale; 4984 line._datasetIndex = me.index; 4985 // Data 4986 line._children = points; 4987 // Model 4988 line._model = me._resolveLineOptions(line); 4989 4990 line.pivot(); 4991 } 4992 4993 // Update Points 4994 for (i = 0, ilen = points.length; i < ilen; ++i) { 4995 me.updateElement(points[i], i, reset); 4996 } 4997 4998 if (showLine && line._model.tension !== 0) { 4999 me.updateBezierControlPoints(); 5000 } 5001 5002 // Now pivot the point for animation 5003 for (i = 0, ilen = points.length; i < ilen; ++i) { 5004 points[i].pivot(); 5005 } 5006 }, 5007 5008 updateElement: function (point, index, reset) { 5009 var me = this; 5010 var meta = me.getMeta(); 5011 var custom = point.custom || {}; 5012 var dataset = me.getDataset(); 5013 var datasetIndex = me.index; 5014 var value = dataset.data[index]; 5015 var yScale = me.getScaleForId(meta.yAxisID); 5016 var xScale = me.getScaleForId(meta.xAxisID); 5017 var lineModel = meta.dataset._model; 5018 var x, y; 5019 5020 var options = me._resolvePointOptions(point, index); 5021 5022 x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); 5023 y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); 5024 5025 // Utility 5026 point._xScale = xScale; 5027 point._yScale = yScale; 5028 point._options = options; 5029 point._datasetIndex = datasetIndex; 5030 point._index = index; 5031 5032 // Desired view properties 5033 point._model = { 5034 x: x, 5035 y: y, 5036 skip: custom.skip || isNaN(x) || isNaN(y), 5037 // Appearance 5038 radius: options.radius, 5039 pointStyle: options.pointStyle, 5040 rotation: options.rotation, 5041 backgroundColor: options.backgroundColor, 5042 borderColor: options.borderColor, 5043 borderWidth: options.borderWidth, 5044 tension: valueOrDefault$5(custom.tension, lineModel ? lineModel.tension : 0), 5045 steppedLine: lineModel ? lineModel.steppedLine : false, 5046 // Tooltip 5047 hitRadius: options.hitRadius 5048 }; 5049 }, 5050 5051 /** 5052 * @private 5053 */ 5054 _resolvePointOptions: function (element, index) { 5055 var me = this; 5056 var chart = me.chart; 5057 var dataset = chart.data.datasets[me.index]; 5058 var custom = element.custom || {}; 5059 var options = chart.options.elements.point; 5060 var values = {}; 5061 var i, ilen, key; 5062 5063 // Scriptable options 5064 var context = { 5065 chart: chart, 5066 dataIndex: index, 5067 dataset: dataset, 5068 datasetIndex: me.index 5069 }; 5070 5071 var ELEMENT_OPTIONS = {
5072 backgroundColor: 'pointBackgroundColor', 5073 borderColor: 'pointBorderColor', 5074 borderWidth: 'pointBorderWidth', 5075 hitRadius: 'pointHitRadius', 5076 hoverBackgroundColor: 'pointHoverBackgroundColor', 5077 hoverBorderColor: 'pointHoverBorderColor', 5078 hoverBorderWidth: 'pointHoverBorderWidth', 5079 hoverRadius: 'pointHoverRadius', 5080 pointStyle: 'pointStyle', 5081 radius: 'pointRadius', 5082 rotation: 'pointRotation' 5083 }; 5084 var keys = Object.keys(ELEMENT_OPTIONS); 5085 5086 for (i = 0, ilen = keys.length; i < ilen; ++i) { 5087 key = keys[i]; 5088 values[key] = resolve$4([ 5089 custom[key], 5090 dataset[ELEMENT_OPTIONS[key]], 5091 dataset[key], 5092 options[key] 5093 ], context, index); 5094 } 5095 5096 return values; 5097 }, 5098 5099 /** 5100 * @private 5101 */ 5102 _resolveLineOptions: function (element) { 5103 var me = this; 5104 var chart = me.chart; 5105 var dataset = chart.data.datasets[me.index]; 5106 var custom = element.custom || {}; 5107 var options = chart.options; 5108 var elementOptions = options.elements.line; 5109 var values = {}; 5110 var i, ilen, key; 5111 5112 var keys = [ 5113 'backgroundColor', 5114 'borderWidth', 5115 'borderColor', 5116 'borderCapStyle', 5117 'borderDash', 5118 'borderDashOffset', 5119 'borderJoinStyle', 5120 'fill', 5121 'cubicInterpolationMode' 5122 ]; 5123 5124 for (i = 0, ilen = keys.length; i < ilen; ++i) { 5125 key = keys[i]; 5126 values[key] = resolve$4([ 5127 custom[key], 5128 dataset[key], 5129 elementOptions[key] 5130 ]); 5131 } 5132 5133 // The default behavior of lines is to break at null values, according 5134 // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 5135 // This option gives lines the ability to span gaps 5136 values.spanGaps = valueOrDefault$5(dataset.spanGaps, options.spanGaps); 5137 values.tension = valueOrDefault$5(dataset.lineTension, elementOptions.tension); 5138 values.steppedLine = resolve$4([custom.steppedLine, dataset.steppedLine, elementOptions.stepped]); 5139 5140 return values; 5141 }, 5142 5143 calculatePointY: function (value, index, datasetIndex) { 5144 var me = this; 5145 var chart = me.chart; 5146 var meta = me.getMeta(); 5147 var yScale = me.getScaleForId(meta.yAxisID); 5148 var sumPos = 0; 5149 var sumNeg = 0; 5150 var i, ds, dsMeta; 5151 5152 if (yScale.options.stacked) { 5153 for (i = 0; i < datasetIndex; i++) { 5154 ds = chart.data.datasets[i]; 5155 dsMeta = chart.getDatasetMeta(i); 5156 if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { 5157 var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); 5158 if (stackedRightValue < 0) { 5159 sumNeg += stackedRightValue || 0; 5160 } else { 5161 sumPos += stackedRightValue || 0; 5162 } 5163 } 5164 } 5165 5166 var rightValue = Number(yScale.getRightValue(value)); 5167 if (rightValue < 0) { 5168 return yScale.getPixelForValue(sumNeg + rightValue); 5169 } 5170 return yScale.getPixelForValue(sumPos + rightValue); 5171 } 5172 5173 return yScale.getPixelForValue(value); 5174 }, 5175 5176 updateBezierControlPoints: function () { 5177 var me = this; 5178 var chart = me.chart; 5179 var meta = me.getMeta(); 5180 var lineModel = meta.dataset._model; 5181 var area = chart.chartArea; 5182 var points = meta.data || []; 5183 var i, ilen, model, controlPoints; 5184 5185 // Only consider points that are drawn in case the spanGaps option is used 5186 if (lineModel.spanGaps) { 5187 points = points.filter(function (pt) { 5188 return !pt._model.skip; 5189 }); 5190 } 5191 5192 function capControlPoint(pt, min, max) { 5193 return Math.max(Math.min(pt, max), min); 5194 } 5195 5196 if (lineModel.cubicInterpolationMode === 'monotone') { 5197 helpers$1.splineCurveMonotone(points); 5198 } else { 5199 for (i = 0, ilen = points.length; i < ilen; ++i) { 5200 model = points[i]._model; 5201 controlPoints = helpers$1.splineCurve( 5202 helpers$1.previousItem(points, i)._model, 5203 model, 5204 helpers$1.nextItem(points, i)._model, 5205 lineModel.tension 5206 ); 5207 model.controlPointPreviousX = controlPoints.previous.x; 5208 model.controlPointPreviousY = controlPoints.previous.y; 5209 model.controlPointNextX = controlPoints.next.x; 5210 model.controlPointNextY = controlPoints.next.y; 5211 } 5212 } 5213 5214 if (chart.options.elements.line.capBezierPoints) { 5215 for (i = 0, ilen = points.length; i < ilen; ++i) { 5216 model = points[i]._model; 5217 if (isPointInArea(model, area)) { 5218 if (i > 0 && isPointInArea(points[i - 1]._model, area)) { 5219 model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); 5220 model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); 5221 } 5222 if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) { 5223 model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); 5224 model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); 5225 } 5226 } 5227 } 5228 } 5229 }, 5230 5231 draw: function () { 5232 var me = this; 5233 var chart = me.chart; 5234 var meta = me.getMeta(); 5235 var points = meta.data || []; 5236 var area = chart.chartArea; 5237 var ilen = points.length; 5238 var halfBorderWidth; 5239 var i = 0; 5240 5241 if (lineEnabled(me.getDataset(), chart.options)) { 5242 halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2; 5243 5244 helpers$1.canvas.clipArea(chart.ctx, { 5245 left: area.left, 5246 right: area.right, 5247 top: area.top - halfBorderWidth, 5248 bottom: area.bottom + halfBorderWidth 5249 }); 5250 5251 meta.dataset.draw(); 5252 5253 helpers$1.canvas.unclipArea(chart.ctx); 5254 } 5255 5256 // Draw the points 5257 for (; i < ilen; ++i) { 5258 points[i].draw(area); 5259 } 5260 }, 5261 5262 /** 5263 * @protected 5264 */ 5265 setHoverStyle: function (point) { 5266 var model = point._model; 5267 var options = point._options; 5268 var getHoverColor = helpers$1.getHoverColor; 5269 5270 point.$previousStyle = { 5271 backgroundColor: model.backgroundColor, 5272 borderColor: model.borderColor, 5273 borderWidth: model.borderWidth, 5274 radius: model.radius 5275 }; 5276 5277 model.backgroundColor = valueOrDefault$5(options.hoverBackgroun
vendor: 39,011 bytes, lines 5277-6339
5277dColor, getHoverColor(options.backgroundColor)); 5278 model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor)); 5279 model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth); 5280 model.radius = valueOrDefault$5(options.hoverRadius, options.radius); 5281 }, 5282 }); 5283 5284 var resolve$5 = helpers$1.options.resolve; 5285 5286 core_defaults._set('polarArea', { 5287 scale: { 5288 type: 'radialLinear', 5289 angleLines: { 5290 display: false 5291 }, 5292 gridLines: { 5293 circular: true 5294 }, 5295 pointLabels: { 5296 display: false 5297 }, 5298 ticks: { 5299 beginAtZero: true 5300 } 5301 }, 5302 5303 // Boolean - Whether to animate the rotation of the chart 5304 animation: { 5305 animateRotate: true, 5306 animateScale: true 5307 }, 5308 5309 startAngle: -0.5 * Math.PI, 5310 legendCallback: function (chart) { 5311 var text = []; 5312 text.push('<ul class="' + chart.id + '-legend">'); 5313 5314 var data = chart.data; 5315 var datasets = data.datasets; 5316 var labels = data.labels; 5317 5318 if (datasets.length) { 5319 for (var i = 0; i < datasets[0].data.length; ++i) { 5320 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); 5321 if (labels[i]) { 5322 text.push(labels[i]); 5323 } 5324 text.push('</li>'); 5325 } 5326 } 5327 5328 text.push('</ul>'); 5329 return text.join(''); 5330 }, 5331 legend: { 5332 labels: { 5333 generateLabels: function (chart) { 5334 var data = chart.data; 5335 if (data.labels.length && data.datasets.length) { 5336 return data.labels.map(function (label, i) { 5337 var meta = chart.getDatasetMeta(0); 5338 var ds = data.datasets[0]; 5339 var arc = meta.data[i]; 5340 var custom = arc.custom || {}; 5341 var arcOpts = chart.options.elements.arc; 5342 var fill = resolve$5([custom.backgroundColor, ds.backgroundColor, arcOpts.backgroundColor], undefined, i); 5343 var stroke = resolve$5([custom.borderColor, ds.borderColor, arcOpts.borderColor], undefined, i); 5344 var bw = resolve$5([custom.borderWidth, ds.borderWidth, arcOpts.borderWidth], undefined, i); 5345 5346 return { 5347 text: label, 5348 fillStyle: fill, 5349 strokeStyle: stroke, 5350 lineWidth: bw, 5351 hidden: isNaN(ds.data[i]) || meta.data[i].hidden, 5352 5353 // Extra data used for toggling the correct item 5354 index: i 5355 }; 5356 }); 5357 } 5358 return []; 5359 } 5360 }, 5361 5362 onClick: function (e, legendItem) { 5363 var index = legendItem.index; 5364 var chart = this.chart; 5365 var i, ilen, meta; 5366 5367 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { 5368 meta = chart.getDatasetMeta(i); 5369 meta.data[index].hidden = !meta.data[index].hidden; 5370 } 5371 5372 chart.update(); 5373 } 5374 }, 5375 5376 // Need to override these to give a nice default 5377 tooltips: { 5378 callbacks: { 5379 title: function () { 5380 return ''; 5381 }, 5382 label: function (item, data) { 5383 return data.labels[item.index] + ': ' + item.yLabel; 5384 } 5385 } 5386 } 5387 }); 5388 5389 var controller_polarArea = core_datasetController.extend({ 5390 5391 dataElementType: elements.Arc, 5392 5393 linkScales: helpers$1.noop, 5394 5395 update: function (reset) { 5396 var me = this; 5397 var dataset = me.getDataset(); 5398 var meta = me.getMeta(); 5399 var start = me.chart.options.startAngle || 0; 5400 var starts = me._starts = []; 5401 var angles = me._angles = []; 5402 var arcs = meta.data; 5403 var i, ilen, angle; 5404 5405 me._updateRadius(); 5406 5407 meta.count = me.countVisibleElements(); 5408 5409 for (i = 0, ilen = dataset.data.length; i < ilen; i++) { 5410 starts[i] = start; 5411 angle = me._computeAngle(i); 5412 angles[i] = angle; 5413 start += angle; 5414 } 5415 5416 for (i = 0, ilen = arcs.length; i < ilen; ++i) { 5417 arcs[i]._options = me._resolveElementOptions(arcs[i], i); 5418 me.updateElement(arcs[i], i, reset); 5419 } 5420 }, 5421 5422 /** 5423 * @private 5424 */ 5425 _updateRadius: function () { 5426 var me = this; 5427 var chart = me.chart; 5428 var chartArea = chart.chartArea; 5429 var opts = chart.options; 5430 var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); 5431 5432 chart.outerRadius = Math.max(minSize / 2, 0); 5433 chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); 5434 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); 5435 5436 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); 5437 me.innerRadius = me.outerRadius - chart.radiusLength; 5438 }, 5439 5440 updateElement: function (arc, index, reset) { 5441 var me = this; 5442 var chart = me.chart; 5443 var dataset = me.getDataset(); 5444 var opts = chart.options; 5445 var animationOpts = opts.animation; 5446 var scale = chart.scale; 5447 var labels = chart.data.labels; 5448 5449 var centerX = scale.xCenter; 5450 var centerY = scale.yCenter; 5451 5452 // var negHalfPI = -0.5 * Math.PI; 5453 var datasetStartAngle = opts.startAngle; 5454 var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); 5455 var startAngle = me._starts[index]; 5456 var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); 5457 5458 var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); 5459 var options = arc._options || {}; 5460 5461 helpers$1.extend(arc, { 5462 // Utility 5463 _datasetIndex: me.index, 5464 _index: index, 5465 _scale: scale, 5466 5467 // Desired view properties 5468 _model: { 5469 backgroundColor: options.backgroundColor, 5470 borderColor: options.borderColor, 5471 borderWidth: options.borderWidth, 5472 borderAlign: options.borderAlign, 5473 x: centerX, 5474 y: centerY, 5475 innerRadius: 0, 5476 outerRadius: reset ? resetRadius : distance, 5477 startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, 5478 endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, 5479 label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index]) 5480 } 5481 }); 5482 5483 arc.pivot(); 5484 }, 5485 5486 countVisibleElements: function () { 5487 var dataset = this.getDataset(); 5488 var meta = this.getMeta(); 5489 var count = 0; 5490 5491 helpers$1.each(meta.data, function (element, index) { 5492 if (!isNaN(dataset.data[index]) && !element.hidden) { 5493 count++; 5494 } 5495 }); 5496 5497 return count; 5498 }, 5499 5500 /** 5501 * @protected 5502 */ 5503 setHoverStyle: function (arc) { 5504 var model = arc._model; 5505 var options = arc._options; 5506 var getHoverColor = helpers$1.getHoverColor; 5507 var valueOrDefault = helpers$1.valueOrDefault; 5508 5509 arc.$previousStyle = { 5510 backgroundColor: model.backgroundColor, 5511 borderColor: model.borderColor, 5512 borderWidth: model.borderWidth, 5513 }; 5514 5515 model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); 5516 model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor)); 5517 model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth); 5518 }, 5519 5520 /** 5521 * @private 5522 */ 5523 _resolveElementOptions: function (arc, index) { 5524 var me = this; 5525 var chart = me.chart; 5526 var dataset = me.getDataset(); 5527 var custom = arc.custom || {}; 5528 var options = chart.options.elements.arc; 5529 var values = {}; 5530 var i, ilen, key; 5531 5532 // Scriptable options 5533 var context = { 5534 chart: chart, 5535 dataIndex: index, 5536 dataset: dataset, 5537 datasetIndex: me.index 5538 }; 5539 5540 var keys = [ 5541 'backgroundColor', 5542 'borderColor', 5543 'borderWidth', 5544 'borderAlign', 5545 'hoverBackgroundColor', 5546 'hoverBorderColor', 5547 'hoverBorderWidth', 5548 ]; 5549 5550 for (i = 0, ilen = keys.length; i < ilen; ++i) { 5551 key = keys[i]; 5552 values[key] = resolve$5([ 5553 custom[key], 5554 dataset[key], 5555 options[key] 5556 ], context, index); 5557 } 5558 5559 return values; 5560 }, 5561 5562 /** 5563 * @private 5564 */ 5565 _computeAngle: function (index) { 5566 var me = this; 5567 var count = this.getMeta().count; 5568 var dataset = me.getDataset(); 5569 var meta = me.getMeta(); 5570 5571 if (isNaN(dataset.data[index]) || meta.data[index].hidden) { 5572 return 0; 5573 } 5574 5575 // Scriptable options 5576 var context = { 5577 chart: me.chart, 5578 dataIndex: index, 5579 dataset: dataset, 5580 datasetIndex: me.index 5581 }; 5582 5583 return resolve$5([ 5584 me.chart.options.elements.arc.angle, 5585 (2 * Math.PI) / count 5586 ], context, index); 5587 } 5588 }); 5589 5590 core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut)); 5591 core_defaults._set('pie', { 5592 cutoutPercentage: 0 5593 }); 5594 5595// Pie charts are Doughnut chart with different defaults 5596 var controller_pie = controller_doughnut; 5597 5598 var valueOrDefault$6 = helpers$1.valueOrDefault; 5599 var resolve$6 = helpers$1.options.resolve; 5600 5601 core_defaults._set('radar', { 5602 scale: { 5603 type: 'radialLinear' 5604 }, 5605 elements: { 5606 line: { 5607 tension: 0 // no bezier in radar 5608 } 5609 } 5610 }); 5611 5612 var controller_radar = core_datasetController.extend({ 5613 5614 datasetElementType: elements.Line, 5615 5616 dataElementType: elements.Point, 5617 5618 linkScales: helpers$1.noop, 5619 5620 update: function (reset) { 5621 var me = this; 5622 var meta = me.getMeta(); 5623 var line = meta.dataset; 5624 var points = meta.data || []; 5625 var scale = me.chart.scale; 5626 var dataset = me.getDataset(); 5627 var i, ilen; 5628 5629 // Compatibility: If the properties are defined with only the old name, use those values 5630 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { 5631 dataset.lineTension = dataset.tension; 5632 } 5633 5634 // Utility 5635 line._scale = scale; 5636 line._datasetIndex = me.index; 5637 // Data 5638 line._children = points; 5639 line._loop = true; 5640 // Model 5641 line._model = me._resolveLineOptions(line); 5642 5643 line.pivot(); 5644 5645 // Update Points 5646 for (i = 0, ilen = points.length; i < ilen; ++i) { 5647 me.updateElement(points[i], i, reset); 5648 } 5649 5650 // Update bezier control points 5651 me.updateBezierControlPoints(); 5652 5653 // Now pivot the point for animation 5654 for (i = 0, ilen = points.length; i < ilen; ++i) { 5655 points[i].pivot(); 5656 } 5657 }, 5658 5659 updateElement: function (point, index, reset) { 5660 var me = this; 5661 var custom = point.custom || {}; 5662 var dataset = me.getDataset(); 5663 var scale = me.chart.scale; 5664 var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); 5665 var options = me._resolvePointOptions(point, index); 5666 var lineModel = me.getMeta().dataset._model; 5667 var x = reset ? scale.xCenter : pointPosition.x; 5668 var y = reset ? scale.yCenter : pointPosition.y; 5669 5670 // Utility 5671 point._scale = scale; 5672 point._options = options; 5673 point._datasetIndex = me.index; 5674 point._index = index; 5675 5676 // Desired view properties 5677 point._model = { 5678 x: x, // value not used in dataset scale, but we want a consistent API between scales 5679 y: y, 5680 skip: custom.skip || isNaN(x) || isNaN(y), 5681 // Appearance 5682 radius: options.radius, 5683 pointStyle: options.pointStyle, 5684 rotation: options.rotation, 5685 backgroundColor: options.backgroundColor, 5686 borderColor: options.borderColor, 5687 borderWidth: options.borderWidth, 5688 tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0), 5689 5690 // Tooltip 5691 hitRadius: options.hitRadius 5692 }; 5693 }, 5694 5695 /** 5696 * @private 5697 */ 5698 _resolvePointOptions: function (element, index) { 5699 var me = this; 5700 var chart = me.chart; 5701 var dataset = chart.data.datasets[me.index]; 5702 var custom = element.custom || {}; 5703 var options = chart.options.elements.point; 5704 var values = {}; 5705 var i, ilen, key; 5706 5707 // Scriptable options 5708 var context = { 5709 chart: chart, 5710 dataIndex: index, 5711 dataset: dataset, 5712 datasetIndex: me.index 5713 }; 5714 5715 var ELEMENT_OPTIONS = { 5716 backgroundColor: 'pointBackgroundColor', 5717 borderColor: 'pointBorderColor', 5718 borderWidth: 'pointBorderWidth', 5719 hitRadius: 'pointHitRadius', 5720 hoverBackgroundColor: 'pointHoverBackgroundColor', 5721 hoverBorderColor: 'pointHoverBorderColor', 5722 hoverBorderWidth: 'pointHoverBorderWidth', 5723 hoverRadius: 'pointHoverRadius', 5724 pointStyle: 'pointStyle', 5725 radius: 'pointRadius', 5726 rotation: 'pointRotation' 5727 }; 5728 var keys = Object.keys(ELEMENT_OPTIONS); 5729 5730 for (i = 0, ilen = keys.length; i < ilen; ++i) { 5731 key = keys[i]; 5732 values[key] = resolve$6([ 5733 custom[key], 5734 dataset[ELEMENT_OPTIONS[key]], 5735 dataset[key], 5736 options[key] 5737 ], context, index); 5738 } 5739 5740 return values; 5741 }, 5742 5743 /** 5744 * @private 5745 */ 5746 _resolveLineOptions: function (element) { 5747 var me = this; 5748 var chart = me.chart; 5749 var dataset = chart.data.datasets[me.index]; 5750 var custom = element.custom || {}; 5751 var options = chart.options.elements.line; 5752 var values = {}; 5753 var i, ilen, key; 5754 5755 var keys = [ 5756 'backgroundColor', 5757 'borderWidth', 5758 'borderColor', 5759 'borderCapStyle', 5760 'borderDash', 5761 'borderDashOffset', 5762 'borderJoinStyle', 5763 'fill' 5764 ]; 5765 5766 for (i = 0, ilen = keys.length; i < ilen; ++i) { 5767 key = keys[i]; 5768 values[key] = resolve$6([ 5769 custom[key], 5770 dataset[key], 5771 options[key] 5772 ]); 5773 } 5774 5775 values.tension = valueOrDefault$6(dataset.lineTension, options.tension); 5776 5777 return values; 5778 }, 5779 5780 updateBezierControlPoints: function () { 5781 var me = this; 5782 var meta = me.getMeta(); 5783 var area = me.chart.chartArea; 5784 var points = meta.data || []; 5785 var i, ilen, model, controlPoints; 5786 5787 function capControlPoint(pt, min, max) { 5788 return Math.max(Math.min(pt, max), min); 5789 } 5790 5791 for (i = 0, ilen = points.length; i < ilen; ++i) { 5792 model = points[i]._model; 5793 controlPoints = helpers$1.splineCurve( 5794 helpers$1.previousItem(points, i, true)._model, 5795 model, 5796 helpers$1.nextItem(points, i, true)._model, 5797 model.tension 5798 ); 5799 5800 // Prevent the bezier going outside of the bounds of the graph 5801 model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); 5802 model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); 5803 model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); 5804 model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); 5805 } 5806 }, 5807 5808 setHoverStyle: function (point) { 5809 var model = point._model; 5810 var options = point._options; 5811 var getHoverColor = helpers$1.getHoverColor; 5812 5813 point.$previousStyle = { 5814 backgroundColor: model.backgroundColor, 5815 borderColor: model.borderColor, 5816 borderWidth: model.borderWidth, 5817 radius: model.radius 5818 }; 5819 5820 model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); 5821 model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor)); 5822 model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth); 5823 model.radius = valueOrDefault$6(options.hoverRadius, options.radius); 5824 } 5825 }); 5826 5827 core_defaults._set('scatter', { 5828 hover: { 5829 mode: 'single' 5830 }, 5831 5832 scales: { 5833 xAxes: [{ 5834 id: 'x-axis-1', // need an ID so datasets can reference the scale 5835 type: 'linear', // scatter should not use a category axis 5836 position: 'bottom' 5837 }], 5838 yAxes: [{ 5839 id: 'y-axis-1', 5840 type: 'linear', 5841 position: 'left' 5842 }] 5843 }, 5844 5845 showLines: false, 5846 5847 tooltips: { 5848 callbacks: { 5849 title: function () { 5850 return ''; // doesn't make sense for scatter since data are formatted as a point 5851 }, 5852 label: function (item) { 5853 return '(' + item.xLabel + ', ' + item.yLabel + ')'; 5854 } 5855 } 5856 } 5857 }); 5858 5859// Scatter charts use line controllers 5860 var controller_scatter = controller_line; 5861 5862// NOTE export a map in which the key represents the controller type, not 5863// the class, and so must be CamelCase in order to be correctly retrieved 5864// by the controller in core.controller.js (`controllers[meta.type]`). 5865 5866 var controllers = { 5867 bar: controller_bar, 5868 bubble: controller_bubble, 5869 doughnut: controller_doughnut, 5870 horizontalBar: controller_horizontalBar, 5871 line: controller_line, 5872 polarArea: controller_polarArea, 5873 pie: controller_pie, 5874 radar: controller_radar, 5875 scatter: controller_scatter 5876 }; 5877 5878 /** 5879 * Helper function to get relative position for an event 5880 * @param {Event|IEvent} event - The event to get the position for 5881 * @param {Chart} chart - The chart 5882 * @returns {object} the event position 5883 */ 5884 function getRelativePosition(e, chart) { 5885 if (e.native) { 5886 return { 5887 x: e.x, 5888 y: e.y 5889 }; 5890 } 5891 5892 return helpers$1.getRelativePosition(e, chart); 5893 } 5894 5895 /** 5896 * Helper function to traverse all of the visible elements in the chart 5897 * @param {Chart} chart - the chart 5898 * @param {function} handler - the callback to execute for each visible item 5899 */ 5900 function parseVisibleItems(chart, handler) { 5901 var datasets = chart.data.datasets; 5902 var meta, i, j, ilen, jlen; 5903 5904 for (i = 0, ilen = datasets.length; i < ilen; ++i) { 5905 if (!chart.isDatasetVisible(i)) { 5906 continue; 5907 } 5908 5909 meta = chart.getDatasetMeta(i); 5910 for (j = 0, jlen = meta.data.length; j < jlen; ++j) { 5911 var element = meta.data[j]; 5912 if (!element._view.skip) { 5913 handler(element); 5914 } 5915 } 5916 } 5917 } 5918 5919 /** 5920 * Helper function to get the items that intersect the event position 5921 * @param {ChartElement[]} items - elements to filter 5922 * @param {object} position - the point to be nearest to 5923 * @return {ChartElement[]} the nearest items 5924 */ 5925 function getIntersectItems(chart, position) { 5926 var elements = []; 5927 5928 parseVisibleItems(chart, function (element) { 5929 if (element.inRange(position.x, position.y)) { 5930 elements.push(element); 5931 } 5932 }); 5933 5934 return elements; 5935 } 5936 5937 /** 5938 * Helper function to get the items nearest to the event position considering all visible items in teh chart 5939 * @param {Chart} chart - the chart to look at elements from 5940 * @param {object} position - the point to be nearest to 5941 * @param {boolean} intersect - if true, only consider items that intersect the position 5942 * @param {function} distanceMetric - function to provide the distance between points 5943 * @return {ChartElement[]} the nearest items 5944 */ 5945 function getNearestItems(chart, position, intersect, distanceMetric) { 5946 var minDistance = Number.POSITIVE_INFINITY; 5947 var nearestItems = []; 5948 5949 parseVisibleItems(chart, function (element) { 5950 if (intersect && !element.inRange(position.x, position.y)) { 5951 return; 5952 } 5953 5954 var center = element.getCenterPoint(); 5955 var distance = distanceMetric(position, center); 5956 if (distance < minDistance) { 5957 nearestItems = [element]; 5958 minDistance = distance; 5959 } else if (distance === minDistance) { 5960 // Can have multiple items at the same distance in which case we sort by size 5961 nearestItems.push(element); 5962 } 5963 }); 5964 5965 return nearestItems; 5966 } 5967 5968 /** 5969 * Get a distance metric function for two points based on the 5970 * axis mode setting 5971 * @param {string} axis - the axis mode. x|y|xy 5972 */ 5973 function getDistanceMetricForAxis(axis) { 5974 var useX = axis.indexOf('x') !== -1; 5975 var useY = axis.indexOf('y') !== -1; 5976 5977 return function (pt1, pt2) { 5978 var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; 5979 var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; 5980 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); 5981 }; 5982 } 5983 5984 function indexMode(chart, e, options) { 5985 var position = getRelativePosition(e, chart); 5986 // Default axis for index mode is 'x' to match old behaviour 5987 options.axis = options.axis || 'x'; 5988 var distanceMetric = getDistanceMetricForAxis(options.axis); 5989 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); 5990 var elements = []; 5991 5992 if (!items.length) { 5993 return []; 5994 } 5995 5996 chart.data.datasets.forEach(function (dataset, datasetIndex) { 5997 if (chart.isDatasetVisible(datasetIndex)) { 5998 var meta = chart.getDatasetMeta(datasetIndex); 5999 var element = meta.data[items[0]._index]; 6000 6001 // don't count items that are skipped (null data) 6002 if (element && !element._view.skip) { 6003 elements.push(element); 6004 } 6005 } 6006 }); 6007 6008 return elements; 6009 } 6010 6011 /** 6012 * @interface IInteractionOptions 6013 */ 6014 /** 6015 * If true, only consider items that intersect the point 6016 * @name IInterfaceOptions#boolean 6017 * @type Boolean 6018 */ 6019 6020 /** 6021 * Contains interaction related functions 6022 * @namespace Chart.Interaction 6023 */ 6024 var core_interaction = { 6025 // Helper function for different modes 6026 modes: { 6027 single: function (chart, e) { 6028 var position = getRelativePosition(e, chart); 6029 var elements = []; 6030 6031 parseVisibleItems(chart, function (element) { 6032 if (element.inRange(position.x, position.y)) { 6033 elements.push(element); 6034 return elements; 6035 } 6036 }); 6037 6038 return elements.slice(0, 1); 6039 }, 6040 6041 /** 6042 * @function Chart.Interaction.modes.label 6043 * @deprecated since version 2.4.0 6044 * @todo remove at version 3 6045 * @private 6046 */ 6047 label: indexMode, 6048 6049 /** 6050 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something 6051 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item 6052 * @function Chart.Interaction.modes.index 6053 * @since v2.4.0 6054 * @param {Chart} chart - the chart we are returning items from 6055 * @param {Event} e - the event we are find things at 6056 * @param {IInteractionOptions} options - options to use during interaction 6057 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6058 */ 6059 index: indexMode, 6060 6061 /** 6062 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something 6063 * If the options.intersect is false, we find the nearest item and return the items in that dataset 6064 * @function Chart.Interaction.modes.dataset 6065 * @param {Chart} chart - the chart we are returning items from 6066 * @param {Event} e - the event we are find things at 6067 * @param {IInteractionOptions} options - options to use during interaction 6068 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6069 */ 6070 dataset: function (chart, e, options) { 6071 var position = getRelativePosition(e, chart); 6072 options.axis = options.axis || 'xy'; 6073 var distanceMetric = getDistanceMetricForAxis(options.axis); 6074 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); 6075 6076 if (items.length > 0) { 6077 items = chart.getDatasetMeta(items[0]._datasetIndex).data; 6078 } 6079 6080 return items; 6081 }, 6082 6083 /** 6084 * @function Chart.Interaction.modes.x-axis 6085 * @deprecated since version 2.4.0. Use index mode and intersect == true 6086 * @todo remove at version 3 6087 * @private 6088 */ 6089 'x-axis': function (chart, e) { 6090 return indexMode(chart, e, {intersect: false}); 6091 }, 6092 6093 /** 6094 * Point mode returns all elements that hit test based on the event position 6095 * of the event 6096 * @function Chart.Interaction.modes.intersect 6097 * @param {Chart} chart - the chart we are returning items from 6098 * @param {Event} e - the event we are find things at 6099 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6100 */ 6101 point: function (chart, e) { 6102 var position = getRelativePosition(e, chart); 6103 return getIntersectItems(chart, position); 6104 }, 6105 6106 /** 6107 * nearest mode returns the element closest to the point 6108 * @function Chart.Interaction.modes.intersect 6109 * @param {Chart} chart - the chart we are returning items from 6110 * @param {Event} e - the event we are find things at 6111 * @param {IInteractionOptions} options - options to use 6112 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6113 */ 6114 nearest: function (chart, e, options) { 6115 var position = getRelativePosition(e, chart); 6116 options.axis = options.axis || 'xy'; 6117 var distanceMetric = getDistanceMetricForAxis(options.axis); 6118 return getNearestItems(chart, position, options.intersect, distanceMetric); 6119 }, 6120 6121 /** 6122 * x mode returns the elements that hit-test at the current x coordinate 6123 * @function Chart.Interaction.modes.x 6124 * @param {Chart} chart - the chart we are returning items from 6125 * @param {Event} e - the event we are find things at 6126 * @param {IInteractionOptions} options - options to use 6127 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6128 */ 6129 x: function (chart, e, options) { 6130 var position = getRelativePosition(e, chart); 6131 var items = []; 6132 var intersectsItem = false; 6133 6134 parseVisibleItems(chart, function (element) { 6135 if (element.inXRange(position.x)) { 6136 items.push(element); 6137 } 6138 6139 if (element.inRange(position.x, position.y)) { 6140 intersectsItem = true; 6141 } 6142 }); 6143 6144 // If we want to trigger on an intersect and we don't have any items 6145 // that intersect the position, return nothing 6146 if (options.intersect && !intersectsItem) { 6147 items = []; 6148 } 6149 return items; 6150 }, 6151 6152 /** 6153 * y mode returns the elements that hit-test at the current y coordinate 6154 * @function Chart.Interaction.modes.y 6155 * @param {Chart} chart - the chart we are returning items from 6156 * @param {Event} e - the event we are find things at 6157 * @param {IInteractionOptions} options - options to use 6158 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned 6159 */ 6160 y: function (chart, e, options) { 6161 var position = getRelativePosition(e, chart); 6162 var items = []; 6163 var intersectsItem = false; 6164 6165 parseVisibleItems(chart, function (element) { 6166 if (element.inYRange(position.y)) { 6167 items.push(element); 6168 } 6169 6170 if (element.inRange(position.x, position.y)) { 6171 intersectsItem = true; 6172 } 6173 }); 6174 6175 // If we want to trigger on an intersect and we don't have any items 6176 // that intersect the position, return nothing 6177 if (options.intersect && !intersectsItem) { 6178 items = []; 6179 } 6180 return items; 6181 } 6182 } 6183 }; 6184 6185 function filterByPosition(array, position) { 6186 return helpers$1.where(array, function (v) { 6187 return v.position === position; 6188 }); 6189 } 6190 6191 function sortByWeight(array, reverse) { 6192 array.forEach(function (v, i) { 6193 v._tmpIndex_ = i; 6194 return v; 6195 }); 6196 array.sort(function (a, b) { 6197 var v0 = reverse ? b : a; 6198 var v1 = reverse ? a : b; 6199 return v0.weight === v1.weight ? 6200 v0._tmpIndex_ - v1._tmpIndex_ : 6201 v0.weight - v1.weight; 6202 }); 6203 array.forEach(function (v) { 6204 delete v._tmpIndex_; 6205 }); 6206 } 6207 6208 function findMaxPadding(boxes) { 6209 var top = 0; 6210 var left = 0; 6211 var bottom = 0; 6212 var right = 0; 6213 helpers$1.each(boxes, function (box) { 6214 if (box.getPadding) { 6215 var boxPadding = box.getPadding(); 6216 top = Math.max(top, boxPadding.top); 6217 left = Math.max(left, boxPadding.left); 6218 bottom = Math.max(bottom, boxPadding.bottom); 6219 right = Math.max(right, boxPadding.right); 6220 } 6221 }); 6222 return { 6223 top: top, 6224 left: left, 6225 bottom: bottom, 6226 right: right 6227 }; 6228 } 6229 6230 function addSizeByPosition(boxes, size) { 6231 helpers$1.each(boxes, function (box) { 6232 size[box.position] += box.isHorizontal() ? box.height : box.width; 6233 }); 6234 } 6235 6236 core_defaults._set('global', { 6237 layout: { 6238 padding: { 6239 top: 0, 6240 right: 0, 6241 bottom: 0, 6242 left: 0 6243 } 6244 } 6245 }); 6246 6247 /** 6248 * @interface ILayoutItem 6249 * @prop {string} position - The position of the item in the chart layout. Possible values are 6250 * 'left', 'top', 'right', 'bottom', and 'chartArea' 6251 * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area 6252 * @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down 6253 * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) 6254 * @prop {function} update - Takes two parameters: width and height. Returns size of item 6255 * @prop {function} getPadding - Returns an object with padding on the edges 6256 * @prop {number} width - Width of item. Must be valid after update() 6257 * @prop {number} height - Height of item. Must be valid after update() 6258 * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update 6259 * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update 6260 * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update 6261 * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update 6262 */ 6263 6264// The layout service is very self explanatory. It's responsible for the layout within a chart. 6265// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need 6266// It is this service's responsibility of carrying out that layout. 6267 var core_layouts = { 6268 defaults: {}, 6269 6270 /** 6271 * Register a box to a chart. 6272 * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. 6273 * @param {Chart} chart - the chart to use 6274 * @param {ILayoutItem} item - the item to add to be layed out 6275 */ 6276 addBox: function (chart, item) { 6277 if (!chart.boxes) { 6278 chart.boxes = []; 6279 } 6280 6281 // initialize item with default values 6282 item.fullWidth = item.fullWidth || false; 6283 item.position = item.position || 'top'; 6284 item.weight = item.weight || 0; 6285 6286 chart.boxes.push(item); 6287 }, 6288 6289 /** 6290 * Remove a layoutItem from a chart 6291 * @param {Chart} chart - the chart to remove the box from 6292 * @param {ILayoutItem} layoutItem - the item to remove from the layout 6293 */ 6294 removeBox: function (chart, layoutItem) { 6295 var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; 6296 if (index !== -1) { 6297 chart.boxes.splice(index, 1); 6298 } 6299 }, 6300 6301 /** 6302 * Sets (or updates) options on the given `item`. 6303 * @param {Chart} chart - the chart in which the item lives (or will be added to) 6304 * @param {ILayoutItem} item - the item to configure with the given options 6305 * @param {object} options - the new item options. 6306 */ 6307 configure: function (chart, item, options) { 6308 var props = ['fullWidth', 'position', 'weight']; 6309 var ilen = props.length; 6310 var i = 0; 6311 var prop; 6312 6313 for (; i < ilen; ++i) { 6314 prop = props[i]; 6315 if (options.hasOwnProperty(prop)) { 6316 item[prop] = options[prop]; 6317 } 6318 } 6319 }, 6320 6321 /** 6322 * Fits boxes of the given chart into the given size by having each box measure itself 6323 * then running a fitting algorithm 6324 * @param {Chart} chart - the chart 6325 * @param {number} width - the width to fit into 6326 * @param {number} height - the height to fit into 6327 */ 6328 update: function (chart, width, height) { 6329 if (!chart) { 6330 return; 6331 } 6332 6333 var layoutOptions = chart.options.layout || {}; 6334 var padding = helpers$1.options.toPadding(layoutOptions.padding); 6335 var leftPadding = padding.left; 6336 var rightPadding = padding.right; 6337 var topPadding = padding.top; 6338 var bottomPadding = padding.bottom; 6339
6340 var leftBoxes = filterByPosition(chart.boxes, 'left'); 6341 var rightBoxes = filterByPosition(chart.boxes, 'right'); 6342 var topBoxes = filterByPosition(chart.boxes, 'top'); 6343 var bottomBoxes = filterByPosition(chart.boxes, 'bottom'); 6344 var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea'); 6345 6346 // Sort boxes by weight. A higher weight is further away from the chart area 6347 sortByWeight(leftBoxes, true); 6348 sortByWeight(rightBoxes, false); 6349 sortByWeight(topBoxes, true); 6350 sortByWeight(bottomBoxes, false); 6351 6352 var verticalBoxes = leftBoxes.concat(rightBoxes); 6353 var horizontalBoxes = topBoxes.concat(bottomBoxes); 6354 var outerBoxes = verticalBoxes.concat(horizontalBoxes); 6355 6356 // Essentially we now have any number of boxes on each of the 4 sides. 6357 // Our canvas looks like the following. 6358 // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and 6359 // B1 is the bottom axis 6360 // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays 6361 // These locations are single-box locations only, when trying to register a chartArea location that is already taken, 6362 // an error will be thrown. 6363 // 6364 // |----------------------------------------------------| 6365 // | T1 (Full Width) | 6366 // |----------------------------------------------------| 6367 // | | | T2 | | 6368 // | |----|-------------------------------------|----| 6369 // | | | C1 | | C2 | | 6370 // | | |----| |----| | 6371 // | | | | | 6372 // | L1 | L2 | ChartArea (C0) | R1 | 6373 // | | | | | 6374 // | | |----| |----| | 6375 // | | | C3 | | C4 | | 6376 // | |----|-------------------------------------|----| 6377 // | | | B1 | | 6378 // |----------------------------------------------------| 6379 // | B2 (Full Width) | 6380 // |----------------------------------------------------| 6381 // 6382 // What we do to find the best sizing, we do the following 6383 // 1. Determine the minimum size of the chart area. 6384 // 2. Split the remaining width equally between each vertical axis 6385 // 3. Split the remaining height equally between each horizontal axis 6386 // 4. Give each layout the maximum size it can be. The layout will return it's minimum size 6387 // 5. Adjust the sizes of each axis based on it's minimum reported size. 6388 // 6. Refit each axis 6389 // 7. Position each axis in the final location 6390 // 8. Tell the chart the final location of the chart area 6391 // 9. Tell any axes that overlay the chart area the positions of the chart area 6392 6393 // Step 1 6394 var chartWidth = width - leftPadding - rightPadding; 6395 var chartHeight = height - topPadding - bottomPadding; 6396 var chartAreaWidth = chartWidth / 2; // min 50% 6397 6398 // Step 2 6399 var verticalBoxWidth = (width - chartAreaWidth) / verticalBoxes.length; 6400 6401 // Step 3 6402 // TODO re-limit horizontal axis height (this limit has affected only padding calculation since PR 1837) 6403 // var horizontalBoxHeight = (height - chartAreaHeight) / horizontalBoxes.length; 6404 6405 // Step 4 6406 var maxChartAreaWidth = chartWidth; 6407 var maxChartAreaHeight = chartHeight; 6408 var outerBoxSizes = {top: topPadding, left: leftPadding, bottom: bottomPadding, right: rightPadding}; 6409 var minBoxSizes = []; 6410 var maxPadding; 6411 6412 function getMinimumBoxSize(box) { 6413 var minSize; 6414 var isHorizontal = box.isHorizontal(); 6415 6416 if (isHorizontal) { 6417 minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHe
6417ight / 2); 6418 maxChartAreaHeight -= minSize.height; 6419 } else { 6420 minSize = box.update(verticalBoxWidth, maxChartAreaHeight); 6421 maxChartAreaWidth -= minSize.width; 6422 } 6423 6424 minBoxSizes.push({ 6425 horizontal: isHorizontal, 6426 width: minSize.width, 6427 box: box, 6428 }); 6429 } 6430 6431 helpers$1.each(outerBoxes, getMinimumBoxSize); 6432 6433 // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478) 6434 maxPadding = findMaxPadding(outerBoxes); 6435 6436 // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could 6437 // be if the axes are drawn at their minimum sizes. 6438 // Steps 5 & 6 6439 6440 // Function to fit a box 6441 function fitBox(box) { 6442 var minBoxSize = helpers$1.findNextWhere(minBoxSizes, function (minBox) { 6443 return minBox.box === box; 6444 }); 6445 6446 if (minBoxSize) { 6447 if (minBoxSize.horizontal) { 6448 var scaleMargin = { 6449 left: Math.max(outerBoxSizes.left, maxPadding.left), 6450 right: Math.max(outerBoxSizes.right, maxPadding.right), 6451 top: 0, 6452 bottom: 0 6453 }; 6454 6455 // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends 6456 // on the margin. Sometimes they need to increase in size slightly 6457 box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); 6458 } else { 6459 box.update(minBoxSize.width, maxChartAreaHeight); 6460 } 6461 } 6462 } 6463 6464 // Update, and calculate the left and right margins for the horizontal boxes 6465 helpers$1.each(verticalBoxes, fitBox); 6466 addSizeByPosition(verticalBoxes, outerBoxSizes); 6467 6468 // Set the Left and Right margins for the horizontal boxes 6469 helpers$1.each(horizontalBoxes, fitBox); 6470 addSizeByPosition(horizontalBoxes, outerBoxSizes); 6471 6472 function finalFitVerticalBox(box) { 6473 var minBoxSize = helpers$1.findNextWhere(minBoxSizes, function (minSize) { 6474 return minSize.box === box; 6475 }); 6476 6477 var scaleMargin = { 6478 left: 0, 6479 right: 0, 6480 top: outerBoxSizes.top, 6481 bottom: outerBoxSizes.bottom 6482 }; 6483 6484 if (minBoxSize) { 6485 box.update(minBoxSize.width, maxChartAreaHeight, scaleMargin); 6486 } 6487 } 6488 6489 // Let the left layout know the final margin 6490 helpers$1.each(verticalBoxes, finalFitVerticalBox); 6491 6492 // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) 6493 outerBoxSizes = {top: topPadding, left: leftPadding, bottom: bottomPadding, right: rightPadding}; 6494 addSizeByPosition(outerBoxes, outerBoxSizes); 6495 6496 // We may be adding some padding to account for rotated x axis labels 6497 var leftPaddingAddition = Math.max(maxPadding.left - outerBoxSizes.left, 0); 6498 outerBoxSizes.left += leftPaddingAddition; 6499 outerBoxSizes.right += Math.max(maxPadding.right - outerBoxSizes.right, 0); 6500 6501 var topPaddingAddition = Math.max(maxPadding.top - outerBoxSizes.top, 0); 6502 outerBoxSizes.top += topPaddingAddition; 6503 outerBoxSizes.bottom += Math.max(maxPadding.bottom - outerBoxSizes.bottom, 0); 6504 6505 // Figure out if our chart area changed. This would occur if the dataset layout label rotation 6506 // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do 6507 // without calling `fit` again 6508 var newMaxChartAreaHeight = height - outerBoxSizes.top - outerBoxSizes.bottom; 6509 var newMaxChartAreaWidth = width - outerBoxSizes.left - outerBoxSizes.right; 6510 6511 if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { 6512 helpers$1.each(verticalBoxes, function (box) { 6513 box.height = newMaxChartAreaHeight; 6514 }); 6515 6516 helpers$1.each(horizontalBoxes, function (box) { 6517 if (!box.fullWidth) { 6518 box.width = newMaxChartAreaWidth; 6519 } 6520 }); 6521 6522 maxChartAreaHeight = newMaxChartAreaHeight; 6523 maxChartAreaWidth = newMaxChartAreaWidth; 6524 } 6525 6526 // Step 7 - Position the boxes 6527 var left = leftPadding + leftPaddingAddition; 6528 var top = topPadding + topPaddingAddition; 6529 6530 function placeBox(box) { 6531 if (box.isHorizontal()) { 6532 box.left = box.fullWidth ? leftPadding : outerBoxSizes.left; 6533 box.right = box.fullWidth ? width - rightPadding : outerBoxSizes.left + maxChartAreaWidth; 6534 box.top = top; 6535 box.bottom = top + box.height; 6536 6537 // Move to next point 6538 top = box.bottom; 6539 6540 } else { 6541 6542 box.left = left; 6543 box.right = left + box.width; 6544 box.top = outerBoxSizes.top; 6545 box.bottom = outerBoxSizes.top + maxChartAreaHeight; 6546 6547 // Move to next point 6548 left = box.right; 6549 } 6550 } 6551 6552 helpers$1.each(leftBoxes.concat(topBoxes), placeBox); 6553 6554 // Account for chart width and height 6555 left += maxChartAreaWidth; 6556 top += maxChartAreaHeight; 6557 6558 helpers$1.each(rightBoxes, placeBox); 6559 helpers$1.each(bottomBoxes, placeBox); 6560 6561 // Step 8 6562 chart.chartArea = { 6563 left: outerBoxSizes.left, 6564 top: outerBoxSizes.top, 6565 right: outerBoxSizes.left + maxChartAreaWidth, 6566 bottom: outerBoxSizes.top + maxChartAreaHeight 6567 }; 6568 6569 // Step 9 6570 helpers$1.each(chartAreaBoxes, function (box) { 6571 box.left = chart.chartArea.left; 6572 box.top = chart.chartArea.top; 6573 box.right = chart.chartArea.right; 6574 box.bottom = chart.chartArea.bottom; 6575 6576 box.update(maxChartAreaWidth, maxChartAreaHeight); 6577 }); 6578 } 6579 }; 6580 6581 /** 6582 * Platform fallback implementation (minimal). 6583 * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 6584 */ 6585 6586 var platform_basic = { 6587 acquireContext: function (item) { 6588 if (item && item.canvas) {
6589 // Support for any object associated to a canvas (including a context2d) 6590 item = item.canvas; 6591 } 6592 6593 return item && item.getContext('2d') || null; 6594 } 6595 }; 6596 6597 var platform_dom = "/*\n * DOM element rendering detection\n * https://davidwalsh.name/detect-node-insertion\n */\n@keyframes chartjs-render-animation {\n\tfrom { opacity: 0.99; }\n\tto { opacity: 1; }\n}\n\n.chartjs-render-monitor {\n\tanimation: chartjs-render-animation 0.001s;\n}\n\n/*\n * DOM element resizing detection\n * https://github.com/marcj/css-element-queries\n */\n.chartjs-size-monitor,\n.chartjs-size-monitor-expand,\n.chartjs-size-monitor-shrink {\n\tposition: absolute;\n\tdirection: ltr;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\toverflow: hidden;\n\tpointer-events: none;\n\tvisibility: hidden;\n\tz-index: -1;\n}\n\n.chartjs-size-monitor-expand > div {\n\tposition: absolute;\n\twidth: 1000000px;\n\theight: 1000000px;\n\tleft: 0;\n\ttop: 0;\n}\n\n.chartjs-size-monitor-shrink > div {\n\tposition: absolute;\n\twidth: 200%;\n\theight: 200%;\n\tleft: 0;\n\ttop: 0;\n}\n"; 6598 6599 var platform_dom$1 = /*#__PURE__*/Object.freeze({ 6600 default: platform_dom 6601 }); 6602 6603 function getCjsExportFromNamespace(n) { 6604 return n && n.default || n; 6605 } 6606 6607 var stylesheet = getCjsExportFromNamespace(platform_dom$1); 6608 6609 var EXPANDO_KEY = '$chartjs'; 6610 var CSS_PREFIX = 'chartjs-'; 6611 var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor'; 6612 var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; 6613 var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; 6614 var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; 6615 6616 /** 6617 * DOM event types -> Chart.js event types. 6618 * Note: only events with different types are mapped. 6619 * @see https://developer.mozilla.org/en-US/docs/Web/Events 6620 */ 6621 var EVENT_TYPES = { 6622 touchstart: 'mousedown', 6623 touchmove: 'mousemove', 6624 touchend: 'mouseup', 6625 pointerenter: 'mouseenter', 6626 pointerdown: 'mousedown', 6627 pointermove: 'mousemove', 6628 pointerup: 'mouseup', 6629 pointerleave: 'mouseout', 6630 pointerout: 'mouseout' 6631 }; 6632 6633 /** 6634 * The "used" size is the final value of a dimension property after all calculations have 6635 * been performed. This method uses the computed style of `element` but returns undefined 6636 * if the computed style is not expressed in pixels. That can happen in some cases where 6637 * `element` has a size relative to its parent and this last one is not yet displayed, 6638 * for example because of `display: none` on a parent node. 6639 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value 6640 * @returns {number} Size in pixels or undefined if unknown. 6641 */ 6642 function readUsedSize(element, property) { 6643 var value = helpers$1.getStyle(element, property); 6644 var matches = value && value.match(/^(\d+)(\.\d+)?px$/); 6645 return matches ? Number(matches[1]) : undefined; 6646 } 6647 6648 /** 6649 * Initializes the canvas style and render size without modifying the canvas display size, 6650 * since responsiveness is handled by the controller.resize() method. The config is used 6651 * to determine the aspect ratio to apply in case no explicit height has been specified. 6652 */ 6653 function initCanvas(canvas, config) { 6654 var style = canvas.style; 6655 6656 // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it 6657 // returns null or '' if no explicit value has been set to the canvas attribute. 6658 var renderHeight = canvas.getAttribute('height'); 6659 var renderWidth = canvas.getAttribute('width'); 6660 6661 // Chart.js modifies some canvas values that we want to restore on de
vendor: 129,850 bytes, lines 6661-10203
6661stroy 6662 canvas[EXPANDO_KEY] = { 6663 initial: { 6664 height: renderHeight, 6665 width: renderWidth, 6666 style: { 6667 display: style.display, 6668 height: style.height, 6669 width: style.width 6670 } 6671 } 6672 }; 6673 6674 // Force canvas to display as block to avoid extra space caused by inline 6675 // elements, which would interfere with the responsive resize process. 6676 // https://github.com/chartjs/Chart.js/issues/2538 6677 style.display = style.display || 'block'; 6678 6679 if (renderWidth === null || renderWidth === '') { 6680 var displayWidth = readUsedSize(canvas, 'width'); 6681 if (displayWidth !== undefined) { 6682 canvas.width = displayWidth; 6683 } 6684 } 6685 6686 if (renderHeight === null || renderHeight === '') { 6687 if (canvas.style.height === '') { 6688 // If no explicit render height and style height, let's apply the aspect ratio, 6689 // which one can be specified by the user but also by charts as default option 6690 // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. 6691 canvas.height = canvas.width / (config.options.aspectRatio || 2); 6692 } else { 6693 var displayHeight = readUsedSize(canvas, 'height'); 6694 if (displayWidth !== undefined) { 6695 canvas.height = displayHeight; 6696 } 6697 } 6698 } 6699 6700 return canvas; 6701 } 6702 6703 /** 6704 * Detects support for options object argument in addEventListener. 6705 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support 6706 * @private 6707 */ 6708 var supportsEventListenerOptions = (function () { 6709 var supports = false; 6710 try { 6711 var options = Object.defineProperty({}, 'passive', { 6712 // eslint-disable-next-line getter-return 6713 get: function () { 6714 supports = true; 6715 } 6716 }); 6717 window.addEventListener('e', null, options); 6718 } catch (e) { 6719 // continue regardless of error 6720 } 6721 return supports; 6722 }()); 6723 6724// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. 6725// https://github.com/chartjs/Chart.js/issues/4287 6726 var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; 6727 6728 function addListener(node, type, listener) { 6729 node.addEventListener(type, listener, eventListenerOptions); 6730 } 6731 6732 function removeListener(node, type, listener) { 6733 node.removeEventListener(type, listener, eventListenerOptions); 6734 } 6735 6736 function createEvent(type, chart, x, y, nativeEvent) { 6737 return { 6738 type: type, 6739 chart: chart, 6740 native: nativeEvent || null, 6741 x: x !== undefined ? x : null, 6742 y: y !== undefined ? y : null, 6743 }; 6744 } 6745 6746 function fromNativeEvent(event, chart) { 6747 var type = EVENT_TYPES[event.type] || event.type; 6748 var pos = helpers$1.getRelativePosition(event, chart); 6749 return createEvent(type, chart, pos.x, pos.y, event); 6750 } 6751 6752 function throttled(fn, thisArg) { 6753 var ticking = false; 6754 var args = []; 6755 6756 return function () { 6757 args = Array.prototype.slice.call(arguments); 6758 thisArg = thisArg || this; 6759 6760 if (!ticking) { 6761 ticking = true; 6762 helpers$1.requestAnimFrame.call(window, function () { 6763 ticking = false; 6764 fn.apply(thisArg, args); 6765 }); 6766 } 6767 }; 6768 } 6769 6770 function createDiv(cls) { 6771 var el = document.createElement('div'); 6772 el.className = cls || ''; 6773 return el; 6774 } 6775 6776// Implementation based on https://github.com/marcj/css-element-queries 6777 function createResizer(handler) { 6778 var maxSize = 1000000; 6779 6780 // NOTE(SB) Don't use innerHTML because it could be considered unsafe. 6781 // https://github.com/chartjs/Chart.js/issues/5902 6782 var resizer = createDiv(CSS_SIZE_MONITOR); 6783 var expand = createDiv(CSS_SIZE_MONITOR + '-expand'); 6784 var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink'); 6785 6786 expand.appendChild(createDiv()); 6787 shrink.appendChild(createDiv()); 6788 6789 resizer.appendChild(expand); 6790 resizer.appendChild(shrink); 6791 resizer._reset = function () { 6792 expand.scrollLeft = maxSize; 6793 expand.scrollTop = maxSize; 6794 shrink.scrollLeft = maxSize; 6795 shrink.scrollTop = maxSize; 6796 }; 6797 6798 var onScroll = function () { 6799 resizer._reset(); 6800 handler(); 6801 }; 6802 6803 addListener(expand, 'scroll', onScroll.bind(expand, 'expand')); 6804 addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); 6805 6806 return resizer; 6807 } 6808 6809// https://davidwalsh.name/detect-node-insertion 6810 function watchForRender(node, handler) { 6811 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); 6812 var proxy = expando.renderProxy = function (e) { 6813 if (e.animationName === CSS_RENDER_ANIMATION) { 6814 handler(); 6815 } 6816 }; 6817 6818 helpers$1.each(ANIMATION_START_EVENTS, function (type) { 6819 addListener(node, type, proxy); 6820 }); 6821 6822 // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class 6823 // is removed then added back immediately (same animation frame?). Accessing the 6824 // `offsetParent` property will force a reflow and re-evaluate the CSS animation. 6825 // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics 6826 // https://github.com/chartjs/Chart.js/issues/4737 6827 expando.reflow = !!node.offsetParent; 6828 6829 node.classList.add(CSS_RENDER_MONITOR); 6830 } 6831 6832 function unwatchForRender(node) { 6833 var expando = node[EXPANDO_KEY] || {}; 6834 var proxy = expando.renderProxy; 6835 6836 if (proxy) { 6837 helpers$1.each(ANIMATION_START_EVENTS, function (type) { 6838 removeListener(node, type, proxy); 6839 }); 6840 6841 delete expando.renderProxy; 6842 } 6843 6844 node.classList.remove(CSS_RENDER_MONITOR); 6845 } 6846 6847 function addResizeListener(node, listener, chart) { 6848 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); 6849 6850 // Let's keep track of this added resizer and thus avoid DOM query when removing it. 6851 var resizer = expando.resizer = createResizer(throttled(function () { 6852 if (expando.resizer) { 6853 var container = chart.options.maintainAspectRatio && node.parentNode; 6854 var w = container ? container.clientWidth : 0; 6855 listener(createEvent('resize', chart)); 6856 if (container && container.clientWidth < w && chart.canvas) { 6857 // If the container size shrank during chart resize, let's assume 6858 // scrollbar appeared. So we resize again with the scrollbar visible - 6859 // effectively making chart smaller and the scrollbar hidden again. 6860 // Because we are inside `throttled`, and currently `ticking`, scroll 6861 // events are ignored during this whole 2 resize process. 6862 // If we assumed wrong and something else happened, we are resizing 6863 // twice in a frame (potential performance issue) 6864 listener(createEvent('resize', chart)); 6865 } 6866 } 6867 })); 6868 6869 // The resizer needs to be attached to the node parent, so we first need to be 6870 // sure that `node` is attached to the DOM before injecting the resizer element. 6871 watchForRender(node, function () { 6872 if (expando.resizer) { 6873 var container = node.parentNode; 6874 if (container && container !== resizer.parentNode) { 6875 container.insertBefore(resizer, container.firstChild); 6876 } 6877 6878 // The container size might have changed, let's reset the resizer state. 6879 resizer._reset(); 6880 } 6881 }); 6882 } 6883 6884 function removeResizeListener(node) { 6885 var expando = node[EXPANDO_KEY] || {}; 6886 var resizer = expando.resizer; 6887 6888 delete expando.resizer; 6889 unwatchForRender(node); 6890 6891 if (resizer && resizer.parentNode) { 6892 resizer.parentNode.removeChild(resizer); 6893 } 6894 } 6895 6896 function injectCSS(platform, css) { 6897 // https://stackoverflow.com/q/3922139 6898 var style = platform._style || document.createElement('style'); 6899 if (!platform._style) { 6900 platform._style = style; 6901 css = '/* Chart.js */\n' + css; 6902 style.setAttribute('type', 'text/css'); 6903 document.getElementsByTagName('head')[0].appendChild(style); 6904 } 6905 6906 style.appendChild(document.createTextNode(css)); 6907 } 6908 6909 var platform_dom$2 = { 6910 /** 6911 * When `true`, prevents the automatic injection of the stylesheet required to 6912 * correctly detect when the chart is added to the DOM and then resized. This 6913 * switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`) 6914 * to be manually imported to make this library compatible with any CSP. 6915 * See https://github.com/chartjs/Chart.js/issues/5208 6916 */ 6917 disableCSSInjection: false, 6918 6919 /** 6920 * This property holds whether this platform is enabled for the current environment. 6921 * Currently used by platform.js to select the proper implementation. 6922 * @private 6923 */ 6924 _enabled: typeof window !== 'undefined' && typeof document !== 'undefined', 6925 6926 /** 6927 * @private 6928 */ 6929 _ensureLoaded: function () { 6930 if (this._loaded) { 6931 return; 6932 } 6933 6934 this._loaded = true; 6935 6936 // https://github.com/chartjs/Chart.js/issues/5208 6937 if (!this.disableCSSInjection) { 6938 injectCSS(this, stylesheet); 6939 } 6940 }, 6941 6942 acquireContext: function (item, config) { 6943 if (typeof item === 'string') { 6944 item = document.getElementById(item); 6945 } else if (item.length) { 6946 // Support for array based queries (such as jQuery) 6947 item = item[0]; 6948 } 6949 6950 if (item && item.canvas) { 6951 // Support for any object associated to a canvas (including a context2d) 6952 item = item.canvas; 6953 } 6954 6955 // To prevent canvas fingerprinting, some add-ons undefine the getContext 6956 // method, for example: https://github.com/kkapsner/CanvasBlocker 6957 // https://github.com/chartjs/Chart.js/issues/2807 6958 var context = item && item.getContext && item.getContext('2d'); 6959 6960 // Load platform resources on first chart creation, to make possible to change 6961 // platform options after importing the library (e.g. `disableCSSInjection`). 6962 this._ensureLoaded(); 6963 6964 // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is 6965 // inside an iframe or when running in a protected environment. We could guess the 6966 // types from their toString() value but let's keep things flexible and assume it's 6967 // a sufficient condition if the item has a context2D which has item as `canvas`. 6968 // https://github.com/chartjs/Chart.js/issues/3887 6969 // https://github.com/chartjs/Chart.js/issues/4102 6970 // https://github.com/chartjs/Chart.js/issues/4152 6971 if (context && context.canvas === item) { 6972 initCanvas(item, config); 6973 return context; 6974 } 6975 6976 return null; 6977 }, 6978 6979 releaseContext: function (context) { 6980 var canvas = context.canvas; 6981 if (!canvas[EXPANDO_KEY]) { 6982 return; 6983 } 6984 6985 var initial = canvas[EXPANDO_KEY].initial; 6986 ['height', 'width'].forEach(function (prop) { 6987 var value = initial[prop]; 6988 if (helpers$1.isNullOrUndef(value)) { 6989 canvas.removeAttribute(prop); 6990 } else { 6991 canvas.setAttribute(prop, value); 6992 } 6993 }); 6994 6995 helpers$1.each(initial.style || {}, function (value, key) { 6996 canvas.style[key] = value; 6997 }); 6998 6999 // The canvas render size might have been changed (and thus the state stack discarded), 7000 // we can't use save() and restore() to restore the initial state. So make sure that at 7001 // least the canvas context is reset to the default state by setting the canvas width. 7002 // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html 7003 // eslint-disable-next-line no-self-assign 7004 canvas.width = canvas.width; 7005 7006 delete canvas[EXPANDO_KEY]; 7007 }, 7008 7009 addEventListener: function (chart, type, listener) { 7010 var canvas = chart.canvas; 7011 if (type === 'resize') { 7012 // Note: the resize event is not supported on all browsers. 7013 addResizeListener(canvas, listener, chart); 7014 return; 7015 } 7016 7017 var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); 7018 var proxies = expando.proxies || (expando.proxies = {}); 7019 var proxy = proxies[chart.id + '_' + type] = function (event) { 7020 listener(fromNativeEvent(event, chart)); 7021 }; 7022 7023 addListener(canvas, type, proxy); 7024 }, 7025 7026 removeEventListener: function (chart, type, listener) { 7027 var canvas = chart.canvas; 7028 if (type === 'resize') { 7029 // Note: the resize event is not supported on all browsers. 7030 removeResizeListener(canvas); 7031 return; 7032 } 7033 7034 var expando = listener[EXPANDO_KEY] || {}; 7035 var proxies = expando.proxies || {}; 7036 var proxy = proxies[chart.id + '_' + type]; 7037 if (!proxy) { 7038 return; 7039 } 7040 7041 removeListener(canvas, type, proxy); 7042 } 7043 }; 7044 7045// DEPRECATIONS 7046 7047 /** 7048 * Provided for backward compatibility, use EventTarget.addEventListener instead. 7049 * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ 7050 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener 7051 * @function Chart.helpers.addEvent 7052 * @deprecated since version 2.7.0 7053 * @todo remove at version 3 7054 * @private 7055 */ 7056 helpers$1.addEvent = addListener; 7057 7058 /** 7059 * Provided for backward compatibility, use EventTarget.removeEventListener instead. 7060 * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ 7061 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener 7062 * @function Chart.helpers.removeEvent 7063 * @deprecated since version 2.7.0 7064 * @todo remove at version 3 7065 * @private 7066 */ 7067 helpers$1.removeEvent = removeListener; 7068 7069// @TODO Make possible to select another platform at build time. 7070 var implementation = platform_dom$2._enabled ? platform_dom$2 : platform_basic; 7071 7072 /** 7073 * @namespace Chart.platform 7074 * @see https://chartjs.gitbooks.io/proposals/content/Platform.html 7075 * @since 2.4.0 7076 */ 7077 var platform = helpers$1.extend({ 7078 /** 7079 * @since 2.7.0 7080 */ 7081 initialize: function () { 7082 }, 7083 7084 /** 7085 * Called at chart construction time, returns a context2d instance implementing 7086 * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. 7087 * @param {*} item - The native item from which to acquire context (platform specific) 7088 * @param {object} options - The chart options 7089 * @returns {CanvasRenderingContext2D} context2d instance 7090 */ 7091 acquireContext: function () { 7092 }, 7093 7094 /** 7095 * Called at chart destruction time, releases any resources associated to the context 7096 * previously returned by the acquireContext() method. 7097 * @param {CanvasRenderingContext2D} context - The context2d instance 7098 * @returns {boolean} true if the method succeeded, else false 7099 */ 7100 releaseContext: function () { 7101 }, 7102 7103 /** 7104 * Registers the specified listener on the given chart. 7105 * @param {Chart} chart - Chart from which to listen for event 7106 * @param {string} type - The ({@link IEvent}) type to listen for 7107 * @param {function} listener - Receives a notification (an object that implements 7108 * the {@link IEvent} interface) when an event of the specified type occurs. 7109 */ 7110 addEventListener: function () { 7111 }, 7112 7113 /** 7114 * Removes the specified listener previously registered with addEventListener. 7115 * @param {Chart} chart - Chart from which to remove the listener 7116 * @param {string} type - The ({@link IEvent}) type to remove 7117 * @param {function} listener - The listener function to remove from the event target. 7118 */ 7119 removeEventListener: function () { 7120 } 7121 7122 }, implementation); 7123 7124 core_defaults._set('global', { 7125 plugins: {} 7126 }); 7127 7128 /** 7129 * The plugin service singleton 7130 * @namespace Chart.plugins 7131 * @since 2.1.0 7132 */ 7133 var core_plugins = { 7134 /** 7135 * Globally registered plugins. 7136 * @private 7137 */ 7138 _plugins: [], 7139 7140 /** 7141 * This identifier is used to invalidate the descriptors cache attached to each chart 7142 * when a global plugin is registered or unregistered. In this case, the cache ID is 7143 * incremented and descriptors are regenerated during following API calls. 7144 * @private 7145 */ 7146 _cacheId: 0, 7147 7148 /** 7149 * Registers the given plugin(s) if not already registered. 7150 * @param {IPlugin[]|IPlugin} plugins plugin instance(s). 7151 */ 7152 register: function (plugins) { 7153 var p = this._plugins; 7154 ([]).concat(plugins).forEach(function (plugin) { 7155 if (p.indexOf(plugin) === -1) { 7156 p.push(plugin); 7157 } 7158 }); 7159 7160 this._cacheId++; 7161 }, 7162 7163 /** 7164 * Unregisters the given plugin(s) only if registered. 7165 * @param {IPlugin[]|IPlugin} plugins plugin instance(s). 7166 */ 7167 unregister: function (plugins) { 7168 var p = this._plugins; 7169 ([]).concat(plugins).forEach(function (plugin) { 7170 var idx = p.indexOf(plugin); 7171 if (idx !== -1) { 7172 p.splice(idx, 1); 7173 } 7174 }); 7175 7176 this._cacheId++; 7177 }, 7178 7179 /** 7180 * Remove all registered plugins. 7181 * @since 2.1.5 7182 */ 7183 clear: function () { 7184 this._plugins = []; 7185 this._cacheId++; 7186 }, 7187 7188 /** 7189 * Returns the number of registered plugins? 7190 * @returns {number} 7191 * @since 2.1.5 7192 */ 7193 count: function () { 7194 return this._plugins.length; 7195 }, 7196 7197 /** 7198 * Returns all registered plugin instances. 7199 * @returns {IPlugin[]} array of plugin objects. 7200 * @since 2.1.5 7201 */ 7202 getAll: function () { 7203 return this._plugins; 7204 }, 7205 7206 /** 7207 * Calls enabled plugins for `chart` on the specified hook and with the given args. 7208 * This method immediately returns as soon as a plugin explicitly returns false. The 7209 * returned value can be used, for instance, to interrupt the current action. 7210 * @param {Chart} chart - The chart instance for which plugins should be called. 7211 * @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). 7212 * @param {Array} [args] - Extra arguments to apply to the hook call. 7213 * @returns {boolean} false if any of the plugins return false, else returns true. 7214 */ 7215 notify: function (chart, hook, args) { 7216 var descriptors = this.descriptors(chart); 7217 var ilen = descriptors.length; 7218 var i, descriptor, plugin, params, method; 7219 7220 for (i = 0; i < ilen; ++i) { 7221 descriptor = descriptors[i]; 7222 plugin = descriptor.plugin; 7223 method = plugin[hook]; 7224 if (typeof method === 'function') { 7225 params = [chart].concat(args || []); 7226 params.push(descriptor.options); 7227 if (method.apply(plugin, params) === false) { 7228 return false; 7229 } 7230 } 7231 } 7232 7233 return true; 7234 }, 7235 7236 /** 7237 * Returns descriptors of enabled plugins for the given chart. 7238 * @returns {object[]} [{ plugin, options }] 7239 * @private 7240 */ 7241 descriptors: function (chart) { 7242 var cache = chart.$plugins || (chart.$plugins = {}); 7243 if (cache.id === this._cacheId) { 7244 return cache.descriptors; 7245 } 7246 7247 var plugins = []; 7248 var descriptors = []; 7249 var config = (chart && chart.config) || {}; 7250 var options = (config.options && config.options.plugins) || {}; 7251 7252 this._plugins.concat(config.plugins || []).forEach(function (plugin) { 7253 var idx = plugins.indexOf(plugin); 7254 if (idx !== -1) { 7255 return; 7256 } 7257 7258 var id = plugin.id; 7259 var opts = options[id]; 7260 if (opts === false) { 7261 return; 7262 } 7263 7264 if (opts === true) { 7265 opts = helpers$1.clone(core_defaults.global.plugins[id]); 7266 } 7267 7268 plugins.push(plugin); 7269 descriptors.push({ 7270 plugin: plugin, 7271 options: opts || {} 7272 }); 7273 }); 7274 7275 cache.descriptors = descriptors; 7276 cache.id = this._cacheId; 7277 return descriptors; 7278 }, 7279 7280 /** 7281 * Invalidates cache for the given chart: descriptors hold a reference on plugin option, 7282 * but in some cases, this reference can be changed by the user when updating options. 7283 * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 7284 * @private 7285 */ 7286 _invalidate: function (chart) { 7287 delete chart.$plugins; 7288 } 7289 }; 7290 7291 var core_scaleService = { 7292 // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then 7293 // use the new chart options to grab the correct scale 7294 constructors: {}, 7295 // Use a registration function so that we can move to an ES6 map when we no longer need to support 7296 // old browsers 7297 7298 // Scale config defaults 7299 defaults: {}, 7300 registerScaleType: function (type, scaleConstructor, scaleDefaults) { 7301 this.constructors[type] = scaleConstructor; 7302 this.defaults[type] = helpers$1.clone(scaleDefaults); 7303 }, 7304 getScaleConstructor: function (type) { 7305 return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; 7306 }, 7307 getScaleDefaults: function (type) { 7308 // Return the scale defaults merged with the global settings so that we always use the latest ones 7309 return this.defaults.hasOwnProperty(type) ? helpers$1.merge({}, [core_defaults.scale, this.defaults[type]]) : {}; 7310 }, 7311 updateScaleDefaults: function (type, additions) { 7312 var me = this; 7313 if (me.defaults.hasOwnProperty(type)) { 7314 me.defaults[type] = helpers$1.extend(me.defaults[type], additions); 7315 } 7316 }, 7317 addScalesToLayout: function (chart) { 7318 // Adds each scale to the chart.boxes array to be sized accordingly 7319 helpers$1.each(chart.scales, function (scale) { 7320 // Set ILayoutItem parameters for backwards compatibility 7321 scale.fullWidth = scale.options.fullWidth; 7322 scale.position = scale.options.position; 7323 scale.weight = scale.options.weight; 7324 core_layouts.addBox(chart, scale); 7325 }); 7326 } 7327 }; 7328 7329 var valueOrDefault$7 = helpers$1.valueOrDefault; 7330 7331 core_defaults._set('global', { 7332 tooltips: { 7333 enabled: true, 7334 custom: null, 7335 mode: 'nearest', 7336 position: 'average', 7337 intersect: true, 7338 backgroundColor: 'rgba(0,0,0,0.8)', 7339 titleFontStyle: 'bold', 7340 titleSpacing: 2, 7341 titleMarginBottom: 6, 7342 titleFontColor: '#fff', 7343 titleAlign: 'left', 7344 bodySpacing: 2, 7345 bodyFontColor: '#fff', 7346 bodyAlign: 'left', 7347 footerFontStyle: 'bold', 7348 footerSpacing: 2, 7349 footerMarginTop: 6, 7350 footerFontColor: '#fff', 7351 footerAlign: 'left', 7352 yPadding: 6, 7353 xPadding: 6, 7354 caretPadding: 2, 7355 caretSize: 5, 7356 cornerRadius: 6, 7357 multiKeyBackground: '#fff', 7358 displayColors: true, 7359 borderColor: 'rgba(0,0,0,0)', 7360 borderWidth: 0, 7361 callbacks: { 7362 // Args are: (tooltipItems, data) 7363 beforeTitle: helpers$1.noop, 7364 title: function (tooltipItems, data) { 7365 var title = ''; 7366 var labels = data.labels; 7367 var labelCount = labels ? labels.length : 0; 7368 7369 if (tooltipItems.length > 0) { 7370 var item = tooltipItems[0]; 7371 if (item.label) { 7372 title = item.label; 7373 } else if (item.xLabel) { 7374 title = item.xLabel; 7375 } else if (labelCount > 0 && item.index < labelCount) { 7376 title = labels[item.index]; 7377 } 7378 } 7379 7380 return title; 7381 }, 7382 afterTitle: helpers$1.noop, 7383 7384 // Args are: (tooltipItems, data) 7385 beforeBody: helpers$1.noop, 7386 7387 // Args are: (tooltipItem, data) 7388 beforeLabel: helpers$1.noop, 7389 label: function (tooltipItem, data) { 7390 var label = data.datasets[tooltipItem.datasetIndex].label || ''; 7391 7392 if (label) { 7393 label += ': '; 7394 } 7395 if (!helpers$1.isNullOrUndef(tooltipItem.value)) { 7396 label += tooltipItem.value; 7397 } else { 7398 label += tooltipItem.yLabel; 7399 } 7400 return label; 7401 }, 7402 labelColor: function (tooltipItem, chart) { 7403 var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); 7404 var activeElement = meta.data[tooltipItem.index]; 7405 var view = activeElement._view; 7406 return { 7407 borderColor: view.borderColor, 7408 backgroundColor: view.backgroundColor 7409 }; 7410 }, 7411 labelTextColor: function () { 7412 return this._options.bodyFontColor; 7413 }, 7414 afterLabel: helpers$1.noop, 7415 7416 // Args are: (tooltipItems, data) 7417 afterBody: helpers$1.noop, 7418 7419 // Args are: (tooltipItems, data) 7420 beforeFooter: helpers$1.noop, 7421 footer: helpers$1.noop, 7422 afterFooter: helpers$1.noop 7423 } 7424 } 7425 }); 7426 7427 var positioners = { 7428 /** 7429 * Average mode places the tooltip at the average position of the elements shown 7430 * @function Chart.Tooltip.positioners.average 7431 * @param elements {ChartElement[]} the elements being displayed in the tooltip 7432 * @returns {object} tooltip position 7433 */ 7434 average: function (elements) { 7435 if (!elements.length) { 7436 return false; 7437 } 7438 7439 var i, len; 7440 var x = 0; 7441 var y = 0; 7442 var count = 0; 7443 7444 for (i = 0, len = elements.length; i < len; ++i) { 7445 var el = elements[i]; 7446 if (el && el.hasValue()) { 7447 var pos = el.tooltipPosition(); 7448 x += pos.x; 7449 y += pos.y; 7450 ++count; 7451 } 7452 } 7453 7454 return { 7455 x: x / count, 7456 y: y / count 7457 }; 7458 }, 7459 7460 /** 7461 * Gets the tooltip position nearest of the item nearest to the event position 7462 * @function Chart.Tooltip.positioners.nearest 7463 * @param elements {Chart.Element[]} the tooltip elements 7464 * @param eventPosition {object} the position of the event in canvas coordinates 7465 * @returns {object} the tooltip position 7466 */ 7467 nearest: function (elements, eventPosition) { 7468 var x = eventPosition.x; 7469 var y = eventPosition.y; 7470 var minDistance = Number.POSITIVE_INFINITY; 7471 var i, len, nearestElement; 7472 7473 for (i = 0, len = elements.length; i < len; ++i) { 7474 var el = elements[i]; 7475 if (el && el.hasValue()) { 7476 var center = el.getCenterPoint(); 7477 var d = helpers$1.distanceBetweenPoints(eventPosition, center); 7478 7479 if (d < minDistance) { 7480 minDistance = d; 7481 nearestElement = el; 7482 } 7483 } 7484 } 7485 7486 if (nearestElement) { 7487 var tp = nearestElement.tooltipPosition(); 7488 x = tp.x; 7489 y = tp.y; 7490 } 7491 7492 return { 7493 x: x, 7494 y: y 7495 }; 7496 } 7497 }; 7498 7499// Helper to push or concat based on if the 2nd parameter is an array or not 7500 function pushOrConcat(base, toPush) { 7501 if (toPush) { 7502 if (helpers$1.isArray(toPush)) { 7503 // base = base.concat(toPush); 7504 Array.prototype.push.apply(base, toPush); 7505 } else { 7506 base.push(toPush); 7507 } 7508 } 7509 7510 return base; 7511 } 7512 7513 /** 7514 * Returns array of strings split by newline 7515 * @param {string} value - The value to split by newline. 7516 * @returns {string[]} value if newline present - Returned from String split() method 7517 * @function 7518 */ 7519 function splitNewlines(str) { 7520 if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { 7521 return str.split('\n'); 7522 } 7523 return str; 7524 } 7525 7526 7527 /** 7528 * Private helper to create a tooltip item model 7529 * @param element - the chart element (point, arc, bar) to create the tooltip item for 7530 * @return new tooltip item 7531 */ 7532 function createTooltipItem(element) { 7533 var xScale = element._xScale; 7534 var yScale = element._yScale || element._scale; // handle radar || polarArea charts 7535 var index = element._index; 7536 var datasetIndex = element._datasetIndex; 7537 var controller = element._chart.getDatasetMeta(datasetIndex).controller; 7538 var indexScale = controller._getIndexScale(); 7539 var valueScale = controller._getValueScale(); 7540 7541 return { 7542 xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', 7543 yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', 7544 label: indexScale ? '' + indexScale.getLabelForIndex(index, datasetIndex) : '', 7545 value: valueScale ? '' + valueScale.getLabelForIndex(index, datasetIndex) : '', 7546 index: index, 7547 datasetIndex: datasetIndex, 7548 x: element._model.x, 7549 y: element._model.y 7550 }; 7551 } 7552 7553 /** 7554 * Helper to get the reset model for the tooltip 7555 * @param tooltipOpts {object} the tooltip options 7556 */ 7557 function getBaseModel(tooltipOpts) { 7558 var globalDefaults = core_defaults.global; 7559 7560 return { 7561 // Positioning 7562 xPadding: tooltipOpts.xPadding, 7563 yPadding: tooltipOpts.yPadding, 7564 xAlign: tooltipOpts.xAlign, 7565 yAlign: tooltipOpts.yAlign, 7566 7567 // Body 7568 bodyFontColor: tooltipOpts.bodyFontColor, 7569 _bodyFontFamily: valueOrDefault$7(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), 7570 _bodyFontStyle: valueOrDefault$7(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), 7571 _bodyAlign: tooltipOpts.bodyAlign, 7572 bodyFontSize: valueOrDefault$7(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), 7573 bodySpacing: tooltipOpts.bodySpacing, 7574 7575 // Title 7576 titleFontColor: tooltipOpts.titleFontColor, 7577 _titleFontFamily: valueOrDefault$7(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), 7578 _titleFontStyle: valueOrDefault$7(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), 7579 titleFontSize: valueOrDefault$7(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), 7580 _titleAlign: tooltipOpts.titleAlign, 7581 titleSpacing: tooltipOpts.titleSpacing, 7582 titleMarginBottom: tooltipOpts.titleMarginBottom, 7583 7584 // Footer 7585 footerFontColor: tooltipOpts.footerFontColor, 7586 _footerFontFamily: valueOrDefault$7(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), 7587 _footerFontStyle: valueOrDefault$7(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), 7588 footerFontSize: valueOrDefault$7(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), 7589 _footerAlign: tooltipOpts.footerAlign, 7590 footerSpacing: tooltipOpts.footerSpacing, 7591 footerMarginTop: tooltipOpts.footerMarginTop, 7592 7593 // Appearance 7594 caretSize: tooltipOpts.caretSize, 7595 cornerRadius: tooltipOpts.cornerRadius, 7596 backgroundColor: tooltipOpts.backgroundColor, 7597 opacity: 0, 7598 legendColorBackground: tooltipOpts.multiKeyBackground, 7599 displayColors: tooltipOpts.displayColors, 7600 borderColor: tooltipOpts.borderColor, 7601 borderWidth: tooltipOpts.borderWidth 7602 }; 7603 } 7604 7605 /** 7606 * Get the size of the tooltip 7607 */ 7608 function getTooltipSize(tooltip, model) { 7609 var ctx = tooltip._chart.ctx; 7610 7611 var height = model.yPadding * 2; // Tooltip Padding 7612 var width = 0; 7613 7614 // Count of all lines in the body 7615 var body = model.body; 7616 var combinedBodyLength = body.reduce(function (count, bodyItem) { 7617 return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; 7618 }, 0); 7619 combinedBodyLength += model.beforeBody.length + model.afterBody.length; 7620 7621 var titleLineCount = model.title.length; 7622 var footerLineCount = model.footer.length; 7623 var titleFontSize = model.titleFontSize; 7624 var bodyFontSize = model.bodyFontSize; 7625 var footerFontSize = model.footerFontSize; 7626 7627 height += titleLineCount * titleFontSize; // Title Lines 7628 height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing 7629 height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin 7630 height += combinedBodyLength * bodyFontSize; // Body Lines 7631 height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing 7632 height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin 7633 height += footerLineCount * (footerFontSize); // Footer Lines 7634 height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing 7635 7636 // Title width 7637 var widthPadding = 0; 7638 var maxLineWidth = function (line) { 7639 width = Math.max(width, ctx.measureText(line).width + widthPadding); 7640 }; 7641 7642 ctx.font = helpers$1.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); 7643 helpers$1.each(model.title, maxLineWidth); 7644 7645 // Body width 7646 ctx.font = helpers$1.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); 7647 helpers$1.each(model.beforeBody.concat(model.afterBody), maxLineWidth); 7648 7649 // Body lines may include some extra width due to the color box 7650 widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; 7651 helpers$1.each(body, function (bodyItem) { 7652 helpers$1.each(bodyItem.before, maxLineWidth); 7653 helpers$1.each(bodyItem.lines, maxLineWidth); 7654 helpers$1.each(bodyItem.after, maxLineWidth); 7655 }); 7656 7657 // Reset back to 0 7658 widthPadding = 0; 7659 7660 // Footer width 7661 ctx.font = helpers$1.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); 7662 helpers$1.each(model.footer, maxLineWidth); 7663 7664 // Add padding 7665 width += 2 * model.xPadding; 7666 7667 return { 7668 width: width, 7669 height: height 7670 }; 7671 } 7672 7673 /** 7674 * Helper to get the alignment of a tooltip given the size 7675 */ 7676 function determineAlignment(tooltip, size) { 7677 var model = tooltip._model; 7678 var chart = tooltip._chart; 7679 var chartArea = tooltip._chart.chartArea; 7680 var xAlign = 'center'; 7681 var yAlign = 'center'; 7682 7683 if (model.y < size.height) { 7684 yAlign = 'top'; 7685 } else if (model.y > (chart.height - size.height)) { 7686 yAlign = 'bottom'; 7687 } 7688 7689 var lf, rf; // functions to determine left, right alignment 7690 var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart 7691 var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges 7692 var midX = (chartArea.left + chartArea.right) / 2; 7693 var midY = (chartArea.top + chartArea.bottom) / 2; 7694 7695 if (yAlign === 'center') { 7696 lf = function (x) { 7697 return x <= midX; 7698 }; 7699 rf = function (x) { 7700 return x > midX; 7701 }; 7702 } else { 7703 lf = function (x) { 7704 return x <= (size.width / 2); 7705 }; 7706 rf = function (x) { 7707 return x >= (chart.width - (size.width / 2)); 7708 }; 7709 } 7710 7711 olf = function (x) { 7712 return x + size.width + model.caretSize + model.caretPadding > chart.width; 7713 }; 7714 orf = function (x) { 7715 return x - size.width - model.caretSize - model.caretPadding < 0; 7716 }; 7717 yf = function (y) { 7718 return y <= midY ? 'top' : 'bottom'; 7719 }; 7720 7721 if (lf(model.x)) { 7722 xAlign = 'left'; 7723 7724 // Is tooltip too wide and goes over the right side of the chart.? 7725 if (olf(model.x)) { 7726 xAlign = 'center'; 7727 yAlign = yf(model.y); 7728 } 7729 } else if (rf(model.x)) { 7730 xAlign = 'right'; 7731 7732 // Is tooltip too wide and goes outside left edge of canvas? 7733 if (orf(model.x)) { 7734 xAlign = 'center'; 7735 yAlign = yf(model.y); 7736 } 7737 } 7738 7739 var opts = tooltip._options; 7740 return { 7741 xAlign: opts.xAlign ? opts.xAlign : xAlign, 7742 yAlign: opts.yAlign ? opts.yAlign : yAlign 7743 }; 7744 } 7745 7746 /** 7747 * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment 7748 */ 7749 function getBackgroundPoint(vm, size, alignment, chart) { 7750 // Background Position 7751 var x = vm.x; 7752 var y = vm.y; 7753 7754 var caretSize = vm.caretSize; 7755 var caretPadding = vm.caretPadding; 7756 var cornerRadius = vm.cornerRadius; 7757 var xAlign = alignment.xAlign; 7758 var yAlign = alignment.yAlign; 7759 var paddingAndSize = caretSize + caretPadding; 7760 var radiusAndPadding = cornerRadius + caretPadding; 7761 7762 if (xAlign === 'right') { 7763 x -= size.width; 7764 } else if (xAlign === 'center') { 7765 x -= (size.width / 2); 7766 if (x + size.width > chart.width) { 7767 x = chart.width - size.width; 7768 } 7769 if (x < 0) { 7770 x = 0; 7771 } 7772 } 7773 7774 if (yAlign === 'top') { 7775 y += paddingAndSize; 7776 } else if (yAlign === 'bottom') { 7777 y -= size.height + paddingAndSize; 7778 } else { 7779 y -= (size.height / 2); 7780 } 7781 7782 if (yAlign === 'center') { 7783 if (xAlign === 'left') { 7784 x += paddingAndSize; 7785 } else if (xAlign === 'right') { 7786 x -= paddingAndSize; 7787 } 7788 } else if (xAlign === 'left') { 7789 x -= radiusAndPadding; 7790 } else if (xAlign === 'right') { 7791 x += radiusAndPadding; 7792 } 7793 7794 return { 7795 x: x, 7796 y: y 7797 }; 7798 } 7799 7800 function getAlignedX(vm, align) { 7801 return align === 'center' 7802 ? vm.x + vm.width / 2 7803 : align === 'right' 7804 ? vm.x + vm.width - vm.xPadding 7805 : vm.x + vm.xPadding; 7806 } 7807 7808 /** 7809 * Helper to build before and after body lines 7810 */ 7811 function getBeforeAfterBodyLines(callback) { 7812 return pushOrConcat([], splitNewlines(callback)); 7813 } 7814 7815 var exports$3 = core_element.extend({ 7816 initialize: function () { 7817 this._model = getBaseModel(this._options); 7818 this._lastActive = []; 7819 }, 7820 7821 // Get the title 7822 // Args are: (tooltipItem, data) 7823 getTitle: function () { 7824 var me = this; 7825 var opts = me._options; 7826 var callbacks = opts.callbacks; 7827 7828 var beforeTitle = callbacks.beforeTitle.apply(me, arguments); 7829 var title = callbacks.title.apply(me, arguments); 7830 var afterTitle = callbacks.afterTitle.apply(me, arguments); 7831 7832 var lines = []; 7833 lines = pushOrConcat(lines, splitNewlines(beforeTitle)); 7834 lines = pushOrConcat(lines, splitNewlines(title)); 7835 lines = pushOrConcat(lines, splitNewlines(afterTitle)); 7836 7837 return lines; 7838 }, 7839 7840 // Args are: (tooltipItem, data) 7841 getBeforeBody: function () { 7842 return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments)); 7843 }, 7844 7845 // Args are: (tooltipItem, data) 7846 getBody: function (tooltipItems, data) { 7847 var me = this; 7848 var callbacks = me._options.callbacks; 7849 var bodyItems = []; 7850 7851 helpers$1.each(tooltipItems, function (tooltipItem) { 7852 var bodyItem = { 7853 before: [], 7854 lines: [], 7855 after: [] 7856 }; 7857 pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data))); 7858 pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); 7859 pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data))); 7860 7861 bodyItems.push(bodyItem); 7862 }); 7863 7864 return bodyItems; 7865 }, 7866 7867 // Args are: (tooltipItem, data) 7868 getAfterBody: function () { 7869 return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments)); 7870 }, 7871 7872 // Get the footer and beforeFooter and afterFooter lines 7873 // Args are: (tooltipItem, data) 7874 getFooter: function () { 7875 var me = this; 7876 var callbacks = me._options.callbacks; 7877 7878 var beforeFooter = callbacks.beforeFooter.apply(me, arguments); 7879 var footer = callbacks.footer.apply(me, arguments); 7880 var afterFooter = callbacks.afterFooter.apply(me, arguments); 7881 7882 var lines = []; 7883 lines = pushOrConcat(lines, splitNewlines(beforeFooter)); 7884 lines = pushOrConcat(lines, splitNewlines(footer)); 7885 lines = pushOrConcat(lines, splitNewlines(afterFooter)); 7886 7887 return lines; 7888 }, 7889 7890 update: function (changed) { 7891 var me = this; 7892 var opts = me._options; 7893 7894 // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition 7895 // 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 7896 // which breaks any animations. 7897 var existingModel = me._model; 7898 var model = me._model = getBaseModel(opts); 7899 var active = me._active; 7900 7901 var data = me._data; 7902 7903 // In the case where active.length === 0 we need to keep these at existing values for good animations 7904 var alignment = { 7905 xAlign: existingModel.xAlign, 7906 yAlign: existingModel.yAlign 7907 }; 7908 var backgroundPoint = { 7909 x: existingModel.x, 7910 y: existingModel.y 7911 }; 7912 var tooltipSize = { 7913 width: existingModel.width, 7914 height: existingModel.height 7915 }; 7916 var tooltipPosition = { 7917 x: existingModel.caretX, 7918 y: existingModel.caretY 7919 }; 7920 7921 var i, len; 7922 7923 if (active.length) { 7924 model.opacity = 1; 7925 7926 var labelColors = []; 7927 var labelTextColors = []; 7928 tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition); 7929 7930 var tooltipItems = []; 7931 for (i = 0, len = active.length; i < len; ++i) { 7932 tooltipItems.push(createTooltipItem(active[i])); 7933 } 7934 7935 // If the user provided a filter function, use it to modify the tooltip items 7936 if (opts.filter) { 7937 tooltipItems = tooltipItems.filter(function (a) { 7938 return opts.filter(a, data); 7939 }); 7940 } 7941 7942 // If the user provided a sorting function, use it to modify the tooltip items 7943 if (opts.itemSort) { 7944 tooltipItems = tooltipItems.sort(function (a, b) { 7945 return opts.itemSort(a, b, data); 7946 }); 7947 } 7948 7949 // Determine colors for boxes 7950 helpers$1.each(tooltipItems, function (tooltipItem) { 7951 labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); 7952 labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); 7953 }); 7954 7955 7956 // Build the Text Lines 7957 model.title = me.getTitle(tooltipItems, data); 7958 model.beforeBody = me.getBeforeBody(tooltipItems, data); 7959 model.body = me.getBody(tooltipItems, data); 7960 model.afterBody = me.getAfterBody(tooltipItems, data); 7961 model.footer = me.getFooter(tooltipItems, data); 7962 7963 // Initial positioning and colors 7964 model.x = tooltipPosition.x; 7965 model.y = tooltipPosition.y; 7966 model.caretPadding = opts.caretPadding; 7967 model.labelColors = labelColors; 7968 model.labelTextColors = labelTextColors; 7969 7970 // data points 7971 model.dataPoints = tooltipItems; 7972 7973 // We need to determine alignment of the tooltip 7974 tooltipSize = getTooltipSize(this, model); 7975 alignment = determineAlignment(this, tooltipSize); 7976 // Final Size and Position 7977 backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart); 7978 } else { 7979 model.opacity = 0; 7980 } 7981 7982 model.xAlign = alignment.xAlign; 7983 model.yAlign = alignment.yAlign; 7984 model.x = backgroundPoint.x; 7985 model.y = backgroundPoint.y; 7986 model.width = tooltipSize.width; 7987 model.height = tooltipSize.height; 7988 7989 // Point where the caret on the tooltip points to 7990 model.caretX = tooltipPosition.x; 7991 model.caretY = tooltipPosition.y; 7992 7993 me._model = model; 7994 7995 if (changed && opts.custom) { 7996 opts.custom.call(me, model); 7997 } 7998 7999 return me; 8000 }, 8001 8002 drawCaret: function (tooltipPoint, size) { 8003 var ctx = this._chart.ctx; 8004 var vm = this._view; 8005 var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); 8006 8007 ctx.lineTo(caretPosition.x1, caretPosition.y1); 8008 ctx.lineTo(caretPosition.x2, caretPosition.y2); 8009 ctx.lineTo(caretPosition.x3, caretPosition.y3); 8010 }, 8011 getCaretPosition: function (tooltipPoint, size, vm) { 8012 var x1, x2, x3, y1, y2, y3; 8013 var caretSize = vm.caretSize; 8014 var cornerRadius = vm.cornerRadius; 8015 var xAlign = vm.xAlign; 8016 var yAlign = vm.yAlign; 8017 var ptX = tooltipPoint.x; 8018 var ptY = tooltipPoint.y; 8019 var width = size.width; 8020 var height = size.height; 8021 8022 if (yAlign === 'center') { 8023 y2 = ptY + (height / 2); 8024 8025 if (xAlign === 'left') { 8026 x1 = ptX; 8027 x2 = x1 - caretSize; 8028 x3 = x1; 8029 8030 y1 = y2 + caretSize; 8031 y3 = y2 - caretSize; 8032 } else { 8033 x1 = ptX + width; 8034 x2 = x1 + caretSize; 8035 x3 = x1; 8036 8037 y1 = y2 - caretSize; 8038 y3 = y2 + caretSize; 8039 } 8040 } else { 8041 if (xAlign === 'left') { 8042 x2 = ptX + cornerRadius + (caretSize); 8043 x1 = x2 - caretSize; 8044 x3 = x2 + caretSize; 8045 } else if (xAlign === 'right') { 8046 x2 = ptX + width - cornerRadius - caretSize; 8047 x1 = x2 - caretSize; 8048 x3 = x2 + caretSize; 8049 } else { 8050 x2 = vm.caretX; 8051 x1 = x2 - caretSize; 8052 x3 = x2 + caretSize; 8053 } 8054 if (yAlign === 'top') { 8055 y1 = ptY; 8056 y2 = y1 - caretSize; 8057 y3 = y1; 8058 } else { 8059 y1 = ptY + height; 8060 y2 = y1 + caretSize; 8061 y3 = y1; 8062 // invert drawing order 8063 var tmp = x3; 8064 x3 = x1; 8065 x1 = tmp; 8066 } 8067 } 8068 return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; 8069 }, 8070 8071 drawTitle: function (pt, vm, ctx) { 8072 var title = vm.title; 8073 8074 if (title.length) { 8075 pt.x = getAlignedX(vm, vm._titleAlign); 8076 8077 ctx.textAlign = vm._titleAlign; 8078 ctx.textBaseline = 'top'; 8079 8080 var titleFontSize = vm.titleFontSize; 8081 var titleSpacing = vm.titleSpacing; 8082 8083 ctx.fillStyle = vm.titleFontColor; 8084 ctx.font = helpers$1.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); 8085 8086 var i, len; 8087 for (i = 0, len = title.length; i < len; ++i) { 8088 ctx.fillText(title[i], pt.x, pt.y); 8089 pt.y += titleFontSize + titleSpacing; // Line Height and spacing 8090 8091 if (i + 1 === title.length) { 8092 pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing 8093 } 8094 } 8095 } 8096 }, 8097 8098 drawBody: function (pt, vm, ctx) { 8099 var bodyFontSize = vm.bodyFontSize; 8100 var bodySpacing = vm.bodySpacing; 8101 var bodyAlign = vm._bodyAlign; 8102 var body = vm.body; 8103 var drawColorBoxes = vm.displayColors; 8104 var labelColors = vm.labelColors; 8105 var xLinePadding = 0; 8106 var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0; 8107 var textColor; 8108 8109 ctx.textAlign = bodyAlign; 8110 ctx.textBaseline = 'top'; 8111 ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); 8112 8113 pt.x = getAlignedX(vm, bodyAlign); 8114 8115 // Before Body 8116 var fillLineOfText = function (line) { 8117 ctx.fillText(line, pt.x + xLinePadding, pt.y); 8118 pt.y += bodyFontSize + bodySpacing; 8119 }; 8120 8121 // Before body lines 8122 ctx.fillStyle = vm.bodyFontColor; 8123 helpers$1.each(vm.beforeBody, fillLineOfText); 8124 8125 xLinePadding = drawColorBoxes && bodyAlign !== 'right' 8126 ? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2) 8127 : 0; 8128 8129 // Draw body lines now 8130 helpers$1.each(body, function (bodyItem, i) { 8131 textColor = vm.labelTextColors[i]; 8132 ctx.fillStyle = textColor; 8133 helpers$1.each(bodyItem.before, fillLineOfText); 8134 8135 helpers$1.each(bodyItem.lines, function (line) { 8136 // Draw Legend-like boxes if needed 8137 if (drawColorBoxes) { 8138 // Fill a white rect so that colours merge nicely if the opacity is < 1 8139 ctx.fillStyle = vm.legendColorBackground; 8140 ctx.fillRect(colorX, pt.y, bodyFontSize, bodyFontSize); 8141 8142 // Border 8143 ctx.lineWidth = 1; 8144 ctx.strokeStyle = labelColors[i].borderColor; 8145 ctx.strokeRect(colorX, pt.y, bodyFontSize, bodyFontSize); 8146 8147 // Inner square 8148 ctx.fillStyle = labelColors[i].backgroundColor; 8149 ctx.fillRect(colorX + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); 8150 ctx.fillStyle = textColor; 8151 } 8152 8153 fillLineOfText(line); 8154 }); 8155 8156 helpers$1.each(bodyItem.after, fillLineOfText); 8157 }); 8158 8159 // Reset back to 0 for after body 8160 xLinePadding = 0; 8161 8162 // After body lines 8163 helpers$1.each(vm.afterBody, fillLineOfText); 8164 pt.y -= bodySpacing; // Remove last body spacing 8165 }, 8166 8167 drawFooter: function (pt, vm, ctx) { 8168 var footer = vm.footer; 8169 8170 if (footer.length) { 8171 pt.x = getAlignedX(vm, vm._footerAlign); 8172 pt.y += vm.footerMarginTop; 8173 8174 ctx.textAlign = vm._footerAlign; 8175 ctx.textBaseline = 'top'; 8176 8177 ctx.fillStyle = vm.footerFontColor; 8178 ctx.font = helpers$1.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); 8179 8180 helpers$1.each(footer, function (line) { 8181 ctx.fillText(line, pt.x, pt.y); 8182 pt.y += vm.footerFontSize + vm.footerSpacing; 8183 }); 8184 } 8185 }, 8186 8187 drawBackground: function (pt, vm, ctx, tooltipSize) { 8188 ctx.fillStyle = vm.backgroundColor; 8189 ctx.strokeStyle = vm.borderColor; 8190 ctx.lineWidth = vm.borderWidth; 8191 var xAlign = vm.xAlign; 8192 var yAlign = vm.yAlign; 8193 var x = pt.x; 8194 var y = pt.y; 8195 var width = tooltipSize.width; 8196 var height = tooltipSize.height; 8197 var radius = vm.cornerRadius; 8198 8199 ctx.beginPath(); 8200 ctx.moveTo(x + radius, y); 8201 if (yAlign === 'top') { 8202 this.drawCaret(pt, tooltipSize); 8203 } 8204 ctx.lineTo(x + width - radius, y); 8205 ctx.quadraticCurveTo(x + width, y, x + width, y + radius); 8206 if (yAlign === 'center' && xAlign === 'right') { 8207 this.drawCaret(pt, tooltipSize); 8208 } 8209 ctx.lineTo(x + width, y + height - radius); 8210 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); 8211 if (yAlign === 'bottom') { 8212 this.drawCaret(pt, tooltipSize); 8213 } 8214 ctx.lineTo(x + radius, y + height); 8215 ctx.quadraticCurveTo(x, y + height, x, y + height - radius); 8216 if (yAlign === 'center' && xAlign === 'left') { 8217 this.drawCaret(pt, tooltipSize); 8218 } 8219 ctx.lineTo(x, y + radius); 8220 ctx.quadraticCurveTo(x, y, x + radius, y); 8221 ctx.closePath(); 8222 8223 ctx.fill(); 8224 8225 if (vm.borderWidth > 0) { 8226 ctx.stroke(); 8227 } 8228 }, 8229 8230 draw: function () { 8231 var ctx = this._chart.ctx; 8232 var vm = this._view; 8233 8234 if (vm.opacity === 0) { 8235 return; 8236 } 8237 8238 var tooltipSize = { 8239 width: vm.width, 8240 height: vm.height 8241 }; 8242 var pt = { 8243 x: vm.x, 8244 y: vm.y 8245 }; 8246 8247 // IE11/Edge does not like very small opacities, so snap to 0 8248 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; 8249 8250 // Truthy/falsey value for empty tooltip 8251 var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; 8252 8253 if (this._options.enabled && hasTooltipContent) { 8254 ctx.save(); 8255 ctx.globalAlpha = opacity; 8256 8257 // Draw Background 8258 this.drawBackground(pt, vm, ctx, tooltipSize); 8259 8260 // Draw Title, Body, and Footer 8261 pt.y += vm.yPadding; 8262 8263 // Titles 8264 this.drawTitle(pt, vm, ctx); 8265 8266 // Body 8267 this.drawBody(pt, vm, ctx); 8268 8269 // Footer 8270 this.drawFooter(pt, vm, ctx); 8271 8272 ctx.restore(); 8273 } 8274 }, 8275 8276 /** 8277 * Handle an event 8278 * @private 8279 * @param {IEvent} event - The event to handle 8280 * @returns {boolean} true if the tooltip changed 8281 */ 8282 handleEvent: function (e) { 8283 var me = this; 8284 var options = me._options; 8285 var changed = false; 8286 8287 me._lastActive = me._lastActive || []; 8288 8289 // Find Active Elements for tooltips 8290 if (e.type === 'mouseout') { 8291 me._active = []; 8292 } else { 8293 me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); 8294 } 8295 8296 // Remember Last Actives 8297 changed = !helpers$1.arrayEquals(me._active, me._lastActive); 8298 8299 // Only handle target event on tooltip change 8300 if (changed) { 8301 me._lastActive = me._active; 8302 8303 if (options.enabled || options.custom) { 8304 me._eventPosition = { 8305 x: e.x, 8306 y: e.y 8307 }; 8308 8309 me.update(true); 8310 me.pivot(); 8311 } 8312 } 8313 8314 return changed; 8315 } 8316 }); 8317 8318 /** 8319 * @namespace Chart.Tooltip.positioners 8320 */ 8321 var positioners_1 = positioners; 8322 8323 var core_tooltip = exports$3; 8324 core_tooltip.positioners = positioners_1; 8325 8326 var valueOrDefault$8 = helpers$1.valueOrDefault; 8327 8328 core_defaults._set('global', { 8329 elements: {}, 8330 events: [ 8331 'mousemove', 8332 'mouseout', 8333 'click', 8334 'touchstart', 8335 'touchmove' 8336 ], 8337 hover: { 8338 onHover: null, 8339 mode: 'nearest', 8340 intersect: true, 8341 animationDuration: 400 8342 }, 8343 onClick: null, 8344 maintainAspectRatio: true, 8345 responsive: true, 8346 responsiveAnimationDuration: 0 8347 }); 8348 8349 /** 8350 * Recursively merge the given config objects representing the `scales` option 8351 * by incorporating scale defaults in `xAxes` and `yAxes` array items, then 8352 * returns a deep copy of the result, thus doesn't alter inputs. 8353 */ 8354 function mergeScaleConfig(/* config objects ... */) { 8355 return helpers$1.merge({}, [].slice.call(arguments), { 8356 merger: function (key, target, source, options) { 8357 if (key === 'xAxes' || key === 'yAxes') { 8358 var slen = source[key].length; 8359 var i, type, scale; 8360 8361 if (!target[key]) { 8362 target[key] = []; 8363 } 8364 8365 for (i = 0; i < slen; ++i) { 8366 scale = source[key][i]; 8367 type = valueOrDefault$8(scale.type, key === 'xAxes' ? 'category' : 'linear'); 8368 8369 if (i >= target[key].length) { 8370 target[key].push({}); 8371 } 8372 8373 if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { 8374 // new/untyped scale or type changed: let's apply the new defaults 8375 // then merge source scale to correctly overwrite the defaults. 8376 helpers$1.merge(target[key][i], [core_scaleService.getScaleDefaults(type), scale]); 8377 } else { 8378 // scales type are the same 8379 helpers$1.merge(target[key][i], scale); 8380 } 8381 } 8382 } else { 8383 helpers$1._merger(key, target, source, options); 8384 } 8385 } 8386 }); 8387 } 8388 8389 /** 8390 * Recursively merge the given config objects as the root options by handling 8391 * default scale options for the `scales` and `scale` properties, then returns 8392 * a deep copy of the result, thus doesn't alter inputs. 8393 */ 8394 function mergeConfig(/* config objects ... */) { 8395 return helpers$1.merge({}, [].slice.call(arguments), { 8396 merger: function (key, target, source, options) { 8397 var tval = target[key] || {}; 8398 var sval = source[key]; 8399 8400 if (key === 'scales') { 8401 // scale config merging is complex. Add our own function here for that 8402 target[key] = mergeScaleConfig(tval, sval); 8403 } else if (key === 'scale') { 8404 // used in polar area & radar charts since there is only one scale 8405 target[key] = helpers$1.merge(tval, [core_scaleService.getScaleDefaults(sval.type), sval]); 8406 } else { 8407 helpers$1._merger(key, target, source, options); 8408 } 8409 } 8410 }); 8411 } 8412 8413 function initConfig(config) { 8414 config = config || {}; 8415 8416 // Do NOT use mergeConfig for the data object because this method merges arrays 8417 // and so would change references to labels and datasets, preventing data updates. 8418 var data = config.data = config.data || {}; 8419 data.datasets = data.datasets || []; 8420 data.labels = data.labels || []; 8421 8422 config.options = mergeConfig( 8423 core_defaults.global, 8424 core_defaults[config.type], 8425 config.options || {}); 8426 8427 return config; 8428 } 8429 8430 function updateConfig(chart) { 8431 var newOptions = chart.options; 8432 8433 helpers$1.each(chart.scales, function (scale) { 8434 core_layouts.removeBox(chart, scale); 8435 }); 8436 8437 newOptions = mergeConfig( 8438 core_defaults.global, 8439 core_defaults[chart.config.type], 8440 newOptions); 8441 8442 chart.options = chart.config.options = newOptions; 8443 chart.ensureScalesHaveIDs(); 8444 chart.buildOrUpdateScales(); 8445 8446 // Tooltip 8447 chart.tooltip._options = newOptions.tooltips; 8448 chart.tooltip.initialize(); 8449 } 8450 8451 function positionIsHorizontal(position) { 8452 return position === 'top' || position === 'bottom'; 8453 } 8454 8455 var Chart = function (item, config) { 8456 this.construct(item, config); 8457 return this; 8458 }; 8459 8460 helpers$1.extend(Chart.prototype, /** @lends Chart */ { 8461 /** 8462 * @private 8463 */ 8464 construct: function (item, config) { 8465 var me = this; 8466 8467 config = initConfig(config); 8468 8469 var context = platform.acquireContext(item, config); 8470 var canvas = context && context.canvas; 8471 var height = canvas && canvas.height; 8472 var width = canvas && canvas.width; 8473 8474 me.id = helpers$1.uid(); 8475 me.ctx = context; 8476 me.canvas = canvas; 8477 me.config = config; 8478 me.width = width; 8479 me.height = height; 8480 me.aspectRatio = height ? width / height : null; 8481 me.options = config.options; 8482 me._bufferedRender = false; 8483 8484 /** 8485 * Provided for backward compatibility, Chart and Chart.Controller have been merged, 8486 * the "instance" still need to be defined since it might be called from plugins. 8487 * @prop Chart#chart 8488 * @deprecated since version 2.6.0 8489 * @todo remove at version 3 8490 * @private 8491 */ 8492 me.chart = me; 8493 me.controller = me; // chart.chart.controller #inception 8494 8495 // Add the chart instance to the global namespace 8496 Chart.instances[me.id] = me; 8497 8498 // Define alias to the config data: `chart.data === chart.config.data` 8499 Object.defineProperty(me, 'data', { 8500 get: function () { 8501 return me.config.data; 8502 }, 8503 set: function (value) { 8504 me.config.data = value; 8505 } 8506 }); 8507 8508 if (!context || !canvas) { 8509 // The given item is not a compatible context2d element, let's return before finalizing 8510 // the chart initialization but after setting basic chart / controller properties that 8511 // can help to figure out that the chart is not valid (e.g chart.canvas !== null); 8512 // https://github.com/chartjs/Chart.js/issues/2807 8513 console.error("Failed to create chart: can't acquire context from the given item"); 8514 return; 8515 } 8516 8517 me.initialize(); 8518 me.update(); 8519 }, 8520 8521 /** 8522 * @private 8523 */ 8524 initialize: function () { 8525 var me = this; 8526 8527 // Before init plugin notification 8528 core_plugins.notify(me, 'beforeInit'); 8529 8530 helpers$1.retinaScale(me, me.options.devicePixelRatio); 8531 8532 me.bindEvents(); 8533 8534 if (me.options.responsive) { 8535 // Initial resize before chart draws (must be silent to preserve initial animations). 8536 me.resize(true); 8537 } 8538 8539 // Make sure scales have IDs and are built before we build any controllers. 8540 me.ensureScalesHaveIDs(); 8541 me.buildOrUpdateScales(); 8542 me.initToolTip(); 8543 8544 // After init plugin notification 8545 core_plugins.notify(me, 'afterInit'); 8546 8547 return me; 8548 }, 8549 8550 clear: function () { 8551 helpers$1.canvas.clear(this); 8552 return this; 8553 }, 8554 8555 stop: function () { 8556 // Stops any current animation loop occurring 8557 core_animations.cancelAnimation(this); 8558 return this; 8559 }, 8560 8561 resize: function (silent) { 8562 var me = this; 8563 var options = me.options; 8564 var canvas = me.canvas; 8565 var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; 8566 8567 // the canvas render width and height will be casted to integers so make sure that 8568 // the canvas display style uses the same integer values to avoid blurring effect. 8569 8570 // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed 8571 var newWidth = Math.max(0, Math.floor(helpers$1.getMaximumWidth(canvas))); 8572 var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers$1.getMaximumHeight(canvas))); 8573 8574 if (me.width === newWidth && me.height === newHeight) { 8575 return; 8576 } 8577 8578 canvas.width = me.width = newWidth; 8579 canvas.height = me.height = newHeight; 8580 canvas.style.width = newWidth + 'px'; 8581 canvas.style.height = newHeight + 'px'; 8582 8583 helpers$1.retinaScale(me, options.devicePixelRatio); 8584 8585 if (!silent) { 8586 // Notify any plugins about the resize 8587 var newSize = {width: newWidth, height: newHeight}; 8588 core_plugins.notify(me, 'resize', [newSize]); 8589 8590 // Notify of resize 8591 if (options.onResize) { 8592 options.onResize(me, newSize); 8593 } 8594 8595 me.stop(); 8596 me.update({ 8597 duration: options.responsiveAnimationDuration 8598 }); 8599 } 8600 }, 8601 8602 ensureScalesHaveIDs: function () { 8603 var options = this.options; 8604 var scalesOptions = options.scales || {}; 8605 var scaleOptions = options.scale; 8606 8607 helpers$1.each(scalesOptions.xAxes, function (xAxisOptions, index) { 8608 xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); 8609 }); 8610 8611 helpers$1.each(scalesOptions.yAxes, function (yAxisOptions, index) { 8612 yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); 8613 }); 8614 8615 if (scaleOptions) { 8616 scaleOptions.id = scaleOptions.id || 'scale'; 8617 } 8618 }, 8619 8620 /** 8621 * Builds a map of scale ID to scale object for future lookup. 8622 */ 8623 buildOrUpdateScales: function () { 8624 var me = this; 8625 var options = me.options; 8626 var scales = me.scales || {}; 8627 var items = []; 8628 var updated = Object.keys(scales).reduce(function (obj, id) { 8629 obj[id] = false; 8630 return obj; 8631 }, {}); 8632 8633 if (options.scales) { 8634 items = items.concat( 8635 (options.scales.xAxes || []).map(function (xAxisOptions) { 8636 return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'}; 8637 }), 8638 (options.scales.yAxes || []).map(function (yAxisOptions) { 8639 return {options: yAxisOptions, dtype: 'linear', dposition: 'left'}; 8640 }) 8641 ); 8642 } 8643 8644 if (options.scale) { 8645 items.push({ 8646 options: options.scale, 8647 dtype: 'radialLinear', 8648 isDefault: true, 8649 dposition: 'chartArea' 8650 }); 8651 } 8652 8653 helpers$1.each(items, function (item) { 8654 var scaleOptions = item.options; 8655 var id = scaleOptions.id; 8656 var scaleType = valueOrDefault$8(scaleOptions.type, item.dtype); 8657 8658 if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { 8659 scaleOptions.position = item.dposition; 8660 } 8661 8662 updated[id] = true; 8663 var scale = null; 8664 if (id in scales && scales[id].type === scaleType) { 8665 scale = scales[id]; 8666 scale.options = scaleOptions; 8667 scale.ctx = me.ctx; 8668 scale.chart = me; 8669 } else { 8670 var scaleClass = core_scaleService.getScaleConstructor(scaleType); 8671 if (!scaleClass) { 8672 return; 8673 } 8674 scale = new scaleClass({ 8675 id: id, 8676 type: scaleType, 8677 options: scaleOptions, 8678 ctx: me.ctx, 8679 chart: me 8680 }); 8681 scales[scale.id] = scale; 8682 } 8683 8684 scale.mergeTicksOptions(); 8685 8686 // TODO(SB): I think we should be able to remove this custom case (options.scale) 8687 // and consider it as a regular scale part of the "scales"" map only! This would 8688 // make the logic easier and remove some useless? custom code. 8689 if (item.isDefault) { 8690 me.scale = scale; 8691 } 8692 }); 8693 // clear up discarded scales 8694 helpers$1.each(updated, function (hasUpdated, id) { 8695 if (!hasUpdated) { 8696 delete scales[id]; 8697 } 8698 }); 8699 8700 me.scales = scales; 8701 8702 core_scaleService.addScalesToLayout(this); 8703 }, 8704 8705 buildOrUpdateControllers: function () { 8706 var me = this; 8707 var newControllers = []; 8708 8709 helpers$1.each(me.data.datasets, function (dataset, datasetIndex) { 8710 var meta = me.getDatasetMeta(datasetIndex); 8711 var type = dataset.type || me.config.type; 8712 8713 if (meta.type && meta.type !== type) { 8714 me.destroyDatasetMeta(datasetIndex); 8715 meta = me.getDatasetMeta(datasetIndex); 8716 } 8717 meta.type = type; 8718 8719 if (meta.controller) { 8720 meta.controller.updateIndex(datasetIndex); 8721 meta.controller.linkScales(); 8722 } else { 8723 var ControllerClass = controllers[meta.type]; 8724 if (ControllerClass === undefined) { 8725 throw new Error('"' + meta.type + '" is not a chart type.'); 8726 } 8727 8728 meta.controller = new ControllerClass(me, datasetIndex); 8729 newControllers.push(meta.controller); 8730 } 8731 }, me); 8732 8733 return newControllers; 8734 }, 8735 8736 /** 8737 * Reset the elements of all datasets 8738 * @private 8739 */ 8740 resetElements: function () { 8741 var me = this; 8742 helpers$1.each(me.data.datasets, function (dataset, datasetIndex) { 8743 me.getDatasetMeta(datasetIndex).controller.reset(); 8744 }, me); 8745 }, 8746 8747 /** 8748 * Resets the chart back to it's state before the initial animation 8749 */ 8750 reset: function () { 8751 this.resetElements(); 8752 this.tooltip.initialize(); 8753 }, 8754 8755 update: function (config) { 8756 var me = this; 8757 8758 if (!config || typeof config !== 'object') { 8759 // backwards compatibility 8760 config = { 8761 duration: config, 8762 lazy: arguments[1] 8763 }; 8764 } 8765 8766 updateConfig(me); 8767 8768 // plugins options references might have change, let's invalidate the cache 8769 // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 8770 core_plugins._invalidate(me); 8771 8772 if (core_plugins.notify(me, 'beforeUpdate') === false) { 8773 return; 8774 } 8775 8776 // In case the entire data object changed 8777 me.tooltip._data = me.data; 8778 8779 // Make sure dataset controllers are updated and new controllers are reset 8780 var newControllers = me.buildOrUpdateControllers(); 8781 8782 // Make sure all dataset controllers have correct meta data counts 8783 helpers$1.each(me.data.datasets, function (dataset, datasetIndex) { 8784 me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); 8785 }, me); 8786 8787 me.updateLayout(); 8788 8789 // Can only reset the new controllers after the scales have been updated 8790 if (me.options.animation && me.options.animation.duration) { 8791 helpers$1.each(newControllers, function (controller) { 8792 controller.reset(); 8793 }); 8794 } 8795 8796 me.updateDatasets(); 8797 8798 // Need to reset tooltip in case it is displayed with elements that are removed 8799 // after update. 8800 me.tooltip.initialize(); 8801 8802 // Last active contains items that were previously in the tooltip. 8803 // When we reset the tooltip, we need to clear it 8804 me.lastActive = []; 8805 8806 // Do this before render so that any plugins that need final scale updates can use it 8807 core_plugins.notify(me, 'afterUpdate'); 8808 8809 if (me._bufferedRender) { 8810 me._bufferedRequest = { 8811 duration: config.duration, 8812 easing: config.easing, 8813 lazy: config.lazy 8814 }; 8815 } else { 8816 me.render(config); 8817 } 8818 }, 8819 8820 /** 8821 * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` 8822 * hook, in which case, plugins will not be called on `afterLayout`. 8823 * @private 8824 */ 8825 updateLayout: function () { 8826 var me = this; 8827 8828 if (core_plugins.notify(me, 'beforeLayout') === false) { 8829 return; 8830 } 8831 8832 core_layouts.update(this, this.width, this.height); 8833 8834 /** 8835 * Provided for backward compatibility, use `afterLayout` instead. 8836 * @method IPlugin#afterScaleUpdate 8837 * @deprecated since version 2.5.0 8838 * @todo remove at version 3 8839 * @private 8840 */ 8841 core_plugins.notify(me, 'afterScaleUpdate'); 8842 core_plugins.notify(me, 'afterLayout'); 8843 }, 8844 8845 /** 8846 * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` 8847 * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. 8848 * @private 8849 */ 8850 updateDatasets: function () { 8851 var me = this; 8852 8853 if (core_plugins.notify(me, 'beforeDatasetsUpdate') === false) { 8854 return; 8855 } 8856 8857 for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { 8858 me.updateDataset(i); 8859 } 8860 8861 core_plugins.notify(me, 'afterDatasetsUpdate'); 8862 }, 8863 8864 /** 8865 * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` 8866 * hook, in which case, plugins will not be called on `afterDatasetUpdate`. 8867 * @private 8868 */ 8869 updateDataset: function (index) { 8870 var me = this; 8871 var meta = me.getDatasetMeta(index); 8872 var args = { 8873 meta: meta, 8874 index: index 8875 }; 8876 8877 if (core_plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { 8878 return; 8879 } 8880 8881 meta.controller.update(); 8882 8883 core_plugins.notify(me, 'afterDatasetUpdate', [args]); 8884 }, 8885 8886 render: function (config) { 8887 var me = this; 8888 8889 if (!config || typeof config !== 'object') { 8890 // backwards compatibility 8891 config = { 8892 duration: config, 8893 lazy: arguments[1] 8894 }; 8895 } 8896 8897 var animationOptions = me.options.animation; 8898 var duration = valueOrDefault$8(config.duration, animationOptions && animationOptions.duration); 8899 var lazy = config.lazy; 8900 8901 if (core_plugins.notify(me, 'beforeRender') === false) { 8902 return; 8903 } 8904 8905 var onComplete = function (animation) { 8906 core_plugins.notify(me, 'afterRender'); 8907 helpers$1.callback(animationOptions && animationOptions.onComplete, [animation], me); 8908 }; 8909 8910 if (animationOptions && duration) { 8911 var animation = new core_animation({ 8912 numSteps: duration / 16.66, // 60 fps 8913 easing: config.easing || animationOptions.easing, 8914 8915 render: function (chart, animationObject) { 8916 var easingFunction = helpers$1.easing.effects[animationObject.easing]; 8917 var currentStep = animationObject.currentStep; 8918 var stepDecimal = currentStep / animationObject.numSteps; 8919 8920 chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); 8921 }, 8922 8923 onAnimationProgress: animationOptions.onProgress, 8924 onAnimationComplete: onComplete 8925 }); 8926 8927 core_animations.addAnimation(me, animation, duration, lazy); 8928 } else { 8929 me.draw(); 8930 8931 // See https://github.com/chartjs/Chart.js/issues/3781 8932 onComplete(new core_animation({numSteps: 0, chart: me})); 8933 } 8934 8935 return me; 8936 }, 8937 8938 draw: function (easingValue) { 8939 var me = this; 8940 8941 me.clear(); 8942 8943 if (helpers$1.isNullOrUndef(easingValue)) { 8944 easingValue = 1; 8945 } 8946 8947 me.transition(easingValue); 8948 8949 if (me.width <= 0 || me.height <= 0) { 8950 return; 8951 } 8952 8953 if (core_plugins.notify(me, 'beforeDraw', [easingValue]) === false) { 8954 return; 8955 } 8956 8957 // Draw all the scales 8958 helpers$1.each(me.boxes, function (box) { 8959 box.draw(me.chartArea); 8960 }, me); 8961 8962 me.drawDatasets(easingValue); 8963 me._drawTooltip(easingValue); 8964 8965 core_plugins.notify(me, 'afterDraw', [easingValue]); 8966 }, 8967 8968 /** 8969 * @private 8970 */ 8971 transition: function (easingValue) { 8972 var me = this; 8973 8974 for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { 8975 if (me.isDatasetVisible(i)) { 8976 me.getDatasetMeta(i).controller.transition(easingValue); 8977 } 8978 } 8979 8980 me.tooltip.transition(easingValue); 8981 }, 8982 8983 /** 8984 * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` 8985 * hook, in which case, plugins will not be called on `afterDatasetsDraw`. 8986 * @private 8987 */ 8988 drawDatasets: function (easingValue) { 8989 var me = this; 8990 8991 if (core_plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { 8992 return; 8993 } 8994 8995 // Draw datasets reversed to support proper line stacking 8996 for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) { 8997 if (me.isDatasetVisible(i)) { 8998 me.drawDataset(i, easingValue); 8999 } 9000 } 9001 9002 core_plugins.notify(me, 'afterDatasetsDraw', [easingValue]); 9003 }, 9004 9005 /** 9006 * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` 9007 * hook, in which case, plugins will not be called on `afterDatasetDraw`. 9008 * @private 9009 */ 9010 drawDataset: function (index, easingValue) { 9011 var me = this; 9012 var meta = me.getDatasetMeta(index); 9013 var args = { 9014 meta: meta, 9015 index: index, 9016 easingValue: easingValue 9017 }; 9018 9019 if (core_plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { 9020 return; 9021 } 9022 9023 meta.controller.draw(easingValue); 9024 9025 core_plugins.notify(me, 'afterDatasetDraw', [args]); 9026 }, 9027 9028 /** 9029 * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` 9030 * hook, in which case, plugins will not be called on `afterTooltipDraw`. 9031 * @private 9032 */ 9033 _drawTooltip: function (easingValue) { 9034 var me = this; 9035 var tooltip = me.tooltip; 9036 var args = { 9037 tooltip: tooltip, 9038 easingValue: easingValue 9039 }; 9040 9041 if (core_plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { 9042 return; 9043 } 9044 9045 tooltip.draw(); 9046 9047 core_plugins.notify(me, 'afterTooltipDraw', [args]); 9048 }, 9049 9050 /** 9051 * Get the single element that was clicked on 9052 * @return An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw 9053 */ 9054 getElementAtEvent: function (e) { 9055 return core_interaction.modes.single(this, e); 9056 }, 9057 9058 getElementsAtEvent: function (e) { 9059 return core_interaction.modes.label(this, e, {intersect: true}); 9060 }, 9061 9062 getElementsAtXAxis: function (e) { 9063 return core_interaction.modes['x-axis'](this, e, {intersect: true}); 9064 }, 9065 9066 getElementsAtEventForMode: function (e, mode, options) { 9067 var method = core_interaction.modes[mode]; 9068 if (typeof method === 'function') { 9069 return method(this, e, options); 9070 } 9071 9072 return []; 9073 }, 9074 9075 getDatasetAtEvent: function (e) { 9076 return core_interaction.modes.dataset(this, e, {intersect: true}); 9077 }, 9078 9079 getDatasetMeta: function (datasetIndex) { 9080 var me = this; 9081 var dataset = me.data.datasets[datasetIndex]; 9082 if (!dataset._meta) { 9083 dataset._meta = {}; 9084 } 9085 9086 var meta = dataset._meta[me.id]; 9087 if (!meta) { 9088 meta = dataset._meta[me.id] = { 9089 type: null, 9090 data: [], 9091 dataset: null, 9092 controller: null, 9093 hidden: null, // See isDatasetVisible() comment 9094 xAxisID: null, 9095 yAxisID: null 9096 }; 9097 } 9098 9099 return meta; 9100 }, 9101 9102 getVisibleDatasetCount: function () { 9103 var count = 0; 9104 for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { 9105 if (this.isDatasetVisible(i)) { 9106 count++; 9107 } 9108 } 9109 return count; 9110 }, 9111 9112 isDatasetVisible: function (datasetIndex) { 9113 var meta = this.getDatasetMeta(datasetIndex); 9114 9115 // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, 9116 // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. 9117 return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; 9118 }, 9119 9120 generateLegend: function () { 9121 return this.options.legendCallback(this); 9122 }, 9123 9124 /** 9125 * @private 9126 */ 9127 destroyDatasetMeta: function (datasetIndex) { 9128 var id = this.id; 9129 var dataset = this.data.datasets[datasetIndex]; 9130 var meta = dataset._meta && dataset._meta[id]; 9131 9132 if (meta) { 9133 meta.controller.destroy(); 9134 delete dataset._meta[id]; 9135 } 9136 }, 9137 9138 destroy: function () { 9139 var me = this; 9140 var canvas = me.canvas; 9141 var i, ilen; 9142 9143 me.stop(); 9144 9145 // dataset controllers need to cleanup associated data 9146 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { 9147 me.destroyDatasetMeta(i); 9148 } 9149 9150 if (canvas) { 9151 me.unbindEvents(); 9152 helpers$1.canvas.clear(me); 9153 platform.releaseContext(me.ctx); 9154 me.canvas = null; 9155 me.ctx = null; 9156 } 9157 9158 core_plugins.notify(me, 'destroy'); 9159 9160 delete Chart.instances[me.id]; 9161 }, 9162 9163 toBase64Image: function () { 9164 return this.canvas.toDataURL.apply(this.canvas, arguments); 9165 }, 9166 9167 initToolTip: function () { 9168 var me = this; 9169 me.tooltip = new core_tooltip({ 9170 _chart: me, 9171 _chartInstance: me, // deprecated, backward compatibility 9172 _data: me.data, 9173 _options: me.options.tooltips 9174 }, me); 9175 }, 9176 9177 /** 9178 * @private 9179 */ 9180 bindEvents: function () { 9181 var me = this; 9182 var listeners = me._listeners = {}; 9183 var listener = function () { 9184 me.eventHandler.apply(me, arguments); 9185 }; 9186 9187 helpers$1.each(me.options.events, function (type) { 9188 platform.addEventListener(me, type, listener); 9189 listeners[type] = listener; 9190 }); 9191 9192 // Elements used to detect size change should not be injected for non responsive charts. 9193 // See https://github.com/chartjs/Chart.js/issues/2210 9194 if (me.options.responsive) { 9195 listener = function () { 9196 me.resize(); 9197 }; 9198 9199 platform.addEventListener(me, 'resize', listener); 9200 listeners.resize = listener; 9201 } 9202 }, 9203 9204 /** 9205 * @private 9206 */ 9207 unbindEvents: function () { 9208 var me = this; 9209 var listeners = me._listeners; 9210 if (!listeners) { 9211 return; 9212 } 9213 9214 delete me._listeners; 9215 helpers$1.each(listeners, function (listener, type) { 9216 platform.removeEventListener(me, type, listener); 9217 }); 9218 }, 9219 9220 updateHoverStyle: function (elements, mode, enabled) { 9221 var method = enabled ? 'setHoverStyle' : 'removeHoverStyle'; 9222 var element, i, ilen; 9223 9224 for (i = 0, ilen = elements.length; i < ilen; ++i) { 9225 element = elements[i]; 9226 if (element) { 9227 this.getDatasetMeta(element._datasetIndex).controller[method](element); 9228 } 9229 } 9230 }, 9231 9232 /** 9233 * @private 9234 */ 9235 eventHandler: function (e) { 9236 var me = this; 9237 var tooltip = me.tooltip; 9238 9239 if (core_plugins.notify(me, 'beforeEvent', [e]) === false) { 9240 return; 9241 } 9242 9243 // Buffer any update calls so that renders do not occur 9244 me._bufferedRender = true; 9245 me._bufferedRequest = null; 9246 9247 var changed = me.handleEvent(e); 9248 // for smooth tooltip animations issue #4989 9249 // the tooltip should be the source of change 9250 // Animation check workaround: 9251 // tooltip._start will be null when tooltip isn't animating 9252 if (tooltip) { 9253 changed = tooltip._start 9254 ? tooltip.handleEvent(e) 9255 : changed | tooltip.handleEvent(e); 9256 } 9257 9258 core_plugins.notify(me, 'afterEvent', [e]); 9259 9260 var bufferedRequest = me._bufferedRequest; 9261 if (bufferedRequest) { 9262 // If we have an update that was triggered, we need to do a normal render 9263 me.render(bufferedRequest); 9264 } else if (changed && !me.animating) { 9265 // If entering, leaving, or changing elements, animate the change via pivot 9266 me.stop(); 9267 9268 // We only need to render at this point. Updating will cause scales to be 9269 // recomputed generating flicker & using more memory than necessary. 9270 me.render({ 9271 duration: me.options.hover.animationDuration, 9272 lazy: true 9273 }); 9274 } 9275 9276 me._bufferedRender = false; 9277 me._bufferedRequest = null; 9278 9279 return me; 9280 }, 9281 9282 /** 9283 * Handle an event 9284 * @private 9285 * @param {IEvent} event the event to handle 9286 * @return {boolean} true if the chart needs to re-render 9287 */ 9288 handleEvent: function (e) { 9289 var me = this; 9290 var options = me.options || {}; 9291 var hoverOptions = options.hover; 9292 var changed = false; 9293 9294 me.lastActive = me.lastActive || []; 9295 9296 // Find Active Elements for hover and tooltips 9297 if (e.type === 'mouseout') { 9298 me.active = []; 9299 } else { 9300 me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); 9301 } 9302 9303 // Invoke onHover hook 9304 // Need to call with native event here to not break backwards compatibility 9305 helpers$1.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); 9306 9307 if (e.type === 'mouseup' || e.type === 'click') { 9308 if (options.onClick) { 9309 // Use e.native here for backwards compatibility 9310 options.onClick.call(me, e.native, me.active); 9311 } 9312 } 9313 9314 // Remove styling for last active (even if it may still be active) 9315 if (me.lastActive.length) { 9316 me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); 9317 } 9318 9319 // Built in hover styling 9320 if (me.active.length && hoverOptions.mode) { 9321 me.updateHoverStyle(me.active, hoverOptions.mode, true); 9322 } 9323 9324 changed = !helpers$1.arrayEquals(me.active, me.lastActive); 9325 9326 // Remember Last Actives 9327 me.lastActive = me.active; 9328 9329 return changed; 9330 } 9331 }); 9332 9333 /** 9334 * NOTE(SB) We actually don't use this container anymore but we need to keep it 9335 * for backward compatibility. Though, it can still be useful for plugins that 9336 * would need to work on multiple charts?! 9337 */ 9338 Chart.instances = {}; 9339 9340 var core_controller = Chart; 9341 9342// DEPRECATIONS 9343 9344 /** 9345 * Provided for backward compatibility, use Chart instead. 9346 * @class Chart.Controller 9347 * @deprecated since version 2.6 9348 * @todo remove at version 3 9349 * @private 9350 */ 9351 Chart.Controller = Chart; 9352 9353 /** 9354 * Provided for backward compatibility, not available anymore. 9355 * @namespace Chart 9356 * @deprecated since version 2.8 9357 * @todo remove at version 3 9358 * @private 9359 */ 9360 Chart.types = {}; 9361 9362 /** 9363 * Provided for backward compatibility, not available anymore. 9364 * @namespace Chart.helpers.configMerge 9365 * @deprecated since version 2.8.0 9366 * @todo remove at version 3 9367 * @private 9368 */ 9369 helpers$1.configMerge = mergeConfig; 9370 9371 /** 9372 * Provided for backward compatibility, not available anymore. 9373 * @namespace Chart.helpers.scaleMerge 9374 * @deprecated since version 2.8.0 9375 * @todo remove at version 3 9376 * @private 9377 */ 9378 helpers$1.scaleMerge = mergeScaleConfig; 9379 9380 var core_helpers = function () { 9381 9382 // -- Basic js utility methods 9383 9384 helpers$1.where = function (collection, filterCallback) { 9385 if (helpers$1.isArray(collection) && Array.prototype.filter) { 9386 return collection.filter(filterCallback); 9387 } 9388 var filtered = []; 9389 9390 helpers$1.each(collection, function (item) { 9391 if (filterCallback(item)) { 9392 filtered.push(item); 9393 } 9394 }); 9395 9396 return filtered; 9397 }; 9398 helpers$1.findIndex = Array.prototype.findIndex ? 9399 function (array, callback, scope) { 9400 return array.findIndex(callback, scope); 9401 } : 9402 function (array, callback, scope) { 9403 scope = scope === undefined ? array : scope; 9404 for (var i = 0, ilen = array.length; i < ilen; ++i) { 9405 if (callback.call(scope, array[i], i, array)) { 9406 return i; 9407 } 9408 } 9409 return -1; 9410 }; 9411 helpers$1.findNextWhere = function (arrayToSearch, filterCallback, startIndex) { 9412 // Default to start of the array 9413 if (helpers$1.isNullOrUndef(startIndex)) { 9414 startIndex = -1; 9415 } 9416 for (var i = startIndex + 1; i < arrayToSearch.length; i++) { 9417 var currentItem = arrayToSearch[i]; 9418 if (filterCallback(currentItem)) { 9419 return currentItem; 9420 } 9421 } 9422 }; 9423 helpers$1.findPreviousWhere = function (arrayToSearch, filterCallback, startIndex) { 9424 // Default to end of the array 9425 if (helpers$1.isNullOrUndef(startIndex)) { 9426 startIndex = arrayToSearch.length; 9427 } 9428 for (var i = startIndex - 1; i >= 0; i--) { 9429 var currentItem = arrayToSearch[i]; 9430 if (filterCallback(currentItem)) { 9431 return currentItem; 9432 } 9433 } 9434 }; 9435 9436 // -- Math methods 9437 helpers$1.isNumber = function (n) { 9438 return !isNaN(parseFloat(n)) && isFinite(n); 9439 }; 9440 helpers$1.almostEquals = function (x, y, epsilon) { 9441 return Math.abs(x - y) < epsilon; 9442 }; 9443 helpers$1.almostWhole = function (x, epsilon) { 9444 var rounded = Math.round(x); 9445 return (((rounded - epsilon) < x) && ((rounded + epsilon) > x)); 9446 }; 9447 helpers$1.max = function (array) { 9448 return array.reduce(function (max, value) { 9449 if (!isNaN(value)) { 9450 return Math.max(max, value); 9451 } 9452 return max; 9453 }, Number.NEGATIVE_INFINITY); 9454 }; 9455 helpers$1.min = function (array) { 9456 return array.reduce(function (min, value) { 9457 if (!isNaN(value)) { 9458 return Math.min(min, value); 9459 } 9460 return min; 9461 }, Number.POSITIVE_INFINITY); 9462 }; 9463 helpers$1.sign = Math.sign ? 9464 function (x) { 9465 return Math.sign(x); 9466 } : 9467 function (x) { 9468 x = +x; // convert to a number 9469 if (x === 0 || isNaN(x)) { 9470 return x; 9471 } 9472 return x > 0 ? 1 : -1; 9473 }; 9474 helpers$1.log10 = Math.log10 ? 9475 function (x) { 9476 return Math.log10(x); 9477 } : 9478 function (x) { 9479 var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. 9480 // Check for whole powers of 10, 9481 // which due to floating point rounding error should be corrected. 9482 var powerOf10 = Math.round(exponent); 9483 var isPowerOf10 = x === Math.pow(10, powerOf10); 9484 9485 return isPowerOf10 ? powerOf10 : exponent; 9486 }; 9487 helpers$1.toRadians = function (degrees) { 9488 return degrees * (Math.PI / 180); 9489 }; 9490 helpers$1.toDegrees = function (radians) { 9491 return radians * (180 / Math.PI); 9492 }; 9493 9494 /** 9495 * Returns the number of decimal places 9496 * i.e. the number of digits after the decimal point, of the value of this Number. 9497 * @param {number} x - A number. 9498 * @returns {number} The number of decimal places. 9499 * @private 9500 */ 9501 helpers$1._decimalPlaces = function (x) { 9502 if (!helpers$1.isFinite(x)) { 9503 return; 9504 } 9505 var e = 1; 9506 var p = 0; 9507 while (Math.round(x * e) / e !== x) { 9508 e *= 10; 9509 p++; 9510 } 9511 return p; 9512 }; 9513 9514 // Gets the angle from vertical upright to the point about a centre. 9515 helpers$1.getAngleFromPoint = function (centrePoint, anglePoint) { 9516 var distanceFromXCenter = anglePoint.x - centrePoint.x; 9517 var distanceFromYCenter = anglePoint.y - centrePoint.y; 9518 var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); 9519 9520 var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); 9521 9522 if (angle < (-0.5 * Math.PI)) { 9523 angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] 9524 } 9525 9526 return { 9527 angle: angle, 9528 distance: radialDistanceFromCenter 9529 }; 9530 }; 9531 helpers$1.distanceBetweenPoints = function (pt1, pt2) { 9532 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); 9533 }; 9534 9535 /** 9536 * Provided for backward compatibility, not available anymore 9537 * @function Chart.helpers.aliasPixel 9538 * @deprecated since version 2.8.0 9539 * @todo remove at version 3 9540 */ 9541 helpers$1.aliasPixel = function (pixelWidth) { 9542 return (pixelWidth % 2 === 0) ? 0 : 0.5; 9543 }; 9544 9545 /** 9546 * Returns the aligned pixel value to avoid anti-aliasing blur 9547 * @param {Chart} chart - The chart instance. 9548 * @param {number} pixel - A pixel value. 9549 * @param {number} width - The width of the element. 9550 * @returns {number} The aligned pixel value. 9551 * @private 9552 */ 9553 helpers$1._alignPixel = function (chart, pixel, width) { 9554 var devicePixelRatio = chart.currentDevicePixelRatio; 9555 var halfWidth = width / 2; 9556 return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth; 9557 }; 9558 9559 helpers$1.splineCurve = function (firstPoint, middlePoint, afterPoint, t) { 9560 // Props to Rob Spencer at scaled innovation for his post on splining between points 9561 // http://scaledinnovation.com/analytics/splines/aboutSplines.html 9562 9563 // This function must also respect "skipped" points 9564 9565 var previous = firstPoint.skip ? middlePoint : firstPoint; 9566 var current = middlePoint; 9567 var next = afterPoint.skip ? middlePoint : afterPoint; 9568 9569 var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); 9570 var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); 9571 9572 var s01 = d01 / (d01 + d12); 9573 var s12 = d12 / (d01 + d12); 9574 9575 // If all points are the same, s01 & s02 will be inf 9576 s01 = isNaN(s01) ? 0 : s01; 9577 s12 = isNaN(s12) ? 0 : s12; 9578 9579 var fa = t * s01; // scaling factor for triangle Ta 9580 var fb = t * s12; 9581 9582 return { 9583 previous: { 9584 x: current.x - fa * (next.x - previous.x), 9585 y: current.y - fa * (next.y - previous.y) 9586 }, 9587 next: { 9588 x: current.x + fb * (next.x - previous.x), 9589 y: current.y + fb * (next.y - previous.y) 9590 } 9591 }; 9592 }; 9593 helpers$1.EPSILON = Number.EPSILON || 1e-14; 9594 helpers$1.splineCurveMonotone = function (points) { 9595 // This function calculates Bézier control points in a similar way than |splineCurve|, 9596 // but preserves monotonicity of the provided data and ensures no local extremums are added 9597 // between the dataset discrete points due to the interpolation. 9598 // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation 9599 9600 var pointsWithTangents = (points || []).map(function (point) { 9601 return { 9602 model: point._model, 9603 deltaK: 0, 9604 mK: 0 9605 }; 9606 }); 9607 9608 // Calculate slopes (deltaK) and initialize tangents (mK) 9609 var pointsLen = pointsWithTangents.length; 9610 var i, pointBefore, pointCurrent, pointAfter; 9611 for (i = 0; i < pointsLen; ++i) { 9612 pointCurrent = pointsWithTangents[i]; 9613 if (pointCurrent.model.skip) { 9614 continue; 9615 } 9616 9617 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; 9618 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; 9619 if (pointAfter && !pointAfter.model.skip) { 9620 var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); 9621 9622 // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 9623 pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; 9624 } 9625 9626 if (!pointBefore || pointBefore.model.skip) { 9627 pointCurrent.mK = pointCurrent.deltaK; 9628 } else if (!pointAfter || pointAfter.model.skip) { 9629 pointCurrent.mK = pointBefore.deltaK; 9630 } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { 9631 pointCurrent.mK = 0; 9632 } else { 9633 pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; 9634 } 9635 } 9636 9637 // Adjust tangents to ensure monotonic properties 9638 var alphaK, betaK, tauK, squaredMagnitude; 9639 for (i = 0; i < pointsLen - 1; ++i) { 9640 pointCurrent = pointsWithTangents[i]; 9641 pointAfter = pointsWithTangents[i + 1]; 9642 if (pointCurrent.model.skip || pointAfter.model.skip) { 9643 continue; 9644 } 9645 9646 if (helpers$1.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { 9647 pointCurrent.mK = pointAfter.mK = 0; 9648 continue; 9649 } 9650 9651 alphaK = pointCurrent.mK / pointCurrent.deltaK; 9652 betaK = pointAfter.mK / pointCurrent.deltaK; 9653 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); 9654 if (squaredMagnitude <= 9) { 9655 continue; 9656 } 9657 9658 tauK = 3 / Math.sqrt(squaredMagnitude); 9659 pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; 9660 pointAfter.mK = betaK * tauK * pointCurrent.deltaK; 9661 } 9662 9663 // Compute control points 9664 var deltaX; 9665 for (i = 0; i < pointsLen; ++i) { 9666 pointCurrent = pointsWithTangents[i]; 9667 if (pointCurrent.model.skip) { 9668 continue; 9669 } 9670 9671 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; 9672 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; 9673 if (pointBefore && !pointBefore.model.skip) { 9674 deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; 9675 pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; 9676 pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; 9677 } 9678 if (pointAfter && !pointAfter.model.skip) { 9679 deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; 9680 pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; 9681 pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; 9682 } 9683 } 9684 }; 9685 helpers$1.nextItem = function (collection, index, loop) { 9686 if (loop) { 9687 return index >= collection.length - 1 ? collection[0] : collection[index + 1]; 9688 } 9689 return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; 9690 }; 9691 helpers$1.previousItem = function (collection, index, loop) { 9692 if (loop) { 9693 return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; 9694 } 9695 return index <= 0 ? collection[0] : collection[index - 1]; 9696 }; 9697 // Implementation of the nice number algorithm used in determining where axis labels will go 9698 helpers$1.niceNum = function (range, round) { 9699 var exponent = Math.floor(helpers$1.log10(range)); 9700 var fraction = range / Math.pow(10, exponent); 9701 var niceFraction; 9702 9703 if (round) { 9704 if (fraction < 1.5) { 9705 niceFraction = 1; 9706 } else if (fraction < 3) { 9707 niceFraction = 2; 9708 } else if (fraction < 7) { 9709 niceFraction = 5; 9710 } else { 9711 niceFraction = 10; 9712 } 9713 } else if (fraction <= 1.0) { 9714 niceFraction = 1; 9715 } else if (fraction <= 2) { 9716 niceFraction = 2; 9717 } else if (fraction <= 5) { 9718 niceFraction = 5; 9719 } else { 9720 niceFraction = 10; 9721 } 9722 9723 return niceFraction * Math.pow(10, exponent); 9724 }; 9725 // Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ 9726 helpers$1.requestAnimFrame = (function () { 9727 if (typeof window === 'undefined') { 9728 return function (callback) { 9729 callback(); 9730 }; 9731 } 9732 return window.requestAnimationFrame || 9733 window.webkitRequestAnimationFrame || 9734 window.mozRequestAnimationFrame || 9735 window.oRequestAnimationFrame || 9736 window.msRequestAnimationFrame || 9737 function (callback) { 9738 return window.setTimeout(callback, 1000 / 60); 9739 }; 9740 }()); 9741 // -- DOM methods 9742 helpers$1.getRelativePosition = function (evt, chart) { 9743 var mouseX, mouseY; 9744 var e = evt.originalEvent || evt; 9745 var canvas = evt.target || evt.srcElement; 9746 var boundingRect = canvas.getBoundingClientRect(); 9747 9748 var touches = e.touches; 9749 if (touches && touches.length > 0) { 9750 mouseX = touches[0].clientX; 9751 mouseY = touches[0].clientY; 9752 9753 } else { 9754 mouseX = e.clientX; 9755 mouseY = e.clientY; 9756 } 9757 9758 // Scale mouse coordinates into canvas coordinates 9759 // by following the pattern laid out by 'jerryj' in the comments of 9760 // https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ 9761 var paddingLeft = parseFloat(helpers$1.getStyle(canvas, 'padding-left')); 9762 var paddingTop = parseFloat(helpers$1.getStyle(canvas, 'padding-top')); 9763 var paddingRight = parseFloat(helpers$1.getStyle(canvas, 'padding-right')); 9764 var paddingBottom = parseFloat(helpers$1.getStyle(canvas, 'padding-bottom')); 9765 var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; 9766 var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; 9767 9768 // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However 9769 // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here 9770 mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); 9771 mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); 9772 9773 return { 9774 x: mouseX, 9775 y: mouseY 9776 }; 9777 9778 }; 9779 9780 // Private helper function to convert max-width/max-height values that may be percentages into a number 9781 function parseMaxStyle(styleValue, node, parentProperty) { 9782 var valueInPixels; 9783 if (typeof styleValue === 'string') { 9784 valueInPixels = parseInt(styleValue, 10); 9785 9786 if (styleValue.indexOf('%') !== -1) { 9787 // percentage * size in dimension 9788 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; 9789 } 9790 } else { 9791 valueInPixels = styleValue; 9792 } 9793 9794 return valueInPixels; 9795 } 9796 9797 /** 9798 * Returns if the given value contains an effective constraint. 9799 * @private 9800 */ 9801 function isConstrainedValue(value) { 9802 return value !== undefined && value !== null && value !== 'none'; 9803 } 9804 9805 /** 9806 * Returns the max width or height of the given DOM node in a cross-browser compatible fashion 9807 * @param {HTMLElement} domNode - the node to check the constraint on 9808 * @param {string} maxStyle - the style that defines the maximum for the direction we are using ('max-width' / 'max-height') 9809 * @param {string} percentageProperty - property of parent to use when calculating width as a percentage 9810 * @see {@link https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser} 9811 */ 9812 function getConstraintDimension(domNode, maxStyle, percentageProperty) { 9813 var view = document.defaultView; 9814 var parentNode = helpers$1._getParentNode(domNode); 9815 var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; 9816 var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; 9817 var hasCNode = isConstrainedValue(constrainedNode); 9818 var hasCContainer = isConstrainedValue(constrainedContainer); 9819 var infinity = Number.POSITIVE_INFINITY; 9820 9821 if (hasCNode || hasCContainer) { 9822 return Math.min( 9823 hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, 9824 hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); 9825 } 9826 9827 return 'none'; 9828 } 9829 9830 // returns Number or undefined if no constraint 9831 helpers$1.getConstraintWidth = function (domNode) { 9832 return getConstraintDimension(domNode, 'max-width', 'clientWidth'); 9833 }; 9834 // returns Number or undefined if no constraint 9835 helpers$1.getConstraintHeight = function (domNode) { 9836 return getConstraintDimension(domNode, 'max-height', 'clientHeight'); 9837 }; 9838 /** 9839 * @private 9840 */ 9841 helpers$1._calculatePadding = function (container, padding, parentDimension) { 9842 padding = helpers$1.getStyle(container, padding); 9843 9844 return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10); 9845 }; 9846 /** 9847 * @private 9848 */ 9849 helpers$1._getParentNode = function (domNode) { 9850 var parent = domNode.parentNode; 9851 if (parent && parent.toString() === '[object ShadowRoot]') { 9852 parent = parent.host; 9853 } 9854 return parent; 9855 }; 9856 helpers$1.getMaximumWidth = function (domNode) { 9857 var container = helpers$1._getParentNode(domNode); 9858 if (!container) { 9859 return domNode.clientWidth; 9860 } 9861 9862 var clientWidth = container.clientWidth; 9863 var paddingLeft = helpers$1._calculatePadding(container, 'padding-left', clientWidth); 9864 var paddingRight = helpers$1._calculatePadding(container, 'padding-right', clientWidth); 9865 9866 var w = clientWidth - paddingLeft - paddingRight; 9867 var cw = helpers$1.getConstraintWidth(domNode); 9868 return isNaN(cw) ? w : Math.min(w, cw); 9869 }; 9870 helpers$1.getMaximumHeight = function (domNode) { 9871 var container = helpers$1._getParentNode(domNode); 9872 if (!container) { 9873 return domNode.clientHeight; 9874 } 9875 9876 var clientHeight = container.clientHeight; 9877 var paddingTop = helpers$1._calculatePadding(container, 'padding-top', clientHeight); 9878 var paddingBottom = helpers$1._calculatePadding(container, 'padding-bottom', clientHeight); 9879 9880 var h = clientHeight - paddingTop - paddingBottom; 9881 var ch = helpers$1.getConstraintHeight(domNode); 9882 return isNaN(ch) ? h : Math.min(h, ch); 9883 }; 9884 helpers$1.getStyle = function (el, property) { 9885 return el.currentStyle ? 9886 el.currentStyle[property] : 9887 document.defaultView.getComputedStyle(el, null).getPropertyValue(property); 9888 }; 9889 helpers$1.retinaScale = function (chart, forceRatio) { 9890 var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1; 9891 if (pixelRatio === 1) { 9892 return; 9893 } 9894 9895 var canvas = chart.canvas; 9896 var height = chart.height; 9897 var width = chart.width; 9898 9899 canvas.height = height * pixelRatio; 9900 canvas.width = width * pixelRatio; 9901 chart.ctx.scale(pixelRatio, pixelRatio); 9902 9903 // If no style has been set on the canvas, the render size is used as display size, 9904 // making the chart visually bigger, so let's enforce it to the "correct" values. 9905 // See https://github.com/chartjs/Chart.js/issues/3575 9906 if (!canvas.style.height && !canvas.style.width) { 9907 canvas.style.height = height + 'px'; 9908 canvas.style.width = width + 'px'; 9909 } 9910 }; 9911 // -- Canvas methods 9912 helpers$1.fontString = function (pixelSize, fontStyle, fontFamily) { 9913 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; 9914 }; 9915 helpers$1.longestText = function (ctx, font, arrayOfThings, cache) { 9916 cache = cache || {}; 9917 var data = cache.data = cache.data || {}; 9918 var gc = cache.garbageCollect = cache.garbageCollect || []; 9919 9920 if (cache.font !== font) { 9921 data = cache.data = {}; 9922 gc = cache.garbageCollect = []; 9923 cache.font = font; 9924 } 9925 9926 ctx.font = font; 9927 var longest = 0; 9928 helpers$1.each(arrayOfThings, function (thing) { 9929 // Undefined strings and arrays should not be measured 9930 if (thing !== undefined && thing !== null && helpers$1.isArray(thing) !== true) { 9931 longest = helpers$1.measureText(ctx, data, gc, longest, thing); 9932 } else if (helpers$1.isArray(thing)) { 9933 // if it is an array lets measure each element 9934 // to do maybe simplify this function a bit so we can do this more recursively? 9935 helpers$1.each(thing, function (nestedThing) { 9936 // Undefined strings and arrays should not be measured 9937 if (nestedThing !== undefined && nestedThing !== null && !helpers$1.isArray(nestedThing)) { 9938 longest = helpers$1.measureText(ctx, data, gc, longest, nestedThing); 9939 } 9940 }); 9941 } 9942 }); 9943 9944 var gcLen = gc.length / 2; 9945 if (gcLen > arrayOfThings.length) { 9946 for (var i = 0; i < gcLen; i++) { 9947 delete data[gc[i]]; 9948 } 9949 gc.splice(0, gcLen); 9950 } 9951 return longest; 9952 }; 9953 helpers$1.measureText = function (ctx, data, gc, longest, string) { 9954 var textWidth = data[string]; 9955 if (!textWidth) { 9956 textWidth = data[string] = ctx.measureText(string).width; 9957 gc.push(string); 9958 } 9959 if (textWidth > longest) { 9960 longest = textWidth; 9961 } 9962 return longest; 9963 }; 9964 helpers$1.numberOfLabelLines = function (arrayOfThings) { 9965 var numberOfLines = 1; 9966 helpers$1.each(arrayOfThings, function (thing) { 9967 if (helpers$1.isArray(thing)) { 9968 if (thing.length > numberOfLines) { 9969 numberOfLines = thing.length; 9970 } 9971 } 9972 }); 9973 return numberOfLines; 9974 }; 9975 9976 helpers$1.color = !chartjsColor ? 9977 function (value) { 9978 console.error('Color.js not found!'); 9979 return value; 9980 } : 9981 function (value) { 9982 /* global CanvasGradient */ 9983 if (value instanceof CanvasGradient) { 9984 value = core_defaults.global.defaultColor; 9985 } 9986 9987 return chartjsColor(value); 9988 }; 9989 9990 helpers$1.getHoverColor = function (colorValue) { 9991 /* global CanvasPattern */ 9992 return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ? 9993 colorValue : 9994 helpers$1.color(colorValue).saturate(0.5).darken(0.1).rgbString(); 9995 }; 9996 }; 9997 9998 function abstract() { 9999 throw new Error( 10000 'This method is not implemented: either no adapter can ' + 10001 'be found or an incomplete integration was provided.' 10002 ); 10003 } 10004 10005 /** 10006 * Date adapter (current used by the time scale) 10007 * @namespace Chart._adapters._date 10008 * @memberof Chart._adapters 10009 * @private 10010 */ 10011 10012 /** 10013 * Currently supported unit string values. 10014 * @typedef {('millisecond'|'second'|'minute'|'hour'|'day'|'week'|'month'|'quarter'|'year')} 10015 * @memberof Chart._adapters._date 10016 * @name Unit 10017 */ 10018 10019 /** 10020 * @class 10021 */ 10022 function DateAdapter(options) { 10023 this.options = options || {}; 10024 } 10025 10026 helpers$1.extend(DateAdapter.prototype, /** @lends DateAdapter */ { 10027 /** 10028 * Returns a map of time formats for the supported formatting units defined 10029 * in Unit as well as 'datetime' representing a detailed date/time string. 10030 * @returns {{string: string}} 10031 */ 10032 formats: abstract, 10033 10034 /** 10035 * Parses the given `value` and return the associated timestamp. 10036 * @param {any} value - the value to parse (usually comes from the data) 10037 * @param {string} [format] - the expected data format 10038 * @returns {(number|null)} 10039 * @function 10040 */ 10041 parse: abstract, 10042 10043 /** 10044 * Returns the formatted date in the specified `format` for a given `timestamp`. 10045 * @param {number} timestamp - the timestamp to format 10046 * @param {string} format - the date/time token 10047 * @return {string} 10048 * @function 10049 */ 10050 format: abstract, 10051 10052 /** 10053 * Adds the specified `amount` of `unit` to the given `timestamp`. 10054 * @param {number} timestamp - the input timestamp 10055 * @param {number} amount - the amount to add 10056 * @param {Unit} unit - the unit as string 10057 * @return {number} 10058 * @function 10059 */ 10060 add: abstract, 10061 10062 /** 10063 * Returns the number of `unit` between the given timestamps. 10064 * @param {number} max - the input timestamp (reference) 10065 * @param {number} min - the timestamp to substract 10066 * @param {Unit} unit - the unit as string 10067 * @return {number} 10068 * @function 10069 */ 10070 diff: abstract, 10071 10072 /** 10073 * Returns start of `unit` for the given `timestamp`. 10074 * @param {number} timestamp - the input timestamp 10075 * @param {Unit} unit - the unit as string 10076 * @param {number} [weekday] - the ISO day of the week with 1 being Monday 10077 * and 7 being Sunday (only needed if param *unit* is `isoWeek`). 10078 * @function 10079 */ 10080 startOf: abstract, 10081 10082 /** 10083 * Returns end of `unit` for the given `timestamp`. 10084 * @param {number} timestamp - the input timestamp 10085 * @param {Unit} unit - the unit as string 10086 * @function 10087 */ 10088 endOf: abstract, 10089 10090 // DEPRECATIONS 10091 10092 /** 10093 * Provided for backward compatibility for scale.getValueForPixel(), 10094 * this method should be overridden only by the moment adapter. 10095 * @deprecated since version 2.8.0 10096 * @todo remove at version 3 10097 * @private 10098 */ 10099 _create: function (value) { 10100 return value; 10101 } 10102 }); 10103 10104 DateAdapter.override = function (members) { 10105 helpers$1.extend(DateAdapter.prototype, members); 10106 }; 10107 10108 var _date = DateAdapter; 10109 10110 var core_adapters = { 10111 _date: _date 10112 }; 10113 10114 /** 10115 * Namespace to hold static tick generation functions 10116 * @namespace Chart.Ticks 10117 */ 10118 var core_ticks = { 10119 /** 10120 * Namespace to hold formatters for different types of ticks 10121 * @namespace Chart.Ticks.formatters 10122 */ 10123 formatters: { 10124 /** 10125 * Formatter for value labels 10126 * @method Chart.Ticks.formatters.values 10127 * @param value the value to display 10128 * @return {string|string[]} the label to display 10129 */ 10130 values: function (value) { 10131 return helpers$1.isArray(value) ? value : '' + value; 10132 }, 10133 10134 /** 10135 * Formatter for linear numeric ticks 10136 * @method Chart.Ticks.formatters.linear 10137 * @param tickValue {number} the value to be formatted 10138 * @param index {number} the position of the tickValue parameter in the ticks array 10139 * @param ticks {number[]} the list of ticks being converted 10140 * @return {string} string representation of the tickValue parameter 10141 */ 10142 linear: function (tickValue, index, ticks) { 10143 // If we have lots of ticks, don't use the ones 10144 var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; 10145 10146 // If we have a number like 2.5 as the delta, figure out how many decimal places we need 10147 if (Math.abs(delta) > 1) { 10148 if (tickValue !== Math.floor(tickValue)) { 10149 // not an integer 10150 delta = tickValue - Math.floor(tickValue); 10151 } 10152 } 10153 10154 var logDelta = helpers$1.log10(Math.abs(delta)); 10155 var tickString = ''; 10156 10157 if (tickValue !== 0) { 10158 var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1])); 10159 if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation 10160 var logTick = helpers$1.log10(Math.abs(tickValue)); 10161 tickString = tickValue.toExponential(Math.floor(logTick) - Math.floor(logDelta)); 10162 } else { 10163 var numDecimal = -1 * Math.floor(logDelta); 10164 numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places 10165 tickString = tickValue.toFixed(numDecimal); 10166 } 10167 } else { 10168 tickString = '0'; // never show decimal places for 0 10169 } 10170 10171 return tickString; 10172 }, 10173 10174 logarithmic: function (tickValue, index, ticks) { 10175 var remain = tickValue / (Math.pow(10, Math.floor(helpers$1.log10(tickValue)))); 10176 10177 if (tickValue === 0) { 10178 return '0'; 10179 } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { 10180 return tickValue.toExponential(); 10181 } 10182 return ''; 10183 } 10184 } 10185 }; 10186 10187 var valueOrDefault$9 = helpers$1.valueOrDefault; 10188 var valueAtIndexOrDefault = helpers$1.valueAtIndexOrDefault; 10189 10190 core_defaults._set('scale', { 10191 display: true, 10192 position: 'left', 10193 offset: false, 10194 10195 // grid line settings 10196 gridLines: { 10197 display: true, 10198 color: 'rgba(0, 0, 0, 0.1)', 10199 lineWidth: 1, 10200 drawBorder: true, 10201 drawOnChartArea: true, 10202 drawTicks: true, 10203 tickMarkLength: 10,
10204 zeroLineWidth: 1, 10205 zeroLineColor: 'rgba(0,0,0,0.25)', 10206 zeroLineBorderDash: [], 10207 zeroLineBorderDashOffset: 0.0, 10208 offsetGridLines: false, 10209 borderDash: [], 10210 borderDashOffset: 0.0 10211 }, 10212 10213 // scale label 10214 scaleLabel: { 10215 // display property 10216 display: false, 10217 10218 // actual label 10219 labelString: '', 10220 10221 // top/bottom padding 10222 padding: { 10223 top: 4, 10224 bottom: 4 10225 } 10226 }, 10227 10228 // label settings 10229 ticks: { 10230 beginAtZero: false, 10231 minRotation: 0, 10232 maxRotation: 50, 10233 mirror: false, 10234 padding: 0, 10235 reverse: false, 10236 display: true, 10237 autoSkip: true, 10238 autoSkipPadding: 0, 10239 labelOffset: 0, 10240 // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. 10241 callback: core_ticks.formatters.values, 10242 minor: {}, 10243 major: {} 10244 } 10245 }); 10246 10247 function labelsFromTicks(ticks) { 10248 var labels = []; 10249 var i, ilen; 10250 10251 for (i = 0, ilen = ticks.length; i < ilen; ++i) { 10252 labels.push(ticks[i].label); 10253 } 10254 10255 return labels; 10256 } 10257 10258 function getPixelForGridLine(scale, index, offsetGridLines) { 10259 var lineValue = scale.getPixelForTick(index); 10260 10261 if (offsetGridLines) { 10262 if (scale.getTicks().length === 1) { 10263 lineValue -= scale.isHorizontal() ? 10264 Math.max(lineValue - scale.left, scale.right - lineValue) : 10265 Math.max(lineValue - scale.top, scale.bottom - lineValue); 10266 } else if (index === 0) { 10267 lineValue -= (scale.getPixelForTick(1) - lineValue) / 2; 10268 } else { 10269 lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2; 10270 } 10271 } 10272 return lineValue; 10273 } 10274 10275 function computeTextSize(context, tick, font) { 10276 return helpers$1.isArray(tick) ? 10277 helpers$1.longestText(context, font, tick) : 10278 context.measureText(tick).width; 10279 } 10280 10281 var core_scale = core_element.extend({ 10282 /** 10283 * Get the padding needed for the scale 10284 * @method getPadding 10285 * @private 10286 * @returns {Padding} the necessary padding 10287 */ 10288 getPadding: function () { 10289 var me = this; 10290 return { 10291 left: me.paddingLeft || 0, 10292 top: me.paddingTop || 0, 10293 right: me.paddingRight || 0, 10294 bottom: me.paddingBottom || 0 10295 }; 10296 }, 10297 10298 /** 10299 * Returns the scale tick objects ({label, major}) 10300 * @since 2.7 10301 */ 10302 getTicks: function () { 10303 return this._ticks; 10304 }, 10305 10306 // These methods are ordered by lifecyle. Utilities then follow. 10307 // Any function defined here is inherited by all scale types. 10308 // Any function can be extended by the scale type 10309 10310 mergeTicksOptions: function () { 10311 var ticks = this.options.ticks; 10312 if (ticks.minor === false) { 10313 ticks.minor = { 10314 display: false 10315 }; 10316 } 10317 if (ticks.major === false) { 10318 ticks.major = { 10319 display: false 10320 }; 10321 } 10322 for (var key in ticks) { 10323 if (key !== 'major' && key !== 'minor') { 10324 if (typeof ticks.minor[key] === 'undefined') { 10325 ticks.minor[key] = ticks[key]; 10326 } 10327 if (typeof ticks.major[key] === 'undefined') { 10328 ticks.major[key] = ticks[key]; 10329 } 10330 } 10331 } 10332 }, 10333 beforeUpdate: function () { 10334 helpers$1.callback(this.options.beforeUpdate, [this]); 10335 }, 10336 10337 update: function (maxWidth, maxHeight, margins) { 10338 var me = this; 10339 var i, ilen, labels, label, ticks, tick; 10340 10341 // Update Lifecycle - Probably don't want to ever extend or over
vendor: 4,912 bytes, lines 10341-10481
10341write this function ;) 10342 me.beforeUpdate(); 10343 10344 // Absorb the master measurements 10345 me.maxWidth = maxWidth; 10346 me.maxHeight = maxHeight; 10347 me.margins = helpers$1.extend({ 10348 left: 0, 10349 right: 0, 10350 top: 0, 10351 bottom: 0 10352 }, margins); 10353 10354 me._maxLabelLines = 0; 10355 me.longestLabelWidth = 0; 10356 me.longestTextCache = me.longestTextCache || {}; 10357 10358 // Dimensions 10359 me.beforeSetDimensions(); 10360 me.setDimensions(); 10361 me.afterSetDimensions(); 10362 10363 // Data min/max 10364 me.beforeDataLimits(); 10365 me.determineDataLimits(); 10366 me.afterDataLimits(); 10367 10368 // Ticks - `this.ticks` is now DEPRECATED! 10369 // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member 10370 // and must not be accessed directly from outside this class. `this.ticks` being 10371 // around for long time and not marked as private, we can't change its structure 10372 // without unexpected breaking changes. If you need to access the scale ticks, 10373 // use scale.getTicks() instead. 10374 10375 me.beforeBuildTicks(); 10376 10377 // New implementations should return an array of objects but for BACKWARD COMPAT, 10378 // we still support no return (`this.ticks` internally set by calling this method). 10379 ticks = me.buildTicks() || []; 10380 10381 // Allow modification of ticks in callback. 10382 ticks = me.afterBuildTicks(ticks) || ticks; 10383 10384 me.beforeTickToLabelConversion(); 10385 10386 // New implementations should return the formatted tick labels but for BACKWARD 10387 // COMPAT, we still support no return (`this.ticks` internally changed by calling 10388 // this method and supposed to contain only string values). 10389 labels = me.convertTicksToLabels(ticks) || me.ticks; 10390 10391 me.afterTickToLabelConversion(); 10392 10393 me.ticks = labels; // BACKWARD COMPATIBILITY 10394 10395 // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change! 10396 10397 // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) 10398 for (i = 0, ilen = labels.length; i < ilen; ++i) { 10399 label = labels[i]; 10400 tick = ticks[i]; 10401 if (!tick) { 10402 ticks.push(tick = { 10403 label: label, 10404 major: false 10405 }); 10406 } else { 10407 tick.label = label; 10408 } 10409 } 10410 10411 me._ticks = ticks; 10412 10413 // Tick Rotation 10414 me.beforeCalculateTickRotation(); 10415 me.calculateTickRotation(); 10416 me.afterCalculateTickRotation(); 10417 // Fit 10418 me.beforeFit(); 10419 me.fit(); 10420 me.afterFit(); 10421 // 10422 me.afterUpdate(); 10423 10424 return me.minSize; 10425 10426 }, 10427 afterUpdate: function () { 10428 helpers$1.callback(this.options.afterUpdate, [this]); 10429 }, 10430 10431 // 10432 10433 beforeSetDimensions: function () { 10434 helpers$1.callback(this.options.beforeSetDimensions, [this]); 10435 }, 10436 setDimensions: function () { 10437 var me = this; 10438 // Set the unconstrained dimension before label rotation 10439 if (me.isHorizontal()) { 10440 // Reset position before calculating rotation 10441 me.width = me.maxWidth; 10442 me.left = 0; 10443 me.right = me.width; 10444 } else { 10445 me.height = me.maxHeight; 10446 10447 // Reset position before calculating rotation 10448 me.top = 0; 10449 me.bottom = me.height; 10450 } 10451 10452 // Reset padding 10453 me.paddingLeft = 0; 10454 me.paddingTop = 0; 10455 me.paddingRight = 0; 10456 me.paddingBottom = 0; 10457 }, 10458 afterSetDimensions: function () { 10459 helpers$1.callback(this.options.afterSetDimensions, [this]); 10460 }, 10461 10462 // Data limits 10463 beforeDataLimits: function () { 10464 helpers$1.callback(this.options.beforeDataLimits, [this]); 10465 }, 10466 determineDataLimits: helpers$1.noop, 10467 afterDataLimits: function () { 10468 helpers$1.callback(this.options.afterDataLimits, [this]); 10469 }, 10470 10471 // 10472 beforeBuildTicks: function () { 10473 helpers$1.callback(this.options.beforeBuildTicks, [this]); 10474 }, 10475 buildTicks: helpers$1.noop, 10476 afterBuildTicks: function (ticks) { 10477 var me = this; 10478 // ticks is empty for old axis implementations here 10479 if (helpers$1.isArray(ticks) && ticks.length) { 10480 return helpers$1.callback(me.options.afterBuildTicks, [me, ticks]); 10481 }
10482 // Support old implementations (that modified `this.ticks` directly in buildTicks) 10483 me.ticks = helpers$1.callback(me.options.afterBuildTicks, [me, me.ticks]) || me.ticks; 10484 return ticks; 10485 }, 10486 10487 beforeTickToLabelConversion: function () { 10488 helpers$1.callback(this.options.beforeTickToLabelConversion, [this]); 10489 }, 10490 convertTicksToLabels: function () { 10491 var me = this; 10492 // Convert ticks to strings 10493 var tickOpts = me.options.ticks; 10494 me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); 10495 }, 10496 afterTickToLabelConversion: function () { 10497 helpers$1.callback(this.options.afterTickToLabelConversion, [this]); 10498 }, 10499 10500 // 10501 10502 beforeCalculateTickRotation: function () { 10503 helpers$1.callback(this.options.beforeCalculateTickRotation, [this]); 10504 }, 10505 calculateTickRotation: function () { 10506 var me = this; 10507 var context = me.ctx; 10508 var tickOpts = me.options.ticks; 10509 var labels = labelsFromTicks(me._ticks); 10510 10511 // Get the width of each grid by calculating the difference 10512 // between x offsets between 0 and 1. 10513 var tickFont = helpers$1.options._parseFont(tickOpts); 10514 context.font = tickFont.string; 10515 10516 var labelRotation = tickOpts.minRotation || 0; 10517 10518 if (labels.length && me.options.display && me.isHorizontal()) { 10519 var originalLabelWidth = helpers$1.longestText(context, tickFont.string, labels, me.longestTextCache); 10520 var labelWidth = originalLabelWidth; 10521 var cosRotation, sinRotation; 10522 10523 // Allow 3 pixels x2 padding either side for label readability 10524 var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; 10525 10526 // Max label rotation can be set or default to 90 - also act as a loop counter 10527 while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) { 10528 var angleRadians = helpers$1.toRadians(labelRotation); 10529 cosRotation = Math.cos(angleRadians); 10530 sinRotation = Math.sin(angleRadians); 10531 10532 if (sinRotation * originalLabelWidth > me.maxHeight) { 10533 // go back one step 10534 labelRotation--; 10535 break; 10536 } 10537 10538 labelRotation++; 10539 labelWidth = cosRotation * originalLabelWidth; 10540 } 10541 } 10542 10543 me.labelRotation = labelRotation; 10544 }, 10545 afterCalculateTickRotation: function () { 10546 helpers$1.callback(this.options.afterCalculateTickRotation, [this]); 10547 }, 10548 10549 // 10550 10551 beforeFit: function () { 10552 helpers$1.callback(this.options.beforeFit, [this]); 10553 }, 10554 fit: function () { 10555 var me = this; 10556 // Reset 10557 var minSize = me.minSize = { 10558 width: 0, 10559 height: 0 10560 }; 10561 10562 var labels = labelsFromTicks(me._ticks); 10563 10564 var opts = me.options; 10565 var tickOpts = opts.ticks; 10566 var scaleLabelOpts = opts.scaleLabel; 10567 var gridLineOpts = opts.gridLines; 10568 var display = me._isVisible(); 10569 var position = opts.position; 10570 var isHorizontal = me.isHorizontal(); 10571 10572 var parseFont = helpers$1.options._parseFont; 10573 var tickFont = parseFont(tickOpts); 10574 var tickMarkLength = opts.gridLines.tickMarkLength; 10575
10576 // Width 10577 if (isHorizontal) { 10578 // subtract the margins to line up with the chartArea if we are a full width scale 10579 minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; 10580 } else { 10581 minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0; 10582 } 10583 10584 // height 10585 if (isHorizontal) { 10586 minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0; 10587 } else { 10588 minSize.height = me.maxHeight; // fill all the height 10589 } 10590 10591 // Are we showing a title for the scale? 10592 if (scaleLabelOpts.display && display) { 10593 var scaleLabelFont = parseFont(scaleLabelOpts); 10594 var scaleLabelPadding = helpers$1.options.toPadding(scaleLabelOpts.padding); 10595 var deltaHeight = scaleLabelFont.lineHeight + scaleLabelPadding.height; 10596 10597 if (isHorizontal) { 10598 minSize.height += deltaHeight; 10599 } else { 10600 minSize.width += deltaHeight; 10601 } 10602 } 10603 10604 // Don't bother fitting the ticks if we are not showing the labels 10605 if (tickOpts.display && display) { 10606 var largestTextWidth = helpers$1.longestText(me.ctx, tickFont.string, labels, me.longestTextCache); 10607 var tallestLabelHeightInLines = helpers$1.numberOfLabelLines(labels); 10608 var lineSpace = tickFont.size * 0.5; 10609 var tickPadding = me.options.ticks.padding; 10610 10611 // Store max number of lines and widest label for _autoSkip 10612 me._maxLabelLines = tallestLabelHeightInLines; 10613 me.longestLabelWidth = largestTextWidth; 10614 10615 if (isHorizontal) { 10616 var angleRadians = helpers$1.toRadians(me.labelRotation); 10617 var cosRotation = Math.cos(angleRadians); 10618 var sinRotation = Math.sin(angleRadians); 10619 10620 // TODO - improve this calculation 10621 var labelHeight = (sinRotation * largestTextWidth) 10622 + (tickFont.lineHeight * tallestLabelHeightInLines) 10623 + lineSpace; // padding 10624 10625 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); 10626 10627 me.ctx.font = tickFont.string; 10628 var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.string); 10629 var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.string); 10630 var offsetLeft = me.getPixelForTick(0) - me.left; 10631 var offsetRight = me.right - me.getPixelForTick(labels.length - 1); 10632 var paddingLeft, paddingRight; 10633 10634 // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned 10635 // which means that the right padding is dominated by the font height 10636 if (me.labelRotation !== 0) { 10637 paddingLeft = position === 'bottom' ? (cosRotation * firstLabelWidth) : (cosRotation * lineSpace); 10638 paddingRight = position === 'bottom' ? (cosRotation * lineSpace) : (cosRotation * lastLabelWidth); 10639 } else { 10640 paddingLeft = firstLabelWidth / 2; 10641 paddingRight = lastLabelWidth / 2; 10642 } 10643 me.paddingLeft = Math.max(paddingLeft - offsetLeft, 0) + 3; // add 3 px to move away from canvas edges 10644 me.paddingRight = Math.max(paddingRight - offsetRight, 0) + 3; 10645 } else { 10646 // A vertical axis is more constrained by the width. Labels are the 10647 // dominant factor here, so get that length first and account for padding 10648 if (tickOpts.mirror) { 10649 largestTextWidth = 0; 10650 } else { 10651 // use lineSpace for consistency with horizontal axis 10652 // tickPadding is not implemented for horizontal 10653 largestTextWidth += tickPadding + lineSpace; 10654 } 10655 10656 minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth); 10657 10658 me.paddingTop = tickFont.size / 2; 10659 me.paddingBottom = tickFont.size / 2; 10660 } 10661 } 10662 10663 me.handleMargins(); 10664 10665 me.width = minSize.width; 10666 me.height = minSize.height; 10667 }, 10668 10669 /** 10670 * Handle margins and padding interactions 10671 * @private 10672 */ 10673 handleMargins: function () { 10674 var me = this; 10675 if (me.margins) { 10676 me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
10677 me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); 10678 me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); 10679 me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); 10680 } 10681 }, 10682 10683 afterFit: function () { 10684 helpers$1.callback(this.options.afterFit, [this]); 10685 }, 10686 10687 // Shared Methods 10688 isHorizontal: function () { 10689 return this.options.position === 'top' || this.options.position === 'bottom'; 10690 }, 10691 isFullWidth: function () { 10692 return (this.options.fullWidth); 10693 }, 10694 10695 // 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 10696 getRightValue: function (rawValue) { 10697 // Null and undefined values first 10698 if (helpers$1.isNullOrUndef(rawValue)) { 10699 return NaN; 10700 } 10701 // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values 10702 if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) { 10703 return NaN; 10704 } 10705 // If it is in fact an object, dive in one more level 10706 if (rawValue) { 10707 if (this.isHorizontal()) { 10708 if (rawValue.x !== undefined) { 10709 return this.getRightValue(rawValue.x); 10710 } 10711 } else if (rawValue.y !== undefined) { 10712 return this.getRightValue(rawValue.y); 10713 } 10714 } 10715 10716 // Value is good, return it 10717 return rawValue; 10718 }, 10719 10720 /** 10721 * Used to get the value to display in the tooltip for the data at the given index 10722 * @param index 10723 * @param datasetIndex 10724 */ 10725 getLabelForIndex: helpers$1.noop, 10726 10727 /** 10728 * Returns the location of the given data point. Value can either be an index or a numerical value 10729 * The coordinate (0, 0) is at the upper-left corner of the canvas 10730 * @param value 10731 * @param index 10732 * @param datasetIndex 10733 */ 10734 getPixelForValue: helpers$1.noop, 10735 10736 /** 10737 * Used to get the data value from a given pixel. This is the inverse of getPixelForValue 10738 * The coordinate (0, 0) is at the upper-left corner of the canvas 10739 * @param pixel 10740 */ 10741 getValueForPixel: helpers$1.noop, 10742 10743 /** 10744 * Returns the location of the tick at the given index 10745 * The coordinate (0, 0) is at the upper-left corner of the canvas 10746 */ 10747 getPixelForTick: function (index) { 10748 var me = this; 10749 var offset = me.options.offset; 10750 if (me.isHorizontal()) { 10751 var innerWidth = me.width - (me.paddingLeft + me.paddingRight); 10752 var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1); 10753 var pixel = (tickWidth * index) + me.paddingLeft; 10754 10755 if (offset) { 10756 pixel += tickWidth / 2; 10757 } 10758 10759 var finalVal = me.left + pixel; 10760 finalVal += me.isFullWidth() ? me.margins.left : 0; 10761 return finalVal; 10762 } 10763 var innerHeight = me.height - (me.paddingTop + me.paddingBottom); 10764 return me.top + (index * (innerHeight / (me._ticks.length - 1))); 10765 }, 10766 10767 /** 10768 * Utility for getting the pixel location of a percentage of scale 10769 * The coordinate (0, 0) is at the upper-left corner of the canvas 10770 */ 10771 getPixelForDecimal: function (decimal) { 10772 var me = this; 10773 if (me.isHorizontal()) { 10774 var innerWidth = me.width - (me.paddingLeft + me.paddingRight); 10775 var valueOffset = (innerWidth * decimal) + me.paddingLeft; 10776 10777 var finalVal = me.left + valueOffset; 10778 finalVal += me.isFullWidth() ? me.margins.left : 0; 10779 return finalVal; 10780 } 10781 return me.top + (decimal * me.height); 10782 }, 10783 10784 /** 10785 * Returns the pixel for the minimum chart value 10786 * The coordinate (0, 0) is at the upper-left corner of the canvas 10787 */ 10788 getBasePixel: function () { 10789 return this.getPixelForValue(this.getBaseValue()); 10790 }, 10791 10792 getBaseValue: function () { 10793 var me = this; 10794 var min = me.min; 10795 var max = me.max; 10796 10797 return me.beginAtZero ? 0 : 10798 min < 0 && max < 0 ? max : 10799 min > 0 && max > 0 ? min : 10800 0; 10801 }, 10802 10803 /** 10804 * Returns a subset of ticks to be plotted to avoid overlapping labels. 10805 * @private 10806 */ 10807 _autoSkip: function (ticks) { 10808 var me = this; 10809 var isHorizontal = me.isHorizontal(); 10810 var optionTicks = me.options.ticks.minor; 10811 var tickCount = ticks.length; 10812 var skipRatio = false;
10813 var maxTicks = optionTicks.maxTicksLimit; 10814 10815 // Total space needed to display all ticks. First and last ticks are 10816 // drawn as their center at end of axis, so tickCount-1 10817 var ticksLength = me._tickSize() * (tickCount - 1); 10818 10819 // Axis length 10820 var axisLength = isHorizontal 10821 ? me.width - (me.paddingLeft + me.paddingRight) 10822 : me.height - (me.paddingTop + me.PaddingBottom); 10823 10824 var result = []; 10825 var i, tick; 10826 10827 if (ticksLength > axisLength) { 10828 skipRatio = 1 + Math.floor(ticksLength / axisLength); 10829 } 10830 10831 // if they defined a max number of optionTicks, 10832 // increase skipRatio until that number is met 10833 if (tickCount > maxTicks) { 10834 skipRatio = Math.max(skipRatio, 1 + Math.floor(tickCount / maxTicks)); 10835 } 10836 10837 for (i = 0; i < tickCount; i++) { 10838 tick = ticks[i]; 10839 10840 if (skipRatio > 1 && i % skipRatio > 0) { 10841 // leave tick in place but make sure it's not displayed (#4635) 10842 delete tick.label; 10843 } 10844 result.push(tick); 10845 } 10846 return result; 10847 }, 10848 10849 /** 10850 * @private 10851 */ 10852 _tickSize: function () { 10853 var me = this; 10854 var isHorizontal = me.isHorizontal(); 10855 var optionTicks = me.options.ticks.minor; 10856 10857 // Calculate space needed by label in axis direction. 10858 var rot = helpers$1.toRadians(me.labelRotation); 10859 var cos = Math.abs(Math.cos(rot)); 10860 var sin = Math.abs(Math.sin(rot)); 10861 10862 var padding = optionTicks.autoSkipPadding || 0; 10863 var w = (me.longestLabelWidth + padding) || 0; 10864 10865 var tickFont = helpers$1.options._parseFont(optionTicks); 10866 var h = (me._maxLabelLines * tickFont.lineHeight + padding) || 0; 10867 10868 // Calculate space needed for 1 tick in axis direction. 10869 return isHorizontal 10870 ? h * cos > w * sin ? w / cos : h / sin 10871 : h * sin < w * cos ? h / cos : w / sin; 10872 }, 10873 10874 /** 10875 * @private 10876 */ 10877 _isVisible: function () { 10878 var me = this; 10879 var chart = me.chart; 10880 var display = me.options.display; 10881 var i, ilen, meta; 10882 10883 if (display !== 'auto') { 10884 return !!display; 10885 } 10886 10887 // When 'auto', the scale is visible if at least one associated dataset is visible. 10888 for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { 10889 if (chart.isDatasetVisible(i)) { 10890 meta = chart.getDatasetMeta(i); 10891 if (meta.xAxisID === me.id || meta.yAxisID === me.id) { 10892 return true; 10893 } 10894 } 10895 } 10896 10897 return false; 10898 }, 10899 10900 /** 10901 * Actually draw the scale on the canvas 10902 * @param {object} chartArea - the area of the chart to draw full grid lines on 10903 */ 10904 draw: function (chartArea) { 10905 var me = this; 10906 var options = me.options; 10907 10908 if (!me._isVisible()) { 10909 return; 10910 } 10911 10912 var chart = me.chart; 10913 var context = me.ctx; 10914 var globalDefaults = core_defaults.global; 10915 var defaultFontColor = globalDefaults.defaultFontColor; 10916 var optionTicks = options.ticks.minor; 10917 var optionMajorTicks = options.ticks.major || optionTicks; 10918 var gridLines = options.gridLines; 10919 var scaleLabel = options.scaleLabel; 10920 var position = options.position; 10921 10922 var isRotated = me.labelRotation !== 0; 10923 var isMirrored = optionTicks.mirror; 10924 var isHorizontal = me.isHorizontal(); 10925 10926 var parseFont = helpers$1.options._parseFont; 10927 var ticks = optionTicks.display && optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
10928 var tickFontColor = valueOrDefault$9(optionTicks.fontColor, defaultFontColor); 10929 var tickFont = parseFont(optionTicks); 10930 var lineHeight = tickFont.lineHeight; 10931 var majorTickFontColor = valueOrDefault$9(optionMajorTicks.fontColor, defaultFontColor); 10932 var majorTickFont = parseFont(optionMajorTicks); 10933 var tickPadding = optionTicks.padding; 10934 var labelOffset = optionTicks.labelOffset; 10935 10936 var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; 10937 10938 var scaleLabelFontColor = valueOrDefault$9(scaleLabel.fontColor, defaultFontColor); 10939 var scaleLabelFont = parseFont(scaleLabel); 10940 var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding); 10941 var labelRotationRadians = helpers$1.toRadians(me.labelRotation); 10942 10943 var itemsToDraw = []; 10944 10945 var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0; 10946 var alignPixel = helpers$1._alignPixel; 10947 var borderValue, tickStart, tickEnd; 10948 10949 if (position === 'top') { 10950 borderValue = alignPixel(chart, me.bottom, axisWidth); 10951 tickStart = me.bottom - tl; 10952 tickEnd = borderValue - axisWidth / 2; 10953 } else if (position === 'bottom') { 10954 borderValue = alignPixel(chart, me.top, axisWidth); 10955 tickStart = borderValue + axisWidth / 2; 10956 tickEnd = me.top + tl; 10957 } else if (position === 'left') { 10958 borderValue = alignPixel(chart, me.right, axisWidth); 10959 tickStart = me.right - tl; 10960 tickEnd = borderValue - axisWidth / 2; 10961 } else { 10962 borderValue = alignPixel(chart, me.left, axisWidth); 10963 tickStart = borderValue + axisWidth / 2; 10964 tickEnd = me.left + tl; 10965 } 10966 10967 var epsilon = 0.0000001; // 0.0000001 is margin in pixels for Accumulated error. 10968 10969 helpers$1.each(ticks, function (tick, index) { 10970 // autoskipper skipped this tick (#4635) 10971 if (helpers$1.isNullOrUndef(tick.label)) { 10972 return; 10973 } 10974 10975 var label = tick.label; 10976 var lineWidth, lineColor, borderDash, borderDashOffset; 10977 if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) { 10978 // Draw the first index specially 10979 lineWidth = gridLines.zeroLineWidth; 10980 lineColor = gridLines.zeroLineColor; 10981 borderDash = gridLines.zeroLineBorderDash || []; 10982 borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0; 10983 } else { 10984 lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, index); 10985 lineColor = valueAtIndexOrDefault(gridLines.color, index); 10986 borderDash = gridLines.borderDash || []; 10987 borderDashOffset = gridLines.borderDashOffset || 0.0; 10988 } 10989 10990 // Common properties 10991 var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY, textOffset, textAlign; 10992 var labelCount = helpers$1.isArray(label) ? label.length : 1; 10993 var lineValue = getPixelForGridLine(me, index, gridLines.offsetGridLines); 10994 10995 if (isHorizontal) { 10996 var labelYOffset = tl + tickPadding; 10997 10998 if (lineValue < me.left - epsilon) { 10999 lineColor = 'rgba(0,0,0,0)'; 11000 } 11001 11002 tx1 = tx2 = x1 = x2 = alignPixel(chart, lineValue, lineWidth); 11003 ty1 = tickStart; 11004 ty2 = tickEnd; 11005 labelX = me.getPixelForTick(index) + labelOffset; // x values for optionTicks (need to consider offsetLabel option) 11006 11007 if (position === 'top') { 11008 y1 = alignPixel(chart, chartArea.top, axisWidth) + axisWidth / 2; 11009 y2 = chartArea.bottom; 11010 textOffset = ((!isRotated ? 0.5 : 1) - labelCount) * lineHeight; 11011 textAlign = !isRotated ? 'center' : 'left'; 11012 labelY = me.bottom - labelYOffset; 11013 } else { 11014 y1 = chartArea.top; 11015 y2 = alignPixel(chart, chartArea.bottom, axisWidth) - axisWidth / 2; 11016 textOffset = (!isRotated ? 0.5 : 0) * lineHeight; 11017 textAlign = !isRotated ? 'center' : 'right'; 11018 labelY = me.top + labelYOffset; 11019 } 11020 } else { 11021 var labelXOffset = (isMirrored ? 0 : tl) + tickPadding; 11022 11023 if (lineValue < me.top - epsilon) { 11024 lineColor = 'rgba(0,0,0,0)'; 11025 } 11026 11027 tx1 = tickStart; 11028 tx2 = tickEnd; 11029 ty1 = ty2 = y1 = y2 = alignPixel(chart, lineValue, lineWidth); 11030 labelY = me.getPixelForTick(index) + labelOffset; 11031 textOffset = (1 - labelCount) * lineHeight / 2; 11032 11033 if (position === 'left') { 11034 x1 = alignPixel(chart, chartArea.left, axisWidth) + axisWidth / 2; 11035 x2 = chartArea.right; 11036 textAlign = isMirrored ? 'left' : 'right'; 11037 labelX = me.right - labelXOffset; 11038 } else { 11039 x1 = chartArea.left; 11040 x2 = alignPixel(chart, chartArea.right, axisWidth) - axisWidth / 2; 11041 textAlign = isMirrored ? 'right' : 'left'; 11042 labelX = me.left + labelXOffset; 11043 } 11044 } 11045 11046 itemsToDraw.push({ 11047 tx1: tx1, 11048 ty1: ty1, 11049 tx2: tx2, 11050 ty2: ty2, 11051 x1: x1, 11052 y1: y1, 11053 x2: x2, 11054 y2: y2, 11055 labelX: labelX, 11056 labelY: labelY, 11057 glWidth: lineWidth, 11058 glColor: lineColor, 11059 glBorderDash: borderDash, 11060 glBorderDashOffset: borderDashOffset, 11061 rotation: -1 * labelRotationRadians, 11062 label: label, 11063 major: tick.major, 11064 textOffset: textOffset, 11065 textAlign: textAlign 11066 }); 11067 }); 11068 11069 // Draw all of the tick labels, tick marks, and grid lines at the correct places 11070 helpers$1.each(itemsToDraw, function (itemToDraw) { 11071 var glWidth = itemToDraw.glWidth; 11072 var glColor = itemToDraw.glColor; 11073 11074 if (gridLines.display && glWidth && glColor) { 11075 context.save(); 11076 context.lineWidth = glWidth; 11077 context.strokeStyle = glColor; 11078 if (context.setLineDash) { 11079 context.setLineDash(itemToDraw.glBorderDash); 11080 context.lineDashOffset = itemToDraw.glBorderDashOffset; 11081 } 11082 11083 context.beginPath(); 11084 11085 if (gridLines.drawTicks) { 11086 context.moveTo(itemToDraw.tx1, itemToDraw.ty1); 11087 context.lineTo(itemToDraw.tx2, itemToDraw.ty2); 11088 } 11089 11090 if (gridLines.drawOnChartArea) { 11091 context.moveTo(itemToDraw.x1, itemToDraw.y1); 11092 context.lineTo(itemToDraw.x2, itemToDraw.y2); 11093 } 11094 11095 context.stroke();
11096 context.restore(); 11097 } 11098 11099 if (optionTicks.display) { 11100 // Make sure we draw text in the correct color and font 11101 context.save(); 11102 context.translate(itemToDraw.labelX, itemToDraw.labelY); 11103 context.rotate(itemToDraw.rotation); 11104 context.font = itemToDraw.major ? majorTickFont.string : tickFont.string; 11105 context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor; 11106 context.textBaseline = 'middle'; 11107 context.textAlign = itemToDraw.textAlign; 11108 11109 var label = itemToDraw.label; 11110 var y = itemToDraw.textOffset; 11111 if (helpers$1.isArray(label)) { 11112 for (var i = 0; i < label.length; ++i) { 11113 // We just make sure the multiline element is a string here.. 11114 context.fillText('' + label[i], 0, y); 11115 y += lineHeight; 11116 } 11117 } else { 11118 context.fillText(label, 0, y); 11119 } 11120 context.restore(); 11121 } 11122 }); 11123 11124 if (scaleLabel.display) { 11125 // Draw the scale label 11126 var scaleLabelX; 11127 var scaleLabelY; 11128 var rotation = 0; 11129 var halfLineHeight = scaleLabelFont.lineHeight / 2; 11130 11131 if (isHorizontal) { 11132 scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width 11133 scaleLabelY = position === 'bottom' 11134 ? me.bottom - halfLineHeight - scaleLabelPadding.bottom 11135 : me.top + halfLineHeight + scaleLabelPadding.top; 11136 } else { 11137 var isLeft = position === 'left'; 11138 scaleLabelX = isLeft 11139 ? me.left + halfLineHeight + scaleLabelPadding.top 11140 : me.right - halfLineHeight - scaleLabelPadding.top; 11141 scaleLabelY = me.top + ((me.bottom - me.top) / 2); 11142 rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; 11143 } 11144 11145 context.save(); 11146 context.translate(scaleLabelX, scaleLabelY); 11147 context.rotate(rotation); 11148 context.textAlign = 'center'; 11149 context.textBaseline = 'middle'; 11150 context.fillStyle = scaleLabelFontColor; // render in correct colour 11151 context.font = scaleLabelFont.string;
11152 context.fillText(scaleLabel.labelString, 0, 0); 11153 context.restore(); 11154 } 11155 11156 if (axisWidth) { 11157 // Draw the line at the edge of the axis 11158 var firstLineWidth = axisWidth; 11159 var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, ticks.length - 1, 0); 11160 var x1, x2, y1, y2; 11161 11162 if (isHorizontal) { 11163 x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2; 11164 x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2; 11165 y1 = y2 = borderValue; 11166 } else { 11167 y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2; 11168 y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2; 11169 x1 = x2 = borderValue; 11170 } 11171 11172 context.lineWidth = axisWidth; 11173 context.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0); 11174 context.beginPath(); 11175 context.moveTo(x1, y1); 11176 context.lineTo(x2, y2); 11177 context.stroke(); 11178 } 11179 } 11180 }); 11181 11182 var defaultConfig = { 11183 position: 'bottom' 11184 }; 11185 11186 var scale_category = core_scale.extend({ 11187 /** 11188 * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those 11189 * else fall back to data.labels 11190 * @private 11191 */ 11192 getLabels: function () { 11193 var data = this.chart.data; 11194 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; 11195 }, 11196 11197 determineDataLimits: function () { 11198 var me = this; 11199 var labels = me.getLabels(); 11200 me.minIndex = 0; 11201 me.maxIndex = labels.length - 1; 11202 var findIndex; 11203 11204 if (me.options.ticks.min !== undefined) { 11205 // user specified min value 11206 findIndex = labels.indexOf(me.options.ticks.min); 11207 me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; 11208 } 11209 11210 if (me.options.ticks.max !== undefined) { 11211 // user specified max value 11212 findIndex = labels.indexOf(me.options.ticks.max); 11213 me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; 11214 } 11215 11216 me.min = labels[me.minIndex]; 11217 me.max = labels[me.maxIndex]; 11218 }, 11219 11220 buildTicks: function () { 11221 var me = this; 11222 var labels = me.getLabels(); 11223 // If we are viewing some subset of labels, slice the original array 11224 me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); 11225 }, 11226 11227 getLabelForIndex: function (index, datasetIndex) { 11228 var me = this; 11229 var chart = me.chart; 11230 11231 if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) { 11232 return me.getRightValue(chart.data.datasets[datasetIndex].data[index]); 11233 } 11234 11235 return me.ticks[index - me.minIndex]; 11236 }, 11237 11238 // Used to get data value locations. Value can either be an index or a numerical value 11239 getPixelForValue: function (value, index) { 11240 var me = this; 11241 var offset = me.options.offset; 11242 // 1 is added because we need the length but we have the indexes 11243 var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1); 11244 11245 // If value is a data object, then index is the index in the data array, 11246 // not the index of the scale. We need to change that. 11247 var valueCategory; 11248 if (value !== undefined && value !== null) { 11249 valueCategory = me.isHorizontal() ? value.x : value.y; 11250 } 11251 if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { 11252 var labels = me.getLabels(); 11253 value = valueCategory || value; 11254 var idx = labels.indexOf(value); 11255 index = idx !== -1 ? idx : index; 11256 } 11257 11258 if (me.isHorizontal()) { 11259 var valueWidth = me.width / offsetAmt; 11260 var widthOffset = (valueWidth * (index - me.minIndex)); 11261 11262 if (offset) { 11263 widthOffset += (valueWidth / 2); 11264 } 11265 11266 return me.left + widthOffset; 11267 } 11268 var valueHeight = me.height / offsetAmt; 11269 var heightOffset = (valueHeight * (index - me.minIndex)); 11270 11271 if (offset) { 11272 heightOffset += (valueHeight / 2); 11273 } 11274 11275 return me.top + heightOffset; 11276 }, 11277 11278 getPixelForTick: function (index) { 11279 return this.getPixelForValue(this.ticks[index], index + this.minIndex, null); 11280 }, 11281 11282 getValueForPixel: function (pixel) { 11283 var me = this; 11284 var offset = me.options.offset; 11285 var value; 11286 var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1); 11287 var horz = me.isHorizontal(); 11288 var valueDimension = (horz ? me.width : me.height) / offsetAmt; 11289 11290 pixel -= horz ? me.left : me.top; 11291 11292 if (offset) { 11293 pixel -= (valueDimension / 2); 11294 } 11295 11296 if (pixel <= 0) { 11297 value = 0; 11298 } else { 11299 value = Math.round(pixel / valueDimension); 11300 } 11301 11302 return value + me.minIndex; 11303 }, 11304 11305 getBasePixel: function () { 11306 return this.bottom; 11307 } 11308 }); 11309 11310// INTERNAL: static default options, registered in src/index.js 11311 var _defaults = defaultConfig; 11312 scale_category._defaults = _defaults; 11313 11314 var noop = helpers$1.noop; 11315 var isNullOrUndef = helpers$1.isNullOrUndef; 11316 11317 /** 11318 * Generate a set of linear ticks 11319 * @param generationOptions the options used to generate the ticks 11320 * @param dataRange the range of the data 11321 * @returns {number[]} array of tick values 11322 */ 11323 function generateTicks(generationOptions, dataRange) { 11324 var ticks = []; 11325 // To get a "nice" value for the tick spacing, we will use the appropriately named 11326 // "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks 11327 // for details. 11328 11329 var MIN_SPACING = 1e-14; 11330 var stepSize = generationOptions.stepSize; 11331 var unit = stepSize || 1;
vendor: 4,913 bytes, lines 11332-11450
11332 var maxNumSpaces = generationOptions.maxTicks - 1; 11333 var min = generationOptions.min; 11334 var max = generationOptions.max; 11335 var precision = generationOptions.precision; 11336 var rmin = dataRange.min; 11337 var rmax = dataRange.max; 11338 var spacing = helpers$1.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit; 11339 var factor, niceMin, niceMax, numSpaces; 11340 11341 // Beyond MIN_SPACING floating point numbers being to lose precision 11342 // such that we can't do the math necessary to generate ticks 11343 if (spacing < MIN_SPACING && isNullOrUndef(min) && isNullOrUndef(max)) { 11344 return [rmin, rmax]; 11345 } 11346 11347 numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing); 11348 if (numSpaces > maxNumSpaces) { 11349 // If the calculated num of spaces exceeds maxNumSpaces, recalculate it 11350 spacing = helpers$1.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit; 11351 } 11352 11353 if (stepSize || isNullOrUndef(precision)) { 11354 // If a precision is not specified, calculate factor based on spacing 11355 factor = Math.pow(10, helpers$1._decimalPlaces(spacing)); 11356 } else { 11357 // If the user specified a precision, round to that number of decimal places 11358 factor = Math.pow(10, precision); 11359 spacing = Math.ceil(spacing * factor) / factor; 11360 } 11361 11362 niceMin = Math.floor(rmin / spacing) * spacing; 11363 niceMax = Math.ceil(rmax / spacing) * spacing; 11364 11365 // If min, max and stepSize is set and they make an evenly spaced scale use it. 11366 if (stepSize) { 11367 // If very close to our whole number, use it. 11368 if (!isNullOrUndef(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) { 11369 niceMin = min; 11370 } 11371 if (!isNullOrUndef(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) { 11372 niceMax = max; 11373 } 11374 } 11375 11376 numSpaces = (niceMax - niceMin) / spacing; 11377 // If very close to our rounded value, use it. 11378 if (helpers$1.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { 11379 numSpaces = Math.round(numSpaces); 11380 } else { 11381 numSpaces = Math.ceil(numSpaces); 11382 } 11383 11384 niceMin = Math.round(niceMin * factor) / factor; 11385 niceMax = Math.round(niceMax * factor) / factor; 11386 ticks.push(isNullOrUndef(min) ? niceMin : min); 11387 for (var j = 1; j < numSpaces; ++j) { 11388 ticks.push(Math.round((niceMin + j * spacing) * factor) / factor); 11389 } 11390 ticks.push(isNullOrUndef(max) ? niceMax : max); 11391 11392 return ticks; 11393 } 11394 11395 var scale_linearbase = core_scale.extend({ 11396 getRightValue: function (value) { 11397 if (typeof value === 'string') { 11398 return +value; 11399 } 11400 return core_scale.prototype.getRightValue.call(this, value); 11401 }, 11402 11403 handleTickRangeOptions: function () { 11404 var me = this; 11405 var opts = me.options; 11406 var tickOpts = opts.ticks; 11407 11408 // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, 11409 // do nothing since that would make the chart weird. If the user really wants a weird chart 11410 // axis, they can manually override it 11411 if (tickOpts.beginAtZero) { 11412 var minSign = helpers$1.sign(me.min); 11413 var maxSign = helpers$1.sign(me.max); 11414 11415 if (minSign < 0 && maxSign < 0) { 11416 // move the top up to 0 11417 me.max = 0; 11418 } else if (minSign > 0 && maxSign > 0) { 11419 // move the bottom down to 0 11420 me.min = 0; 11421 } 11422 } 11423 11424 var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; 11425 var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; 11426 11427 if (tickOpts.min !== undefined) { 11428 me.min = tickOpts.min; 11429 } else if (tickOpts.suggestedMin !== undefined) { 11430 if (me.min === null) { 11431 me.min = tickOpts.suggestedMin; 11432 } else { 11433 me.min = Math.min(me.min, tickOpts.suggestedMin); 11434 } 11435 } 11436 11437 if (tickOpts.max !== undefined) { 11438 me.max = tickOpts.max; 11439 } else if (tickOpts.suggestedMax !== undefined) { 11440 if (me.max === null) { 11441 me.max = tickOpts.suggestedMax; 11442 } else { 11443 me.max = Math.max(me.max, tickOpts.suggestedMax); 11444 } 11445 } 11446 11447 if (setMin !== setMax) { 11448 // We set the min or the max but not both. 11449 // So ensure that our range is good 11450 // Inverted or 0 length range can happen when
11451 // ticks.min is set, and no datasets are visible 11452 if (me.min >= me.max) { 11453 if (setMin) { 11454 me.max = me.min + 1; 11455 } else { 11456 me.min = me.max - 1; 11457 } 11458 } 11459 } 11460 11461 if (me.min === me.max) { 11462 me.max++; 11463 11464 if (!tickOpts.beginAtZero) { 11465 me.min--; 11466 } 11467 } 11468 }, 11469 11470 getTickLimit: function () { 11471 var me = this; 11472 var tickOpts = me.options.ticks; 11473 var stepSize = tickOpts.stepSize; 11474 var maxTicksLimit = tickOpts.maxTicksLimit; 11475 var maxTicks; 11476 11477 if (stepSize) { 11478 maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1; 11479 } else { 11480 maxTicks = me._computeTickLimit(); 11481 maxTicksLimit = maxTicksLimit || 11; 11482 } 11483 11484 if (maxTicksLimit) { 11485 maxTicks = Math.min(maxTicksLimit, maxTicks); 11486 } 11487 11488 return maxTicks; 11489 }, 11490 11491 _computeTickLimit: function () { 11492 return Number.POSITIVE_INFINITY; 11493 }, 11494 11495 handleDirectionalChanges: noop, 11496 11497 buildTicks: function () { 11498 var me = this; 11499 var opts = me.options; 11500 var tickOpts = opts.ticks; 11501 11502 // Figure out what the max number of ticks we can support it is based on the size of 11503 // the axis area. For now, we say that the minimum tick spacing in pixels must be 40 11504 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on 11505 // the graph. Make sure we always have at least 2 ticks 11506 var maxTicks = me.getTickLimit(); 11507 maxTicks = Math.max(2, maxTicks); 11508 11509 var numericGeneratorOptions = { 11510 maxTicks: maxTicks, 11511 min: tickOpts.min, 11512 max: tickOpts.max, 11513 precision: tickOpts.precision, 11514 stepSize: helpers$1.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) 11515 }; 11516 var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); 11517 11518 me.handleDirectionalChanges(); 11519 11520 // At this point, we need to update our max and min given the tick values since we have expanded the 11521 // range of the scale 11522 me.max = helpers$1.max(ticks); 11523 me.min = helpers$1.min(ticks); 11524 11525 if (tickOpts.reverse) { 11526 ticks.reverse(); 11527 11528 me.start = me.max; 11529 me.end = me.min; 11530 } else { 11531 me.start = me.min; 11532 me.end = me.max; 11533 } 11534 }, 11535 11536 convertTicksToLabels: function () { 11537 var me = this; 11538 me.ticksAsNumbers = me.ticks.slice(); 11539 me.zeroLineIndex = me.ticks.indexOf(0); 11540 11541 core_scale.prototype.convertTicksToLabels.call(me); 11542 } 11543 }); 11544 11545 var defaultConfig$1 = { 11546 position: 'left', 11547 ticks: { 11548 callback: core_ticks.formatters.linear 11549 } 11550 }; 11551 11552 var scale_linear = scale_linearbase.extend({ 11553 determineDataLimits: function () { 11554 var me = this; 11555 var opts = me.options; 11556 var chart = me.chart; 11557 var data = chart.data; 11558 var datasets = data.datasets; 11559 var isHorizontal = me.isHorizontal(); 11560 var DEFAULT_MIN = 0; 11561 var DEFAULT_MAX = 1; 11562 11563 function IDMatches(meta) { 11564 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; 11565 } 11566 11567 // First Calculate the range 11568 me.min = null; 11569 me.max = null; 11570 11571 var hasStacks = opts.stacked; 11572 if (hasStacks === undefined) { 11573 helpers$1.each(datasets, function (dataset, datasetIndex) { 11574 if (hasStacks) { 11575 return; 11576 } 11577 11578 var meta = chart.getDatasetMeta(datasetIndex); 11579 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && 11580 meta.stack !== undefined) { 11581 hasStacks = true; 11582 } 11583 }); 11584 } 11585 11586 if (opts.stacked || hasStacks) { 11587 var valuesPerStack = {}; 11588 11589 helpers$1.each(datasets, function (dataset, datasetIndex) { 11590 var meta = chart.getDatasetMeta(datasetIndex); 11591 var key = [ 11592 meta.type, 11593 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined 11594 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), 11595 meta.stack 11596 ].join('.'); 11597 11598 if (valuesPerStack[key] === undefined) { 11599 valuesPerStack[key] = { 11600 positiveValues: [], 11601 negativeValues: [] 11602 }; 11603 } 11604 11605 // Store these per type 11606 var positiveValues = valuesPerStack[key].positiveValues; 11607 var negativeValues = valuesPerStack[key].negativeValues; 11608 11609 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 11610 helpers$1.each(dataset.data, function (rawValue, index) { 11611 var value = +me.getRightValue(rawValue); 11612 if (isNaN(value) || meta.data[index].hidden) { 11613 return; 11614 } 11615 11616 positiveValues[index] = positiveValues[index] || 0; 11617 negativeValues[index] = negativeValues[index] || 0; 11618 11619 if (opts.relativePoints) { 11620 positiveValues[index] = 100; 11621 } else if (value < 0) { 11622 negativeValues[index] += value; 11623 } else { 11624 positiveValues[index] += value; 11625 } 11626 }); 11627 } 11628 }); 11629 11630 helpers$1.each(valuesPerStack, function (valuesForType) { 11631 var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); 11632 var minVal = helpers$1.min(values); 11633 var maxVal = helpers$1.max(values); 11634 me.min = me.min === null ? minVal : Math.min(me.min, minVal); 11635 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); 11636 }); 11637 11638 } else { 11639 helpers$1.each(datasets, function (dataset, datasetIndex) { 11640 var meta = chart.getDatasetMeta(datasetIndex); 11641 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 11642 helpers$1.each(dataset.data, function (rawValue, index) { 11643 var value = +me.getRightValue(rawValue); 11644 if (isNaN(value) || meta.data[index].hidden) { 11645 return; 11646 } 11647 11648 if (me.min === null) { 11649 me.min = value; 11650 } else if (value < me.min) { 11651 me.min = value; 11652 } 11653 11654 if (me.max === null) { 11655 me.max = value; 11656 } else if (value > me.max) { 11657 me.max = value; 11658 } 11659 }); 11660 } 11661 }); 11662 } 11663 11664 me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; 11665 me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; 11666 11667 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero 11668 this.handleTickRangeOptions(); 11669 }, 11670 11671 // Returns the maximum number of ticks based on the scale dimension 11672 _computeTickLimit: function () { 11673 var me = this;
11674 var tickFont; 11675 11676 if (me.isHorizontal()) { 11677 return Math.ceil(me.width / 40); 11678 } 11679 tickFont = helpers$1.options._parseFont(me.options.ticks); 11680 return Math.ceil(me.height / tickFont.lineHeight); 11681 }, 11682 11683 // Called after the ticks are built. We need 11684 handleDirectionalChanges: function () { 11685 if (!this.isHorizontal()) { 11686 // We are in a vertical orientation. The top value is the highest. So reverse the array 11687 this.ticks.reverse(); 11688 } 11689 }, 11690 11691 getLabelForIndex: function (index, datasetIndex) { 11692 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); 11693 }, 11694 11695 // Utils 11696 getPixelForValue: function (value) { 11697 // This must be called after fit has been run so that 11698 // this.left, this.top, this.right, and this.bottom have been defined 11699 var me = this; 11700 var start = me.start; 11701 11702 var rightValue = +me.getRightValue(value); 11703 var pixel; 11704 var range = me.end - start; 11705 11706 if (me.isHorizontal()) { 11707 pixel = me.left + (me.width / range * (rightValue - start)); 11708 } else { 11709 pixel = me.bottom - (me.height / range * (rightValue - start)); 11710 } 11711 return pixel; 11712 }, 11713 11714 getValueForPixel: function (pixel) { 11715 var me = this; 11716 var isHorizontal = me.isHorizontal(); 11717 var innerDimension = isHorizontal ? me.width : me.height; 11718 var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension; 11719 return me.start + ((me.end - me.start) * offset); 11720 }, 11721 11722 getPixelForTick: function (index) { 11723 return this.getPixelForValue(this.ticksAsNumbers[index]); 11724 } 11725 }); 11726 11727// INTERNAL: static default options, registered in src/index.js 11728 var _defaults$1 = defaultConfig$1; 11729 scale_linear._defaults = _defaults$1; 11730 11731 var valueOrDefault$a = helpers$1.valueOrDefault; 11732 11733 /** 11734 * Generate a set of logarithmic ticks 11735 * @param generationOptions the options used to generate the ticks 11736 * @param dataRange the range of the data 11737 * @returns {number[]} array of tick values 11738 */ 11739 function generateTicks$1(generationOptions, dataRange) { 11740 var ticks = []; 11741 11742 var tickVal = valueOrDefault$a(generationOptions.min, Math.pow(10, Math.floor(helpers$1.log10(dataRange.min)))); 11743 11744 var endExp = Math.floor(helpers$1.log10(dataRange.max)); 11745 var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); 11746 var exp, significand; 11747 11748 if (tickVal === 0) { 11749 exp = Math.floor(helpers$1.log10(dataRange.minNotZero)); 11750 significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); 11751 11752 ticks.push(tickVal); 11753 tickVal = significand * Math.pow(10, exp); 11754 } else { 11755 exp = Math.floor(helpers$1.log10(tickVal)); 11756 significand = Math.floor(tickVal / Math.pow(10, exp)); 11757 } 11758 var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; 11759 11760 do { 11761 ticks.push(tickVal); 11762 11763 ++significand; 11764 if (significand === 10) { 11765 significand = 1; 11766 ++exp; 11767 precision = exp >= 0 ? 1 : precision; 11768 } 11769 11770 tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; 11771 } while (exp < endExp || (exp === endExp && significand < endSignificand)); 11772 11773 var lastTick = valueOrDefault$a(generationOptions.max, tickVal); 11774 ticks.push(lastTick); 11775 11776 return ticks; 11777 } 11778 11779 var defaultConfig$2 = { 11780 position: 'left', 11781 11782 // label settings 11783 ticks: { 11784 callback: core_ticks.formatters.logarithmic 11785 } 11786 }; 11787 11788// TODO(v3): change this to positiveOrDefault 11789 function nonNegativeOrDefault(value, defaultValue) { 11790 return helpers$1.isFinite(value) && value >= 0 ? value : defaultValue; 11791 } 11792 11793 var scale_logarithmic = core_scale.extend({ 11794 determineDataLimits: function () { 11795 var me = this; 11796 var opts = me.options; 11797 var chart = me.chart; 11798 var data = chart.data; 11799 var datasets = data.datasets; 11800 var isHorizontal = me.isHorizontal(); 11801 11802 function IDMatches(meta) { 11803 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; 11804 } 11805 11806 // Calculate Range 11807 me.min = null; 11808 me.max = null; 11809 me.minNotZero = null; 11810 11811 var hasStacks = opts.stacked; 11812 if (hasStacks === undefined) { 11813 helpers$1.each(datasets, function (dataset, datasetIndex) { 11814 if (hasStacks) { 11815 return; 11816 } 11817 11818 var meta = chart.getDatasetMeta(datasetIndex); 11819 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && 11820 meta.stack !== undefined) { 11821 hasStacks = true; 11822 } 11823 }); 11824 } 11825 11826 if (opts.stacked || hasStacks) { 11827 var valuesPerStack = {}; 11828 11829 helpers$1.each(datasets, function (dataset, datasetIndex) { 11830 var meta = chart.getDatasetMeta(datasetIndex); 11831 var key = [ 11832 meta.type, 11833 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined 11834 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), 11835 meta.stack 11836 ].join('.'); 11837 11838 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 11839 if (valuesPerStack[key] === undefined) { 11840 valuesPerStack[key] = []; 11841 } 11842 11843 helpers$1.each(dataset.data, function (rawValue, index) { 11844 var values = valuesPerStack[key]; 11845 var value = +me.getRightValue(rawValue); 11846 // invalid, hidden and negative values are ignored 11847 if (isNaN(value) || meta.data[index].hidden || value < 0) { 11848 return; 11849 } 11850 values[index] = values[index] || 0; 11851 values[index] += value; 11852 }); 11853 } 11854 }); 11855 11856 helpers$1.each(valuesPerStack, function (valuesForType) { 11857 if (valuesForType.length > 0) { 11858 var minVal = helpers$1.min(valuesForType); 11859 var maxVal = helpers$1.max(valuesForType); 11860 me.min = me.min === null ? minVal : Math.min(me.min, minVal); 11861 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); 11862 } 11863 }); 11864 11865 } else { 11866 helpers$1.each(datasets, function (dataset, datasetIndex) { 11867 var meta = chart.getDatasetMeta(datasetIndex); 11868 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { 11869 helpers$1.each(dataset.data, function (rawValue, index) { 11870 var value = +me.getRightValue(rawValue); 11871 // invalid, hidden and negative values are ignored 11872 if (isNaN(value) || meta.data[index].hidden || value < 0) { 11873 return; 11874 } 11875 11876 if (me.min === null) { 11877 me.min = value; 11878 } else if (value < me.min) { 11879 me.min = value; 11880 } 11881 11882 if (me.max === null) { 11883 me.max = value; 11884 } else if (value > me.max) { 11885 me.max = value; 11886 } 11887 11888 if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { 11889 me.minNotZero = value; 11890 } 11891 }); 11892 } 11893 }); 11894 } 11895 11896 // Common base implementation to handle ticks.min, ticks.max 11897 this.handleTickRangeOptions(); 11898 }, 11899 11900 handleTickRangeOptions: function () { 11901 var me = this; 11902 var tickOpts = me.options.ticks; 11903 var DEFAULT_MIN = 1; 11904 var DEFAULT_MAX = 10; 11905 11906 me.min = nonNegativeOrDefault(tickOpts.min, me.min); 11907 me.max = nonNegativeOrDefault(tickOpts.max, me.max); 11908 11909 if (me.min === me.max) { 11910 if (me.min !== 0 && me.min !== null) { 11911 me.min = Math.pow(10, Math.floor(helpers$1.log10(me.min))
vendor: 19,710 bytes, lines 11911-12460
11911- 1); 11912 me.max = Math.pow(10, Math.floor(helpers$1.log10(me.max)) + 1); 11913 } else { 11914 me.min = DEFAULT_MIN; 11915 me.max = DEFAULT_MAX; 11916 } 11917 } 11918 if (me.min === null) { 11919 me.min = Math.pow(10, Math.floor(helpers$1.log10(me.max)) - 1); 11920 } 11921 if (me.max === null) { 11922 me.max = me.min !== 0 11923 ? Math.pow(10, Math.floor(helpers$1.log10(me.min)) + 1) 11924 : DEFAULT_MAX; 11925 } 11926 if (me.minNotZero === null) { 11927 if (me.min > 0) { 11928 me.minNotZero = me.min; 11929 } else if (me.max < 1) { 11930 me.minNotZero = Math.pow(10, Math.floor(helpers$1.log10(me.max))); 11931 } else { 11932 me.minNotZero = DEFAULT_MIN; 11933 } 11934 } 11935 }, 11936 11937 buildTicks: function () { 11938 var me = this; 11939 var tickOpts = me.options.ticks; 11940 var reverse = !me.isHorizontal(); 11941 11942 var generationOptions = { 11943 min: nonNegativeOrDefault(tickOpts.min), 11944 max: nonNegativeOrDefault(tickOpts.max) 11945 }; 11946 var ticks = me.ticks = generateTicks$1(generationOptions, me); 11947 11948 // At this point, we need to update our max and min given the tick values since we have expanded the 11949 // range of the scale 11950 me.max = helpers$1.max(ticks); 11951 me.min = helpers$1.min(ticks); 11952 11953 if (tickOpts.reverse) { 11954 reverse = !reverse; 11955 me.start = me.max; 11956 me.end = me.min; 11957 } else { 11958 me.start = me.min; 11959 me.end = me.max; 11960 } 11961 if (reverse) { 11962 ticks.reverse(); 11963 } 11964 }, 11965 11966 convertTicksToLabels: function () { 11967 this.tickValues = this.ticks.slice(); 11968 11969 core_scale.prototype.convertTicksToLabels.call(this); 11970 }, 11971 11972 // Get the correct tooltip label 11973 getLabelForIndex: function (index, datasetIndex) { 11974 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); 11975 }, 11976 11977 getPixelForTick: function (index) { 11978 return this.getPixelForValue(this.tickValues[index]); 11979 }, 11980 11981 /** 11982 * Returns the value of the first tick. 11983 * @param {number} value - The minimum not zero value. 11984 * @return {number} The first tick value. 11985 * @private 11986 */ 11987 _getFirstTickValue: function (value) { 11988 var exp = Math.floor(helpers$1.log10(value)); 11989 var significand = Math.floor(value / Math.pow(10, exp)); 11990 11991 return significand * Math.pow(10, exp); 11992 }, 11993 11994 getPixelForValue: function (value) { 11995 var me = this; 11996 var tickOpts = me.options.ticks; 11997 var reverse = tickOpts.reverse; 11998 var log10 = helpers$1.log10; 11999 var firstTickValue = me._getFirstTickValue(me.minNotZero); 12000 var offset = 0; 12001 var innerDimension, pixel, start, end, sign; 12002 12003 value = +me.getRightValue(value); 12004 if (reverse) { 12005 start = me.end; 12006 end = me.start; 12007 sign = -1; 12008 } else { 12009 start = me.start; 12010 end = me.end; 12011 sign = 1; 12012 } 12013 if (me.isHorizontal()) { 12014 innerDimension = me.width; 12015 pixel = reverse ? me.right : me.left; 12016 } else { 12017 innerDimension = me.height; 12018 sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0) 12019 pixel = reverse ? me.top : me.bottom; 12020 } 12021 if (value !== start) { 12022 if (start === 0) { // include zero tick 12023 offset = valueOrDefault$a(tickOpts.fontSize, core_defaults.global.defaultFontSize); 12024 innerDimension -= offset; 12025 start = firstTickValue; 12026 } 12027 if (value !== 0) { 12028 offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start)); 12029 } 12030 pixel += sign * offset; 12031 } 12032 return pixel; 12033 }, 12034 12035 getValueForPixel: function (pixel) { 12036 var me = this; 12037 var tickOpts = me.options.ticks; 12038 var reverse = tickOpts.reverse; 12039 var log10 = helpers$1.log10; 12040 var firstTickValue = me._getFirstTickValue(me.minNotZero); 12041 var innerDimension, start, end, value; 12042 12043 if (reverse) { 12044 start = me.end; 12045 end = me.start; 12046 } else { 12047 start = me.start; 12048 end = me.end; 12049 } 12050 if (me.isHorizontal()) { 12051 innerDimension = me.width; 12052 value = reverse ? me.right - pixel : pixel - me.left; 12053 } else { 12054 innerDimension = me.height; 12055 value = reverse ? pixel - me.top : me.bottom - pixel; 12056 } 12057 if (value !== start) { 12058 if (start === 0) { // include zero tick 12059 var offset = valueOrDefault$a(tickOpts.fontSize, core_defaults.global.defaultFontSize); 12060 value -= offset; 12061 innerDimension -= offset; 12062 start = firstTickValue; 12063 } 12064 value *= log10(end) - log10(start); 12065 value /= innerDimension; 12066 value = Math.pow(10, log10(start) + value); 12067 } 12068 return value; 12069 } 12070 }); 12071 12072// INTERNAL: static default options, registered in src/index.js 12073 var _defaults$2 = defaultConfig$2; 12074 scale_logarithmic._defaults = _defaults$2; 12075 12076 var valueOrDefault$b = helpers$1.valueOrDefault; 12077 var valueAtIndexOrDefault$1 = helpers$1.valueAtIndexOrDefault; 12078 var resolve$7 = helpers$1.options.resolve; 12079 12080 var defaultConfig$3 = { 12081 display: true, 12082 12083 // Boolean - Whether to animate scaling the chart from the centre 12084 animate: true, 12085 position: 'chartArea', 12086 12087 angleLines: { 12088 display: true, 12089 color: 'rgba(0, 0, 0, 0.1)', 12090 lineWidth: 1, 12091 borderDash: [], 12092 borderDashOffset: 0.0 12093 }, 12094 12095 gridLines: { 12096 circular: false 12097 }, 12098 12099 // label settings 12100 ticks: { 12101 // Boolean - Show a backdrop to the scale label 12102 showLabelBackdrop: true, 12103 12104 // String - The colour of the label backdrop 12105 backdropColor: 'rgba(255,255,255,0.75)', 12106 12107 // Number - The backdrop padding above & below the label in pixels 12108 backdropPaddingY: 2, 12109 12110 // Number - The backdrop padding to the side of the label in pixels 12111 backdropPaddingX: 2, 12112 12113 callback: core_ticks.formatters.linear 12114 }, 12115 12116 pointLabels: { 12117 // Boolean - if true, show point labels 12118 display: true, 12119 12120 // Number - Point label font size in pixels 12121 fontSize: 10, 12122 12123 // Function - Used to convert point labels 12124 callback: function (label) { 12125 return label; 12126 } 12127 } 12128 }; 12129 12130 function getValueCount(scale) { 12131 var opts = scale.options; 12132 return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0; 12133 } 12134 12135 function getTickBackdropHeight(opts) { 12136 var tickOpts = opts.ticks; 12137 12138 if (tickOpts.display && opts.display) { 12139 return valueOrDefault$b(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2; 12140 } 12141 return 0; 12142 } 12143 12144 function measureLabelSize(ctx, lineHeight, label) { 12145 if (helpers$1.isArray(label)) { 12146 return { 12147 w: helpers$1.longestText(ctx, ctx.font, label), 12148 h: label.length * lineHeight 12149 }; 12150 } 12151 12152 return { 12153 w: ctx.measureText(label).width, 12154 h: lineHeight 12155 }; 12156 } 12157 12158 function determineLimits(angle, pos, size, min, max) { 12159 if (angle === min || angle === max) { 12160 return { 12161 start: pos - (size / 2), 12162 end: pos + (size / 2) 12163 }; 12164 } else if (angle < min || angle > max) { 12165 return { 12166 start: pos - size, 12167 end: pos 12168 }; 12169 } 12170 12171 return { 12172 start: pos, 12173 end: pos + size 12174 }; 12175 } 12176 12177 /** 12178 * Helper function to fit a radial linear scale with point labels 12179 */ 12180 function fitWithPointLabels(scale) { 12181 12182 // Right, this is really confusing and there is a lot of maths going on here 12183 // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 12184 // 12185 // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif 12186 // 12187 // Solution: 12188 // 12189 // We assume the radius of the polygon is half the size of the canvas at first 12190 // at each index we check if the text overlaps. 12191 // 12192 // Where it does, we store that angle and that index. 12193 // 12194 // After finding the largest index and angle we calculate how much we need to remove 12195 // from the shape radius to move the point inwards by that x. 12196 // 12197 // We average the left and right distances to get the maximum shape radius that can fit in the box 12198 // along with labels. 12199 // 12200 // Once we have that, we can find the centre point for the chart, by taking the x text protrusion 12201 // on each side, removing that from the size, halving it and adding the left x protrusion width. 12202 // 12203 // This will mean we have a shape fitted to the canvas, as large as it can be with the labels 12204 // and position it in the most space efficient manner 12205 // 12206 // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif 12207 12208 var plFont = helpers$1.options._parseFont(scale.options.pointLabels); 12209 12210 // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. 12211 // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points 12212 var furthestLimits = { 12213 l: 0, 12214 r: scale.width, 12215 t: 0, 12216 b: scale.height - scale.paddingTop 12217 }; 12218 var furthestAngles = {}; 12219 var i, textSize, pointPosition; 12220 12221 scale.ctx.font = plFont.string; 12222 scale._pointLabelSizes = []; 12223 12224 var valueCount = getValueCount(scale); 12225 for (i = 0; i < valueCount; i++) { 12226 pointPosition = scale.getPointPosition(i, scale.drawingArea + 5); 12227 textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i] || ''); 12228 scale._pointLabelSizes[i] = textSize; 12229 12230 // Add quarter circle to make degree 0 mean top of circle 12231 var angleRadians = scale.getIndexAngle(i); 12232 var angle = helpers$1.toDegrees(angleRadians) % 360; 12233 var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); 12234 var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); 12235 12236 if (hLimits.start < furthestLimits.l) { 12237 furthestLimits.l = hLimits.start; 12238 furthestAngles.l = angleRadians; 12239 } 12240 12241 if (hLimits.end > furthestLimits.r) { 12242 furthestLimits.r = hLimits.end; 12243 furthestAngles.r = angleRadians; 12244 } 12245 12246 if (vLimits.start < furthestLimits.t) { 12247 furthestLimits.t = vLimits.start; 12248 furthestAngles.t = angleRadians; 12249 } 12250 12251 if (vLimits.end > furthestLimits.b) { 12252 furthestLimits.b = vLimits.end; 12253 furthestAngles.b = angleRadians; 12254 } 12255 } 12256 12257 scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles); 12258 } 12259 12260 function getTextAlignForAngle(angle) { 12261 if (angle === 0 || angle === 180) { 12262 return 'center'; 12263 } else if (angle < 180) { 12264 return 'left'; 12265 } 12266 12267 return 'right'; 12268 } 12269 12270 function fillText(ctx, text, position, lineHeight) { 12271 var y = position.y + lineHeight / 2; 12272 var i, ilen; 12273 12274 if (helpers$1.isArray(text)) { 12275 for (i = 0, ilen = text.length; i < ilen; ++i) { 12276 ctx.fillText(text[i], position.x, y); 12277 y += lineHeight; 12278 } 12279 } else { 12280 ctx.fillText(text, position.x, y); 12281 } 12282 } 12283 12284 function adjustPointPositionForLabelHeight(angle, textSize, position) { 12285 if (angle === 90 || angle === 270) { 12286 position.y -= (textSize.h / 2); 12287 } else if (angle > 270 || angle < 90) { 12288 position.y -= textSize.h; 12289 } 12290 } 12291 12292 function drawPointLabels(scale) { 12293 var ctx = scale.ctx; 12294 var opts = scale.options; 12295 var angleLineOpts = opts.angleLines; 12296 var gridLineOpts = opts.gridLines; 12297 var pointLabelOpts = opts.pointLabels; 12298 var lineWidth = valueOrDefault$b(angleLineOpts.lineWidth, gridLineOpts.lineWidth); 12299 var lineColor = valueOrDefault$b(angleLineOpts.color, gridLineOpts.color); 12300 var tickBackdropHeight = getTickBackdropHeight(opts); 12301 12302 ctx.save(); 12303 ctx.lineWidth = lineWidth; 12304 ctx.strokeStyle = lineColor; 12305 if (ctx.setLineDash) { 12306 ctx.setLineDash(resolve$7([angleLineOpts.borderDash, gridLineOpts.borderDash, []])); 12307 ctx.lineDashOffset = resolve$7([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]); 12308 } 12309 12310 var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); 12311 12312 // Point Label Font 12313 var plFont = helpers$1.options._parseFont(pointLabelOpts); 12314 12315 ctx.font = plFont.string; 12316 ctx.textBaseline = 'middle'; 12317 12318 for (var i = getValueCount(scale) - 1; i >= 0; i--) { 12319 if (angleLineOpts.display && lineWidth && lineColor) { 12320 var outerPosition = scale.getPointPosition(i, outerDistance); 12321 ctx.beginPath(); 12322 ctx.moveTo(scale.xCenter, scale.yCenter); 12323 ctx.lineTo(outerPosition.x, outerPosition.y); 12324 ctx.stroke(); 12325 } 12326 12327 if (pointLabelOpts.display) { 12328 // Extra pixels out for some label spacing 12329 var extra = (i === 0 ? tickBackdropHeight / 2 : 0); 12330 var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5); 12331 12332 // Keep this in loop since we may support array properties here 12333 var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor); 12334 ctx.fillStyle = pointLabelFontColor; 12335 12336 var angleRadians = scale.getIndexAngle(i); 12337 var angle = helpers$1.toDegrees(angleRadians); 12338 ctx.textAlign = getTextAlignForAngle(angle); 12339 adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); 12340 fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.lineHeight); 12341 } 12342 } 12343 ctx.restore(); 12344 } 12345 12346 function drawRadiusLine(scale, gridLineOpts, radius, index) { 12347 var ctx = scale.ctx; 12348 var circular = gridLineOpts.circular; 12349 var valueCount = getValueCount(scale); 12350 var lineColor = valueAtIndexOrDefault$1(gridLineOpts.color, index - 1); 12351 var lineWidth = valueAtIndexOrDefault$1(gridLineOpts.lineWidth, index - 1); 12352 var pointPosition; 12353 12354 if ((!circular && !valueCount) || !lineColor || !lineWidth) { 12355 return; 12356 } 12357 12358 ctx.save(); 12359 ctx.strokeStyle = lineColor; 12360 ctx.lineWidth = lineWidth; 12361 if (ctx.setLineDash) { 12362 ctx.setLineDash(gridLineOpts.borderDash || []); 12363 ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0; 12364 } 12365 12366 ctx.beginPath(); 12367 if (circular) { 12368 // Draw circular arcs between the points 12369 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); 12370 } else { 12371 // Draw straight lines connecting each index 12372 pointPosition = scale.getPointPosition(0, radius); 12373 ctx.moveTo(pointPosition.x, pointPosition.y); 12374 12375 for (var i = 1; i < valueCount; i++) { 12376 pointPosition = scale.getPointPosition(i, radius); 12377 ctx.lineTo(pointPosition.x, pointPosition.y); 12378 } 12379 } 12380 ctx.closePath(); 12381 ctx.stroke(); 12382 ctx.restore(); 12383 } 12384 12385 function numberOrZero(param) { 12386 return helpers$1.isNumber(param) ? param : 0; 12387 } 12388 12389 var scale_radialLinear = scale_linearbase.extend({ 12390 setDimensions: function () { 12391 var me = this; 12392 12393 // Set the unconstrained dimension before label rotation 12394 me.width = me.maxWidth; 12395 me.height = me.maxHeight; 12396 me.paddingTop = getTickBackdropHeight(me.options) / 2; 12397 me.xCenter = Math.floor(me.width / 2); 12398 me.yCenter = Math.floor((me.height - me.paddingTop) / 2); 12399 me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2; 12400 }, 12401 12402 determineDataLimits: function () { 12403 var me = this; 12404 var chart = me.chart; 12405 var min = Number.POSITIVE_INFINITY; 12406 var max = Number.NEGATIVE_INFINITY; 12407 12408 helpers$1.each(chart.data.datasets, function (dataset, datasetIndex) { 12409 if (chart.isDatasetVisible(datasetIndex)) { 12410 var meta = chart.getDatasetMeta(datasetIndex); 12411 12412 helpers$1.each(dataset.data, function (rawValue, index) { 12413 var value = +me.getRightValue(rawValue); 12414 if (isNaN(value) || meta.data[index].hidden) { 12415 return; 12416 } 12417 12418 min = Math.min(value, min); 12419 max = Math.max(value, max); 12420 }); 12421 } 12422 }); 12423 12424 me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); 12425 me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); 12426 12427 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero 12428 me.handleTickRangeOptions(); 12429 }, 12430 12431 // Returns the maximum number of ticks based on the scale dimension 12432 _computeTickLimit: function () { 12433 return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); 12434 }, 12435 12436 convertTicksToLabels: function () { 12437 var me = this; 12438 12439 scale_linearbase.prototype.convertTicksToLabels.call(me); 12440 12441 // Point labels 12442 me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); 12443 }, 12444 12445 getLabelForIndex: function (index, datasetIndex) { 12446 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); 12447 }, 12448 12449 fit: function () { 12450 var me = this; 12451 var opts = me.options; 12452 12453 if (opts.display && opts.pointLabels.display) { 12454 fitWithPointLabels(me); 12455 } else { 12456 me.setCenterPoint(0, 0, 0, 0); 12457 } 12458 }, 12459 12460 /**
12461 * Set radius reductions and determine new radius and center point 12462 * @private 12463 */ 12464 setReductions: function (largestPossibleRadius, furthestLimits, furthestAngles) { 12465 var me = this; 12466 var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); 12467 var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); 12468 var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); 12469 var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b); 12470 12471 radiusReductionLeft = numberOrZero(radiusReductionLeft); 12472 radiusReductionRight = numberOrZero(radiusReductionRight); 12473 radiusReductionTop = numberOrZero(radiusReductionTop); 12474 radiusReductionBottom = numberOrZero(radiusReductionBottom); 12475 12476 me.drawingArea = Math.min( 12477 Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), 12478 Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); 12479 me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); 12480 }, 12481 12482 setCenterPoint: function (leftMovement, rightMovement, topMovement, bottomMovement) { 12483 var me = this; 12484 var maxRight = me.width - rightMovement - me.drawingArea; 12485 var maxLeft = leftMovement + me.drawingArea; 12486 var maxTop = topMovement + me.drawingArea; 12487 var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea; 12488 12489 me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); 12490 me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop); 12491 }, 12492 12493 getIndexAngle: function (index) { 12494 var angleMultiplier = (Math.PI * 2) / getValueCount(this); 12495 var startAngle = this.chart.options && this.chart.options.startAngle ? 12496 this.chart.options.startAngle : 12497 0; 12498 12499 var startAngleRadians = startAngle * Math.PI * 2 / 360; 12500 12501 // Start from the top instead of right, so remove a quarter of the circle 12502 return index * angleMultiplier + startAngleRadians; 12503 }, 12504 12505 getDistanceFromCenterForValue: function (value) { 12506 var me = this; 12507 12508 if (value === null) { 12509 return 0; // null always in center 12510 } 12511 12512 // Take into account half font size + the yPadding of the top value 12513 var scalingFactor = me.drawingArea / (me.max - me.min); 12514 if (me.options.ticks.reverse) { 12515 return (me.max - value) * scalingFactor; 12516 } 12517 return (value - me.min) * scalingFactor; 12518 }, 12519 12520 getPointPosition: function (index, distanceFromCenter) { 12521 var me = this; 12522 var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); 12523 return { 12524 x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter, 12525 y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter 12526 }; 12527 }, 12528 12529 getPointPositionForValue: function (index, value) { 12530 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); 12531 }, 12532 12533 getBasePosition: function () { 12534 var me = this; 12535 var min = me.min; 12536 var max = me.max; 12537 12538 return me.getPointPositionForValue(0, 12539 me.beginAtZero ? 0 : 12540 min < 0 && max < 0 ? max : 12541 min > 0 && max > 0 ? min : 12542 0); 12543 }, 12544 12545 draw: function () { 12546 var me = this; 12547 var opts = me.options; 12548 var gridLineOpts = opts.gridLines; 12549 var tickOpts = opts.ticks; 12550 12551 if (opts.display) { 12552 var ctx = me.ctx; 12553 var startAngle = this.getIndexAngle(0); 12554 var tickFont = helpers$1.options._parseFont(tickOpts); 12555 12556 if (opts.angleLines.display || opts.pointLabels.display) { 12557 drawPointLabels(me); 12558 } 12559 12560 helpers$1.each(me.ticks, function (label, index) { 12561 // Don't draw a centre value (if it is minimum) 12562 if (index > 0 || tickOpts.reverse) { 12563 var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); 12564 12565 // Draw circular lines around the scale 12566 if (gridLineOpts.display && index !== 0) { 12567 drawRadiusLine(me, gridLineOpts, yCenterOffset, index); 12568 } 12569 12570 if (tickOpts.display) {
vendor: 11,719 bytes, lines 12571-12913
12571 var tickFontColor = valueOrDefault$b(tickOpts.fontColor, core_defaults.global.defaultFontColor); 12572 ctx.font = tickFont.string; 12573 12574 ctx.save(); 12575 ctx.translate(me.xCenter, me.yCenter); 12576 ctx.rotate(startAngle); 12577 12578 if (tickOpts.showLabelBackdrop) { 12579 var labelWidth = ctx.measureText(label).width; 12580 ctx.fillStyle = tickOpts.backdropColor; 12581 ctx.fillRect( 12582 -labelWidth / 2 - tickOpts.backdropPaddingX, 12583 -yCenterOffset - tickFont.size / 2 - tickOpts.backdropPaddingY, 12584 labelWidth + tickOpts.backdropPaddingX * 2, 12585 tickFont.size + tickOpts.backdropPaddingY * 2 12586 ); 12587 } 12588 12589 ctx.textAlign = 'center'; 12590 ctx.textBaseline = 'middle'; 12591 ctx.fillStyle = tickFontColor; 12592 ctx.fillText(label, 0, -yCenterOffset); 12593 ctx.restore(); 12594 } 12595 } 12596 }); 12597 } 12598 } 12599 }); 12600 12601// INTERNAL: static default options, registered in src/index.js 12602 var _defaults$3 = defaultConfig$3; 12603 scale_radialLinear._defaults = _defaults$3; 12604 12605 var valueOrDefault$c = helpers$1.valueOrDefault; 12606 12607// Integer constants are from the ES6 spec. 12608 var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; 12609 var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; 12610 12611 var INTERVALS = { 12612 millisecond: { 12613 common: true, 12614 size: 1, 12615 steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] 12616 }, 12617 second: { 12618 common: true, 12619 size: 1000, 12620 steps: [1, 2, 5, 10, 15, 30] 12621 }, 12622 minute: { 12623 common: true, 12624 size: 60000, 12625 steps: [1, 2, 5, 10, 15, 30] 12626 }, 12627 hour: { 12628 common: true, 12629 size: 3600000, 12630 steps: [1, 2, 3, 6, 12] 12631 }, 12632 day: { 12633 common: true, 12634 size: 86400000, 12635 steps: [1, 2, 5] 12636 }, 12637 week: { 12638 common: false, 12639 size: 604800000, 12640 steps: [1, 2, 3, 4] 12641 }, 12642 month: { 12643 common: true, 12644 size: 2.628e9, 12645 steps: [1, 2, 3] 12646 }, 12647 quarter: { 12648 common: false, 12649 size: 7.884e9, 12650 steps: [1, 2, 3, 4] 12651 }, 12652 year: { 12653 common: true, 12654 size: 3.154e10 12655 } 12656 }; 12657 12658 var UNITS = Object.keys(INTERVALS); 12659 12660 function sorter(a, b) { 12661 return a - b; 12662 } 12663 12664 function arrayUnique(items) { 12665 var hash = {}; 12666 var out = []; 12667 var i, ilen, item; 12668 12669 for (i = 0, ilen = items.length; i < ilen; ++i) { 12670 item = items[i]; 12671 if (!hash[item]) { 12672 hash[item] = true; 12673 out.push(item); 12674 } 12675 } 12676 12677 return out; 12678 } 12679 12680 /** 12681 * Returns an array of {time, pos} objects used to interpolate a specific `time` or position 12682 * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is 12683 * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other 12684 * extremity (left + width or top + height). Note that it would be more optimized to directly 12685 * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need 12686 * to create the lookup table. The table ALWAYS contains at least two items: min and max. 12687 * 12688 * @param {number[]} timestamps - timestamps sorted from lowest to highest. 12689 * @param {string} distribution - If 'linear', timestamps will be spread linearly along the min 12690 * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. 12691 * If 'series', timestamps will be positioned at the same distance from each other. In this 12692 * case, only timestamps that break the time linearity are registered, meaning that in the 12693 * best case, all timestamps are linear, the table contains only min and max. 12694 */ 12695 function buildLookupTable(timestamps, min, max, distribution) { 12696 if (distribution === 'linear' || !timestamps.length) { 12697 return [ 12698 {time: min, pos: 0}, 12699 {time: max, pos: 1} 12700 ]; 12701 } 12702 12703 var table = []; 12704 var items = [min]; 12705 var i, ilen, prev, curr, next; 12706 12707 for (i = 0, ilen = timestamps.length; i < ilen; ++i) { 12708 curr = timestamps[i]; 12709 if (curr > min && curr < max) { 12710 items.push(curr); 12711 } 12712 } 12713 12714 items.push(max); 12715 12716 for (i = 0, ilen = items.length; i < ilen; ++i) { 12717 next = items[i + 1]; 12718 prev = items[i - 1]; 12719 curr = items[i]; 12720 12721 // only add points that breaks the scale linearity 12722 if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { 12723 table.push({time: curr, pos: i / (ilen - 1)}); 12724 } 12725 } 12726 12727 return table; 12728 } 12729 12730// @see adapted from https://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ 12731 function lookup(table, key, value) { 12732 var lo = 0; 12733 var hi = table.length - 1; 12734 var mid, i0, i1; 12735 12736 while (lo >= 0 && lo <= hi) { 12737 mid = (lo + hi) >> 1; 12738 i0 = table[mid - 1] || null; 12739 i1 = table[mid]; 12740 12741 if (!i0) { 12742 // given value is outside table (before first item) 12743 return {lo: null, hi: i1}; 12744 } else if (i1[key] < value) { 12745 lo = mid + 1; 12746 } else if (i0[key] > value) { 12747 hi = mid - 1; 12748 } else { 12749 return {lo: i0, hi: i1}; 12750 } 12751 } 12752 12753 // given value is outside table (after last item) 12754 return {lo: i1, hi: null}; 12755 } 12756 12757 /** 12758 * Linearly interpolates the given source `value` using the table items `skey` values and 12759 * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') 12760 * returns the position for a timestamp equal to 42. If value is out of bounds, values at 12761 * index [0, 1] or [n - 1, n] are used for the interpolation. 12762 */ 12763 function interpolate$1(table, skey, sval, tkey) { 12764 var range = lookup(table, skey, sval); 12765 12766 // Note: the lookup table ALWAYS contains at least 2 items (min and max) 12767 var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; 12768 var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; 12769 12770 var span = next[skey] - prev[skey]; 12771 var ratio = span ? (sval - prev[skey]) / span : 0; 12772 var offset = (next[tkey] - prev[tkey]) * ratio; 12773 12774 return prev[tkey] + offset; 12775 } 12776 12777 function toTimestamp(scale, input) { 12778 var adapter = scale._adapter; 12779 var options = scale.options.time; 12780 var parser = options.parser; 12781 var format = parser || options.format; 12782 var value = input; 12783 12784 if (typeof parser === 'function') { 12785 value = parser(value); 12786 } 12787 12788 // Only parse if its not a timestamp already 12789 if (!helpers$1.isFinite(value)) { 12790 value = typeof format === 'string' 12791 ? adapter.parse(value, format) 12792 : adapter.parse(value); 12793 } 12794 12795 if (value !== null) { 12796 return +value; 12797 } 12798 12799 // Labels are in an incompatible format and no `parser` has been provided. 12800 // The user might still use the deprecated `format` option for parsing. 12801 if (!parser && typeof format === 'function') { 12802 value = format(input); 12803 12804 // `format` could return something else than a timestamp, if so, parse it 12805 if (!helpers$1.isFinite(value)) { 12806 value = adapter.parse(value); 12807 } 12808 } 12809 12810 return value; 12811 } 12812 12813 function parse(scale, input) { 12814 if (helpers$1.isNullOrUndef(input)) { 12815 return null; 12816 } 12817 12818 var options = scale.options.time; 12819 var value = toTimestamp(scale, scale.getRightValue(input)); 12820 if (value === null) { 12821 return value; 12822 } 12823 12824 if (options.round) { 12825 value = +scale._adapter.startOf(value, options.round); 12826 } 12827 12828 return value; 12829 } 12830 12831 /** 12832 * Returns the number of unit to skip to be able to display up to `capacity` number of ticks 12833 * in `unit` for the given `min` / `max` range and respecting the interval steps constraints. 12834 */ 12835 function determineStepSize(min, max, unit, capacity) { 12836 var range = max - min; 12837 var interval = INTERVALS[unit]; 12838 var milliseconds = interval.size; 12839 var steps = interval.steps; 12840 var i, ilen, factor; 12841 12842 if (!steps) { 12843 return Math.ceil(range / (capacity * milliseconds)); 12844 } 12845 12846 for (i = 0, ilen = steps.length; i < ilen; ++i) { 12847 factor = steps[i]; 12848 if (Math.ceil(range / (milliseconds * factor)) <= capacity) { 12849 break; 12850 } 12851 } 12852 12853 return factor; 12854 } 12855 12856 /** 12857 * Figures out what unit results in an appropriate number of auto-generated ticks 12858 */ 12859 function determineUnitForAutoTicks(minUnit, min, max, capacity) { 12860 var ilen = UNITS.length; 12861 var i, interval, factor; 12862 12863 for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { 12864 interval = INTERVALS[UNITS[i]]; 12865 factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER; 12866 12867 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { 12868 return UNITS[i]; 12869 } 12870 } 12871 12872 return UNITS[ilen - 1]; 12873 } 12874 12875 /** 12876 * Figures out what unit to format a set of ticks with 12877 */ 12878 function determineUnitForFormatting(scale, ticks, minUnit, min, max) { 12879 var ilen = UNITS.length; 12880 var i, unit; 12881 12882 for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) { 12883 unit = UNITS[i]; 12884 if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= ticks.length) { 12885 return unit; 12886 } 12887 } 12888 12889 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; 12890 } 12891 12892 function determineMajorUnit(unit) { 12893 for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { 12894 if (INTERVALS[UNITS[i]].common) { 12895 return UNITS[i]; 12896 } 12897 } 12898 } 12899 12900 /** 12901 * Generates a maximum of `capacity` timestamps between min and max, rounded to the 12902 * `minor` unit, aligned on the `major` unit and using the given scale time `options`. 12903 * Important: this method can return ticks outside the min and max range, it's the 12904 * responsibility of the calling code to clamp values if needed. 12905 */ 12906 function generate(scale, min, max, capacity) { 12907 var adapter = scale._adapter; 12908 var options = scale.options; 12909 var timeOpts = options.time; 12910 var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); 12911 var major = determineMajorUnit(minor); 12912 var stepSize = valueOrDefault$c(timeOpts.stepSize, timeOpts.unitStepSize); 12913 var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
12914 var majorTicksEnabled = options.ticks.major.enabled; 12915 var interval = INTERVALS[minor]; 12916 var first = min; 12917 var last = max; 12918 var ticks = []; 12919 var time; 12920 12921 if (!stepSize) { 12922 stepSize = determineStepSize(min, max, minor, capacity); 12923 } 12924 12925 // For 'week' unit, handle the first day of week option 12926 if (weekday) { 12927 first = +adapter.startOf(first, 'isoWeek', weekday); 12928 last = +adapter.startOf(last, 'isoWeek', weekday); 12929 } 12930 12931 // Align first/last ticks on unit 12932 first = +adapter.startOf(first, weekday ? 'day' : minor); 12933 last = +adapter.startOf(last, weekday ? 'day' : minor); 12934 12935 // Make sure that the last tick include max 12936 if (last < max) { 12937 last = +adapter.add(last, 1, minor); 12938 } 12939 12940 time = first; 12941 12942 if (majorTicksEnabled && major && !weekday && !timeOpts.round) { 12943 // Align the first tick on the previous `minor` unit aligned on the `major` unit: 12944 // we first aligned time on the previous `major` unit then add the number of full 12945 // stepSize there is between first and the previous major time. 12946 time = +adapter.startOf(time, major); 12947 time = +adapter.add(time, ~~((first - time) / (interval.size * stepSize)) * stepSize, minor); 12948 } 12949 12950 for (; time < last; time = +adapter.add(time, stepSize, minor)) { 12951 ticks.push(+time); 12952 } 12953 12954 ticks.push(+time); 12955 12956 return ticks; 12957 } 12958 12959 /** 12960 * Returns the start and end offsets from edges in the form of {start, end} 12961 * where each value is a relative width to the scale and ranges between 0 and 1. 12962 * They add extra margins on the both sides by scaling down the original scale. 12963 * Offsets are added when the `offset` option is true. 12964 */ 12965 function computeOffsets(table, ticks, min, max, options) { 12966 var start = 0; 12967 var end = 0; 12968 var first, last; 12969 12970 if (options.offset && ticks.length) { 12971 if (!options.time.min) { 12972 first = interpolate$1(table, 'time', ticks[0], 'pos'); 12973 if (ticks.length === 1) { 12974 start = 1 - first; 12975 } else { 12976 start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2; 12977 } 12978 } 12979 if (!options.time.max) { 12980 last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos'); 12981 if (ticks.length === 1) { 12982 end = last; 12983 } else { 12984 end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2; 12985 } 12986 } 12987 } 12988 12989 return {start: start, end: end}; 12990 } 12991 12992 function ticksFromTimestamps(scale, values, majorUnit) { 12993 var ticks = []; 12994 var i, ilen, value, major; 12995 12996 for (i = 0, ilen = values.length; i < ilen; ++i) { 12997 value = values[i]; 12998 major = majorUnit ? value === +scale._adapter.startOf(value, majorUnit) : false; 12999 13000 ticks.push({ 13001 value: value, 13002 major: major 13003 }); 13004 } 13005 13006 return ticks; 13007 } 13008 13009 var defaultConfig$4 = { 13010 position: 'bottom', 13011 13012 /** 13013 * Data distribution along the scale: 13014 * - 'linear': data are spread according to their time (distances can vary), 13015 * - 'series': data are spread at the same distance from each other. 13016 * @see https://github.com/chartjs/Chart.js/pull/4507 13017 * @since 2.7.0 13018 */ 13019 distribution: 'linear', 13020 13021 /** 13022 * Scale boundary strategy (bypassed by min/max time options) 13023 * - `data`: make sure data are fully visible, ticks outside are removed 13024 * - `ticks`: make sure ticks are fully visible, data outside are truncated 13025 * @see https://github.com/chartjs/Chart.js/pull/4556 13026 * @since 2.7.0 13027 */ 13028 bounds: 'data', 13029 13030 adapters: {}, 13031 time: { 13032 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 13033 format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from https://momentjs.com/docs/#/parsing/string-format/ 13034 unit: false, // false == automatic or override with week, month, year, etc. 13035 round: false, // none, or override with week, month, year, etc. 13036 displayFormat: false, // DEPRECATED 13037 isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/ 13038 minUnit: 'millisecond', 13039 displayFormats: {} 13040 }, 13041 ticks: { 13042 autoSkip: false, 13043 13044 /** 13045 * Ticks generation input values: 13046 * - 'auto': generates "optimal" ticks based on scale size and time options. 13047 * - 'data': generates ticks from data (including labels from data {t|x|y} objects). 13048 * - 'labels': generates ticks from user given `data.labels` values ONLY. 13049 * @see https://github.com/chartjs/Chart.js/pull/4507 13050 * @since 2.7.0 13051 */ 13052 source: 'auto', 13053 13054 major: { 13055 enabled: false 13056 } 13057 } 13058 }; 13059 13060 var scale_time = core_scale.extend({ 13061 initialize: function () { 13062 this.mergeTicksOptions(); 13063 core_scale.prototype.initialize.call(this); 13064 }, 13065 13066 update: function () { 13067 var me = this; 13068 var options = me.options; 13069 var time = options.time || (options.time = {}); 13070 var adapter = me._adapter = new core_adapters._date(options.adapters.date); 13071 13072 // DEPRECATIONS: output a message only one time per update 13073 if (time.format) { 13074 console.warn('options.time.format is deprecated and replaced by options.time.parser.'); 13075 } 13076 13077 // Backward compatibility: before introducing adapter, `displayFormats` was 13078 // supposed to contain *all* unit/string pairs but this can't be resolved
13079 // when loading the scale (adapters are loaded afterward), so let's populate 13080 // missing formats on update 13081 helpers$1.mergeIf(time.displayFormats, adapter.formats()); 13082 13083 return core_scale.prototype.update.apply(me, arguments); 13084 }, 13085 13086 /** 13087 * Allows data to be referenced via 't' attribute 13088 */ 13089 getRightValue: function (rawValue) { 13090 if (rawValue && rawValue.t !== undefined) { 13091 rawValue = rawValue.t; 13092 } 13093 return core_scale.prototype.getRightValue.call(this, rawValue); 13094 }, 13095 13096 determineDataLimits: function () { 13097 var me = this; 13098 var chart = me.chart; 13099 var adapter = me._adapter; 13100 var timeOpts = me.options.time; 13101 var unit = timeOpts.unit || 'day'; 13102 var min = MAX_INTEGER; 13103 var max = MIN_INTEGER; 13104 var timestamps = []; 13105 var datasets = []; 13106 var labels = []; 13107 var i, j, ilen, jlen, data, timestamp; 13108 var dataLabels = chart.data.labels || []; 13109 13110 // Convert labels to timestamps 13111 for (i = 0, ilen = dataLabels.length; i < ilen; ++i) { 13112 labels.push(parse(me, dataLabels[i])); 13113 } 13114 13115 // Convert data to timestamps 13116 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { 13117 if (chart.isDatasetVisible(i)) { 13118 data = chart.data.datasets[i].data; 13119 13120 // Let's consider that all data have the same format. 13121 if (helpers$1.isObject(data[0])) { 13122 datasets[i] = []; 13123 13124 for (j = 0, jlen = data.length; j < jlen; ++j) { 13125 timestamp = parse(me, data[j]); 13126 timestamps.push(timestamp); 13127 datasets[i][j] = timestamp; 13128 } 13129 } else { 13130 for (j = 0, jlen = labels.length; j < jlen; ++j) { 13131 timestamps.push(labels[j]); 13132 } 13133 datasets[i] = labels.slice(0); 13134 } 13135 } else { 13136 datasets[i] = []; 13137 } 13138 } 13139 13140 if (labels.length) { 13141 // Sort labels **after** data have been converted 13142 labels = arrayUnique(labels).sort(sorter); 13143 min = Math.min(min, labels[0]); 13144 max = Math.max(max, labels[labels.length - 1]); 13145 } 13146 13147 if (timestamps.length) { 13148 timestamps = arrayUnique(timestamps).sort(sorter); 13149 min = Math.min(min, timestamps[0]); 13150 max = Math.max(max, timestamps[timestamps.length - 1]); 13151 } 13152 13153 min = parse(me, timeOpts.min) || min; 13154 max = parse(me, timeOpts.max) || max; 13155 13156 // In case there is no valid min/max, set limits based on unit time option 13157 min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min; 13158 max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), unit) + 1 : max; 13159 13160 // Make sure that max is strictly higher than min (required by the lookup table) 13161 me.min = Math.min(min, max); 13162 me.max = Math.max(min + 1, max); 13163 13164 // PRIVATE 13165 me._horizontal = me.isHorizontal(); 13166 me._table = []; 13167 me._timestamps = { 13168 data: timestamps, 13169 datasets: datasets, 13170 labels: labels 13171 }; 13172 }, 13173 13174 buildTicks: function () { 13175 var me = this; 13176 var min = me.min; 13177 var max = me.max; 13178 var options = me.options; 13179 var timeOpts = options.time; 13180 var timestamps = []; 13181 var ticks = []; 13182 var i, ilen, timestamp; 13183 13184 switch (options.ticks.source) { 13185 case 'data': 13186 timestamps = me._timestamps.data; 13187 break; 13188 case 'labels': 13189 timestamps = me._timestamps.labels; 13190 break; 13191 case 'auto': 13192 default: 13193 timestamps = generate(me, min, max, me.getLabelCapacity(min), options); 13194 } 13195 13196 if (options.bounds === 'ticks' && timestamps.length) { 13197 min = timestamps[0]; 13198 max = timestamps[timestamps.length - 1]; 13199 } 13200 13201 // Enforce limits with user min/max options 13202 min = parse(me, timeOpts.min) || min; 13203 max = parse(me, timeOpts.max) || max; 13204 13205 // Remove ticks outside the min/max range 13206 for (i = 0, ilen = timestamps.length; i < ilen; ++i) { 13207 timestamp = timestamps[i]; 13208 if (timestamp >= min && timestamp <= max) { 13209 ticks.push(timestamp); 13210 } 13211 } 13212 13213 me.min = min; 13214 me.max = max; 13215 13216 // PRIVATE 13217 me._unit = timeOpts.unit || determineUnitForFormatting(me, ticks, timeOpts.minUnit, me.min, me.max); 13218 me._majorUnit = determineMajorUnit(me._unit); 13219 me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution); 13220 me._offsets = computeOffsets(me._table, ticks, min, max, options); 13221 13222 if (options.ticks.reverse) { 13223 ticks.reverse(); 13224 } 13225 13226 return ticksFromTimestamps(me, ticks, me._majorUnit); 13227 }, 13228 13229 getLabelForIndex: function (index, datasetIndex) { 13230 var me = this; 13231 var adapter = me._adapter; 13232 var data = me.chart.data; 13233 var timeOpts = me.options.time; 13234 var label = data.labels && index < data.labels.length ? data.labels[index] : ''; 13235 var value = data.datasets[datasetIndex].data[index]; 13236 13237 if (helpers$1.isObject(value)) { 13238 label = me.getRightValue(value); 13239 } 13240 if (timeOpts.tooltipFormat) { 13241 return adapter.format(toTimestamp(me, label), timeOpts.tooltipFormat); 13242 } 13243 if (typeof label === 'string') { 13244 return label; 13245 } 13246 return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime); 13247 }, 13248 13249 /** 13250 * Function to format an individual tick mark 13251 * @private 13252 */ 13253 tickFormatFunction: function (time, index, ticks, format) { 13254 var me = this; 13255 var adapter = me._adapter; 13256 var options = me.options; 13257 var formats = options.time.displayFormats; 13258 var minorFormat = formats[me._unit]; 13259 var majorUnit = me._majorUnit; 13260 var majorFormat = formats[majorUnit]; 13261 var majorTime = +adapter.startOf(time, majorUnit); 13262 var majorTickOpts = options.ticks.major; 13263 var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; 13264 var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat); 13265 var tickOpts = major ? majorTickOpts : options.ticks.minor; 13266 var formatter = valueOrDefault$c(tickOpts.callback, tickOpts.userCallback); 13267 13268 return formatter ? formatter(label, index, ticks) : label; 13269 }, 13270 13271 convertTicksToLabels: function (ticks) { 13272 var labels = []; 13273 var i, ilen; 13274 13275 for (i = 0, ilen = ticks.length; i < ilen; ++i) { 13276 labels.push(this.tickFormatFunction(ticks[i].value, i, ticks)); 13277 } 13278 13279 return labels; 13280 }, 13281 13282 /** 13283 * @private 13284 */ 13285 getPixelForOffset: function (time) { 13286 var me = this; 13287 var isReverse = me.options.ticks.reverse; 13288 var size = me._horizontal ? me.width : me.height; 13289 var start = me._horizontal ? isReverse ? me.right : me.left : isReverse ? me.bottom : me.top; 13290 var pos = interpolate$1(me._table, 'time', time, 'pos'); 13291 var offset = size * (me._offsets.start + pos) / (me._offsets.start + 1 + me._offsets.end); 13292 13293 return isReverse ? start - offset : start + offset; 13294 }, 13295 13296 getPixelForValue: function (value, index, datasetIndex) { 13297 var me = this; 13298 var time = null; 13299 13300 if (index !== undefined && datasetIndex !== undefined) { 13301 time = me._timestamps.datasets[datasetIndex][index]; 13302 } 13303 13304 if (time === null) { 13305 time = parse(me, value); 13306 } 13307 13308 if (time !== null) { 13309 return me.getPixelForOffset(time); 13310 } 13311 }, 13312 13313 getPixelForTick: function (index) { 13314 var ticks = this.getTicks(); 13315 return index >= 0 && index < ticks.length ? 13316 this.getPixelForOffset(ticks[index].value) : 13317 null; 13318 }, 13319 13320 getValueForPixel: function (pixel) { 13321 var me = this; 13322 var size = me._horizontal ? me.width : me.height; 13323 var start = me._horizontal ? me.left : me.top; 13324 var pos = (size ? (pixel - start) / size : 0) * (me._offsets.start + 1 + me._offsets.start) - me._offsets.end; 13325 var time = interpolate$1(me._table, 'pos', pos, 'time'); 13326 13327 // DEPRECATION, we should return time directly 13328 return me._adapter._create(time); 13329 }, 13330 13331 /** 13332 * Crude approximation of what the label width might be 13333 * @private 13334 */ 13335 getLabelWidth: function (label) { 13336 var me = this; 13337 var ticksOpts = me.options.ticks; 13338 var tickLabelWidth = me.ctx.measureText(label).width; 13339 var angle = helpers$1.toRadians(ticksOpts.maxRotation); 13340 var cosRotation = Math.cos(angle); 13341 var sinRotation = Math.sin(angle);
vendor: 45,677 bytes, lines 13342-14736
13342 var tickFontSize = valueOrDefault$c(ticksOpts.fontSize, core_defaults.global.defaultFontSize); 13343 13344 return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); 13345 }, 13346 13347 /** 13348 * @private 13349 */ 13350 getLabelCapacity: function (exampleTime) { 13351 var me = this; 13352 13353 // pick the longest format (milliseconds) for guestimation 13354 var format = me.options.time.displayFormats.millisecond; 13355 var exampleLabel = me.tickFormatFunction(exampleTime, 0, [], format); 13356 var tickLabelWidth = me.getLabelWidth(exampleLabel); 13357 var innerWidth = me.isHorizontal() ? me.width : me.height; 13358 var capacity = Math.floor(innerWidth / tickLabelWidth); 13359 13360 return capacity > 0 ? capacity : 1; 13361 } 13362 }); 13363 13364// INTERNAL: static default options, registered in src/index.js 13365 var _defaults$4 = defaultConfig$4; 13366 scale_time._defaults = _defaults$4; 13367 13368 var scales = { 13369 category: scale_category, 13370 linear: scale_linear, 13371 logarithmic: scale_logarithmic, 13372 radialLinear: scale_radialLinear, 13373 time: scale_time 13374 }; 13375 13376 var FORMATS = { 13377 datetime: 'MMM D, YYYY, h:mm:ss a', 13378 millisecond: 'h:mm:ss.SSS a', 13379 second: 'h:mm:ss a', 13380 minute: 'h:mm a', 13381 hour: 'hA', 13382 day: 'MMM D', 13383 week: 'll', 13384 month: 'MMM YYYY', 13385 quarter: '[Q]Q - YYYY', 13386 year: 'YYYY' 13387 }; 13388 13389 core_adapters._date.override(typeof moment === 'function' ? { 13390 _id: 'moment', // DEBUG ONLY 13391 13392 formats: function () { 13393 return FORMATS; 13394 }, 13395 13396 parse: function (value, format) { 13397 if (typeof value === 'string' && typeof format === 'string') { 13398 value = moment(value, format); 13399 } else if (!(value instanceof moment)) { 13400 value = moment(value); 13401 } 13402 return value.isValid() ? value.valueOf() : null; 13403 }, 13404 13405 format: function (time, format) { 13406 return moment(time).format(format); 13407 }, 13408 13409 add: function (time, amount, unit) { 13410 return moment(time).add(amount, unit).valueOf(); 13411 }, 13412 13413 diff: function (max, min, unit) { 13414 return moment.duration(moment(max).diff(moment(min))).as(unit); 13415 }, 13416 13417 startOf: function (time, unit, weekday) { 13418 time = moment(time); 13419 if (unit === 'isoWeek') { 13420 return time.isoWeekday(weekday).valueOf(); 13421 } 13422 return time.startOf(unit).valueOf(); 13423 }, 13424 13425 endOf: function (time, unit) { 13426 return moment(time).endOf(unit).valueOf(); 13427 }, 13428 13429 // DEPRECATIONS 13430 13431 /** 13432 * Provided for backward compatibility with scale.getValueForPixel(). 13433 * @deprecated since version 2.8.0 13434 * @todo remove at version 3 13435 * @private 13436 */ 13437 _create: function (time) { 13438 return moment(time); 13439 }, 13440 } : {}); 13441 13442 core_defaults._set('global', { 13443 plugins: { 13444 filler: { 13445 propagate: true 13446 } 13447 } 13448 }); 13449 13450 var mappers = { 13451 dataset: function (source) { 13452 var index = source.fill; 13453 var chart = source.chart; 13454 var meta = chart.getDatasetMeta(index); 13455 var visible = meta && chart.isDatasetVisible(index); 13456 var points = (visible && meta.dataset._children) || []; 13457 var length = points.length || 0; 13458 13459 return !length ? null : function (point, i) { 13460 return (i < length && points[i]._view) || null; 13461 }; 13462 }, 13463 13464 boundary: function (source) { 13465 var boundary = source.boundary; 13466 var x = boundary ? boundary.x : null; 13467 var y = boundary ? boundary.y : null; 13468 13469 return function (point) { 13470 return { 13471 x: x === null ? point.x : x, 13472 y: y === null ? point.y : y, 13473 }; 13474 }; 13475 } 13476 }; 13477 13478// @todo if (fill[0] === '#') 13479 function decodeFill(el, index, count) { 13480 var model = el._model || {}; 13481 var fill = model.fill; 13482 var target; 13483 13484 if (fill === undefined) { 13485 fill = !!model.backgroundColor; 13486 } 13487 13488 if (fill === false || fill === null) { 13489 return false; 13490 } 13491 13492 if (fill === true) { 13493 return 'origin'; 13494 } 13495 13496 target = parseFloat(fill, 10); 13497 if (isFinite(target) && Math.floor(target) === target) { 13498 if (fill[0] === '-' || fill[0] === '+') { 13499 target = index + target; 13500 } 13501 13502 if (target === index || target < 0 || target >= count) { 13503 return false; 13504 } 13505 13506 return target; 13507 } 13508 13509 switch (fill) { 13510 // compatibility 13511 case 'bottom': 13512 return 'start'; 13513 case 'top': 13514 return 'end'; 13515 case 'zero': 13516 return 'origin'; 13517 // supported boundaries 13518 case 'origin': 13519 case 'start': 13520 case 'end': 13521 return fill; 13522 // invalid fill values 13523 default: 13524 return false; 13525 } 13526 } 13527 13528 function computeBoundary(source) { 13529 var model = source.el._model || {}; 13530 var scale = source.el._scale || {}; 13531 var fill = source.fill; 13532 var target = null; 13533 var horizontal; 13534 13535 if (isFinite(fill)) { 13536 return null; 13537 } 13538 13539 // Backward compatibility: until v3, we still need to support boundary values set on 13540 // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and 13541 // controllers might still use it (e.g. the Smith chart). 13542 13543 if (fill === 'start') { 13544 target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; 13545 } else if (fill === 'end') { 13546 target = model.scaleTop === undefined ? scale.top : model.scaleTop; 13547 } else if (model.scaleZero !== undefined) { 13548 target = model.scaleZero; 13549 } else if (scale.getBasePosition) { 13550 target = scale.getBasePosition(); 13551 } else if (scale.getBasePixel) { 13552 target = scale.getBasePixel(); 13553 } 13554 13555 if (target !== undefined && target !== null) { 13556 if (target.x !== undefined && target.y !== undefined) { 13557 return target; 13558 } 13559 13560 if (helpers$1.isFinite(target)) { 13561 horizontal = scale.isHorizontal(); 13562 return { 13563 x: horizontal ? target : null, 13564 y: horizontal ? null : target 13565 }; 13566 } 13567 } 13568 13569 return null; 13570 } 13571 13572 function resolveTarget(sources, index, propagate) { 13573 var source = sources[index]; 13574 var fill = source.fill; 13575 var visited = [index]; 13576 var target; 13577 13578 if (!propagate) { 13579 return fill; 13580 } 13581 13582 while (fill !== false && visited.indexOf(fill) === -1) { 13583 if (!isFinite(fill)) { 13584 return fill; 13585 } 13586 13587 target = sources[fill]; 13588 if (!target) { 13589 return false; 13590 } 13591 13592 if (target.visible) { 13593 return fill; 13594 } 13595 13596 visited.push(fill); 13597 fill = target.fill; 13598 } 13599 13600 return false; 13601 } 13602 13603 function createMapper(source) { 13604 var fill = source.fill; 13605 var type = 'dataset'; 13606 13607 if (fill === false) { 13608 return null; 13609 } 13610 13611 if (!isFinite(fill)) { 13612 type = 'boundary'; 13613 } 13614 13615 return mappers[type](source); 13616 } 13617 13618 function isDrawable(point) { 13619 return point && !point.skip; 13620 } 13621 13622 function drawArea(ctx, curve0, curve1, len0, len1) { 13623 var i; 13624 13625 if (!len0 || !len1) { 13626 return; 13627 } 13628 13629 // building first area curve (normal) 13630 ctx.moveTo(curve0[0].x, curve0[0].y); 13631 for (i = 1; i < len0; ++i) { 13632 helpers$1.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); 13633 } 13634 13635 // joining the two area curves 13636 ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); 13637 13638 // building opposite area curve (reverse) 13639 for (i = len1 - 1; i > 0; --i) { 13640 helpers$1.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); 13641 } 13642 } 13643 13644 function doFill(ctx, points, mapper, view, color, loop) { 13645 var count = points.length; 13646 var span = view.spanGaps; 13647 var curve0 = []; 13648 var curve1 = []; 13649 var len0 = 0; 13650 var len1 = 0; 13651 var i, ilen, index, p0, p1, d0, d1; 13652 13653 ctx.beginPath(); 13654 13655 for (i = 0, ilen = (count + !!loop); i < ilen; ++i) { 13656 index = i % count; 13657 p0 = points[index]._view; 13658 p1 = mapper(p0, index, view); 13659 d0 = isDrawable(p0); 13660 d1 = isDrawable(p1); 13661 13662 if (d0 && d1) { 13663 len0 = curve0.push(p0); 13664 len1 = curve1.push(p1); 13665 } else if (len0 && len1) { 13666 if (!span) { 13667 drawArea(ctx, curve0, curve1, len0, len1); 13668 len0 = len1 = 0; 13669 curve0 = []; 13670 curve1 = []; 13671 } else { 13672 if (d0) { 13673 curve0.push(p0); 13674 } 13675 if (d1) { 13676 curve1.push(p1); 13677 } 13678 } 13679 } 13680 } 13681 13682 drawArea(ctx, curve0, curve1, len0, len1); 13683 13684 ctx.closePath(); 13685 ctx.fillStyle = color; 13686 ctx.fill(); 13687 } 13688 13689 var plugin_filler = { 13690 id: 'filler', 13691 13692 afterDatasetsUpdate: function (chart, options) { 13693 var count = (chart.data.datasets || []).length; 13694 var propagate = options.propagate; 13695 var sources = []; 13696 var meta, i, el, source; 13697 13698 for (i = 0; i < count; ++i) { 13699 meta = chart.getDatasetMeta(i); 13700 el = meta.dataset; 13701 source = null; 13702 13703 if (el && el._model && el instanceof elements.Line) { 13704 source = { 13705 visible: chart.isDatasetVisible(i), 13706 fill: decodeFill(el, i, count), 13707 chart: chart, 13708 el: el 13709 }; 13710 } 13711 13712 meta.$filler = source; 13713 sources.push(source); 13714 } 13715 13716 for (i = 0; i < count; ++i) { 13717 source = sources[i]; 13718 if (!source) { 13719 continue; 13720 } 13721 13722 source.fill = resolveTarget(sources, i, propagate); 13723 source.boundary = computeBoundary(source); 13724 source.mapper = createMapper(source); 13725 } 13726 }, 13727 13728 beforeDatasetDraw: function (chart, args) { 13729 var meta = args.meta.$filler; 13730 if (!meta) { 13731 return; 13732 } 13733 13734 var ctx = chart.ctx; 13735 var el = meta.el; 13736 var view = el._view; 13737 var points = el._children || []; 13738 var mapper = meta.mapper; 13739 var color = view.backgroundColor || core_defaults.global.defaultColor; 13740 13741 if (mapper && color && points.length) { 13742 helpers$1.canvas.clipArea(ctx, chart.chartArea); 13743 doFill(ctx, points, mapper, view, color, el._loop); 13744 helpers$1.canvas.unclipArea(ctx); 13745 } 13746 } 13747 }; 13748 13749 var noop$1 = helpers$1.noop; 13750 var valueOrDefault$d = helpers$1.valueOrDefault; 13751 13752 core_defaults._set('global', { 13753 legend: { 13754 display: true, 13755 position: 'top', 13756 fullWidth: true, 13757 reverse: false, 13758 weight: 1000, 13759 13760 // a callback that will handle 13761 onClick: function (e, legendItem) { 13762 var index = legendItem.datasetIndex; 13763 var ci = this.chart; 13764 var meta = ci.getDatasetMeta(index); 13765 13766 // See controller.isDatasetVisible comment 13767 meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; 13768 13769 // We hid a dataset ... rerender the chart 13770 ci.update(); 13771 }, 13772 13773 onHover: null, 13774 onLeave: null, 13775 13776 labels: { 13777 boxWidth: 40, 13778 padding: 10, 13779 // Generates labels shown in the legend 13780 // Valid properties to return: 13781 // text : text to display 13782 // fillStyle : fill of coloured box 13783 // strokeStyle: stroke of coloured box 13784 // hidden : if this legend item refers to a hidden item 13785 // lineCap : cap style for line 13786 // lineDash 13787 // lineDashOffset : 13788 // lineJoin : 13789 // lineWidth : 13790 generateLabels: function (chart) { 13791 var data = chart.data; 13792 return helpers$1.isArray(data.datasets) ? data.datasets.map(function (dataset, i) { 13793 return { 13794 text: dataset.label, 13795 fillStyle: (!helpers$1.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), 13796 hidden: !chart.isDatasetVisible(i), 13797 lineCap: dataset.borderCapStyle, 13798 lineDash: dataset.borderDash, 13799 lineDashOffset: dataset.borderDashOffset, 13800 lineJoin: dataset.borderJoinStyle, 13801 lineWidth: dataset.borderWidth, 13802 strokeStyle: dataset.borderColor, 13803 pointStyle: dataset.pointStyle, 13804 13805 // Below is extra data used for toggling the datasets 13806 datasetIndex: i 13807 }; 13808 }, this) : []; 13809 } 13810 } 13811 }, 13812 13813 legendCallback: function (chart) { 13814 var text = []; 13815 text.push('<ul class="' + chart.id + '-legend">'); 13816 for (var i = 0; i < chart.data.datasets.length; i++) { 13817 text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>'); 13818 if (chart.data.datasets[i].label) { 13819 text.push(chart.data.datasets[i].label); 13820 } 13821 text.push('</li>'); 13822 } 13823 text.push('</ul>'); 13824 return text.join(''); 13825 } 13826 }); 13827 13828 /** 13829 * Helper function to get the box width based on the usePointStyle option 13830 * @param {object} labelopts - the label options on the legend 13831 * @param {number} fontSize - the label font size 13832 * @return {number} width of the color box area 13833 */ 13834 function getBoxWidth(labelOpts, fontSize) { 13835 return labelOpts.usePointStyle && labelOpts.boxWidth > fontSize ? 13836 fontSize : 13837 labelOpts.boxWidth; 13838 } 13839 13840 /** 13841 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! 13842 */ 13843 var Legend = core_element.extend({ 13844 13845 initialize: function (config) { 13846 helpers$1.extend(this, config); 13847 13848 // Contains hit boxes for each dataset (in dataset order) 13849 this.legendHitBoxes = []; 13850 13851 /** 13852 * @private 13853 */ 13854 this._hoveredItem = null; 13855 13856 // Are we in doughnut mode which has a different data type 13857 this.doughnutMode = false; 13858 }, 13859 13860 // These methods are ordered by lifecycle. Utilities then follow. 13861 // Any function defined here is inherited by all legend types. 13862 // Any function can be extended by the legend type 13863 13864 beforeUpdate: noop$1, 13865 update: function (maxWidth, maxHeight, margins) { 13866 var me = this; 13867 13868 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 13869 me.beforeUpdate(); 13870 13871 // Absorb the master measurements 13872 me.maxWidth = maxWidth; 13873 me.maxHeight = maxHeight; 13874 me.margins = margins; 13875 13876 // Dimensions 13877 me.beforeSetDimensions(); 13878 me.setDimensions(); 13879 me.afterSetDimensions(); 13880 // Labels 13881 me.beforeBuildLabels(); 13882 me.buildLabels(); 13883 me.afterBuildLabels(); 13884 13885 // Fit 13886 me.beforeFit(); 13887 me.fit(); 13888 me.afterFit(); 13889 // 13890 me.afterUpdate(); 13891 13892 return me.minSize; 13893 }, 13894 afterUpdate: noop$1, 13895 13896 // 13897 13898 beforeSetDimensions: noop$1, 13899 setDimensions: function () { 13900 var me = this; 13901 // Set the unconstrained dimension before label rotation 13902 if (me.isHorizontal()) { 13903 // Reset position before calculating rotation 13904 me.width = me.maxWidth; 13905 me.left = 0; 13906 me.right = me.width; 13907 } else { 13908 me.height = me.maxHeight; 13909 13910 // Reset position before calculating rotation 13911 me.top = 0; 13912 me.bottom = me.height; 13913 } 13914 13915 // Reset padding 13916 me.paddingLeft = 0; 13917 me.paddingTop = 0; 13918 me.paddingRight = 0; 13919 me.paddingBottom = 0; 13920 13921 // Reset minSize 13922 me.minSize = { 13923 width: 0, 13924 height: 0 13925 }; 13926 }, 13927 afterSetDimensions: noop$1, 13928 13929 // 13930 13931 beforeBuildLabels: noop$1, 13932 buildLabels: function () { 13933 var me = this; 13934 var labelOpts = me.options.labels || {}; 13935 var legendItems = helpers$1.callback(labelOpts.generateLabels, [me.chart], me) || []; 13936 13937 if (labelOpts.filter) { 13938 legendItems = legendItems.filter(function (item) { 13939 return labelOpts.filter(item, me.chart.data); 13940 }); 13941 } 13942 13943 if (me.options.reverse) { 13944 legendItems.reverse(); 13945 } 13946 13947 me.legendItems = legendItems; 13948 }, 13949 afterBuildLabels: noop$1, 13950 13951 // 13952 13953 beforeFit: noop$1, 13954 fit: function () { 13955 var me = this; 13956 var opts = me.options; 13957 var labelOpts = opts.labels; 13958 var display = opts.display; 13959 13960 var ctx = me.ctx; 13961 13962 var labelFont = helpers$1.options._parseFont(labelOpts); 13963 var fontSize = labelFont.size; 13964 13965 // Reset hit boxes 13966 var hitboxes = me.legendHitBoxes = []; 13967 13968 var minSize = me.minSize; 13969 var isHorizontal = me.isHorizontal(); 13970 13971 if (isHorizontal) { 13972 minSize.width = me.maxWidth; // fill all the width 13973 minSize.height = display ? 10 : 0; 13974 } else { 13975 minSize.width = display ? 10 : 0; 13976 minSize.height = me.maxHeight; // fill all the height 13977 } 13978 13979 // Increase sizes here 13980 if (display) { 13981 ctx.font = labelFont.string; 13982 13983 if (isHorizontal) { 13984 // Labels 13985 13986 // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one 13987 var lineWidths = me.lineWidths = [0]; 13988 var totalHeight = 0; 13989 13990 ctx.textAlign = 'left'; 13991 ctx.textBaseline = 'top'; 13992 13993 helpers$1.each(me.legendItems, function (legendItem, i) { 13994 var boxWidth = getBoxWidth(labelOpts, fontSize); 13995 var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; 13996 13997 if (i === 0 || lineWidths[lineWidths.length - 1] + width + labelOpts.padding > minSize.width) { 13998 totalHeight += fontSize + labelOpts.padding; 13999 lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = labelOpts.padding; 14000 } 14001 14002 // Store the hitbox width and height here. Final position will be updated in `draw` 14003 hitboxes[i] = { 14004 left: 0, 14005 top: 0, 14006 width: width, 14007 height: fontSize 14008 }; 14009 14010 lineWidths[lineWidths.length - 1] += width + labelOpts.padding; 14011 }); 14012 14013 minSize.height += totalHeight; 14014 14015 } else { 14016 var vPadding = labelOpts.padding; 14017 var columnWidths = me.columnWidths = []; 14018 var totalWidth = labelOpts.padding; 14019 var currentColWidth = 0; 14020 var currentColHeight = 0; 14021 var itemHeight = fontSize + vPadding; 14022 14023 helpers$1.each(me.legendItems, function (legendItem, i) { 14024 var boxWidth = getBoxWidth(labelOpts, fontSize); 14025 var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; 14026 14027 // If too tall, go to new column 14028 if (i > 0 && currentColHeight + itemHeight > minSize.height - vPadding) { 14029 totalWidth += currentColWidth + labelOpts.padding; 14030 columnWidths.push(currentColWidth); // previous column width 14031 14032 currentColWidth = 0; 14033 currentColHeight = 0; 14034 } 14035 14036 // Get max width 14037 currentColWidth = Math.max(currentColWidth, itemWidth); 14038 currentColHeight += itemHeight; 14039 14040 // Store the hitbox width and height here. Final position will be updated in `draw` 14041 hitboxes[i] = { 14042 left: 0, 14043 top: 0, 14044 width: itemWidth, 14045 height: fontSize 14046 }; 14047 }); 14048 14049 totalWidth += currentColWidth; 14050 columnWidths.push(currentColWidth); 14051 minSize.width += totalWidth; 14052 } 14053 } 14054 14055 me.width = minSize.width; 14056 me.height = minSize.height; 14057 }, 14058 afterFit: noop$1, 14059 14060 // Shared Methods 14061 isHorizontal: function () { 14062 return this.options.position === 'top' || this.options.position === 'bottom'; 14063 }, 14064 14065 // Actually draw the legend on the canvas 14066 draw: function () { 14067 var me = this; 14068 var opts = me.options; 14069 var labelOpts = opts.labels; 14070 var globalDefaults = core_defaults.global; 14071 var defaultColor = globalDefaults.defaultColor; 14072 var lineDefault = globalDefaults.elements.line; 14073 var legendWidth = me.width; 14074 var lineWidths = me.lineWidths; 14075 14076 if (opts.display) { 14077 var ctx = me.ctx; 14078 var fontColor = valueOrDefault$d(labelOpts.fontColor, globalDefaults.defaultFontColor); 14079 var labelFont = helpers$1.options._parseFont(labelOpts); 14080 var fontSize = labelFont.size; 14081 var cursor; 14082 14083 // Canvas setup 14084 ctx.textAlign = 'left'; 14085 ctx.textBaseline = 'middle'; 14086 ctx.lineWidth = 0.5; 14087 ctx.strokeStyle = fontColor; // for strikethrough effect 14088 ctx.fillStyle = fontColor; // render in correct colour 14089 ctx.font = labelFont.string; 14090 14091 var boxWidth = getBoxWidth(labelOpts, fontSize); 14092 var hitboxes = me.legendHitBoxes; 14093 14094 // current position 14095 var drawLegendBox = function (x, y, legendItem) { 14096 if (isNaN(boxWidth) || boxWidth <= 0) { 14097 return; 14098 } 14099 14100 // Set the ctx for the box 14101 ctx.save(); 14102 14103 var lineWidth = valueOrDefault$d(legendItem.lineWidth, lineDefault.borderWidth); 14104 ctx.fillStyle = valueOrDefault$d(legendItem.fillStyle, defaultColor); 14105 ctx.lineCap = valueOrDefault$d(legendItem.lineCap, lineDefault.borderCapStyle); 14106 ctx.lineDashOffset = valueOrDefault$d(legendItem.lineDashOffset, lineDefault.borderDashOffset); 14107 ctx.lineJoin = valueOrDefault$d(legendItem.lineJoin, lineDefault.borderJoinStyle); 14108 ctx.lineWidth = lineWidth; 14109 ctx.strokeStyle = valueOrDefault$d(legendItem.strokeStyle, defaultColor); 14110 14111 if (ctx.setLineDash) { 14112 // IE 9 and 10 do not support line dash 14113 ctx.setLineDash(valueOrDefault$d(legendItem.lineDash, lineDefault.borderDash)); 14114 } 14115 14116 if (opts.labels && opts.labels.usePointStyle) { 14117 // Recalculate x and y for drawPoint() because its expecting 14118 // x and y to be center of figure (instead of top left) 14119 var radius = boxWidth * Math.SQRT2 / 2; 14120 var centerX = x + boxWidth / 2; 14121 var centerY = y + fontSize / 2; 14122 14123 // Draw pointStyle as legend symbol 14124 helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); 14125 } else { 14126 // Draw box as legend symbol 14127 if (lineWidth !== 0) { 14128 ctx.strokeRect(x, y, boxWidth, fontSize); 14129 } 14130 ctx.fillRect(x, y, boxWidth, fontSize); 14131 } 14132 14133 ctx.restore(); 14134 }; 14135 var fillText = function (x, y, legendItem, textWidth) { 14136 var halfFontSize = fontSize / 2; 14137 var xLeft = boxWidth + halfFontSize + x; 14138 var yMiddle = y + halfFontSize; 14139 14140 ctx.fillText(legendItem.text, xLeft, yMiddle); 14141 14142 if (legendItem.hidden) { 14143 // Strikethrough the text if hidden 14144 ctx.beginPath(); 14145 ctx.lineWidth = 2; 14146 ctx.moveTo(xLeft, yMiddle); 14147 ctx.lineTo(xLeft + textWidth, yMiddle); 14148 ctx.stroke(); 14149 } 14150 }; 14151 14152 // Horizontal 14153 var isHorizontal = me.isHorizontal(); 14154 if (isHorizontal) { 14155 cursor = { 14156 x: me.left + ((legendWidth - lineWidths[0]) / 2) + labelOpts.padding, 14157 y: me.top + labelOpts.padding, 14158 line: 0 14159 }; 14160 } else { 14161 cursor = { 14162 x: me.left + labelOpts.padding, 14163 y: me.top + labelOpts.padding, 14164 line: 0 14165 }; 14166 } 14167 14168 var itemHeight = fontSize + labelOpts.padding; 14169 helpers$1.each(me.legendItems, function (legendItem, i) { 14170 var textWidth = ctx.measureText(legendItem.text).width; 14171 var width = boxWidth + (fontSize / 2) + textWidth; 14172 var x = cursor.x; 14173 var y = cursor.y; 14174 14175 // Use (me.left + me.minSize.width) and (me.top + me.minSize.height) 14176 // instead of me.right and me.bottom because me.width and me.height 14177 // may have been changed since me.minSize was calculated 14178 if (isHorizontal) { 14179 if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) { 14180 y = cursor.y += itemHeight; 14181 cursor.line++; 14182 x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2) + labelOpts.padding; 14183 } 14184 } else if (i > 0 && y + itemHeight > me.top + me.minSize.height) { 14185 x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; 14186 y = cursor.y = me.top + labelOpts.padding; 14187 cursor.line++; 14188 } 14189 14190 drawLegendBox(x, y, legendItem); 14191 14192 hitboxes[i].left = x; 14193 hitboxes[i].top = y; 14194 14195 // Fill the actual label 14196 fillText(x, y, legendItem, textWidth); 14197 14198 if (isHorizontal) { 14199 cursor.x += width + labelOpts.padding; 14200 } else { 14201 cursor.y += itemHeight; 14202 } 14203 14204 }); 14205 } 14206 }, 14207 14208 /** 14209 * @private 14210 */ 14211 _getLegendItemAt: function (x, y) { 14212 var me = this; 14213 var i, hitBox, lh; 14214 14215 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { 14216 // See if we are touching one of the dataset boxes 14217 lh = me.legendHitBoxes; 14218 for (i = 0; i < lh.length; ++i) { 14219 hitBox = lh[i]; 14220 14221 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { 14222 // Touching an element 14223 return me.legendItems[i]; 14224 } 14225 } 14226 } 14227 14228 return null; 14229 }, 14230 14231 /** 14232 * Handle an event 14233 * @private 14234 * @param {IEvent} event - The event to handle 14235 */ 14236 handleEvent: function (e) { 14237 var me = this; 14238 var opts = me.options; 14239 var type = e.type === 'mouseup' ? 'click' : e.type; 14240 var hoveredItem; 14241 14242 if (type === 'mousemove') { 14243 if (!opts.onHover && !opts.onLeave) { 14244 return; 14245 } 14246 } else if (type === 'click') { 14247 if (!opts.onClick) { 14248 return; 14249 } 14250 } else { 14251 return; 14252 } 14253 14254 // Chart event already has relative position in it 14255 hoveredItem = me._getLegendItemAt(e.x, e.y); 14256 14257 if (type === 'click') { 14258 if (hoveredItem && opts.onClick) { 14259 // use e.native for backwards compatibility 14260 opts.onClick.call(me, e.native, hoveredItem); 14261 } 14262 } else { 14263 if (opts.onLeave && hoveredItem !== me._hoveredItem) { 14264 if (me._hoveredItem) { 14265 opts.onLeave.call(me, e.native, me._hoveredItem); 14266 } 14267 me._hoveredItem = hoveredItem; 14268 } 14269 14270 if (opts.onHover && hoveredItem) { 14271 // use e.native for backwards compatibility 14272 opts.onHover.call(me, e.native, hoveredItem); 14273 } 14274 } 14275 } 14276 }); 14277 14278 function createNewLegendAndAttach(chart, legendOpts) { 14279 var legend = new Legend({ 14280 ctx: chart.ctx, 14281 options: legendOpts, 14282 chart: chart 14283 }); 14284 14285 core_layouts.configure(chart, legend, legendOpts); 14286 core_layouts.addBox(chart, legend); 14287 chart.legend = legend; 14288 } 14289 14290 var plugin_legend = { 14291 id: 'legend', 14292 14293 /** 14294 * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making 14295 * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of 14296 * the plugin, which one will be re-exposed in the chart.js file. 14297 * https://github.com/chartjs/Chart.js/pull/2640 14298 * @private 14299 */ 14300 _element: Legend, 14301 14302 beforeInit: function (chart) { 14303 var legendOpts = chart.options.legend; 14304 14305 if (legendOpts) { 14306 createNewLegendAndAttach(chart, legendOpts); 14307 } 14308 }, 14309 14310 beforeUpdate: function (chart) { 14311 var legendOpts = chart.options.legend; 14312 var legend = chart.legend; 14313 14314 if (legendOpts) { 14315 helpers$1.mergeIf(legendOpts, core_defaults.global.legend); 14316 14317 if (legend) { 14318 core_layouts.configure(chart, legend, legendOpts); 14319 legend.options = legendOpts; 14320 } else { 14321 createNewLegendAndAttach(chart, legendOpts); 14322 } 14323 } else if (legend) { 14324 core_layouts.removeBox(chart, legend); 14325 delete chart.legend; 14326 } 14327 }, 14328 14329 afterEvent: function (chart, e) { 14330 var legend = chart.legend; 14331 if (legend) { 14332 legend.handleEvent(e); 14333 } 14334 } 14335 }; 14336 14337 var noop$2 = helpers$1.noop; 14338 14339 core_defaults._set('global', { 14340 title: { 14341 display: false, 14342 fontStyle: 'bold', 14343 fullWidth: true, 14344 padding: 10, 14345 position: 'top', 14346 text: '', 14347 weight: 2000 // by default greater than legend (1000) to be above 14348 } 14349 }); 14350 14351 /** 14352 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! 14353 */ 14354 var Title = core_element.extend({ 14355 initialize: function (config) { 14356 var me = this; 14357 helpers$1.extend(me, config); 14358 14359 // Contains hit boxes for each dataset (in dataset order) 14360 me.legendHitBoxes = []; 14361 }, 14362 14363 // These methods are ordered by lifecycle. Utilities then follow. 14364 14365 beforeUpdate: noop$2, 14366 update: function (maxWidth, maxHeight, margins) { 14367 var me = this; 14368 14369 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 14370 me.beforeUpdate(); 14371 14372 // Absorb the master measurements 14373 me.maxWidth = maxWidth; 14374 me.maxHeight = maxHeight; 14375 me.margins = margins; 14376 14377 // Dimensions 14378 me.beforeSetDimensions(); 14379 me.setDimensions(); 14380 me.afterSetDimensions(); 14381 // Labels 14382 me.beforeBuildLabels(); 14383 me.buildLabels(); 14384 me.afterBuildLabels(); 14385 14386 // Fit 14387 me.beforeFit(); 14388 me.fit(); 14389 me.afterFit(); 14390 // 14391 me.afterUpdate(); 14392 14393 return me.minSize; 14394 14395 }, 14396 afterUpdate: noop$2, 14397 14398 // 14399 14400 beforeSetDimensions: noop$2, 14401 setDimensions: function () { 14402 var me = this; 14403 // Set the unconstrained dimension before label rotation 14404 if (me.isHorizontal()) { 14405 // Reset position before calculating rotation 14406 me.width = me.maxWidth; 14407 me.left = 0; 14408 me.right = me.width; 14409 } else { 14410 me.height = me.maxHeight; 14411 14412 // Reset position before calculating rotation 14413 me.top = 0; 14414 me.bottom = me.height; 14415 } 14416 14417 // Reset padding 14418 me.paddingLeft = 0; 14419 me.paddingTop = 0; 14420 me.paddingRight = 0; 14421 me.paddingBottom = 0; 14422 14423 // Reset minSize 14424 me.minSize = { 14425 width: 0, 14426 height: 0 14427 }; 14428 }, 14429 afterSetDimensions: noop$2, 14430 14431 // 14432 14433 beforeBuildLabels: noop$2, 14434 buildLabels: noop$2, 14435 afterBuildLabels: noop$2, 14436 14437 // 14438 14439 beforeFit: noop$2, 14440 fit: function () { 14441 var me = this; 14442 var opts = me.options; 14443 var display = opts.display; 14444 var minSize = me.minSize; 14445 var lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1; 14446 var fontOpts = helpers$1.options._parseFont(opts); 14447 var textSize = display ? (lineCount * fontOpts.lineHeight) + (opts.padding * 2) : 0; 14448 14449 if (me.isHorizontal()) { 14450 minSize.width = me.maxWidth; // fill all the width 14451 minSize.height = textSize; 14452 } else { 14453 minSize.width = textSize; 14454 minSize.height = me.maxHeight; // fill all the height 14455 } 14456 14457 me.width = minSize.width; 14458 me.height = minSize.height; 14459 14460 }, 14461 afterFit: noop$2, 14462 14463 // Shared Methods 14464 isHorizontal: function () { 14465 var pos = this.options.position; 14466 return pos === 'top' || pos === 'bottom'; 14467 }, 14468 14469 // Actually draw the title block on the canvas 14470 draw: function () { 14471 var me = this; 14472 var ctx = me.ctx; 14473 var opts = me.options; 14474 14475 if (opts.display) { 14476 var fontOpts = helpers$1.options._parseFont(opts); 14477 var lineHeight = fontOpts.lineHeight; 14478 var offset = lineHeight / 2 + opts.padding; 14479 var rotation = 0; 14480 var top = me.top; 14481 var left = me.left; 14482 var bottom = me.bottom; 14483 var right = me.right; 14484 var maxWidth, titleX, titleY; 14485 14486 ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour 14487 ctx.font = fontOpts.string; 14488 14489 // Horizontal 14490 if (me.isHorizontal()) { 14491 titleX = left + ((right - left) / 2); // midpoint of the width 14492 titleY = top + offset; 14493 maxWidth = right - left; 14494 } else { 14495 titleX = opts.position === 'left' ? left + offset : right - offset; 14496 titleY = top + ((bottom - top) / 2); 14497 maxWidth = bottom - top; 14498 rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); 14499 } 14500 14501 ctx.save(); 14502 ctx.translate(titleX, titleY); 14503 ctx.rotate(rotation); 14504 ctx.textAlign = 'center'; 14505 ctx.textBaseline = 'middle'; 14506 14507 var text = opts.text; 14508 if (helpers$1.isArray(text)) { 14509 var y = 0; 14510 for (var i = 0; i < text.length; ++i) { 14511 ctx.fillText(text[i], 0, y, maxWidth); 14512 y += lineHeight; 14513 } 14514 } else { 14515 ctx.fillText(text, 0, 0, maxWidth); 14516 } 14517 14518 ctx.restore(); 14519 } 14520 } 14521 }); 14522 14523 function createNewTitleBlockAndAttach(chart, titleOpts) { 14524 var title = new Title({ 14525 ctx: chart.ctx, 14526 options: titleOpts, 14527 chart: chart 14528 }); 14529 14530 core_layouts.configure(chart, title, titleOpts); 14531 core_layouts.addBox(chart, title); 14532 chart.titleBlock = title; 14533 } 14534 14535 var plugin_title = { 14536 id: 'title', 14537 14538 /** 14539 * Backward compatibility: since 2.1.5, the title is registered as a plugin, making 14540 * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of 14541 * the plugin, which one will be re-exposed in the chart.js file. 14542 * https://github.com/chartjs/Chart.js/pull/2640 14543 * @private 14544 */ 14545 _element: Title, 14546 14547 beforeInit: function (chart) { 14548 var titleOpts = chart.options.title; 14549 14550 if (titleOpts) { 14551 createNewTitleBlockAndAttach(chart, titleOpts); 14552 } 14553 }, 14554 14555 beforeUpdate: function (chart) { 14556 var titleOpts = chart.options.title; 14557 var titleBlock = chart.titleBlock; 14558 14559 if (titleOpts) { 14560 helpers$1.mergeIf(titleOpts, core_defaults.global.title); 14561 14562 if (titleBlock) { 14563 core_layouts.configure(chart, titleBlock, titleOpts); 14564 titleBlock.options = titleOpts; 14565 } else { 14566 createNewTitleBlockAndAttach(chart, titleOpts); 14567 } 14568 } else if (titleBlock) { 14569 core_layouts.removeBox(chart, titleBlock); 14570 delete chart.titleBlock; 14571 } 14572 } 14573 }; 14574 14575 var plugins = {}; 14576 var filler = plugin_filler; 14577 var legend = plugin_legend; 14578 var title = plugin_title; 14579 plugins.filler = filler; 14580 plugins.legend = legend; 14581 plugins.title = title; 14582 14583 /** 14584 * @namespace Chart 14585 */ 14586 14587 14588 core_controller.helpers = helpers$1; 14589 14590// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! 14591 core_helpers(core_controller); 14592 14593 core_controller._adapters = core_adapters; 14594 core_controller.Animation = core_animation; 14595 core_controller.animationService = core_animations; 14596 core_controller.controllers = controllers; 14597 core_controller.DatasetController = core_datasetController; 14598 core_controller.defaults = core_defaults; 14599 core_controller.Element = core_element; 14600 core_controller.elements = elements; 14601 core_controller.Interaction = core_interaction; 14602 core_controller.layouts = core_layouts; 14603 core_controller.platform = platform; 14604 core_controller.plugins = core_plugins; 14605 core_controller.Scale = core_scale; 14606 core_controller.scaleService = core_scaleService; 14607 core_controller.Ticks = core_ticks; 14608 core_controller.Tooltip = core_tooltip; 14609 14610// Register built-in scales 14611 14612 core_controller.helpers.each(scales, function (scale, type) { 14613 core_controller.scaleService.registerScaleType(type, scale, scale._defaults); 14614 }); 14615 14616// Load to register built-in adapters (as side effects) 14617 14618 14619// Loading built-in plugins 14620 14621 for (var k in plugins) { 14622 if (plugins.hasOwnProperty(k)) { 14623 core_controller.plugins.register(plugins[k]); 14624 } 14625 } 14626 14627 core_controller.platform.initialize(); 14628 14629 var src = core_controller; 14630 if (typeof window !== 'undefined') { 14631 window.Chart = core_controller; 14632 } 14633 14634// DEPRECATIONS 14635 14636 /** 14637 * Provided for backward compatibility, not available anymore 14638 * @namespace Chart.Chart 14639 * @deprecated since version 2.8.0 14640 * @todo remove at version 3 14641 * @private 14642 */ 14643 core_controller.Chart = core_controller; 14644 14645 /** 14646 * Provided for backward compatibility, not available anymore 14647 * @namespace Chart.Legend 14648 * @deprecated since version 2.1.5 14649 * @todo remove at version 3 14650 * @private 14651 */ 14652 core_controller.Legend = plugins.legend._element; 14653 14654 /** 14655 * Provided for backward compatibility, not available anymore 14656 * @namespace Chart.Title 14657 * @deprecated since version 2.1.5 14658 * @todo remove at version 3 14659 * @private 14660 */ 14661 core_controller.Title = plugins.title._element; 14662 14663 /** 14664 * Provided for backward compatibility, use Chart.plugins instead 14665 * @namespace Chart.pluginService 14666 * @deprecated since version 2.1.5 14667 * @todo remove at version 3 14668 * @private 14669 */ 14670 core_controller.pluginService = core_controller.plugins; 14671 14672 /** 14673 * Provided for backward compatibility, inheriting from Chart.PlugingBase has no 14674 * effect, instead simply create/register plugins via plain JavaScript objects. 14675 * @interface Chart.PluginBase 14676 * @deprecated since version 2.5.0 14677 * @todo remove at version 3 14678 * @private 14679 */ 14680 core_controller.PluginBase = core_controller.Element.extend({}); 14681 14682 /** 14683 * Provided for backward compatibility, use Chart.helpers.canvas instead. 14684 * @namespace Chart.canvasHelpers 14685 * @deprecated since version 2.6.0 14686 * @todo remove at version 3 14687 * @private 14688 */ 14689 core_controller.canvasHelpers = core_controller.helpers.canvas; 14690 14691 /** 14692 * Provided for backward compatibility, use Chart.layouts instead. 14693 * @namespace Chart.layoutService 14694 * @deprecated since version 2.7.3 14695 * @todo remove at version 3 14696 * @private 14697 */ 14698 core_controller.layoutService = core_controller.layouts; 14699 14700 /** 14701 * Provided for backward compatibility, not available anymore. 14702 * @namespace Chart.LinearScaleBase 14703 * @deprecated since version 2.8 14704 * @todo remove at version 3 14705 * @private 14706 */ 14707 core_controller.LinearScaleBase = scale_linearbase; 14708 14709 /** 14710 * Provided for backward compatibility, instead we should create a new Chart 14711 * by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`). 14712 * @deprecated since version 2.8.0 14713 * @todo remove at version 3 14714 */ 14715 core_controller.helpers.each( 14716 [ 14717 'Bar', 14718 'Bubble', 14719 'Doughnut', 14720 'Line', 14721 'PolarArea', 14722 'Radar', 14723 'Scatter' 14724 ], 14725 function (klass) { 14726 core_controller[klass] = function (ctx, cfg) { 14727 return new core_controller(ctx, core_controller.helpers.merge(cfg || {}, { 14728 type: klass.charAt(0).toLowerCase() + klass.slice(1) 14729 })); 14730 }; 14731 } 14732 ); 14733 14734 return src; 14735 14736})));
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.