· 7 years ago · Jan 10, 2019, 02:22 PM
1/*!
2 * Chart.js v2.7.3
3 * https://www.chartjs.org
4 * (c) 2019 Chart.js Contributors
5 * Released under the MIT License
6 */
7(function (global, factory) {
8typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
9typeof define === 'function' && define.amd ? define(factory) :
10(global.Chart = factory());
11}(this, (function () { 'use strict';
12
13/* MIT license */
14
15var conversions = {
16 rgb2hsl: rgb2hsl,
17 rgb2hsv: rgb2hsv,
18 rgb2hwb: rgb2hwb,
19 rgb2cmyk: rgb2cmyk,
20 rgb2keyword: rgb2keyword,
21 rgb2xyz: rgb2xyz,
22 rgb2lab: rgb2lab,
23 rgb2lch: rgb2lch,
24
25 hsl2rgb: hsl2rgb,
26 hsl2hsv: hsl2hsv,
27 hsl2hwb: hsl2hwb,
28 hsl2cmyk: hsl2cmyk,
29 hsl2keyword: hsl2keyword,
30
31 hsv2rgb: hsv2rgb,
32 hsv2hsl: hsv2hsl,
33 hsv2hwb: hsv2hwb,
34 hsv2cmyk: hsv2cmyk,
35 hsv2keyword: hsv2keyword,
36
37 hwb2rgb: hwb2rgb,
38 hwb2hsl: hwb2hsl,
39 hwb2hsv: hwb2hsv,
40 hwb2cmyk: hwb2cmyk,
41 hwb2keyword: hwb2keyword,
42
43 cmyk2rgb: cmyk2rgb,
44 cmyk2hsl: cmyk2hsl,
45 cmyk2hsv: cmyk2hsv,
46 cmyk2hwb: cmyk2hwb,
47 cmyk2keyword: cmyk2keyword,
48
49 keyword2rgb: keyword2rgb,
50 keyword2hsl: keyword2hsl,
51 keyword2hsv: keyword2hsv,
52 keyword2hwb: keyword2hwb,
53 keyword2cmyk: keyword2cmyk,
54 keyword2lab: keyword2lab,
55 keyword2xyz: keyword2xyz,
56
57 xyz2rgb: xyz2rgb,
58 xyz2lab: xyz2lab,
59 xyz2lch: xyz2lch,
60
61 lab2xyz: lab2xyz,
62 lab2rgb: lab2rgb,
63 lab2lch: lab2lch,
64
65 lch2lab: lch2lab,
66 lch2xyz: lch2xyz,
67 lch2rgb: lch2rgb
68};
69
70
71function rgb2hsl(rgb) {
72 var r = rgb[0]/255,
73 g = rgb[1]/255,
74 b = rgb[2]/255,
75 min = Math.min(r, g, b),
76 max = Math.max(r, g, b),
77 delta = max - min,
78 h, s, l;
79
80 if (max == min)
81 h = 0;
82 else if (r == max)
83 h = (g - b) / delta;
84 else if (g == max)
85 h = 2 + (b - r) / delta;
86 else if (b == max)
87 h = 4 + (r - g)/ delta;
88
89 h = Math.min(h * 60, 360);
90
91 if (h < 0)
92 h += 360;
93
94 l = (min + max) / 2;
95
96 if (max == min)
97 s = 0;
98 else if (l <= 0.5)
99 s = delta / (max + min);
100 else
101 s = delta / (2 - max - min);
102
103 return [h, s * 100, l * 100];
104}
105
106function rgb2hsv(rgb) {
107 var r = rgb[0],
108 g = rgb[1],
109 b = rgb[2],
110 min = Math.min(r, g, b),
111 max = Math.max(r, g, b),
112 delta = max - min,
113 h, s, v;
114
115 if (max == 0)
116 s = 0;
117 else
118 s = (delta/max * 1000)/10;
119
120 if (max == min)
121 h = 0;
122 else if (r == max)
123 h = (g - b) / delta;
124 else if (g == max)
125 h = 2 + (b - r) / delta;
126 else if (b == max)
127 h = 4 + (r - g) / delta;
128
129 h = Math.min(h * 60, 360);
130
131 if (h < 0)
132 h += 360;
133
134 v = ((max / 255) * 1000) / 10;
135
136 return [h, s, v];
137}
138
139function rgb2hwb(rgb) {
140 var r = rgb[0],
141 g = rgb[1],
142 b = rgb[2],
143 h = rgb2hsl(rgb)[0],
144 w = 1/255 * Math.min(r, Math.min(g, b)),
145 b = 1 - 1/255 * Math.max(r, Math.max(g, b));
146
147 return [h, w * 100, b * 100];
148}
149
150function rgb2cmyk(rgb) {
151 var r = rgb[0] / 255,
152 g = rgb[1] / 255,
153 b = rgb[2] / 255,
154 c, m, y, k;
155
156 k = Math.min(1 - r, 1 - g, 1 - b);
157 c = (1 - r - k) / (1 - k) || 0;
158 m = (1 - g - k) / (1 - k) || 0;
159 y = (1 - b - k) / (1 - k) || 0;
160 return [c * 100, m * 100, y * 100, k * 100];
161}
162
163function rgb2keyword(rgb) {
164 return reverseKeywords[JSON.stringify(rgb)];
165}
166
167function rgb2xyz(rgb) {
168 var r = rgb[0] / 255,
169 g = rgb[1] / 255,
170 b = rgb[2] / 255;
171
172 // assume sRGB
173 r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
174 g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
175 b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
176
177 var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
178 var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
179 var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
180
181 return [x * 100, y *100, z * 100];
182}
183
184function rgb2lab(rgb) {
185 var xyz = rgb2xyz(rgb),
186 x = xyz[0],
187 y = xyz[1],
188 z = xyz[2],
189 l, a, b;
190
191 x /= 95.047;
192 y /= 100;
193 z /= 108.883;
194
195 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
196 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
197 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
198
199 l = (116 * y) - 16;
200 a = 500 * (x - y);
201 b = 200 * (y - z);
202
203 return [l, a, b];
204}
205
206function rgb2lch(args) {
207 return lab2lch(rgb2lab(args));
208}
209
210function hsl2rgb(hsl) {
211 var h = hsl[0] / 360,
212 s = hsl[1] / 100,
213 l = hsl[2] / 100,
214 t1, t2, t3, rgb, val;
215
216 if (s == 0) {
217 val = l * 255;
218 return [val, val, val];
219 }
220
221 if (l < 0.5)
222 t2 = l * (1 + s);
223 else
224 t2 = l + s - l * s;
225 t1 = 2 * l - t2;
226
227 rgb = [0, 0, 0];
228 for (var i = 0; i < 3; i++) {
229 t3 = h + 1 / 3 * - (i - 1);
230 t3 < 0 && t3++;
231 t3 > 1 && t3--;
232
233 if (6 * t3 < 1)
234 val = t1 + (t2 - t1) * 6 * t3;
235 else if (2 * t3 < 1)
236 val = t2;
237 else if (3 * t3 < 2)
238 val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
239 else
240 val = t1;
241
242 rgb[i] = val * 255;
243 }
244
245 return rgb;
246}
247
248function hsl2hsv(hsl) {
249 var h = hsl[0],
250 s = hsl[1] / 100,
251 l = hsl[2] / 100,
252 sv, v;
253
254 if(l === 0) {
255 // no need to do calc on black
256 // also avoids divide by 0 error
257 return [0, 0, 0];
258 }
259
260 l *= 2;
261 s *= (l <= 1) ? l : 2 - l;
262 v = (l + s) / 2;
263 sv = (2 * s) / (l + s);
264 return [h, sv * 100, v * 100];
265}
266
267function hsl2hwb(args) {
268 return rgb2hwb(hsl2rgb(args));
269}
270
271function hsl2cmyk(args) {
272 return rgb2cmyk(hsl2rgb(args));
273}
274
275function hsl2keyword(args) {
276 return rgb2keyword(hsl2rgb(args));
277}
278
279
280function hsv2rgb(hsv) {
281 var h = hsv[0] / 60,
282 s = hsv[1] / 100,
283 v = hsv[2] / 100,
284 hi = Math.floor(h) % 6;
285
286 var f = h - Math.floor(h),
287 p = 255 * v * (1 - s),
288 q = 255 * v * (1 - (s * f)),
289 t = 255 * v * (1 - (s * (1 - f))),
290 v = 255 * v;
291
292 switch(hi) {
293 case 0:
294 return [v, t, p];
295 case 1:
296 return [q, v, p];
297 case 2:
298 return [p, v, t];
299 case 3:
300 return [p, q, v];
301 case 4:
302 return [t, p, v];
303 case 5:
304 return [v, p, q];
305 }
306}
307
308function hsv2hsl(hsv) {
309 var h = hsv[0],
310 s = hsv[1] / 100,
311 v = hsv[2] / 100,
312 sl, l;
313
314 l = (2 - s) * v;
315 sl = s * v;
316 sl /= (l <= 1) ? l : 2 - l;
317 sl = sl || 0;
318 l /= 2;
319 return [h, sl * 100, l * 100];
320}
321
322function hsv2hwb(args) {
323 return rgb2hwb(hsv2rgb(args))
324}
325
326function hsv2cmyk(args) {
327 return rgb2cmyk(hsv2rgb(args));
328}
329
330function hsv2keyword(args) {
331 return rgb2keyword(hsv2rgb(args));
332}
333
334// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
335function hwb2rgb(hwb) {
336 var h = hwb[0] / 360,
337 wh = hwb[1] / 100,
338 bl = hwb[2] / 100,
339 ratio = wh + bl,
340 i, v, f, n;
341
342 // wh + bl cant be > 1
343 if (ratio > 1) {
344 wh /= ratio;
345 bl /= ratio;
346 }
347
348 i = Math.floor(6 * h);
349 v = 1 - bl;
350 f = 6 * h - i;
351 if ((i & 0x01) != 0) {
352 f = 1 - f;
353 }
354 n = wh + f * (v - wh); // linear interpolation
355
356 switch (i) {
357 default:
358 case 6:
359 case 0: r = v; g = n; b = wh; break;
360 case 1: r = n; g = v; b = wh; break;
361 case 2: r = wh; g = v; b = n; break;
362 case 3: r = wh; g = n; b = v; break;
363 case 4: r = n; g = wh; b = v; break;
364 case 5: r = v; g = wh; b = n; break;
365 }
366
367 return [r * 255, g * 255, b * 255];
368}
369
370function hwb2hsl(args) {
371 return rgb2hsl(hwb2rgb(args));
372}
373
374function hwb2hsv(args) {
375 return rgb2hsv(hwb2rgb(args));
376}
377
378function hwb2cmyk(args) {
379 return rgb2cmyk(hwb2rgb(args));
380}
381
382function hwb2keyword(args) {
383 return rgb2keyword(hwb2rgb(args));
384}
385
386function cmyk2rgb(cmyk) {
387 var c = cmyk[0] / 100,
388 m = cmyk[1] / 100,
389 y = cmyk[2] / 100,
390 k = cmyk[3] / 100,
391 r, g, b;
392
393 r = 1 - Math.min(1, c * (1 - k) + k);
394 g = 1 - Math.min(1, m * (1 - k) + k);
395 b = 1 - Math.min(1, y * (1 - k) + k);
396 return [r * 255, g * 255, b * 255];
397}
398
399function cmyk2hsl(args) {
400 return rgb2hsl(cmyk2rgb(args));
401}
402
403function cmyk2hsv(args) {
404 return rgb2hsv(cmyk2rgb(args));
405}
406
407function cmyk2hwb(args) {
408 return rgb2hwb(cmyk2rgb(args));
409}
410
411function cmyk2keyword(args) {
412 return rgb2keyword(cmyk2rgb(args));
413}
414
415
416function xyz2rgb(xyz) {
417 var x = xyz[0] / 100,
418 y = xyz[1] / 100,
419 z = xyz[2] / 100,
420 r, g, b;
421
422 r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
423 g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
424 b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
425
426 // assume sRGB
427 r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
428 : r = (r * 12.92);
429
430 g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
431 : g = (g * 12.92);
432
433 b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
434 : b = (b * 12.92);
435
436 r = Math.min(Math.max(0, r), 1);
437 g = Math.min(Math.max(0, g), 1);
438 b = Math.min(Math.max(0, b), 1);
439
440 return [r * 255, g * 255, b * 255];
441}
442
443function xyz2lab(xyz) {
444 var x = xyz[0],
445 y = xyz[1],
446 z = xyz[2],
447 l, a, b;
448
449 x /= 95.047;
450 y /= 100;
451 z /= 108.883;
452
453 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
454 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
455 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
456
457 l = (116 * y) - 16;
458 a = 500 * (x - y);
459 b = 200 * (y - z);
460
461 return [l, a, b];
462}
463
464function xyz2lch(args) {
465 return lab2lch(xyz2lab(args));
466}
467
468function lab2xyz(lab) {
469 var l = lab[0],
470 a = lab[1],
471 b = lab[2],
472 x, y, z, y2;
473
474 if (l <= 8) {
475 y = (l * 100) / 903.3;
476 y2 = (7.787 * (y / 100)) + (16 / 116);
477 } else {
478 y = 100 * Math.pow((l + 16) / 116, 3);
479 y2 = Math.pow(y / 100, 1/3);
480 }
481
482 x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
483
484 z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
485
486 return [x, y, z];
487}
488
489function lab2lch(lab) {
490 var l = lab[0],
491 a = lab[1],
492 b = lab[2],
493 hr, h, c;
494
495 hr = Math.atan2(b, a);
496 h = hr * 360 / 2 / Math.PI;
497 if (h < 0) {
498 h += 360;
499 }
500 c = Math.sqrt(a * a + b * b);
501 return [l, c, h];
502}
503
504function lab2rgb(args) {
505 return xyz2rgb(lab2xyz(args));
506}
507
508function lch2lab(lch) {
509 var l = lch[0],
510 c = lch[1],
511 h = lch[2],
512 a, b, hr;
513
514 hr = h / 360 * 2 * Math.PI;
515 a = c * Math.cos(hr);
516 b = c * Math.sin(hr);
517 return [l, a, b];
518}
519
520function lch2xyz(args) {
521 return lab2xyz(lch2lab(args));
522}
523
524function lch2rgb(args) {
525 return lab2rgb(lch2lab(args));
526}
527
528function keyword2rgb(keyword) {
529 return cssKeywords[keyword];
530}
531
532function keyword2hsl(args) {
533 return rgb2hsl(keyword2rgb(args));
534}
535
536function keyword2hsv(args) {
537 return rgb2hsv(keyword2rgb(args));
538}
539
540function keyword2hwb(args) {
541 return rgb2hwb(keyword2rgb(args));
542}
543
544function keyword2cmyk(args) {
545 return rgb2cmyk(keyword2rgb(args));
546}
547
548function keyword2lab(args) {
549 return rgb2lab(keyword2rgb(args));
550}
551
552function keyword2xyz(args) {
553 return rgb2xyz(keyword2rgb(args));
554}
555
556var cssKeywords = {
557 aliceblue: [240,248,255],
558 antiquewhite: [250,235,215],
559 aqua: [0,255,255],
560 aquamarine: [127,255,212],
561 azure: [240,255,255],
562 beige: [245,245,220],
563 bisque: [255,228,196],
564 black: [0,0,0],
565 blanchedalmond: [255,235,205],
566 blue: [0,0,255],
567 blueviolet: [138,43,226],
568 brown: [165,42,42],
569 burlywood: [222,184,135],
570 cadetblue: [95,158,160],
571 chartreuse: [127,255,0],
572 chocolate: [210,105,30],
573 coral: [255,127,80],
574 cornflowerblue: [100,149,237],
575 cornsilk: [255,248,220],
576 crimson: [220,20,60],
577 cyan: [0,255,255],
578 darkblue: [0,0,139],
579 darkcyan: [0,139,139],
580 darkgoldenrod: [184,134,11],
581 darkgray: [169,169,169],
582 darkgreen: [0,100,0],
583 darkgrey: [169,169,169],
584 darkkhaki: [189,183,107],
585 darkmagenta: [139,0,139],
586 darkolivegreen: [85,107,47],
587 darkorange: [255,140,0],
588 darkorchid: [153,50,204],
589 darkred: [139,0,0],
590 darksalmon: [233,150,122],
591 darkseagreen: [143,188,143],
592 darkslateblue: [72,61,139],
593 darkslategray: [47,79,79],
594 darkslategrey: [47,79,79],
595 darkturquoise: [0,206,209],
596 darkviolet: [148,0,211],
597 deeppink: [255,20,147],
598 deepskyblue: [0,191,255],
599 dimgray: [105,105,105],
600 dimgrey: [105,105,105],
601 dodgerblue: [30,144,255],
602 firebrick: [178,34,34],
603 floralwhite: [255,250,240],
604 forestgreen: [34,139,34],
605 fuchsia: [255,0,255],
606 gainsboro: [220,220,220],
607 ghostwhite: [248,248,255],
608 gold: [255,215,0],
609 goldenrod: [218,165,32],
610 gray: [128,128,128],
611 green: [0,128,0],
612 greenyellow: [173,255,47],
613 grey: [128,128,128],
614 honeydew: [240,255,240],
615 hotpink: [255,105,180],
616 indianred: [205,92,92],
617 indigo: [75,0,130],
618 ivory: [255,255,240],
619 khaki: [240,230,140],
620 lavender: [230,230,250],
621 lavenderblush: [255,240,245],
622 lawngreen: [124,252,0],
623 lemonchiffon: [255,250,205],
624 lightblue: [173,216,230],
625 lightcoral: [240,128,128],
626 lightcyan: [224,255,255],
627 lightgoldenrodyellow: [250,250,210],
628 lightgray: [211,211,211],
629 lightgreen: [144,238,144],
630 lightgrey: [211,211,211],
631 lightpink: [255,182,193],
632 lightsalmon: [255,160,122],
633 lightseagreen: [32,178,170],
634 lightskyblue: [135,206,250],
635 lightslategray: [119,136,153],
636 lightslategrey: [119,136,153],
637 lightsteelblue: [176,196,222],
638 lightyellow: [255,255,224],
639 lime: [0,255,0],
640 limegreen: [50,205,50],
641 linen: [250,240,230],
642 magenta: [255,0,255],
643 maroon: [128,0,0],
644 mediumaquamarine: [102,205,170],
645 mediumblue: [0,0,205],
646 mediumorchid: [186,85,211],
647 mediumpurple: [147,112,219],
648 mediumseagreen: [60,179,113],
649 mediumslateblue: [123,104,238],
650 mediumspringgreen: [0,250,154],
651 mediumturquoise: [72,209,204],
652 mediumvioletred: [199,21,133],
653 midnightblue: [25,25,112],
654 mintcream: [245,255,250],
655 mistyrose: [255,228,225],
656 moccasin: [255,228,181],
657 navajowhite: [255,222,173],
658 navy: [0,0,128],
659 oldlace: [253,245,230],
660 olive: [128,128,0],
661 olivedrab: [107,142,35],
662 orange: [255,165,0],
663 orangered: [255,69,0],
664 orchid: [218,112,214],
665 palegoldenrod: [238,232,170],
666 palegreen: [152,251,152],
667 paleturquoise: [175,238,238],
668 palevioletred: [219,112,147],
669 papayawhip: [255,239,213],
670 peachpuff: [255,218,185],
671 peru: [205,133,63],
672 pink: [255,192,203],
673 plum: [221,160,221],
674 powderblue: [176,224,230],
675 purple: [128,0,128],
676 rebeccapurple: [102, 51, 153],
677 red: [255,0,0],
678 rosybrown: [188,143,143],
679 royalblue: [65,105,225],
680 saddlebrown: [139,69,19],
681 salmon: [250,128,114],
682 sandybrown: [244,164,96],
683 seagreen: [46,139,87],
684 seashell: [255,245,238],
685 sienna: [160,82,45],
686 silver: [192,192,192],
687 skyblue: [135,206,235],
688 slateblue: [106,90,205],
689 slategray: [112,128,144],
690 slategrey: [112,128,144],
691 snow: [255,250,250],
692 springgreen: [0,255,127],
693 steelblue: [70,130,180],
694 tan: [210,180,140],
695 teal: [0,128,128],
696 thistle: [216,191,216],
697 tomato: [255,99,71],
698 turquoise: [64,224,208],
699 violet: [238,130,238],
700 wheat: [245,222,179],
701 white: [255,255,255],
702 whitesmoke: [245,245,245],
703 yellow: [255,255,0],
704 yellowgreen: [154,205,50]
705};
706
707var reverseKeywords = {};
708for (var key in cssKeywords) {
709 reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
710}
711
712var convert = function() {
713 return new Converter();
714};
715
716for (var func in conversions) {
717 // export Raw versions
718 convert[func + "Raw"] = (function(func) {
719 // accept array or plain args
720 return function(arg) {
721 if (typeof arg == "number")
722 arg = Array.prototype.slice.call(arguments);
723 return conversions[func](arg);
724 }
725 })(func);
726
727 var pair = /(\w+)2(\w+)/.exec(func),
728 from = pair[1],
729 to = pair[2];
730
731 // export rgb2hsl and ["rgb"]["hsl"]
732 convert[from] = convert[from] || {};
733
734 convert[from][to] = convert[func] = (function(func) {
735 return function(arg) {
736 if (typeof arg == "number")
737 arg = Array.prototype.slice.call(arguments);
738
739 var val = conversions[func](arg);
740 if (typeof val == "string" || val === undefined)
741 return val; // keyword
742
743 for (var i = 0; i < val.length; i++)
744 val[i] = Math.round(val[i]);
745 return val;
746 }
747 })(func);
748}
749
750
751/* Converter does lazy conversion and caching */
752var Converter = function() {
753 this.convs = {};
754};
755
756/* Either get the values for a space or
757 set the values for a space, depending on args */
758Converter.prototype.routeSpace = function(space, args) {
759 var values = args[0];
760 if (values === undefined) {
761 // color.rgb()
762 return this.getValues(space);
763 }
764 // color.rgb(10, 10, 10)
765 if (typeof values == "number") {
766 values = Array.prototype.slice.call(args);
767 }
768
769 return this.setValues(space, values);
770};
771
772/* Set the values for a space, invalidating cache */
773Converter.prototype.setValues = function(space, values) {
774 this.space = space;
775 this.convs = {};
776 this.convs[space] = values;
777 return this;
778};
779
780/* Get the values for a space. If there's already
781 a conversion for the space, fetch it, otherwise
782 compute it */
783Converter.prototype.getValues = function(space) {
784 var vals = this.convs[space];
785 if (!vals) {
786 var fspace = this.space,
787 from = this.convs[fspace];
788 vals = convert[fspace][space](from);
789
790 this.convs[space] = vals;
791 }
792 return vals;
793};
794
795["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
796 Converter.prototype[space] = function(vals) {
797 return this.routeSpace(space, arguments);
798 };
799});
800
801var colorConvert = convert;
802
803var colorName = {
804 "aliceblue": [240, 248, 255],
805 "antiquewhite": [250, 235, 215],
806 "aqua": [0, 255, 255],
807 "aquamarine": [127, 255, 212],
808 "azure": [240, 255, 255],
809 "beige": [245, 245, 220],
810 "bisque": [255, 228, 196],
811 "black": [0, 0, 0],
812 "blanchedalmond": [255, 235, 205],
813 "blue": [0, 0, 255],
814 "blueviolet": [138, 43, 226],
815 "brown": [165, 42, 42],
816 "burlywood": [222, 184, 135],
817 "cadetblue": [95, 158, 160],
818 "chartreuse": [127, 255, 0],
819 "chocolate": [210, 105, 30],
820 "coral": [255, 127, 80],
821 "cornflowerblue": [100, 149, 237],
822 "cornsilk": [255, 248, 220],
823 "crimson": [220, 20, 60],
824 "cyan": [0, 255, 255],
825 "darkblue": [0, 0, 139],
826 "darkcyan": [0, 139, 139],
827 "darkgoldenrod": [184, 134, 11],
828 "darkgray": [169, 169, 169],
829 "darkgreen": [0, 100, 0],
830 "darkgrey": [169, 169, 169],
831 "darkkhaki": [189, 183, 107],
832 "darkmagenta": [139, 0, 139],
833 "darkolivegreen": [85, 107, 47],
834 "darkorange": [255, 140, 0],
835 "darkorchid": [153, 50, 204],
836 "darkred": [139, 0, 0],
837 "darksalmon": [233, 150, 122],
838 "darkseagreen": [143, 188, 143],
839 "darkslateblue": [72, 61, 139],
840 "darkslategray": [47, 79, 79],
841 "darkslategrey": [47, 79, 79],
842 "darkturquoise": [0, 206, 209],
843 "darkviolet": [148, 0, 211],
844 "deeppink": [255, 20, 147],
845 "deepskyblue": [0, 191, 255],
846 "dimgray": [105, 105, 105],
847 "dimgrey": [105, 105, 105],
848 "dodgerblue": [30, 144, 255],
849 "firebrick": [178, 34, 34],
850 "floralwhite": [255, 250, 240],
851 "forestgreen": [34, 139, 34],
852 "fuchsia": [255, 0, 255],
853 "gainsboro": [220, 220, 220],
854 "ghostwhite": [248, 248, 255],
855 "gold": [255, 215, 0],
856 "goldenrod": [218, 165, 32],
857 "gray": [128, 128, 128],
858 "green": [0, 128, 0],
859 "greenyellow": [173, 255, 47],
860 "grey": [128, 128, 128],
861 "honeydew": [240, 255, 240],
862 "hotpink": [255, 105, 180],
863 "indianred": [205, 92, 92],
864 "indigo": [75, 0, 130],
865 "ivory": [255, 255, 240],
866 "khaki": [240, 230, 140],
867 "lavender": [230, 230, 250],
868 "lavenderblush": [255, 240, 245],
869 "lawngreen": [124, 252, 0],
870 "lemonchiffon": [255, 250, 205],
871 "lightblue": [173, 216, 230],
872 "lightcoral": [240, 128, 128],
873 "lightcyan": [224, 255, 255],
874 "lightgoldenrodyellow": [250, 250, 210],
875 "lightgray": [211, 211, 211],
876 "lightgreen": [144, 238, 144],
877 "lightgrey": [211, 211, 211],
878 "lightpink": [255, 182, 193],
879 "lightsalmon": [255, 160, 122],
880 "lightseagreen": [32, 178, 170],
881 "lightskyblue": [135, 206, 250],
882 "lightslategray": [119, 136, 153],
883 "lightslategrey": [119, 136, 153],
884 "lightsteelblue": [176, 196, 222],
885 "lightyellow": [255, 255, 224],
886 "lime": [0, 255, 0],
887 "limegreen": [50, 205, 50],
888 "linen": [250, 240, 230],
889 "magenta": [255, 0, 255],
890 "maroon": [128, 0, 0],
891 "mediumaquamarine": [102, 205, 170],
892 "mediumblue": [0, 0, 205],
893 "mediumorchid": [186, 85, 211],
894 "mediumpurple": [147, 112, 219],
895 "mediumseagreen": [60, 179, 113],
896 "mediumslateblue": [123, 104, 238],
897 "mediumspringgreen": [0, 250, 154],
898 "mediumturquoise": [72, 209, 204],
899 "mediumvioletred": [199, 21, 133],
900 "midnightblue": [25, 25, 112],
901 "mintcream": [245, 255, 250],
902 "mistyrose": [255, 228, 225],
903 "moccasin": [255, 228, 181],
904 "navajowhite": [255, 222, 173],
905 "navy": [0, 0, 128],
906 "oldlace": [253, 245, 230],
907 "olive": [128, 128, 0],
908 "olivedrab": [107, 142, 35],
909 "orange": [255, 165, 0],
910 "orangered": [255, 69, 0],
911 "orchid": [218, 112, 214],
912 "palegoldenrod": [238, 232, 170],
913 "palegreen": [152, 251, 152],
914 "paleturquoise": [175, 238, 238],
915 "palevioletred": [219, 112, 147],
916 "papayawhip": [255, 239, 213],
917 "peachpuff": [255, 218, 185],
918 "peru": [205, 133, 63],
919 "pink": [255, 192, 203],
920 "plum": [221, 160, 221],
921 "powderblue": [176, 224, 230],
922 "purple": [128, 0, 128],
923 "rebeccapurple": [102, 51, 153],
924 "red": [255, 0, 0],
925 "rosybrown": [188, 143, 143],
926 "royalblue": [65, 105, 225],
927 "saddlebrown": [139, 69, 19],
928 "salmon": [250, 128, 114],
929 "sandybrown": [244, 164, 96],
930 "seagreen": [46, 139, 87],
931 "seashell": [255, 245, 238],
932 "sienna": [160, 82, 45],
933 "silver": [192, 192, 192],
934 "skyblue": [135, 206, 235],
935 "slateblue": [106, 90, 205],
936 "slategray": [112, 128, 144],
937 "slategrey": [112, 128, 144],
938 "snow": [255, 250, 250],
939 "springgreen": [0, 255, 127],
940 "steelblue": [70, 130, 180],
941 "tan": [210, 180, 140],
942 "teal": [0, 128, 128],
943 "thistle": [216, 191, 216],
944 "tomato": [255, 99, 71],
945 "turquoise": [64, 224, 208],
946 "violet": [238, 130, 238],
947 "wheat": [245, 222, 179],
948 "white": [255, 255, 255],
949 "whitesmoke": [245, 245, 245],
950 "yellow": [255, 255, 0],
951 "yellowgreen": [154, 205, 50]
952};
953
954/* MIT license */
955
956
957var colorString = {
958 getRgba: getRgba,
959 getHsla: getHsla,
960 getRgb: getRgb,
961 getHsl: getHsl,
962 getHwb: getHwb,
963 getAlpha: getAlpha,
964
965 hexString: hexString,
966 rgbString: rgbString,
967 rgbaString: rgbaString,
968 percentString: percentString,
969 percentaString: percentaString,
970 hslString: hslString,
971 hslaString: hslaString,
972 hwbString: hwbString,
973 keyword: keyword
974};
975
976function getRgba(string) {
977 if (!string) {
978 return;
979 }
980 var abbr = /^#([a-fA-F0-9]{3})$/i,
981 hex = /^#([a-fA-F0-9]{6})$/i,
982 rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
983 per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
984 keyword = /(\w+)/;
985
986 var rgb = [0, 0, 0],
987 a = 1,
988 match = string.match(abbr);
989 if (match) {
990 match = match[1];
991 for (var i = 0; i < rgb.length; i++) {
992 rgb[i] = parseInt(match[i] + match[i], 16);
993 }
994 }
995 else if (match = string.match(hex)) {
996 match = match[1];
997 for (var i = 0; i < rgb.length; i++) {
998 rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
999 }
1000 }
1001 else if (match = string.match(rgba)) {
1002 for (var i = 0; i < rgb.length; i++) {
1003 rgb[i] = parseInt(match[i + 1]);
1004 }
1005 a = parseFloat(match[4]);
1006 }
1007 else if (match = string.match(per)) {
1008 for (var i = 0; i < rgb.length; i++) {
1009 rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
1010 }
1011 a = parseFloat(match[4]);
1012 }
1013 else if (match = string.match(keyword)) {
1014 if (match[1] == "transparent") {
1015 return [0, 0, 0, 0];
1016 }
1017 rgb = colorName[match[1]];
1018 if (!rgb) {
1019 return;
1020 }
1021 }
1022
1023 for (var i = 0; i < rgb.length; i++) {
1024 rgb[i] = scale(rgb[i], 0, 255);
1025 }
1026 if (!a && a != 0) {
1027 a = 1;
1028 }
1029 else {
1030 a = scale(a, 0, 1);
1031 }
1032 rgb[3] = a;
1033 return rgb;
1034}
1035
1036function getHsla(string) {
1037 if (!string) {
1038 return;
1039 }
1040 var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
1041 var match = string.match(hsl);
1042 if (match) {
1043 var alpha = parseFloat(match[4]);
1044 var h = scale(parseInt(match[1]), 0, 360),
1045 s = scale(parseFloat(match[2]), 0, 100),
1046 l = scale(parseFloat(match[3]), 0, 100),
1047 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
1048 return [h, s, l, a];
1049 }
1050}
1051
1052function getHwb(string) {
1053 if (!string) {
1054 return;
1055 }
1056 var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
1057 var match = string.match(hwb);
1058 if (match) {
1059 var alpha = parseFloat(match[4]);
1060 var h = scale(parseInt(match[1]), 0, 360),
1061 w = scale(parseFloat(match[2]), 0, 100),
1062 b = scale(parseFloat(match[3]), 0, 100),
1063 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
1064 return [h, w, b, a];
1065 }
1066}
1067
1068function getRgb(string) {
1069 var rgba = getRgba(string);
1070 return rgba && rgba.slice(0, 3);
1071}
1072
1073function getHsl(string) {
1074 var hsla = getHsla(string);
1075 return hsla && hsla.slice(0, 3);
1076}
1077
1078function getAlpha(string) {
1079 var vals = getRgba(string);
1080 if (vals) {
1081 return vals[3];
1082 }
1083 else if (vals = getHsla(string)) {
1084 return vals[3];
1085 }
1086 else if (vals = getHwb(string)) {
1087 return vals[3];
1088 }
1089}
1090
1091// generators
1092function hexString(rgb) {
1093 return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
1094 + hexDouble(rgb[2]);
1095}
1096
1097function rgbString(rgba, alpha) {
1098 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
1099 return rgbaString(rgba, alpha);
1100 }
1101 return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
1102}
1103
1104function rgbaString(rgba, alpha) {
1105 if (alpha === undefined) {
1106 alpha = (rgba[3] !== undefined ? rgba[3] : 1);
1107 }
1108 return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
1109 + ", " + alpha + ")";
1110}
1111
1112function percentString(rgba, alpha) {
1113 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
1114 return percentaString(rgba, alpha);
1115 }
1116 var r = Math.round(rgba[0]/255 * 100),
1117 g = Math.round(rgba[1]/255 * 100),
1118 b = Math.round(rgba[2]/255 * 100);
1119
1120 return "rgb(" + r + "%, " + g + "%, " + b + "%)";
1121}
1122
1123function percentaString(rgba, alpha) {
1124 var r = Math.round(rgba[0]/255 * 100),
1125 g = Math.round(rgba[1]/255 * 100),
1126 b = Math.round(rgba[2]/255 * 100);
1127 return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
1128}
1129
1130function hslString(hsla, alpha) {
1131 if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
1132 return hslaString(hsla, alpha);
1133 }
1134 return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
1135}
1136
1137function hslaString(hsla, alpha) {
1138 if (alpha === undefined) {
1139 alpha = (hsla[3] !== undefined ? hsla[3] : 1);
1140 }
1141 return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
1142 + alpha + ")";
1143}
1144
1145// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
1146// (hwb have alpha optional & 1 is default value)
1147function hwbString(hwb, alpha) {
1148 if (alpha === undefined) {
1149 alpha = (hwb[3] !== undefined ? hwb[3] : 1);
1150 }
1151 return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
1152 + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
1153}
1154
1155function keyword(rgb) {
1156 return reverseNames[rgb.slice(0, 3)];
1157}
1158
1159// helpers
1160function scale(num, min, max) {
1161 return Math.min(Math.max(min, num), max);
1162}
1163
1164function hexDouble(num) {
1165 var str = num.toString(16).toUpperCase();
1166 return (str.length < 2) ? "0" + str : str;
1167}
1168
1169
1170//create a list of reverse color names
1171var reverseNames = {};
1172for (var name in colorName) {
1173 reverseNames[colorName[name]] = name;
1174}
1175
1176/* MIT license */
1177
1178
1179
1180var Color = function (obj) {
1181 if (obj instanceof Color) {
1182 return obj;
1183 }
1184 if (!(this instanceof Color)) {
1185 return new Color(obj);
1186 }
1187
1188 this.valid = false;
1189 this.values = {
1190 rgb: [0, 0, 0],
1191 hsl: [0, 0, 0],
1192 hsv: [0, 0, 0],
1193 hwb: [0, 0, 0],
1194 cmyk: [0, 0, 0, 0],
1195 alpha: 1
1196 };
1197
1198 // parse Color() argument
1199 var vals;
1200 if (typeof obj === 'string') {
1201 vals = colorString.getRgba(obj);
1202 if (vals) {
1203 this.setValues('rgb', vals);
1204 } else if (vals = colorString.getHsla(obj)) {
1205 this.setValues('hsl', vals);
1206 } else if (vals = colorString.getHwb(obj)) {
1207 this.setValues('hwb', vals);
1208 }
1209 } else if (typeof obj === 'object') {
1210 vals = obj;
1211 if (vals.r !== undefined || vals.red !== undefined) {
1212 this.setValues('rgb', vals);
1213 } else if (vals.l !== undefined || vals.lightness !== undefined) {
1214 this.setValues('hsl', vals);
1215 } else if (vals.v !== undefined || vals.value !== undefined) {
1216 this.setValues('hsv', vals);
1217 } else if (vals.w !== undefined || vals.whiteness !== undefined) {
1218 this.setValues('hwb', vals);
1219 } else if (vals.c !== undefined || vals.cyan !== undefined) {
1220 this.setValues('cmyk', vals);
1221 }
1222 }
1223};
1224
1225Color.prototype = {
1226 isValid: function () {
1227 return this.valid;
1228 },
1229 rgb: function () {
1230 return this.setSpace('rgb', arguments);
1231 },
1232 hsl: function () {
1233 return this.setSpace('hsl', arguments);
1234 },
1235 hsv: function () {
1236 return this.setSpace('hsv', arguments);
1237 },
1238 hwb: function () {
1239 return this.setSpace('hwb', arguments);
1240 },
1241 cmyk: function () {
1242 return this.setSpace('cmyk', arguments);
1243 },
1244
1245 rgbArray: function () {
1246 return this.values.rgb;
1247 },
1248 hslArray: function () {
1249 return this.values.hsl;
1250 },
1251 hsvArray: function () {
1252 return this.values.hsv;
1253 },
1254 hwbArray: function () {
1255 var values = this.values;
1256 if (values.alpha !== 1) {
1257 return values.hwb.concat([values.alpha]);
1258 }
1259 return values.hwb;
1260 },
1261 cmykArray: function () {
1262 return this.values.cmyk;
1263 },
1264 rgbaArray: function () {
1265 var values = this.values;
1266 return values.rgb.concat([values.alpha]);
1267 },
1268 hslaArray: function () {
1269 var values = this.values;
1270 return values.hsl.concat([values.alpha]);
1271 },
1272 alpha: function (val) {
1273 if (val === undefined) {
1274 return this.values.alpha;
1275 }
1276 this.setValues('alpha', val);
1277 return this;
1278 },
1279
1280 red: function (val) {
1281 return this.setChannel('rgb', 0, val);
1282 },
1283 green: function (val) {
1284 return this.setChannel('rgb', 1, val);
1285 },
1286 blue: function (val) {
1287 return this.setChannel('rgb', 2, val);
1288 },
1289 hue: function (val) {
1290 if (val) {
1291 val %= 360;
1292 val = val < 0 ? 360 + val : val;
1293 }
1294 return this.setChannel('hsl', 0, val);
1295 },
1296 saturation: function (val) {
1297 return this.setChannel('hsl', 1, val);
1298 },
1299 lightness: function (val) {
1300 return this.setChannel('hsl', 2, val);
1301 },
1302 saturationv: function (val) {
1303 return this.setChannel('hsv', 1, val);
1304 },
1305 whiteness: function (val) {
1306 return this.setChannel('hwb', 1, val);
1307 },
1308 blackness: function (val) {
1309 return this.setChannel('hwb', 2, val);
1310 },
1311 value: function (val) {
1312 return this.setChannel('hsv', 2, val);
1313 },
1314 cyan: function (val) {
1315 return this.setChannel('cmyk', 0, val);
1316 },
1317 magenta: function (val) {
1318 return this.setChannel('cmyk', 1, val);
1319 },
1320 yellow: function (val) {
1321 return this.setChannel('cmyk', 2, val);
1322 },
1323 black: function (val) {
1324 return this.setChannel('cmyk', 3, val);
1325 },
1326
1327 hexString: function () {
1328 return colorString.hexString(this.values.rgb);
1329 },
1330 rgbString: function () {
1331 return colorString.rgbString(this.values.rgb, this.values.alpha);
1332 },
1333 rgbaString: function () {
1334 return colorString.rgbaString(this.values.rgb, this.values.alpha);
1335 },
1336 percentString: function () {
1337 return colorString.percentString(this.values.rgb, this.values.alpha);
1338 },
1339 hslString: function () {
1340 return colorString.hslString(this.values.hsl, this.values.alpha);
1341 },
1342 hslaString: function () {
1343 return colorString.hslaString(this.values.hsl, this.values.alpha);
1344 },
1345 hwbString: function () {
1346 return colorString.hwbString(this.values.hwb, this.values.alpha);
1347 },
1348 keyword: function () {
1349 return colorString.keyword(this.values.rgb, this.values.alpha);
1350 },
1351
1352 rgbNumber: function () {
1353 var rgb = this.values.rgb;
1354 return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
1355 },
1356
1357 luminosity: function () {
1358 // http://www.w3.org/TR/WCAG20/#relativeluminancedef
1359 var rgb = this.values.rgb;
1360 var lum = [];
1361 for (var i = 0; i < rgb.length; i++) {
1362 var chan = rgb[i] / 255;
1363 lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
1364 }
1365 return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
1366 },
1367
1368 contrast: function (color2) {
1369 // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
1370 var lum1 = this.luminosity();
1371 var lum2 = color2.luminosity();
1372 if (lum1 > lum2) {
1373 return (lum1 + 0.05) / (lum2 + 0.05);
1374 }
1375 return (lum2 + 0.05) / (lum1 + 0.05);
1376 },
1377
1378 level: function (color2) {
1379 var contrastRatio = this.contrast(color2);
1380 if (contrastRatio >= 7.1) {
1381 return 'AAA';
1382 }
1383
1384 return (contrastRatio >= 4.5) ? 'AA' : '';
1385 },
1386
1387 dark: function () {
1388 // YIQ equation from http://24ways.org/2010/calculating-color-contrast
1389 var rgb = this.values.rgb;
1390 var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
1391 return yiq < 128;
1392 },
1393
1394 light: function () {
1395 return !this.dark();
1396 },
1397
1398 negate: function () {
1399 var rgb = [];
1400 for (var i = 0; i < 3; i++) {
1401 rgb[i] = 255 - this.values.rgb[i];
1402 }
1403 this.setValues('rgb', rgb);
1404 return this;
1405 },
1406
1407 lighten: function (ratio) {
1408 var hsl = this.values.hsl;
1409 hsl[2] += hsl[2] * ratio;
1410 this.setValues('hsl', hsl);
1411 return this;
1412 },
1413
1414 darken: function (ratio) {
1415 var hsl = this.values.hsl;
1416 hsl[2] -= hsl[2] * ratio;
1417 this.setValues('hsl', hsl);
1418 return this;
1419 },
1420
1421 saturate: function (ratio) {
1422 var hsl = this.values.hsl;
1423 hsl[1] += hsl[1] * ratio;
1424 this.setValues('hsl', hsl);
1425 return this;
1426 },
1427
1428 desaturate: function (ratio) {
1429 var hsl = this.values.hsl;
1430 hsl[1] -= hsl[1] * ratio;
1431 this.setValues('hsl', hsl);
1432 return this;
1433 },
1434
1435 whiten: function (ratio) {
1436 var hwb = this.values.hwb;
1437 hwb[1] += hwb[1] * ratio;
1438 this.setValues('hwb', hwb);
1439 return this;
1440 },
1441
1442 blacken: function (ratio) {
1443 var hwb = this.values.hwb;
1444 hwb[2] += hwb[2] * ratio;
1445 this.setValues('hwb', hwb);
1446 return this;
1447 },
1448
1449 greyscale: function () {
1450 var rgb = this.values.rgb;
1451 // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
1452 var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
1453 this.setValues('rgb', [val, val, val]);
1454 return this;
1455 },
1456
1457 clearer: function (ratio) {
1458 var alpha = this.values.alpha;
1459 this.setValues('alpha', alpha - (alpha * ratio));
1460 return this;
1461 },
1462
1463 opaquer: function (ratio) {
1464 var alpha = this.values.alpha;
1465 this.setValues('alpha', alpha + (alpha * ratio));
1466 return this;
1467 },
1468
1469 rotate: function (degrees) {
1470 var hsl = this.values.hsl;
1471 var hue = (hsl[0] + degrees) % 360;
1472 hsl[0] = hue < 0 ? 360 + hue : hue;
1473 this.setValues('hsl', hsl);
1474 return this;
1475 },
1476
1477 /**
1478 * Ported from sass implementation in C
1479 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
1480 */
1481 mix: function (mixinColor, weight) {
1482 var color1 = this;
1483 var color2 = mixinColor;
1484 var p = weight === undefined ? 0.5 : weight;
1485
1486 var w = 2 * p - 1;
1487 var a = color1.alpha() - color2.alpha();
1488
1489 var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
1490 var w2 = 1 - w1;
1491
1492 return this
1493 .rgb(
1494 w1 * color1.red() + w2 * color2.red(),
1495 w1 * color1.green() + w2 * color2.green(),
1496 w1 * color1.blue() + w2 * color2.blue()
1497 )
1498 .alpha(color1.alpha() * p + color2.alpha() * (1 - p));
1499 },
1500
1501 toJSON: function () {
1502 return this.rgb();
1503 },
1504
1505 clone: function () {
1506 // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
1507 // making the final build way to big to embed in Chart.js. So let's do it manually,
1508 // assuming that values to clone are 1 dimension arrays containing only numbers,
1509 // except 'alpha' which is a number.
1510 var result = new Color();
1511 var source = this.values;
1512 var target = result.values;
1513 var value, type;
1514
1515 for (var prop in source) {
1516 if (source.hasOwnProperty(prop)) {
1517 value = source[prop];
1518 type = ({}).toString.call(value);
1519 if (type === '[object Array]') {
1520 target[prop] = value.slice(0);
1521 } else if (type === '[object Number]') {
1522 target[prop] = value;
1523 } else {
1524 console.error('unexpected color value:', value);
1525 }
1526 }
1527 }
1528
1529 return result;
1530 }
1531};
1532
1533Color.prototype.spaces = {
1534 rgb: ['red', 'green', 'blue'],
1535 hsl: ['hue', 'saturation', 'lightness'],
1536 hsv: ['hue', 'saturation', 'value'],
1537 hwb: ['hue', 'whiteness', 'blackness'],
1538 cmyk: ['cyan', 'magenta', 'yellow', 'black']
1539};
1540
1541Color.prototype.maxes = {
1542 rgb: [255, 255, 255],
1543 hsl: [360, 100, 100],
1544 hsv: [360, 100, 100],
1545 hwb: [360, 100, 100],
1546 cmyk: [100, 100, 100, 100]
1547};
1548
1549Color.prototype.getValues = function (space) {
1550 var values = this.values;
1551 var vals = {};
1552
1553 for (var i = 0; i < space.length; i++) {
1554 vals[space.charAt(i)] = values[space][i];
1555 }
1556
1557 if (values.alpha !== 1) {
1558 vals.a = values.alpha;
1559 }
1560
1561 // {r: 255, g: 255, b: 255, a: 0.4}
1562 return vals;
1563};
1564
1565Color.prototype.setValues = function (space, vals) {
1566 var values = this.values;
1567 var spaces = this.spaces;
1568 var maxes = this.maxes;
1569 var alpha = 1;
1570 var i;
1571
1572 this.valid = true;
1573
1574 if (space === 'alpha') {
1575 alpha = vals;
1576 } else if (vals.length) {
1577 // [10, 10, 10]
1578 values[space] = vals.slice(0, space.length);
1579 alpha = vals[space.length];
1580 } else if (vals[space.charAt(0)] !== undefined) {
1581 // {r: 10, g: 10, b: 10}
1582 for (i = 0; i < space.length; i++) {
1583 values[space][i] = vals[space.charAt(i)];
1584 }
1585
1586 alpha = vals.a;
1587 } else if (vals[spaces[space][0]] !== undefined) {
1588 // {red: 10, green: 10, blue: 10}
1589 var chans = spaces[space];
1590
1591 for (i = 0; i < space.length; i++) {
1592 values[space][i] = vals[chans[i]];
1593 }
1594
1595 alpha = vals.alpha;
1596 }
1597
1598 values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
1599
1600 if (space === 'alpha') {
1601 return false;
1602 }
1603
1604 var capped;
1605
1606 // cap values of the space prior converting all values
1607 for (i = 0; i < space.length; i++) {
1608 capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
1609 values[space][i] = Math.round(capped);
1610 }
1611
1612 // convert to all the other color spaces
1613 for (var sname in spaces) {
1614 if (sname !== space) {
1615 values[sname] = colorConvert[space][sname](values[space]);
1616 }
1617 }
1618
1619 return true;
1620};
1621
1622Color.prototype.setSpace = function (space, args) {
1623 var vals = args[0];
1624
1625 if (vals === undefined) {
1626 // color.rgb()
1627 return this.getValues(space);
1628 }
1629
1630 // color.rgb(10, 10, 10)
1631 if (typeof vals === 'number') {
1632 vals = Array.prototype.slice.call(args);
1633 }
1634
1635 this.setValues(space, vals);
1636 return this;
1637};
1638
1639Color.prototype.setChannel = function (space, index, val) {
1640 var svalues = this.values[space];
1641 if (val === undefined) {
1642 // color.red()
1643 return svalues[index];
1644 } else if (val === svalues[index]) {
1645 // color.red(color.red())
1646 return this;
1647 }
1648
1649 // color.red(100)
1650 svalues[index] = val;
1651 this.setValues(space, svalues);
1652
1653 return this;
1654};
1655
1656if (typeof window !== 'undefined') {
1657 window.Color = Color;
1658}
1659
1660var chartjsColor = Color;
1661
1662/**
1663 * @namespace Chart.helpers
1664 */
1665var helpers = {
1666 /**
1667 * An empty function that can be used, for example, for optional callback.
1668 */
1669 noop: function() {},
1670
1671 /**
1672 * Returns a unique id, sequentially generated from a global variable.
1673 * @returns {Number}
1674 * @function
1675 */
1676 uid: (function() {
1677 var id = 0;
1678 return function() {
1679 return id++;
1680 };
1681 }()),
1682
1683 /**
1684 * Returns true if `value` is neither null nor undefined, else returns false.
1685 * @param {*} value - The value to test.
1686 * @returns {Boolean}
1687 * @since 2.7.0
1688 */
1689 isNullOrUndef: function(value) {
1690 return value === null || typeof value === 'undefined';
1691 },
1692
1693 /**
1694 * Returns true if `value` is an array (including typed arrays), else returns false.
1695 * @param {*} value - The value to test.
1696 * @returns {Boolean}
1697 * @function
1698 */
1699 isArray: function(value) {
1700 if (Array.isArray && Array.isArray(value)) {
1701 return true;
1702 }
1703 var type = Object.prototype.toString.call(value);
1704 if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') {
1705 return true;
1706 }
1707 return false;
1708 },
1709
1710 /**
1711 * Returns true if `value` is an object (excluding null), else returns false.
1712 * @param {*} value - The value to test.
1713 * @returns {Boolean}
1714 * @since 2.7.0
1715 */
1716 isObject: function(value) {
1717 return value !== null && Object.prototype.toString.call(value) === '[object Object]';
1718 },
1719
1720 /**
1721 * Returns true if `value` is a finite number, else returns false
1722 * @param {*} value - The value to test.
1723 * @returns {Boolean}
1724 */
1725 isFinite: function(value) {
1726 return (typeof value === 'number' || value instanceof Number) && isFinite(value);
1727 },
1728
1729 /**
1730 * Returns `value` if defined, else returns `defaultValue`.
1731 * @param {*} value - The value to return if defined.
1732 * @param {*} defaultValue - The value to return if `value` is undefined.
1733 * @returns {*}
1734 */
1735 valueOrDefault: function(value, defaultValue) {
1736 return typeof value === 'undefined' ? defaultValue : value;
1737 },
1738
1739 /**
1740 * Returns value at the given `index` in array if defined, else returns `defaultValue`.
1741 * @param {Array} value - The array to lookup for value at `index`.
1742 * @param {Number} index - The index in `value` to lookup for value.
1743 * @param {*} defaultValue - The value to return if `value[index]` is undefined.
1744 * @returns {*}
1745 */
1746 valueAtIndexOrDefault: function(value, index, defaultValue) {
1747 return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
1748 },
1749
1750 /**
1751 * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
1752 * value returned by `fn`. If `fn` is not a function, this method returns undefined.
1753 * @param {Function} fn - The function to call.
1754 * @param {Array|undefined|null} args - The arguments with which `fn` should be called.
1755 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
1756 * @returns {*}
1757 */
1758 callback: function(fn, args, thisArg) {
1759 if (fn && typeof fn.call === 'function') {
1760 return fn.apply(thisArg, args);
1761 }
1762 },
1763
1764 /**
1765 * Note(SB) for performance sake, this method should only be used when loopable type
1766 * is unknown or in none intensive code (not called often and small loopable). Else
1767 * it's preferable to use a regular for() loop and save extra function calls.
1768 * @param {Object|Array} loopable - The object or array to be iterated.
1769 * @param {Function} fn - The function to call for each item.
1770 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
1771 * @param {Boolean} [reverse] - If true, iterates backward on the loopable.
1772 */
1773 each: function(loopable, fn, thisArg, reverse) {
1774 var i, len, keys;
1775 if (helpers.isArray(loopable)) {
1776 len = loopable.length;
1777 if (reverse) {
1778 for (i = len - 1; i >= 0; i--) {
1779 fn.call(thisArg, loopable[i], i);
1780 }
1781 } else {
1782 for (i = 0; i < len; i++) {
1783 fn.call(thisArg, loopable[i], i);
1784 }
1785 }
1786 } else if (helpers.isObject(loopable)) {
1787 keys = Object.keys(loopable);
1788 len = keys.length;
1789 for (i = 0; i < len; i++) {
1790 fn.call(thisArg, loopable[keys[i]], keys[i]);
1791 }
1792 }
1793 },
1794
1795 /**
1796 * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
1797 * @see https://stackoverflow.com/a/14853974
1798 * @param {Array} a0 - The array to compare
1799 * @param {Array} a1 - The array to compare
1800 * @returns {Boolean}
1801 */
1802 arrayEquals: function(a0, a1) {
1803 var i, ilen, v0, v1;
1804
1805 if (!a0 || !a1 || a0.length !== a1.length) {
1806 return false;
1807 }
1808
1809 for (i = 0, ilen = a0.length; i < ilen; ++i) {
1810 v0 = a0[i];
1811 v1 = a1[i];
1812
1813 if (v0 instanceof Array && v1 instanceof Array) {
1814 if (!helpers.arrayEquals(v0, v1)) {
1815 return false;
1816 }
1817 } else if (v0 !== v1) {
1818 // NOTE: two different object instances will never be equal: {x:20} != {x:20}
1819 return false;
1820 }
1821 }
1822
1823 return true;
1824 },
1825
1826 /**
1827 * Returns a deep copy of `source` without keeping references on objects and arrays.
1828 * @param {*} source - The value to clone.
1829 * @returns {*}
1830 */
1831 clone: function(source) {
1832 if (helpers.isArray(source)) {
1833 return source.map(helpers.clone);
1834 }
1835
1836 if (helpers.isObject(source)) {
1837 var target = {};
1838 var keys = Object.keys(source);
1839 var klen = keys.length;
1840 var k = 0;
1841
1842 for (; k < klen; ++k) {
1843 target[keys[k]] = helpers.clone(source[keys[k]]);
1844 }
1845
1846 return target;
1847 }
1848
1849 return source;
1850 },
1851
1852 /**
1853 * The default merger when Chart.helpers.merge is called without merger option.
1854 * Note(SB): this method is also used by configMerge and scaleMerge as fallback.
1855 * @private
1856 */
1857 _merger: function(key, target, source, options) {
1858 var tval = target[key];
1859 var sval = source[key];
1860
1861 if (helpers.isObject(tval) && helpers.isObject(sval)) {
1862 helpers.merge(tval, sval, options);
1863 } else {
1864 target[key] = helpers.clone(sval);
1865 }
1866 },
1867
1868 /**
1869 * Merges source[key] in target[key] only if target[key] is undefined.
1870 * @private
1871 */
1872 _mergerIf: function(key, target, source) {
1873 var tval = target[key];
1874 var sval = source[key];
1875
1876 if (helpers.isObject(tval) && helpers.isObject(sval)) {
1877 helpers.mergeIf(tval, sval);
1878 } else if (!target.hasOwnProperty(key)) {
1879 target[key] = helpers.clone(sval);
1880 }
1881 },
1882
1883 /**
1884 * Recursively deep copies `source` properties into `target` with the given `options`.
1885 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
1886 * @param {Object} target - The target object in which all sources are merged into.
1887 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
1888 * @param {Object} [options] - Merging options:
1889 * @param {Function} [options.merger] - The merge method (key, target, source, options)
1890 * @returns {Object} The `target` object.
1891 */
1892 merge: function(target, source, options) {
1893 var sources = helpers.isArray(source) ? source : [source];
1894 var ilen = sources.length;
1895 var merge, i, keys, klen, k;
1896
1897 if (!helpers.isObject(target)) {
1898 return target;
1899 }
1900
1901 options = options || {};
1902 merge = options.merger || helpers._merger;
1903
1904 for (i = 0; i < ilen; ++i) {
1905 source = sources[i];
1906 if (!helpers.isObject(source)) {
1907 continue;
1908 }
1909
1910 keys = Object.keys(source);
1911 for (k = 0, klen = keys.length; k < klen; ++k) {
1912 merge(keys[k], target, source, options);
1913 }
1914 }
1915
1916 return target;
1917 },
1918
1919 /**
1920 * Recursively deep copies `source` properties into `target` *only* if not defined in target.
1921 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
1922 * @param {Object} target - The target object in which all sources are merged into.
1923 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
1924 * @returns {Object} The `target` object.
1925 */
1926 mergeIf: function(target, source) {
1927 return helpers.merge(target, source, {merger: helpers._mergerIf});
1928 },
1929
1930 /**
1931 * Applies the contents of two or more objects together into the first object.
1932 * @param {Object} target - The target object in which all objects are merged into.
1933 * @param {Object} arg1 - Object containing additional properties to merge in target.
1934 * @param {Object} argN - Additional objects containing properties to merge in target.
1935 * @returns {Object} The `target` object.
1936 */
1937 extend: function(target) {
1938 var setFn = function(value, key) {
1939 target[key] = value;
1940 };
1941 for (var i = 1, ilen = arguments.length; i < ilen; ++i) {
1942 helpers.each(arguments[i], setFn);
1943 }
1944 return target;
1945 },
1946
1947 /**
1948 * Basic javascript inheritance based on the model created in Backbone.js
1949 */
1950 inherits: function(extensions) {
1951 var me = this;
1952 var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
1953 return me.apply(this, arguments);
1954 };
1955
1956 var Surrogate = function() {
1957 this.constructor = ChartElement;
1958 };
1959
1960 Surrogate.prototype = me.prototype;
1961 ChartElement.prototype = new Surrogate();
1962 ChartElement.extend = helpers.inherits;
1963
1964 if (extensions) {
1965 helpers.extend(ChartElement.prototype, extensions);
1966 }
1967
1968 ChartElement.__super__ = me.prototype;
1969 return ChartElement;
1970 }
1971};
1972
1973var helpers_core = helpers;
1974
1975// DEPRECATIONS
1976
1977/**
1978 * Provided for backward compatibility, use Chart.helpers.callback instead.
1979 * @function Chart.helpers.callCallback
1980 * @deprecated since version 2.6.0
1981 * @todo remove at version 3
1982 * @private
1983 */
1984helpers.callCallback = helpers.callback;
1985
1986/**
1987 * Provided for backward compatibility, use Array.prototype.indexOf instead.
1988 * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
1989 * @function Chart.helpers.indexOf
1990 * @deprecated since version 2.7.0
1991 * @todo remove at version 3
1992 * @private
1993 */
1994helpers.indexOf = function(array, item, fromIndex) {
1995 return Array.prototype.indexOf.call(array, item, fromIndex);
1996};
1997
1998/**
1999 * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
2000 * @function Chart.helpers.getValueOrDefault
2001 * @deprecated since version 2.7.0
2002 * @todo remove at version 3
2003 * @private
2004 */
2005helpers.getValueOrDefault = helpers.valueOrDefault;
2006
2007/**
2008 * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
2009 * @function Chart.helpers.getValueAtIndexOrDefault
2010 * @deprecated since version 2.7.0
2011 * @todo remove at version 3
2012 * @private
2013 */
2014helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2015
2016/**
2017 * Easing functions adapted from Robert Penner's easing equations.
2018 * @namespace Chart.helpers.easingEffects
2019 * @see http://www.robertpenner.com/easing/
2020 */
2021var effects = {
2022 linear: function(t) {
2023 return t;
2024 },
2025
2026 easeInQuad: function(t) {
2027 return t * t;
2028 },
2029
2030 easeOutQuad: function(t) {
2031 return -t * (t - 2);
2032 },
2033
2034 easeInOutQuad: function(t) {
2035 if ((t /= 0.5) < 1) {
2036 return 0.5 * t * t;
2037 }
2038 return -0.5 * ((--t) * (t - 2) - 1);
2039 },
2040
2041 easeInCubic: function(t) {
2042 return t * t * t;
2043 },
2044
2045 easeOutCubic: function(t) {
2046 return (t = t - 1) * t * t + 1;
2047 },
2048
2049 easeInOutCubic: function(t) {
2050 if ((t /= 0.5) < 1) {
2051 return 0.5 * t * t * t;
2052 }
2053 return 0.5 * ((t -= 2) * t * t + 2);
2054 },
2055
2056 easeInQuart: function(t) {
2057 return t * t * t * t;
2058 },
2059
2060 easeOutQuart: function(t) {
2061 return -((t = t - 1) * t * t * t - 1);
2062 },
2063
2064 easeInOutQuart: function(t) {
2065 if ((t /= 0.5) < 1) {
2066 return 0.5 * t * t * t * t;
2067 }
2068 return -0.5 * ((t -= 2) * t * t * t - 2);
2069 },
2070
2071 easeInQuint: function(t) {
2072 return t * t * t * t * t;
2073 },
2074
2075 easeOutQuint: function(t) {
2076 return (t = t - 1) * t * t * t * t + 1;
2077 },
2078
2079 easeInOutQuint: function(t) {
2080 if ((t /= 0.5) < 1) {
2081 return 0.5 * t * t * t * t * t;
2082 }
2083 return 0.5 * ((t -= 2) * t * t * t * t + 2);
2084 },
2085
2086 easeInSine: function(t) {
2087 return -Math.cos(t * (Math.PI / 2)) + 1;
2088 },
2089
2090 easeOutSine: function(t) {
2091 return Math.sin(t * (Math.PI / 2));
2092 },
2093
2094 easeInOutSine: function(t) {
2095 return -0.5 * (Math.cos(Math.PI * t) - 1);
2096 },
2097
2098 easeInExpo: function(t) {
2099 return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
2100 },
2101
2102 easeOutExpo: function(t) {
2103 return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
2104 },
2105
2106 easeInOutExpo: function(t) {
2107 if (t === 0) {
2108 return 0;
2109 }
2110 if (t === 1) {
2111 return 1;
2112 }
2113 if ((t /= 0.5) < 1) {
2114 return 0.5 * Math.pow(2, 10 * (t - 1));
2115 }
2116 return 0.5 * (-Math.pow(2, -10 * --t) + 2);
2117 },
2118
2119 easeInCirc: function(t) {
2120 if (t >= 1) {
2121 return t;
2122 }
2123 return -(Math.sqrt(1 - t * t) - 1);
2124 },
2125
2126 easeOutCirc: function(t) {
2127 return Math.sqrt(1 - (t = t - 1) * t);
2128 },
2129
2130 easeInOutCirc: function(t) {
2131 if ((t /= 0.5) < 1) {
2132 return -0.5 * (Math.sqrt(1 - t * t) - 1);
2133 }
2134 return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
2135 },
2136
2137 easeInElastic: function(t) {
2138 var s = 1.70158;
2139 var p = 0;
2140 var a = 1;
2141 if (t === 0) {
2142 return 0;
2143 }
2144 if (t === 1) {
2145 return 1;
2146 }
2147 if (!p) {
2148 p = 0.3;
2149 }
2150 if (a < 1) {
2151 a = 1;
2152 s = p / 4;
2153 } else {
2154 s = p / (2 * Math.PI) * Math.asin(1 / a);
2155 }
2156 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
2157 },
2158
2159 easeOutElastic: function(t) {
2160 var s = 1.70158;
2161 var p = 0;
2162 var a = 1;
2163 if (t === 0) {
2164 return 0;
2165 }
2166 if (t === 1) {
2167 return 1;
2168 }
2169 if (!p) {
2170 p = 0.3;
2171 }
2172 if (a < 1) {
2173 a = 1;
2174 s = p / 4;
2175 } else {
2176 s = p / (2 * Math.PI) * Math.asin(1 / a);
2177 }
2178 return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
2179 },
2180
2181 easeInOutElastic: function(t) {
2182 var s = 1.70158;
2183 var p = 0;
2184 var a = 1;
2185 if (t === 0) {
2186 return 0;
2187 }
2188 if ((t /= 0.5) === 2) {
2189 return 1;
2190 }
2191 if (!p) {
2192 p = 0.45;
2193 }
2194 if (a < 1) {
2195 a = 1;
2196 s = p / 4;
2197 } else {
2198 s = p / (2 * Math.PI) * Math.asin(1 / a);
2199 }
2200 if (t < 1) {
2201 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
2202 }
2203 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
2204 },
2205 easeInBack: function(t) {
2206 var s = 1.70158;
2207 return t * t * ((s + 1) * t - s);
2208 },
2209
2210 easeOutBack: function(t) {
2211 var s = 1.70158;
2212 return (t = t - 1) * t * ((s + 1) * t + s) + 1;
2213 },
2214
2215 easeInOutBack: function(t) {
2216 var s = 1.70158;
2217 if ((t /= 0.5) < 1) {
2218 return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
2219 }
2220 return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
2221 },
2222
2223 easeInBounce: function(t) {
2224 return 1 - effects.easeOutBounce(1 - t);
2225 },
2226
2227 easeOutBounce: function(t) {
2228 if (t < (1 / 2.75)) {
2229 return 7.5625 * t * t;
2230 }
2231 if (t < (2 / 2.75)) {
2232 return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
2233 }
2234 if (t < (2.5 / 2.75)) {
2235 return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
2236 }
2237 return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
2238 },
2239
2240 easeInOutBounce: function(t) {
2241 if (t < 0.5) {
2242 return effects.easeInBounce(t * 2) * 0.5;
2243 }
2244 return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
2245 }
2246};
2247
2248var helpers_easing = {
2249 effects: effects
2250};
2251
2252// DEPRECATIONS
2253
2254/**
2255 * Provided for backward compatibility, use Chart.helpers.easing.effects instead.
2256 * @function Chart.helpers.easingEffects
2257 * @deprecated since version 2.7.0
2258 * @todo remove at version 3
2259 * @private
2260 */
2261helpers_core.easingEffects = effects;
2262
2263var PI = Math.PI;
2264var RAD_PER_DEG = PI / 180;
2265var DOUBLE_PI = PI * 2;
2266var HALF_PI = PI / 2;
2267var QUARTER_PI = PI / 4;
2268var TWO_THIRDS_PI = PI * 2 / 3;
2269
2270/**
2271 * @namespace Chart.helpers.canvas
2272 */
2273var exports$1 = {
2274 /**
2275 * Clears the entire canvas associated to the given `chart`.
2276 * @param {Chart} chart - The chart for which to clear the canvas.
2277 */
2278 clear: function(chart) {
2279 chart.ctx.clearRect(0, 0, chart.width, chart.height);
2280 },
2281
2282 /**
2283 * Creates a "path" for a rectangle with rounded corners at position (x, y) with a
2284 * given size (width, height) and the same `radius` for all corners.
2285 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
2286 * @param {Number} x - The x axis of the coordinate for the rectangle starting point.
2287 * @param {Number} y - The y axis of the coordinate for the rectangle starting point.
2288 * @param {Number} width - The rectangle's width.
2289 * @param {Number} height - The rectangle's height.
2290 * @param {Number} radius - The rounded amount (in pixels) for the four corners.
2291 * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
2292 */
2293 roundedRect: function(ctx, x, y, width, height, radius) {
2294 if (radius) {
2295 var r = Math.min(radius, height / 2, width / 2);
2296 var left = x + r;
2297 var top = y + r;
2298 var right = x + width - r;
2299 var bottom = y + height - r;
2300
2301 ctx.moveTo(x, top);
2302 if (left < right && top < bottom) {
2303 ctx.arc(left, top, r, -PI, -HALF_PI);
2304 ctx.arc(right, top, r, -HALF_PI, 0);
2305 ctx.arc(right, bottom, r, 0, HALF_PI);
2306 ctx.arc(left, bottom, r, HALF_PI, PI);
2307 } else if (left < right) {
2308 ctx.moveTo(left, y);
2309 ctx.arc(right, top, r, -HALF_PI, HALF_PI);
2310 ctx.arc(left, top, r, HALF_PI, PI + HALF_PI);
2311 } else if (top < bottom) {
2312 ctx.arc(left, top, r, -PI, 0);
2313 ctx.arc(left, bottom, r, 0, PI);
2314 } else {
2315 ctx.arc(left, top, r, -PI, PI);
2316 }
2317 ctx.closePath();
2318 ctx.moveTo(x, y);
2319 } else {
2320 ctx.rect(x, y, width, height);
2321 }
2322 },
2323
2324 drawPoint: function(ctx, style, radius, x, y, rotation) {
2325 var type, xOffset, yOffset, size, cornerRadius;
2326 var rad = (rotation || 0) * RAD_PER_DEG;
2327
2328 if (style && typeof style === 'object') {
2329 type = style.toString();
2330 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
2331 ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height);
2332 return;
2333 }
2334 }
2335
2336 if (isNaN(radius) || radius <= 0) {
2337 return;
2338 }
2339
2340 ctx.beginPath();
2341
2342 switch (style) {
2343 // Default includes circle
2344 default:
2345 ctx.arc(x, y, radius, 0, DOUBLE_PI);
2346 ctx.closePath();
2347 break;
2348 case 'triangle':
2349 ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
2350 rad += TWO_THIRDS_PI;
2351 ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
2352 rad += TWO_THIRDS_PI;
2353 ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
2354 ctx.closePath();
2355 break;
2356 case 'rectRounded':
2357 // NOTE: the rounded rect implementation changed to use `arc` instead of
2358 // `quadraticCurveTo` since it generates better results when rect is
2359 // almost a circle. 0.516 (instead of 0.5) produces results with visually
2360 // closer proportion to the previous impl and it is inscribed in the
2361 // circle with `radius`. For more details, see the following PRs:
2362 // https://github.com/chartjs/Chart.js/issues/5597
2363 // https://github.com/chartjs/Chart.js/issues/5858
2364 cornerRadius = radius * 0.516;
2365 size = radius - cornerRadius;
2366 xOffset = Math.cos(rad + QUARTER_PI) * size;
2367 yOffset = Math.sin(rad + QUARTER_PI) * size;
2368 ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
2369 ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad);
2370 ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI);
2371 ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
2372 ctx.closePath();
2373 break;
2374 case 'rect':
2375 if (!rotation) {
2376 size = Math.SQRT1_2 * radius;
2377 ctx.rect(x - size, y - size, 2 * size, 2 * size);
2378 break;
2379 }
2380 rad += QUARTER_PI;
2381 /* falls through */
2382 case 'rectRot':
2383 xOffset = Math.cos(rad) * radius;
2384 yOffset = Math.sin(rad) * radius;
2385 ctx.moveTo(x - xOffset, y - yOffset);
2386 ctx.lineTo(x + yOffset, y - xOffset);
2387 ctx.lineTo(x + xOffset, y + yOffset);
2388 ctx.lineTo(x - yOffset, y + xOffset);
2389 ctx.closePath();
2390 break;
2391 case 'crossRot':
2392 rad += QUARTER_PI;
2393 /* falls through */
2394 case 'cross':
2395 xOffset = Math.cos(rad) * radius;
2396 yOffset = Math.sin(rad) * radius;
2397 ctx.moveTo(x - xOffset, y - yOffset);
2398 ctx.lineTo(x + xOffset, y + yOffset);
2399 ctx.moveTo(x + yOffset, y - xOffset);
2400 ctx.lineTo(x - yOffset, y + xOffset);
2401 break;
2402 case 'star':
2403 xOffset = Math.cos(rad) * radius;
2404 yOffset = Math.sin(rad) * radius;
2405 ctx.moveTo(x - xOffset, y - yOffset);
2406 ctx.lineTo(x + xOffset, y + yOffset);
2407 ctx.moveTo(x + yOffset, y - xOffset);
2408 ctx.lineTo(x - yOffset, y + xOffset);
2409 rad += QUARTER_PI;
2410 xOffset = Math.cos(rad) * radius;
2411 yOffset = Math.sin(rad) * radius;
2412 ctx.moveTo(x - xOffset, y - yOffset);
2413 ctx.lineTo(x + xOffset, y + yOffset);
2414 ctx.moveTo(x + yOffset, y - xOffset);
2415 ctx.lineTo(x - yOffset, y + xOffset);
2416 break;
2417 case 'line':
2418 xOffset = Math.cos(rad) * radius;
2419 yOffset = Math.sin(rad) * radius;
2420 ctx.moveTo(x - xOffset, y - yOffset);
2421 ctx.lineTo(x + xOffset, y + yOffset);
2422 break;
2423 case 'dash':
2424 ctx.moveTo(x, y);
2425 ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius);
2426 break;
2427 }
2428
2429 ctx.fill();
2430 ctx.stroke();
2431 },
2432
2433 /**
2434 * Returns true if the point is inside the rectangle
2435 * @param {Object} point - The point to test
2436 * @param {Object} area - The rectangle
2437 * @returns {Boolean}
2438 * @private
2439 */
2440 _isPointInArea: function(point, area) {
2441 var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.
2442
2443 return point.x > area.left - epsilon && point.x < area.right + epsilon &&
2444 point.y > area.top - epsilon && point.y < area.bottom + epsilon;
2445 },
2446
2447 clipArea: function(ctx, area) {
2448 ctx.save();
2449 ctx.beginPath();
2450 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
2451 ctx.clip();
2452 },
2453
2454 unclipArea: function(ctx) {
2455 ctx.restore();
2456 },
2457
2458 lineTo: function(ctx, previous, target, flip) {
2459 var stepped = target.steppedLine;
2460 if (stepped) {
2461 if (stepped === 'middle') {
2462 var midpoint = (previous.x + target.x) / 2.0;
2463 ctx.lineTo(midpoint, flip ? target.y : previous.y);
2464 ctx.lineTo(midpoint, flip ? previous.y : target.y);
2465 } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) {
2466 ctx.lineTo(previous.x, target.y);
2467 } else {
2468 ctx.lineTo(target.x, previous.y);
2469 }
2470 ctx.lineTo(target.x, target.y);
2471 return;
2472 }
2473
2474 if (!target.tension) {
2475 ctx.lineTo(target.x, target.y);
2476 return;
2477 }
2478
2479 ctx.bezierCurveTo(
2480 flip ? previous.controlPointPreviousX : previous.controlPointNextX,
2481 flip ? previous.controlPointPreviousY : previous.controlPointNextY,
2482 flip ? target.controlPointNextX : target.controlPointPreviousX,
2483 flip ? target.controlPointNextY : target.controlPointPreviousY,
2484 target.x,
2485 target.y);
2486 }
2487};
2488
2489var helpers_canvas = exports$1;
2490
2491// DEPRECATIONS
2492
2493/**
2494 * Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
2495 * @namespace Chart.helpers.clear
2496 * @deprecated since version 2.7.0
2497 * @todo remove at version 3
2498 * @private
2499 */
2500helpers_core.clear = exports$1.clear;
2501
2502/**
2503 * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
2504 * @namespace Chart.helpers.drawRoundedRectangle
2505 * @deprecated since version 2.7.0
2506 * @todo remove at version 3
2507 * @private
2508 */
2509helpers_core.drawRoundedRectangle = function(ctx) {
2510 ctx.beginPath();
2511 exports$1.roundedRect.apply(exports$1, arguments);
2512};
2513
2514var defaults = {
2515 /**
2516 * @private
2517 */
2518 _set: function(scope, values) {
2519 return helpers_core.merge(this[scope] || (this[scope] = {}), values);
2520 }
2521};
2522
2523defaults._set('global', {
2524 defaultColor: 'rgba(0,0,0,0.1)',
2525 defaultFontColor: '#666',
2526 defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
2527 defaultFontSize: 12,
2528 defaultFontStyle: 'normal',
2529 defaultLineHeight: 1.2,
2530 showLines: true
2531});
2532
2533var core_defaults = defaults;
2534
2535var valueOrDefault = helpers_core.valueOrDefault;
2536
2537/**
2538 * Converts the given font object into a CSS font string.
2539 * @param {Object} font - A font object.
2540 * @return {String} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
2541 * @private
2542 */
2543function toFontString(font) {
2544 if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) {
2545 return null;
2546 }
2547
2548 return (font.style ? font.style + ' ' : '')
2549 + (font.weight ? font.weight + ' ' : '')
2550 + font.size + 'px '
2551 + font.family;
2552}
2553
2554/**
2555 * @alias Chart.helpers.options
2556 * @namespace
2557 */
2558var helpers_options = {
2559 /**
2560 * Converts the given line height `value` in pixels for a specific font `size`.
2561 * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
2562 * @param {Number} size - The font size (in pixels) used to resolve relative `value`.
2563 * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid).
2564 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
2565 * @since 2.7.0
2566 */
2567 toLineHeight: function(value, size) {
2568 var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
2569 if (!matches || matches[1] === 'normal') {
2570 return size * 1.2;
2571 }
2572
2573 value = +matches[2];
2574
2575 switch (matches[3]) {
2576 case 'px':
2577 return value;
2578 case '%':
2579 value /= 100;
2580 break;
2581 default:
2582 break;
2583 }
2584
2585 return size * value;
2586 },
2587
2588 /**
2589 * Converts the given value into a padding object with pre-computed width/height.
2590 * @param {Number|Object} value - If a number, set the value to all TRBL component,
2591 * else, if and object, use defined properties and sets undefined ones to 0.
2592 * @returns {Object} The padding values (top, right, bottom, left, width, height)
2593 * @since 2.7.0
2594 */
2595 toPadding: function(value) {
2596 var t, r, b, l;
2597
2598 if (helpers_core.isObject(value)) {
2599 t = +value.top || 0;
2600 r = +value.right || 0;
2601 b = +value.bottom || 0;
2602 l = +value.left || 0;
2603 } else {
2604 t = r = b = l = +value || 0;
2605 }
2606
2607 return {
2608 top: t,
2609 right: r,
2610 bottom: b,
2611 left: l,
2612 height: t + b,
2613 width: l + r
2614 };
2615 },
2616
2617 /**
2618 * Parses font options and returns the font object.
2619 * @param {Object} options - A object that contains font options to be parsed.
2620 * @return {Object} The font object.
2621 * @todo Support font.* options and renamed to toFont().
2622 * @private
2623 */
2624 _parseFont: function(options) {
2625 var globalDefaults = core_defaults.global;
2626 var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
2627 var font = {
2628 family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily),
2629 lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size),
2630 size: size,
2631 style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle),
2632 weight: null,
2633 string: ''
2634 };
2635
2636 font.string = toFontString(font);
2637 return font;
2638 },
2639
2640 /**
2641 * Evaluates the given `inputs` sequentially and returns the first defined value.
2642 * @param {Array[]} inputs - An array of values, falling back to the last value.
2643 * @param {Object} [context] - If defined and the current value is a function, the value
2644 * is called with `context` as first argument and the result becomes the new input.
2645 * @param {Number} [index] - If defined and the current value is an array, the value
2646 * at `index` become the new input.
2647 * @since 2.7.0
2648 */
2649 resolve: function(inputs, context, index) {
2650 var i, ilen, value;
2651
2652 for (i = 0, ilen = inputs.length; i < ilen; ++i) {
2653 value = inputs[i];
2654 if (value === undefined) {
2655 continue;
2656 }
2657 if (context !== undefined && typeof value === 'function') {
2658 value = value(context);
2659 }
2660 if (index !== undefined && helpers_core.isArray(value)) {
2661 value = value[index];
2662 }
2663 if (value !== undefined) {
2664 return value;
2665 }
2666 }
2667 }
2668};
2669
2670var helpers$1 = helpers_core;
2671var easing = helpers_easing;
2672var canvas = helpers_canvas;
2673var options = helpers_options;
2674helpers$1.easing = easing;
2675helpers$1.canvas = canvas;
2676helpers$1.options = options;
2677
2678function interpolate(start, view, model, ease) {
2679 var keys = Object.keys(model);
2680 var i, ilen, key, actual, origin, target, type, c0, c1;
2681
2682 for (i = 0, ilen = keys.length; i < ilen; ++i) {
2683 key = keys[i];
2684
2685 target = model[key];
2686
2687 // if a value is added to the model after pivot() has been called, the view
2688 // doesn't contain it, so let's initialize the view to the target value.
2689 if (!view.hasOwnProperty(key)) {
2690 view[key] = target;
2691 }
2692
2693 actual = view[key];
2694
2695 if (actual === target || key[0] === '_') {
2696 continue;
2697 }
2698
2699 if (!start.hasOwnProperty(key)) {
2700 start[key] = actual;
2701 }
2702
2703 origin = start[key];
2704
2705 type = typeof target;
2706
2707 if (type === typeof origin) {
2708 if (type === 'string') {
2709 c0 = chartjsColor(origin);
2710 if (c0.valid) {
2711 c1 = chartjsColor(target);
2712 if (c1.valid) {
2713 view[key] = c1.mix(c0, ease).rgbString();
2714 continue;
2715 }
2716 }
2717 } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) {
2718 view[key] = origin + (target - origin) * ease;
2719 continue;
2720 }
2721 }
2722
2723 view[key] = target;
2724 }
2725}
2726
2727var Element = function(configuration) {
2728 helpers$1.extend(this, configuration);
2729 this.initialize.apply(this, arguments);
2730};
2731
2732helpers$1.extend(Element.prototype, {
2733
2734 initialize: function() {
2735 this.hidden = false;
2736 },
2737
2738 pivot: function() {
2739 var me = this;
2740 if (!me._view) {
2741 me._view = helpers$1.clone(me._model);
2742 }
2743 me._start = {};
2744 return me;
2745 },
2746
2747 transition: function(ease) {
2748 var me = this;
2749 var model = me._model;
2750 var start = me._start;
2751 var view = me._view;
2752
2753 // No animation -> No Transition
2754 if (!model || ease === 1) {
2755 me._view = model;
2756 me._start = null;
2757 return me;
2758 }
2759
2760 if (!view) {
2761 view = me._view = {};
2762 }
2763
2764 if (!start) {
2765 start = me._start = {};
2766 }
2767
2768 interpolate(start, view, model, ease);
2769
2770 return me;
2771 },
2772
2773 tooltipPosition: function() {
2774 return {
2775 x: this._model.x,
2776 y: this._model.y
2777 };
2778 },
2779
2780 hasValue: function() {
2781 return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y);
2782 }
2783});
2784
2785Element.extend = helpers$1.inherits;
2786
2787var core_element = Element;
2788
2789var exports$2 = core_element.extend({
2790 chart: null, // the animation associated chart instance
2791 currentStep: 0, // the current animation step
2792 numSteps: 60, // default number of steps
2793 easing: '', // the easing to use for this animation
2794 render: null, // render function used by the animation service
2795
2796 onAnimationProgress: null, // user specified callback to fire on each step of the animation
2797 onAnimationComplete: null, // user specified callback to fire when the animation finishes
2798});
2799
2800var core_animation = exports$2;
2801
2802// DEPRECATIONS
2803
2804/**
2805 * Provided for backward compatibility, use Chart.Animation instead
2806 * @prop Chart.Animation#animationObject
2807 * @deprecated since version 2.6.0
2808 * @todo remove at version 3
2809 */
2810Object.defineProperty(exports$2.prototype, 'animationObject', {
2811 get: function() {
2812 return this;
2813 }
2814});
2815
2816/**
2817 * Provided for backward compatibility, use Chart.Animation#chart instead
2818 * @prop Chart.Animation#chartInstance
2819 * @deprecated since version 2.6.0
2820 * @todo remove at version 3
2821 */
2822Object.defineProperty(exports$2.prototype, 'chartInstance', {
2823 get: function() {
2824 return this.chart;
2825 },
2826 set: function(value) {
2827 this.chart = value;
2828 }
2829});
2830
2831core_defaults._set('global', {
2832 animation: {
2833 duration: 1000,
2834 easing: 'easeOutQuart',
2835 onProgress: helpers$1.noop,
2836 onComplete: helpers$1.noop
2837 }
2838});
2839
2840var core_animations = {
2841 animations: [],
2842 request: null,
2843
2844 /**
2845 * @param {Chart} chart - The chart to animate.
2846 * @param {Chart.Animation} animation - The animation that we will animate.
2847 * @param {Number} duration - The animation duration in ms.
2848 * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
2849 */
2850 addAnimation: function(chart, animation, duration, lazy) {
2851 var animations = this.animations;
2852 var i, ilen;
2853
2854 animation.chart = chart;
2855 animation.startTime = Date.now();
2856 animation.duration = duration;
2857
2858 if (!lazy) {
2859 chart.animating = true;
2860 }
2861
2862 for (i = 0, ilen = animations.length; i < ilen; ++i) {
2863 if (animations[i].chart === chart) {
2864 animations[i] = animation;
2865 return;
2866 }
2867 }
2868
2869 animations.push(animation);
2870
2871 // If there are no animations queued, manually kickstart a digest, for lack of a better word
2872 if (animations.length === 1) {
2873 this.requestAnimationFrame();
2874 }
2875 },
2876
2877 cancelAnimation: function(chart) {
2878 var index = helpers$1.findIndex(this.animations, function(animation) {
2879 return animation.chart === chart;
2880 });
2881
2882 if (index !== -1) {
2883 this.animations.splice(index, 1);
2884 chart.animating = false;
2885 }
2886 },
2887
2888 requestAnimationFrame: function() {
2889 var me = this;
2890 if (me.request === null) {
2891 // Skip animation frame requests until the active one is executed.
2892 // This can happen when processing mouse events, e.g. 'mousemove'
2893 // and 'mouseout' events will trigger multiple renders.
2894 me.request = helpers$1.requestAnimFrame.call(window, function() {
2895 me.request = null;
2896 me.startDigest();
2897 });
2898 }
2899 },
2900
2901 /**
2902 * @private
2903 */
2904 startDigest: function() {
2905 var me = this;
2906
2907 me.advance();
2908
2909 // Do we have more stuff to animate?
2910 if (me.animations.length > 0) {
2911 me.requestAnimationFrame();
2912 }
2913 },
2914
2915 /**
2916 * @private
2917 */
2918 advance: function() {
2919 var animations = this.animations;
2920 var animation, chart;
2921 var i = 0;
2922
2923 while (i < animations.length) {
2924 animation = animations[i];
2925 chart = animation.chart;
2926
2927 animation.currentStep = Math.floor((Date.now() - animation.startTime) / animation.duration * animation.numSteps);
2928 animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
2929
2930 helpers$1.callback(animation.render, [chart, animation], chart);
2931 helpers$1.callback(animation.onAnimationProgress, [animation], chart);
2932
2933 if (animation.currentStep >= animation.numSteps) {
2934 helpers$1.callback(animation.onAnimationComplete, [animation], chart);
2935 chart.animating = false;
2936 animations.splice(i, 1);
2937 } else {
2938 ++i;
2939 }
2940 }
2941 }
2942};
2943
2944var resolve = helpers$1.options.resolve;
2945
2946var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
2947
2948/**
2949 * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
2950 * 'unshift') and notify the listener AFTER the array has been altered. Listeners are
2951 * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
2952 */
2953function listenArrayEvents(array, listener) {
2954 if (array._chartjs) {
2955 array._chartjs.listeners.push(listener);
2956 return;
2957 }
2958
2959 Object.defineProperty(array, '_chartjs', {
2960 configurable: true,
2961 enumerable: false,
2962 value: {
2963 listeners: [listener]
2964 }
2965 });
2966
2967 arrayEvents.forEach(function(key) {
2968 var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
2969 var base = array[key];
2970
2971 Object.defineProperty(array, key, {
2972 configurable: true,
2973 enumerable: false,
2974 value: function() {
2975 var args = Array.prototype.slice.call(arguments);
2976 var res = base.apply(this, args);
2977
2978 helpers$1.each(array._chartjs.listeners, function(object) {
2979 if (typeof object[method] === 'function') {
2980 object[method].apply(object, args);
2981 }
2982 });
2983
2984 return res;
2985 }
2986 });
2987 });
2988}
2989
2990/**
2991 * Removes the given array event listener and cleanup extra attached properties (such as
2992 * the _chartjs stub and overridden methods) if array doesn't have any more listeners.
2993 */
2994function unlistenArrayEvents(array, listener) {
2995 var stub = array._chartjs;
2996 if (!stub) {
2997 return;
2998 }
2999
3000 var listeners = stub.listeners;
3001 var index = listeners.indexOf(listener);
3002 if (index !== -1) {
3003 listeners.splice(index, 1);
3004 }
3005
3006 if (listeners.length > 0) {
3007 return;
3008 }
3009
3010 arrayEvents.forEach(function(key) {
3011 delete array[key];
3012 });
3013
3014 delete array._chartjs;
3015}
3016
3017// Base class for all dataset controllers (line, bar, etc)
3018var DatasetController = function(chart, datasetIndex) {
3019 this.initialize(chart, datasetIndex);
3020};
3021
3022helpers$1.extend(DatasetController.prototype, {
3023
3024 /**
3025 * Element type used to generate a meta dataset (e.g. Chart.element.Line).
3026 * @type {Chart.core.element}
3027 */
3028 datasetElementType: null,
3029
3030 /**
3031 * Element type used to generate a meta data (e.g. Chart.element.Point).
3032 * @type {Chart.core.element}
3033 */
3034 dataElementType: null,
3035
3036 initialize: function(chart, datasetIndex) {
3037 var me = this;
3038 me.chart = chart;
3039 me.index = datasetIndex;
3040 me.linkScales();
3041 me.addElements();
3042 },
3043
3044 updateIndex: function(datasetIndex) {
3045 this.index = datasetIndex;
3046 },
3047
3048 linkScales: function() {
3049 var me = this;
3050 var meta = me.getMeta();
3051 var dataset = me.getDataset();
3052
3053 if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) {
3054 meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
3055 }
3056 if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) {
3057 meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
3058 }
3059 },
3060
3061 getDataset: function() {
3062 return this.chart.data.datasets[this.index];
3063 },
3064
3065 getMeta: function() {
3066 return this.chart.getDatasetMeta(this.index);
3067 },
3068
3069 getScaleForId: function(scaleID) {
3070 return this.chart.scales[scaleID];
3071 },
3072
3073 reset: function() {
3074 this.update(true);
3075 },
3076
3077 /**
3078 * @private
3079 */
3080 destroy: function() {
3081 if (this._data) {
3082 unlistenArrayEvents(this._data, this);
3083 }
3084 },
3085
3086 createMetaDataset: function() {
3087 var me = this;
3088 var type = me.datasetElementType;
3089 return type && new type({
3090 _chart: me.chart,
3091 _datasetIndex: me.index
3092 });
3093 },
3094
3095 createMetaData: function(index) {
3096 var me = this;
3097 var type = me.dataElementType;
3098 return type && new type({
3099 _chart: me.chart,
3100 _datasetIndex: me.index,
3101 _index: index
3102 });
3103 },
3104
3105 addElements: function() {
3106 var me = this;
3107 var meta = me.getMeta();
3108 var data = me.getDataset().data || [];
3109 var metaData = meta.data;
3110 var i, ilen;
3111
3112 for (i = 0, ilen = data.length; i < ilen; ++i) {
3113 metaData[i] = metaData[i] || me.createMetaData(i);
3114 }
3115
3116 meta.dataset = meta.dataset || me.createMetaDataset();
3117 },
3118
3119 addElementAndReset: function(index) {
3120 var element = this.createMetaData(index);
3121 this.getMeta().data.splice(index, 0, element);
3122 this.updateElement(element, index, true);
3123 },
3124
3125 buildOrUpdateElements: function() {
3126 var me = this;
3127 var dataset = me.getDataset();
3128 var data = dataset.data || (dataset.data = []);
3129
3130 // In order to correctly handle data addition/deletion animation (an thus simulate
3131 // real-time charts), we need to monitor these data modifications and synchronize
3132 // the internal meta data accordingly.
3133 if (me._data !== data) {
3134 if (me._data) {
3135 // This case happens when the user replaced the data array instance.
3136 unlistenArrayEvents(me._data, me);
3137 }
3138
3139 listenArrayEvents(data, me);
3140 me._data = data;
3141 }
3142
3143 // Re-sync meta data in case the user replaced the data array or if we missed
3144 // any updates and so make sure that we handle number of datapoints changing.
3145 me.resyncElements();
3146 },
3147
3148 update: helpers$1.noop,
3149
3150 transition: function(easingValue) {
3151 var meta = this.getMeta();
3152 var elements = meta.data || [];
3153 var ilen = elements.length;
3154 var i = 0;
3155
3156 for (; i < ilen; ++i) {
3157 elements[i].transition(easingValue);
3158 }
3159
3160 if (meta.dataset) {
3161 meta.dataset.transition(easingValue);
3162 }
3163 },
3164
3165 draw: function() {
3166 var meta = this.getMeta();
3167 var elements = meta.data || [];
3168 var ilen = elements.length;
3169 var i = 0;
3170
3171 if (meta.dataset) {
3172 meta.dataset.draw();
3173 }
3174
3175 for (; i < ilen; ++i) {
3176 elements[i].draw();
3177 }
3178 },
3179
3180 removeHoverStyle: function(element) {
3181 helpers$1.merge(element._model, element.$previousStyle || {});
3182 delete element.$previousStyle;
3183 },
3184
3185 setHoverStyle: function(element) {
3186 var dataset = this.chart.data.datasets[element._datasetIndex];
3187 var index = element._index;
3188 var custom = element.custom || {};
3189 var model = element._model;
3190 var getHoverColor = helpers$1.getHoverColor;
3191
3192 element.$previousStyle = {
3193 backgroundColor: model.backgroundColor,
3194 borderColor: model.borderColor,
3195 borderWidth: model.borderWidth
3196 };
3197
3198 model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index);
3199 model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index);
3200 model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index);
3201 },
3202
3203 /**
3204 * @private
3205 */
3206 resyncElements: function() {
3207 var me = this;
3208 var meta = me.getMeta();
3209 var data = me.getDataset().data;
3210 var numMeta = meta.data.length;
3211 var numData = data.length;
3212
3213 if (numData < numMeta) {
3214 meta.data.splice(numData, numMeta - numData);
3215 } else if (numData > numMeta) {
3216 me.insertElements(numMeta, numData - numMeta);
3217 }
3218 },
3219
3220 /**
3221 * @private
3222 */
3223 insertElements: function(start, count) {
3224 for (var i = 0; i < count; ++i) {
3225 this.addElementAndReset(start + i);
3226 }
3227 },
3228
3229 /**
3230 * @private
3231 */
3232 onDataPush: function() {
3233 this.insertElements(this.getDataset().data.length - 1, arguments.length);
3234 },
3235
3236 /**
3237 * @private
3238 */
3239 onDataPop: function() {
3240 this.getMeta().data.pop();
3241 },
3242
3243 /**
3244 * @private
3245 */
3246 onDataShift: function() {
3247 this.getMeta().data.shift();
3248 },
3249
3250 /**
3251 * @private
3252 */
3253 onDataSplice: function(start, count) {
3254 this.getMeta().data.splice(start, count);
3255 this.insertElements(start, arguments.length - 2);
3256 },
3257
3258 /**
3259 * @private
3260 */
3261 onDataUnshift: function() {
3262 this.insertElements(0, arguments.length);
3263 }
3264});
3265
3266DatasetController.extend = helpers$1.inherits;
3267
3268var core_datasetController = DatasetController;
3269
3270core_defaults._set('global', {
3271 elements: {
3272 arc: {
3273 backgroundColor: core_defaults.global.defaultColor,
3274 borderColor: '#fff',
3275 borderWidth: 2,
3276 borderAlign: 'center'
3277 }
3278 }
3279});
3280
3281var element_arc = core_element.extend({
3282 inLabelRange: function(mouseX) {
3283 var vm = this._view;
3284
3285 if (vm) {
3286 return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
3287 }
3288 return false;
3289 },
3290
3291 inRange: function(chartX, chartY) {
3292 var vm = this._view;
3293
3294 if (vm) {
3295 var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY});
3296 var angle = pointRelativePosition.angle;
3297 var distance = pointRelativePosition.distance;
3298
3299 // Sanitise angle range
3300 var startAngle = vm.startAngle;
3301 var endAngle = vm.endAngle;
3302 while (endAngle < startAngle) {
3303 endAngle += 2.0 * Math.PI;
3304 }
3305 while (angle > endAngle) {
3306 angle -= 2.0 * Math.PI;
3307 }
3308 while (angle < startAngle) {
3309 angle += 2.0 * Math.PI;
3310 }
3311
3312 // Check if within the range of the open/close angle
3313 var betweenAngles = (angle >= startAngle && angle <= endAngle);
3314 var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
3315
3316 return (betweenAngles && withinRadius);
3317 }
3318 return false;
3319 },
3320
3321 getCenterPoint: function() {
3322 var vm = this._view;
3323 var halfAngle = (vm.startAngle + vm.endAngle) / 2;
3324 var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
3325 return {
3326 x: vm.x + Math.cos(halfAngle) * halfRadius,
3327 y: vm.y + Math.sin(halfAngle) * halfRadius
3328 };
3329 },
3330
3331 getArea: function() {
3332 var vm = this._view;
3333 return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
3334 },
3335
3336 tooltipPosition: function() {
3337 var vm = this._view;
3338 var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
3339 var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
3340
3341 return {
3342 x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
3343 y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
3344 };
3345 },
3346
3347 draw: function() {
3348 var ctx = this._chart.ctx;
3349 var vm = this._view;
3350 var sA = vm.startAngle;
3351 var eA = vm.endAngle;
3352 var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0;
3353 var angleMargin;
3354
3355 ctx.save();
3356
3357 ctx.beginPath();
3358 ctx.arc(vm.x, vm.y, vm.outerRadius - pixelMargin, sA, eA);
3359 ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
3360 ctx.closePath();
3361
3362 ctx.fillStyle = vm.backgroundColor;
3363 ctx.fill();
3364
3365 if (vm.borderWidth) {
3366 if (vm.borderAlign === 'inner') {
3367 // Draw an inner border by cliping the arc and drawing a double-width border
3368 // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
3369 ctx.beginPath();
3370 angleMargin = pixelMargin / vm.outerRadius;
3371 ctx.arc(vm.x, vm.y, vm.outerRadius, sA - angleMargin, eA + angleMargin);
3372 if (vm.innerRadius > pixelMargin) {
3373 angleMargin = pixelMargin / vm.innerRadius;
3374 ctx.arc(vm.x, vm.y, vm.innerRadius - pixelMargin, eA + angleMargin, sA - angleMargin, true);
3375 } else {
3376 ctx.arc(vm.x, vm.y, pixelMargin, eA + Math.PI / 2, sA - Math.PI / 2);
3377 }
3378 ctx.closePath();
3379 ctx.clip();
3380
3381 ctx.beginPath();
3382 ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
3383 ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
3384 ctx.closePath();
3385
3386 ctx.lineWidth = vm.borderWidth * 2;
3387 ctx.lineJoin = 'round';
3388 } else {
3389 ctx.lineWidth = vm.borderWidth;
3390 ctx.lineJoin = 'bevel';
3391 }
3392
3393 ctx.strokeStyle = vm.borderColor;
3394 ctx.stroke();
3395 }
3396
3397 ctx.restore();
3398 }
3399});
3400
3401var valueOrDefault$1 = helpers$1.valueOrDefault;
3402
3403var defaultColor = core_defaults.global.defaultColor;
3404
3405core_defaults._set('global', {
3406 elements: {
3407 line: {
3408 tension: 0.4,
3409 backgroundColor: defaultColor,
3410 borderWidth: 3,
3411 borderColor: defaultColor,
3412 borderCapStyle: 'butt',
3413 borderDash: [],
3414 borderDashOffset: 0.0,
3415 borderJoinStyle: 'miter',
3416 capBezierPoints: true,
3417 fill: true, // do we fill in the area between the line and its base axis
3418 }
3419 }
3420});
3421
3422var element_line = core_element.extend({
3423 draw: function() {
3424 var me = this;
3425 var vm = me._view;
3426 var ctx = me._chart.ctx;
3427 var spanGaps = vm.spanGaps;
3428 var points = me._children.slice(); // clone array
3429 var globalDefaults = core_defaults.global;
3430 var globalOptionLineElements = globalDefaults.elements.line;
3431 var lastDrawnIndex = -1;
3432 var index, current, previous, currentVM;
3433
3434 // If we are looping, adding the first point again
3435 if (me._loop && points.length) {
3436 points.push(points[0]);
3437 }
3438
3439 ctx.save();
3440
3441 // Stroke Line Options
3442 ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
3443
3444 // IE 9 and 10 do not support line dash
3445 if (ctx.setLineDash) {
3446 ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
3447 }
3448
3449 ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset);
3450 ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
3451 ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth);
3452 ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
3453
3454 // Stroke Line
3455 ctx.beginPath();
3456 lastDrawnIndex = -1;
3457
3458 for (index = 0; index < points.length; ++index) {
3459 current = points[index];
3460 previous = helpers$1.previousItem(points, index);
3461 currentVM = current._view;
3462
3463 // First point moves to it's starting position no matter what
3464 if (index === 0) {
3465 if (!currentVM.skip) {
3466 ctx.moveTo(currentVM.x, currentVM.y);
3467 lastDrawnIndex = index;
3468 }
3469 } else {
3470 previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
3471
3472 if (!currentVM.skip) {
3473 if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
3474 // There was a gap and this is the first point after the gap
3475 ctx.moveTo(currentVM.x, currentVM.y);
3476 } else {
3477 // Line to next point
3478 helpers$1.canvas.lineTo(ctx, previous._view, current._view);
3479 }
3480 lastDrawnIndex = index;
3481 }
3482 }
3483 }
3484
3485 ctx.stroke();
3486 ctx.restore();
3487 }
3488});
3489
3490var valueOrDefault$2 = helpers$1.valueOrDefault;
3491
3492var defaultColor$1 = core_defaults.global.defaultColor;
3493
3494core_defaults._set('global', {
3495 elements: {
3496 point: {
3497 radius: 3,
3498 pointStyle: 'circle',
3499 backgroundColor: defaultColor$1,
3500 borderColor: defaultColor$1,
3501 borderWidth: 1,
3502 // Hover
3503 hitRadius: 1,
3504 hoverRadius: 4,
3505 hoverBorderWidth: 1
3506 }
3507 }
3508});
3509
3510function xRange(mouseX) {
3511 var vm = this._view;
3512 return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false;
3513}
3514
3515function yRange(mouseY) {
3516 var vm = this._view;
3517 return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false;
3518}
3519
3520var element_point = core_element.extend({
3521 inRange: function(mouseX, mouseY) {
3522 var vm = this._view;
3523 return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
3524 },
3525
3526 inLabelRange: xRange,
3527 inXRange: xRange,
3528 inYRange: yRange,
3529
3530 getCenterPoint: function() {
3531 var vm = this._view;
3532 return {
3533 x: vm.x,
3534 y: vm.y
3535 };
3536 },
3537
3538 getArea: function() {
3539 return Math.PI * Math.pow(this._view.radius, 2);
3540 },
3541
3542 tooltipPosition: function() {
3543 var vm = this._view;
3544 return {
3545 x: vm.x,
3546 y: vm.y,
3547 padding: vm.radius + vm.borderWidth
3548 };
3549 },
3550
3551 draw: function(chartArea) {
3552 var vm = this._view;
3553 var ctx = this._chart.ctx;
3554 var pointStyle = vm.pointStyle;
3555 var rotation = vm.rotation;
3556 var radius = vm.radius;
3557 var x = vm.x;
3558 var y = vm.y;
3559 var globalDefaults = core_defaults.global;
3560 var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow
3561
3562 if (vm.skip) {
3563 return;
3564 }
3565
3566 // Clipping for Points.
3567 if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) {
3568 ctx.strokeStyle = vm.borderColor || defaultColor;
3569 ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth);
3570 ctx.fillStyle = vm.backgroundColor || defaultColor;
3571 helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation);
3572 }
3573 }
3574});
3575
3576var defaultColor$2 = core_defaults.global.defaultColor;
3577
3578core_defaults._set('global', {
3579 elements: {
3580 rectangle: {
3581 backgroundColor: defaultColor$2,
3582 borderColor: defaultColor$2,
3583 borderSkipped: 'bottom',
3584 borderWidth: 0
3585 }
3586 }
3587});
3588
3589function isVertical(bar) {
3590 return bar._view.width !== undefined;
3591}
3592
3593/**
3594 * Helper function to get the bounds of the bar regardless of the orientation
3595 * @param bar {Chart.Element.Rectangle} the bar
3596 * @return {Bounds} bounds of the bar
3597 * @private
3598 */
3599function getBarBounds(bar) {
3600 var vm = bar._view;
3601 var x1, x2, y1, y2;
3602
3603 if (isVertical(bar)) {
3604 // vertical
3605 var halfWidth = vm.width / 2;
3606 x1 = vm.x - halfWidth;
3607 x2 = vm.x + halfWidth;
3608 y1 = Math.min(vm.y, vm.base);
3609 y2 = Math.max(vm.y, vm.base);
3610 } else {
3611 // horizontal bar
3612 var halfHeight = vm.height / 2;
3613 x1 = Math.min(vm.x, vm.base);
3614 x2 = Math.max(vm.x, vm.base);
3615 y1 = vm.y - halfHeight;
3616 y2 = vm.y + halfHeight;
3617 }
3618
3619 return {
3620 left: x1,
3621 top: y1,
3622 right: x2,
3623 bottom: y2
3624 };
3625}
3626
3627var element_rectangle = core_element.extend({
3628 draw: function() {
3629 var ctx = this._chart.ctx;
3630 var vm = this._view;
3631 var left, right, top, bottom, signX, signY, borderSkipped;
3632 var borderWidth = vm.borderWidth;
3633
3634 if (!vm.horizontal) {
3635 // bar
3636 left = vm.x - vm.width / 2;
3637 right = vm.x + vm.width / 2;
3638 top = vm.y;
3639 bottom = vm.base;
3640 signX = 1;
3641 signY = bottom > top ? 1 : -1;
3642 borderSkipped = vm.borderSkipped || 'bottom';
3643 } else {
3644 // horizontal bar
3645 left = vm.base;
3646 right = vm.x;
3647 top = vm.y - vm.height / 2;
3648 bottom = vm.y + vm.height / 2;
3649 signX = right > left ? 1 : -1;
3650 signY = 1;
3651 borderSkipped = vm.borderSkipped || 'left';
3652 }
3653
3654 // Canvas doesn't allow us to stroke inside the width so we can
3655 // adjust the sizes to fit if we're setting a stroke on the line
3656 if (borderWidth) {
3657 // borderWidth shold be less than bar width and bar height.
3658 var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
3659 borderWidth = borderWidth > barSize ? barSize : borderWidth;
3660 var halfStroke = borderWidth / 2;
3661 // Adjust borderWidth when bar top position is near vm.base(zero).
3662 var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
3663 var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
3664 var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
3665 var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
3666 // not become a vertical line?
3667 if (borderLeft !== borderRight) {
3668 top = borderTop;
3669 bottom = borderBottom;
3670 }
3671 // not become a horizontal line?
3672 if (borderTop !== borderBottom) {
3673 left = borderLeft;
3674 right = borderRight;
3675 }
3676 }
3677
3678 ctx.beginPath();
3679 ctx.fillStyle = vm.backgroundColor;
3680 ctx.strokeStyle = vm.borderColor;
3681 ctx.lineWidth = borderWidth;
3682
3683 // Corner points, from bottom-left to bottom-right clockwise
3684 // | 1 2 |
3685 // | 0 3 |
3686 var corners = [
3687 [left, bottom],
3688 [left, top],
3689 [right, top],
3690 [right, bottom]
3691 ];
3692
3693 // Find first (starting) corner with fallback to 'bottom'
3694 var borders = ['bottom', 'left', 'top', 'right'];
3695 var startCorner = borders.indexOf(borderSkipped, 0);
3696 if (startCorner === -1) {
3697 startCorner = 0;
3698 }
3699
3700 function cornerAt(index) {
3701 return corners[(startCorner + index) % 4];
3702 }
3703
3704 // Draw rectangle from 'startCorner'
3705 var corner = cornerAt(0);
3706 ctx.moveTo(corner[0], corner[1]);
3707
3708 for (var i = 1; i < 4; i++) {
3709 corner = cornerAt(i);
3710 ctx.lineTo(corner[0], corner[1]);
3711 }
3712
3713 ctx.fill();
3714 if (borderWidth) {
3715 ctx.stroke();
3716 }
3717 },
3718
3719 height: function() {
3720 var vm = this._view;
3721 return vm.base - vm.y;
3722 },
3723
3724 inRange: function(mouseX, mouseY) {
3725 var inRange = false;
3726
3727 if (this._view) {
3728 var bounds = getBarBounds(this);
3729 inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom;
3730 }
3731
3732 return inRange;
3733 },
3734
3735 inLabelRange: function(mouseX, mouseY) {
3736 var me = this;
3737 if (!me._view) {
3738 return false;
3739 }
3740
3741 var inRange = false;
3742 var bounds = getBarBounds(me);
3743
3744 if (isVertical(me)) {
3745 inRange = mouseX >= bounds.left && mouseX <= bounds.right;
3746 } else {
3747 inRange = mouseY >= bounds.top && mouseY <= bounds.bottom;
3748 }
3749
3750 return inRange;
3751 },
3752
3753 inXRange: function(mouseX) {
3754 var bounds = getBarBounds(this);
3755 return mouseX >= bounds.left && mouseX <= bounds.right;
3756 },
3757
3758 inYRange: function(mouseY) {
3759 var bounds = getBarBounds(this);
3760 return mouseY >= bounds.top && mouseY <= bounds.bottom;
3761 },
3762
3763 getCenterPoint: function() {
3764 var vm = this._view;
3765 var x, y;
3766 if (isVertical(this)) {
3767 x = vm.x;
3768 y = (vm.y + vm.base) / 2;
3769 } else {
3770 x = (vm.x + vm.base) / 2;
3771 y = vm.y;
3772 }
3773
3774 return {x: x, y: y};
3775 },
3776
3777 getArea: function() {
3778 var vm = this._view;
3779 return vm.width * Math.abs(vm.y - vm.base);
3780 },
3781
3782 tooltipPosition: function() {
3783 var vm = this._view;
3784 return {
3785 x: vm.x,
3786 y: vm.y
3787 };
3788 }
3789});
3790
3791var elements = {};
3792var Arc = element_arc;
3793var Line = element_line;
3794var Point = element_point;
3795var Rectangle = element_rectangle;
3796elements.Arc = Arc;
3797elements.Line = Line;
3798elements.Point = Point;
3799elements.Rectangle = Rectangle;
3800
3801var resolve$1 = helpers$1.options.resolve;
3802
3803core_defaults._set('bar', {
3804 hover: {
3805 mode: 'label'
3806 },
3807
3808 scales: {
3809 xAxes: [{
3810 type: 'category',
3811 categoryPercentage: 0.8,
3812 barPercentage: 0.9,
3813 offset: true,
3814 gridLines: {
3815 offsetGridLines: true
3816 }
3817 }],
3818
3819 yAxes: [{
3820 type: 'linear'
3821 }]
3822 }
3823});
3824
3825/**
3826 * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap.
3827 * @private
3828 */
3829function computeMinSampleSize(scale, pixels) {
3830 var min = scale.isHorizontal() ? scale.width : scale.height;
3831 var ticks = scale.getTicks();
3832 var prev, curr, i, ilen;
3833
3834 for (i = 1, ilen = pixels.length; i < ilen; ++i) {
3835 min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1]));
3836 }
3837
3838 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
3839 curr = scale.getPixelForTick(i);
3840 min = i > 0 ? Math.min(min, curr - prev) : min;
3841 prev = curr;
3842 }
3843
3844 return min;
3845}
3846
3847/**
3848 * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null,
3849 * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This
3850 * mode currently always generates bars equally sized (until we introduce scriptable options?).
3851 * @private
3852 */
3853function computeFitCategoryTraits(index, ruler, options) {
3854 var thickness = options.barThickness;
3855 var count = ruler.stackCount;
3856 var curr = ruler.pixels[index];
3857 var size, ratio;
3858
3859 if (helpers$1.isNullOrUndef(thickness)) {
3860 size = ruler.min * options.categoryPercentage;
3861 ratio = options.barPercentage;
3862 } else {
3863 // When bar thickness is enforced, category and bar percentages are ignored.
3864 // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%')
3865 // and deprecate barPercentage since this value is ignored when thickness is absolute.
3866 size = thickness * count;
3867 ratio = 1;
3868 }
3869
3870 return {
3871 chunk: size / count,
3872 ratio: ratio,
3873 start: curr - (size / 2)
3874 };
3875}
3876
3877/**
3878 * Computes an "optimal" category that globally arranges bars side by side (no gap when
3879 * percentage options are 1), based on the previous and following categories. This mode
3880 * generates bars with different widths when data are not evenly spaced.
3881 * @private
3882 */
3883function computeFlexCategoryTraits(index, ruler, options) {
3884 var pixels = ruler.pixels;
3885 var curr = pixels[index];
3886 var prev = index > 0 ? pixels[index - 1] : null;
3887 var next = index < pixels.length - 1 ? pixels[index + 1] : null;
3888 var percent = options.categoryPercentage;
3889 var start, size;
3890
3891 if (prev === null) {
3892 // first data: its size is double based on the next point or,
3893 // if it's also the last data, we use the scale size.
3894 prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
3895 }
3896
3897 if (next === null) {
3898 // last data: its size is also double based on the previous point.
3899 next = curr + curr - prev;
3900 }
3901
3902 start = curr - (curr - Math.min(prev, next)) / 2 * percent;
3903 size = Math.abs(next - prev) / 2 * percent;
3904
3905 return {
3906 chunk: size / ruler.stackCount,
3907 ratio: options.barPercentage,
3908 start: start
3909 };
3910}
3911
3912var controller_bar = core_datasetController.extend({
3913
3914 dataElementType: elements.Rectangle,
3915
3916 initialize: function() {
3917 var me = this;
3918 var meta;
3919
3920 core_datasetController.prototype.initialize.apply(me, arguments);
3921
3922 meta = me.getMeta();
3923 meta.stack = me.getDataset().stack;
3924 meta.bar = true;
3925 },
3926
3927 update: function(reset) {
3928 var me = this;
3929 var rects = me.getMeta().data;
3930 var i, ilen;
3931
3932 me._ruler = me.getRuler();
3933
3934 for (i = 0, ilen = rects.length; i < ilen; ++i) {
3935 me.updateElement(rects[i], i, reset);
3936 }
3937 },
3938
3939 updateElement: function(rectangle, index, reset) {
3940 var me = this;
3941 var meta = me.getMeta();
3942 var dataset = me.getDataset();
3943 var options = me._resolveElementOptions(rectangle, index);
3944
3945 rectangle._xScale = me.getScaleForId(meta.xAxisID);
3946 rectangle._yScale = me.getScaleForId(meta.yAxisID);
3947 rectangle._datasetIndex = me.index;
3948 rectangle._index = index;
3949 rectangle._model = {
3950 backgroundColor: options.backgroundColor,
3951 borderColor: options.borderColor,
3952 borderSkipped: options.borderSkipped,
3953 borderWidth: options.borderWidth,
3954 datasetLabel: dataset.label,
3955 label: me.chart.data.labels[index]
3956 };
3957
3958 me._updateElementGeometry(rectangle, index, reset);
3959
3960 rectangle.pivot();
3961 },
3962
3963 /**
3964 * @private
3965 */
3966 _updateElementGeometry: function(rectangle, index, reset) {
3967 var me = this;
3968 var model = rectangle._model;
3969 var vscale = me.getValueScale();
3970 var base = vscale.getBasePixel();
3971 var horizontal = vscale.isHorizontal();
3972 var ruler = me._ruler || me.getRuler();
3973 var vpixels = me.calculateBarValuePixels(me.index, index);
3974 var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
3975
3976 model.horizontal = horizontal;
3977 model.base = reset ? base : vpixels.base;
3978 model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
3979 model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
3980 model.height = horizontal ? ipixels.size : undefined;
3981 model.width = horizontal ? undefined : ipixels.size;
3982 },
3983
3984 /**
3985 * @private
3986 */
3987 getValueScaleId: function() {
3988 return this.getMeta().yAxisID;
3989 },
3990
3991 /**
3992 * @private
3993 */
3994 getIndexScaleId: function() {
3995 return this.getMeta().xAxisID;
3996 },
3997
3998 /**
3999 * @private
4000 */
4001 getValueScale: function() {
4002 return this.getScaleForId(this.getValueScaleId());
4003 },
4004
4005 /**
4006 * @private
4007 */
4008 getIndexScale: function() {
4009 return this.getScaleForId(this.getIndexScaleId());
4010 },
4011
4012 /**
4013 * Returns the stacks based on groups and bar visibility.
4014 * @param {Number} [last] - The dataset index
4015 * @returns {Array} The stack list
4016 * @private
4017 */
4018 _getStacks: function(last) {
4019 var me = this;
4020 var chart = me.chart;
4021 var scale = me.getIndexScale();
4022 var stacked = scale.options.stacked;
4023 var ilen = last === undefined ? chart.data.datasets.length : last + 1;
4024 var stacks = [];
4025 var i, meta;
4026
4027 for (i = 0; i < ilen; ++i) {
4028 meta = chart.getDatasetMeta(i);
4029 if (meta.bar && chart.isDatasetVisible(i) &&
4030 (stacked === false ||
4031 (stacked === true && stacks.indexOf(meta.stack) === -1) ||
4032 (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
4033 stacks.push(meta.stack);
4034 }
4035 }
4036
4037 return stacks;
4038 },
4039
4040 /**
4041 * Returns the effective number of stacks based on groups and bar visibility.
4042 * @private
4043 */
4044 getStackCount: function() {
4045 return this._getStacks().length;
4046 },
4047
4048 /**
4049 * Returns the stack index for the given dataset based on groups and bar visibility.
4050 * @param {Number} [datasetIndex] - The dataset index
4051 * @param {String} [name] - The stack name to find
4052 * @returns {Number} The stack index
4053 * @private
4054 */
4055 getStackIndex: function(datasetIndex, name) {
4056 var stacks = this._getStacks(datasetIndex);
4057 var index = (name !== undefined)
4058 ? stacks.indexOf(name)
4059 : -1; // indexOf returns -1 if element is not present
4060
4061 return (index === -1)
4062 ? stacks.length - 1
4063 : index;
4064 },
4065
4066 /**
4067 * @private
4068 */
4069 getRuler: function() {
4070 var me = this;
4071 var scale = me.getIndexScale();
4072 var stackCount = me.getStackCount();
4073 var datasetIndex = me.index;
4074 var isHorizontal = scale.isHorizontal();
4075 var start = isHorizontal ? scale.left : scale.top;
4076 var end = start + (isHorizontal ? scale.width : scale.height);
4077 var pixels = [];
4078 var i, ilen, min;
4079
4080 for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
4081 pixels.push(scale.getPixelForValue(null, i, datasetIndex));
4082 }
4083
4084 min = helpers$1.isNullOrUndef(scale.options.barThickness)
4085 ? computeMinSampleSize(scale, pixels)
4086 : -1;
4087
4088 return {
4089 min: min,
4090 pixels: pixels,
4091 start: start,
4092 end: end,
4093 stackCount: stackCount,
4094 scale: scale
4095 };
4096 },
4097
4098 /**
4099 * Note: pixel values are not clamped to the scale area.
4100 * @private
4101 */
4102 calculateBarValuePixels: function(datasetIndex, index) {
4103 var me = this;
4104 var chart = me.chart;
4105 var meta = me.getMeta();
4106 var scale = me.getValueScale();
4107 var isHorizontal = scale.isHorizontal();
4108 var datasets = chart.data.datasets;
4109 var value = +scale.getRightValue(datasets[datasetIndex].data[index]);
4110 var minBarLength = scale.options.minBarLength;
4111 var stacked = scale.options.stacked;
4112 var stack = meta.stack;
4113 var start = 0;
4114 var i, imeta, ivalue, base, head, size;
4115
4116 if (stacked || (stacked === undefined && stack !== undefined)) {
4117 for (i = 0; i < datasetIndex; ++i) {
4118 imeta = chart.getDatasetMeta(i);
4119
4120 if (imeta.bar &&
4121 imeta.stack === stack &&
4122 imeta.controller.getValueScaleId() === scale.id &&
4123 chart.isDatasetVisible(i)) {
4124
4125 ivalue = +scale.getRightValue(datasets[i].data[index]);
4126 if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
4127 start += ivalue;
4128 }
4129 }
4130 }
4131 }
4132
4133 base = scale.getPixelForValue(start);
4134 head = scale.getPixelForValue(start + value);
4135 size = head - base;
4136
4137 if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
4138 size = minBarLength;
4139 if (value >= 0 && !isHorizontal || value < 0 && isHorizontal) {
4140 head = base - minBarLength;
4141 } else {
4142 head = base + minBarLength;
4143 }
4144 }
4145
4146 return {
4147 size: size,
4148 base: base,
4149 head: head,
4150 center: head + size / 2
4151 };
4152 },
4153
4154 /**
4155 * @private
4156 */
4157 calculateBarIndexPixels: function(datasetIndex, index, ruler) {
4158 var me = this;
4159 var options = ruler.scale.options;
4160 var range = options.barThickness === 'flex'
4161 ? computeFlexCategoryTraits(index, ruler, options)
4162 : computeFitCategoryTraits(index, ruler, options);
4163
4164 var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);
4165 var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
4166 var size = Math.min(
4167 helpers$1.valueOrDefault(options.maxBarThickness, Infinity),
4168 range.chunk * range.ratio);
4169
4170 return {
4171 base: center - size / 2,
4172 head: center + size / 2,
4173 center: center,
4174 size: size
4175 };
4176 },
4177
4178 draw: function() {
4179 var me = this;
4180 var chart = me.chart;
4181 var scale = me.getValueScale();
4182 var rects = me.getMeta().data;
4183 var dataset = me.getDataset();
4184 var ilen = rects.length;
4185 var i = 0;
4186
4187 helpers$1.canvas.clipArea(chart.ctx, chart.chartArea);
4188
4189 for (; i < ilen; ++i) {
4190 if (!isNaN(scale.getRightValue(dataset.data[i]))) {
4191 rects[i].draw();
4192 }
4193 }
4194
4195 helpers$1.canvas.unclipArea(chart.ctx);
4196 },
4197
4198 /**
4199 * @private
4200 */
4201 _resolveElementOptions: function(rectangle, index) {
4202 var me = this;
4203 var chart = me.chart;
4204 var datasets = chart.data.datasets;
4205 var dataset = datasets[me.index];
4206 var custom = rectangle.custom || {};
4207 var options = chart.options.elements.rectangle;
4208 var values = {};
4209 var i, ilen, key;
4210
4211 // Scriptable options
4212 var context = {
4213 chart: chart,
4214 dataIndex: index,
4215 dataset: dataset,
4216 datasetIndex: me.index
4217 };
4218
4219 var keys = [
4220 'backgroundColor',
4221 'borderColor',
4222 'borderSkipped',
4223 'borderWidth'
4224 ];
4225
4226 for (i = 0, ilen = keys.length; i < ilen; ++i) {
4227 key = keys[i];
4228 values[key] = resolve$1([
4229 custom[key],
4230 dataset[key],
4231 options[key]
4232 ], context, index);
4233 }
4234
4235 return values;
4236 }
4237});
4238
4239var valueOrDefault$3 = helpers$1.valueOrDefault;
4240var resolve$2 = helpers$1.options.resolve;
4241
4242core_defaults._set('bubble', {
4243 hover: {
4244 mode: 'single'
4245 },
4246
4247 scales: {
4248 xAxes: [{
4249 type: 'linear', // bubble should probably use a linear scale by default
4250 position: 'bottom',
4251 id: 'x-axis-0' // need an ID so datasets can reference the scale
4252 }],
4253 yAxes: [{
4254 type: 'linear',
4255 position: 'left',
4256 id: 'y-axis-0'
4257 }]
4258 },
4259
4260 tooltips: {
4261 callbacks: {
4262 title: function() {
4263 // Title doesn't make sense for scatter since we format the data as a point
4264 return '';
4265 },
4266 label: function(item, data) {
4267 var datasetLabel = data.datasets[item.datasetIndex].label || '';
4268 var dataPoint = data.datasets[item.datasetIndex].data[item.index];
4269 return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
4270 }
4271 }
4272 }
4273});
4274
4275var controller_bubble = core_datasetController.extend({
4276 /**
4277 * @protected
4278 */
4279 dataElementType: elements.Point,
4280
4281 /**
4282 * @protected
4283 */
4284 update: function(reset) {
4285 var me = this;
4286 var meta = me.getMeta();
4287 var points = meta.data;
4288
4289 // Update Points
4290 helpers$1.each(points, function(point, index) {
4291 me.updateElement(point, index, reset);
4292 });
4293 },
4294
4295 /**
4296 * @protected
4297 */
4298 updateElement: function(point, index, reset) {
4299 var me = this;
4300 var meta = me.getMeta();
4301 var custom = point.custom || {};
4302 var xScale = me.getScaleForId(meta.xAxisID);
4303 var yScale = me.getScaleForId(meta.yAxisID);
4304 var options = me._resolveElementOptions(point, index);
4305 var data = me.getDataset().data[index];
4306 var dsIndex = me.index;
4307
4308 var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
4309 var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
4310
4311 point._xScale = xScale;
4312 point._yScale = yScale;
4313 point._options = options;
4314 point._datasetIndex = dsIndex;
4315 point._index = index;
4316 point._model = {
4317 backgroundColor: options.backgroundColor,
4318 borderColor: options.borderColor,
4319 borderWidth: options.borderWidth,
4320 hitRadius: options.hitRadius,
4321 pointStyle: options.pointStyle,
4322 rotation: options.rotation,
4323 radius: reset ? 0 : options.radius,
4324 skip: custom.skip || isNaN(x) || isNaN(y),
4325 x: x,
4326 y: y,
4327 };
4328
4329 point.pivot();
4330 },
4331
4332 /**
4333 * @protected
4334 */
4335 setHoverStyle: function(point) {
4336 var model = point._model;
4337 var options = point._options;
4338 var getHoverColor = helpers$1.getHoverColor;
4339
4340 point.$previousStyle = {
4341 backgroundColor: model.backgroundColor,
4342 borderColor: model.borderColor,
4343 borderWidth: model.borderWidth,
4344 radius: model.radius
4345 };
4346
4347 model.backgroundColor = valueOrDefault$3(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
4348 model.borderColor = valueOrDefault$3(options.hoverBorderColor, getHoverColor(options.borderColor));
4349 model.borderWidth = valueOrDefault$3(options.hoverBorderWidth, options.borderWidth);
4350 model.radius = options.radius + options.hoverRadius;
4351 },
4352
4353 /**
4354 * @private
4355 */
4356 _resolveElementOptions: function(point, index) {
4357 var me = this;
4358 var chart = me.chart;
4359 var datasets = chart.data.datasets;
4360 var dataset = datasets[me.index];
4361 var custom = point.custom || {};
4362 var options = chart.options.elements.point;
4363 var data = dataset.data[index];
4364 var values = {};
4365 var i, ilen, key;
4366
4367 // Scriptable options
4368 var context = {
4369 chart: chart,
4370 dataIndex: index,
4371 dataset: dataset,
4372 datasetIndex: me.index
4373 };
4374
4375 var keys = [
4376 'backgroundColor',
4377 'borderColor',
4378 'borderWidth',
4379 'hoverBackgroundColor',
4380 'hoverBorderColor',
4381 'hoverBorderWidth',
4382 'hoverRadius',
4383 'hitRadius',
4384 'pointStyle',
4385 'rotation'
4386 ];
4387
4388 for (i = 0, ilen = keys.length; i < ilen; ++i) {
4389 key = keys[i];
4390 values[key] = resolve$2([
4391 custom[key],
4392 dataset[key],
4393 options[key]
4394 ], context, index);
4395 }
4396
4397 // Custom radius resolution
4398 values.radius = resolve$2([
4399 custom.radius,
4400 data ? data.r : undefined,
4401 dataset.radius,
4402 options.radius
4403 ], context, index);
4404
4405 return values;
4406 }
4407});
4408
4409var resolve$3 = helpers$1.options.resolve;
4410
4411core_defaults._set('doughnut', {
4412 animation: {
4413 // Boolean - Whether we animate the rotation of the Doughnut
4414 animateRotate: true,
4415 // Boolean - Whether we animate scaling the Doughnut from the centre
4416 animateScale: false
4417 },
4418 hover: {
4419 mode: 'single'
4420 },
4421 legendCallback: function(chart) {
4422 var text = [];
4423 text.push('<ul class="' + chart.id + '-legend">');
4424
4425 var data = chart.data;
4426 var datasets = data.datasets;
4427 var labels = data.labels;
4428
4429 if (datasets.length) {
4430 for (var i = 0; i < datasets[0].data.length; ++i) {
4431 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
4432 if (labels[i]) {
4433 text.push(labels[i]);
4434 }
4435 text.push('</li>');
4436 }
4437 }
4438
4439 text.push('</ul>');
4440 return text.join('');
4441 },
4442 legend: {
4443 labels: {
4444 generateLabels: function(chart) {
4445 var data = chart.data;
4446 if (data.labels.length && data.datasets.length) {
4447 return data.labels.map(function(label, i) {
4448 var meta = chart.getDatasetMeta(0);
4449 var ds = data.datasets[0];
4450 var arc = meta.data[i];
4451 var custom = arc && arc.custom || {};
4452 var arcOpts = chart.options.elements.arc;
4453 var fill = resolve$3([custom.backgroundColor, ds.backgroundColor, arcOpts.backgroundColor], undefined, i);
4454 var stroke = resolve$3([custom.borderColor, ds.borderColor, arcOpts.borderColor], undefined, i);
4455 var bw = resolve$3([custom.borderWidth, ds.borderWidth, arcOpts.borderWidth], undefined, i);
4456
4457 return {
4458 text: label,
4459 fillStyle: fill,
4460 strokeStyle: stroke,
4461 lineWidth: bw,
4462 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
4463
4464 // Extra data used for toggling the correct item
4465 index: i
4466 };
4467 });
4468 }
4469 return [];
4470 }
4471 },
4472
4473 onClick: function(e, legendItem) {
4474 var index = legendItem.index;
4475 var chart = this.chart;
4476 var i, ilen, meta;
4477
4478 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
4479 meta = chart.getDatasetMeta(i);
4480 // toggle visibility of index if exists
4481 if (meta.data[index]) {
4482 meta.data[index].hidden = !meta.data[index].hidden;
4483 }
4484 }
4485
4486 chart.update();
4487 }
4488 },
4489
4490 // The percentage of the chart that we cut out of the middle.
4491 cutoutPercentage: 50,
4492
4493 // The rotation of the chart, where the first data arc begins.
4494 rotation: Math.PI * -0.5,
4495
4496 // The total circumference of the chart.
4497 circumference: Math.PI * 2.0,
4498
4499 // Need to override these to give a nice default
4500 tooltips: {
4501 callbacks: {
4502 title: function() {
4503 return '';
4504 },
4505 label: function(tooltipItem, data) {
4506 var dataLabel = data.labels[tooltipItem.index];
4507 var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
4508
4509 if (helpers$1.isArray(dataLabel)) {
4510 // show value on first line of multiline label
4511 // need to clone because we are changing the value
4512 dataLabel = dataLabel.slice();
4513 dataLabel[0] += value;
4514 } else {
4515 dataLabel += value;
4516 }
4517
4518 return dataLabel;
4519 }
4520 }
4521 }
4522});
4523
4524var controller_doughnut = core_datasetController.extend({
4525
4526 dataElementType: elements.Arc,
4527
4528 linkScales: helpers$1.noop,
4529
4530 // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
4531 getRingIndex: function(datasetIndex) {
4532 var ringIndex = 0;
4533
4534 for (var j = 0; j < datasetIndex; ++j) {
4535 if (this.chart.isDatasetVisible(j)) {
4536 ++ringIndex;
4537 }
4538 }
4539
4540 return ringIndex;
4541 },
4542
4543 update: function(reset) {
4544 var me = this;
4545 var chart = me.chart;
4546 var chartArea = chart.chartArea;
4547 var opts = chart.options;
4548 var availableWidth = chartArea.right - chartArea.left;
4549 var availableHeight = chartArea.bottom - chartArea.top;
4550 var minSize = Math.min(availableWidth, availableHeight);
4551 var offset = {x: 0, y: 0};
4552 var meta = me.getMeta();
4553 var arcs = meta.data;
4554 var cutoutPercentage = opts.cutoutPercentage;
4555 var circumference = opts.circumference;
4556 var i, ilen;
4557
4558 // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
4559 if (circumference < Math.PI * 2.0) {
4560 var startAngle = opts.rotation % (Math.PI * 2.0);
4561 startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
4562 var endAngle = startAngle + circumference;
4563 var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
4564 var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
4565 var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
4566 var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
4567 var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
4568 var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
4569 var cutout = cutoutPercentage / 100.0;
4570 var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
4571 var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
4572 var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
4573 minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
4574 offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
4575 }
4576
4577 for (i = 0, ilen = arcs.length; i < ilen; ++i) {
4578 arcs[i]._options = me._resolveElementOptions(arcs[i], i, reset);
4579 }
4580
4581 chart.borderWidth = me.getMaxBorderWidth();
4582 chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
4583 chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
4584 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
4585 chart.offsetX = offset.x * chart.outerRadius;
4586 chart.offsetY = offset.y * chart.outerRadius;
4587
4588 meta.total = me.calculateTotal();
4589
4590 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
4591 me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
4592
4593 for (i = 0, ilen = arcs.length; i < ilen; ++i) {
4594 me.updateElement(arcs[i], i, reset);
4595 }
4596 },
4597
4598 updateElement: function(arc, index, reset) {
4599 var me = this;
4600 var chart = me.chart;
4601 var chartArea = chart.chartArea;
4602 var opts = chart.options;
4603 var animationOpts = opts.animation;
4604 var centerX = (chartArea.left + chartArea.right) / 2;
4605 var centerY = (chartArea.top + chartArea.bottom) / 2;
4606 var startAngle = opts.rotation; // non reset case handled later
4607 var endAngle = opts.rotation; // non reset case handled later
4608 var dataset = me.getDataset();
4609 var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI));
4610 var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
4611 var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
4612 var options = arc._options || {};
4613
4614 helpers$1.extend(arc, {
4615 // Utility
4616 _datasetIndex: me.index,
4617 _index: index,
4618
4619 // Desired view properties
4620 _model: {
4621 backgroundColor: options.backgroundColor,
4622 borderColor: options.borderColor,
4623 borderWidth: options.borderWidth,
4624 borderAlign: options.borderAlign,
4625 x: centerX + chart.offsetX,
4626 y: centerY + chart.offsetY,
4627 startAngle: startAngle,
4628 endAngle: endAngle,
4629 circumference: circumference,
4630 outerRadius: outerRadius,
4631 innerRadius: innerRadius,
4632 label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
4633 }
4634 });
4635
4636 var model = arc._model;
4637
4638 // Set correct angles if not resetting
4639 if (!reset || !animationOpts.animateRotate) {
4640 if (index === 0) {
4641 model.startAngle = opts.rotation;
4642 } else {
4643 model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
4644 }
4645
4646 model.endAngle = model.startAngle + model.circumference;
4647 }
4648
4649 arc.pivot();
4650 },
4651
4652 calculateTotal: function() {
4653 var dataset = this.getDataset();
4654 var meta = this.getMeta();
4655 var total = 0;
4656 var value;
4657
4658 helpers$1.each(meta.data, function(element, index) {
4659 value = dataset.data[index];
4660 if (!isNaN(value) && !element.hidden) {
4661 total += Math.abs(value);
4662 }
4663 });
4664
4665 /* if (total === 0) {
4666 total = NaN;
4667 }*/
4668
4669 return total;
4670 },
4671
4672 calculateCircumference: function(value) {
4673 var total = this.getMeta().total;
4674 if (total > 0 && !isNaN(value)) {
4675 return (Math.PI * 2.0) * (Math.abs(value) / total);
4676 }
4677 return 0;
4678 },
4679
4680 // gets the max border or hover width to properly scale pie charts
4681 getMaxBorderWidth: function(arcs) {
4682 var me = this;
4683 var max = 0;
4684 var chart = me.chart;
4685 var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth;
4686
4687 if (!arcs) {
4688 // Find the outmost visible dataset
4689 for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
4690 if (chart.isDatasetVisible(i)) {
4691 meta = chart.getDatasetMeta(i);
4692 arcs = meta.data;
4693 if (i !== me.index) {
4694 controller = meta.controller;
4695 }
4696 break;
4697 }
4698 }
4699 }
4700
4701 if (!arcs) {
4702 return 0;
4703 }
4704
4705 for (i = 0, ilen = arcs.length; i < ilen; ++i) {
4706 arc = arcs[i];
4707 options = controller ? controller._resolveElementOptions(arc, i) : arc._options;
4708 if (options.borderAlign !== 'inner') {
4709 borderWidth = options.borderWidth;
4710 hoverWidth = options.hoverBorderWidth;
4711
4712 max = borderWidth > max ? borderWidth : max;
4713 max = hoverWidth > max ? hoverWidth : max;
4714 }
4715 }
4716 return max;
4717 },
4718
4719 /**
4720 * @private
4721 */
4722 _resolveElementOptions: function(arc, index) {
4723 var me = this;
4724 var dataset = me.getDataset();
4725 var custom = arc.custom || {};
4726 var options = me.chart.options.elements.arc;
4727
4728 return {
4729 backgroundColor: resolve$3([custom.backgroundColor, dataset.backgroundColor, options.backgroundColor], undefined, index),
4730 borderColor: resolve$3([custom.borderColor, dataset.borderColor, options.borderColor], undefined, index),
4731 borderWidth: resolve$3([custom.borderWidth, dataset.borderWidth, options.borderWidth], undefined, index),
4732 borderAlign: resolve$3([custom.borderAlign, dataset.borderAlign, options.borderAlign], undefined, index)
4733 };
4734 }
4735});
4736
4737core_defaults._set('horizontalBar', {
4738 hover: {
4739 mode: 'index',
4740 axis: 'y'
4741 },
4742
4743 scales: {
4744 xAxes: [{
4745 type: 'linear',
4746 position: 'bottom'
4747 }],
4748
4749 yAxes: [{
4750 type: 'category',
4751 position: 'left',
4752 categoryPercentage: 0.8,
4753 barPercentage: 0.9,
4754 offset: true,
4755 gridLines: {
4756 offsetGridLines: true
4757 }
4758 }]
4759 },
4760
4761 elements: {
4762 rectangle: {
4763 borderSkipped: 'left'
4764 }
4765 },
4766
4767 tooltips: {
4768 callbacks: {
4769 title: function(item, data) {
4770 // Pick first xLabel for now
4771 var title = '';
4772
4773 if (item.length > 0) {
4774 if (item[0].yLabel) {
4775 title = item[0].yLabel;
4776 } else if (data.labels.length > 0 && item[0].index < data.labels.length) {
4777 title = data.labels[item[0].index];
4778 }
4779 }
4780
4781 return title;
4782 },
4783
4784 label: function(item, data) {
4785 var datasetLabel = data.datasets[item.datasetIndex].label || '';
4786 return datasetLabel + ': ' + item.xLabel;
4787 }
4788 },
4789 mode: 'index',
4790 axis: 'y'
4791 }
4792});
4793
4794var controller_horizontalBar = controller_bar.extend({
4795 /**
4796 * @private
4797 */
4798 getValueScaleId: function() {
4799 return this.getMeta().xAxisID;
4800 },
4801
4802 /**
4803 * @private
4804 */
4805 getIndexScaleId: function() {
4806 return this.getMeta().yAxisID;
4807 }
4808});
4809
4810var valueOrDefault$4 = helpers$1.valueOrDefault;
4811var resolve$4 = helpers$1.options.resolve;
4812var isPointInArea = helpers$1.canvas._isPointInArea;
4813
4814core_defaults._set('line', {
4815 showLines: true,
4816 spanGaps: false,
4817
4818 hover: {
4819 mode: 'label'
4820 },
4821
4822 scales: {
4823 xAxes: [{
4824 type: 'category',
4825 id: 'x-axis-0'
4826 }],
4827 yAxes: [{
4828 type: 'linear',
4829 id: 'y-axis-0'
4830 }]
4831 }
4832});
4833
4834function lineEnabled(dataset, options) {
4835 return valueOrDefault$4(dataset.showLine, options.showLines);
4836}
4837
4838var controller_line = core_datasetController.extend({
4839
4840 datasetElementType: elements.Line,
4841
4842 dataElementType: elements.Point,
4843
4844 update: function(reset) {
4845 var me = this;
4846 var meta = me.getMeta();
4847 var line = meta.dataset;
4848 var points = meta.data || [];
4849 var options = me.chart.options;
4850 var lineElementOptions = options.elements.line;
4851 var scale = me.getScaleForId(meta.yAxisID);
4852 var i, ilen, custom;
4853 var dataset = me.getDataset();
4854 var showLine = lineEnabled(dataset, options);
4855
4856 // Update Line
4857 if (showLine) {
4858 custom = line.custom || {};
4859
4860 // Compatibility: If the properties are defined with only the old name, use those values
4861 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
4862 dataset.lineTension = dataset.tension;
4863 }
4864
4865 // Utility
4866 line._scale = scale;
4867 line._datasetIndex = me.index;
4868 // Data
4869 line._children = points;
4870 // Model
4871 line._model = {
4872 // Appearance
4873 // The default behavior of lines is to break at null values, according
4874 // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
4875 // This option gives lines the ability to span gaps
4876 spanGaps: valueOrDefault$4(dataset.spanGaps, options.spanGaps),
4877 tension: resolve$4([custom.tension, dataset.lineTension, lineElementOptions.tension]),
4878 backgroundColor: resolve$4([custom.backgroundColor, dataset.backgroundColor, lineElementOptions.backgroundColor]),
4879 borderWidth: resolve$4([custom.borderWidth, dataset.borderWidth, lineElementOptions.borderWidth]),
4880 borderColor: resolve$4([custom.borderColor, dataset.borderColor, lineElementOptions.borderColor]),
4881 borderCapStyle: resolve$4([custom.borderCapStyle, dataset.borderCapStyle, lineElementOptions.borderCapStyle]),
4882 borderDash: resolve$4([custom.borderDash, dataset.borderDash, lineElementOptions.borderDash]),
4883 borderDashOffset: resolve$4([custom.borderDashOffset, dataset.borderDashOffset, lineElementOptions.borderDashOffset]),
4884 borderJoinStyle: resolve$4([custom.borderJoinStyle, dataset.borderJoinStyle, lineElementOptions.borderJoinStyle]),
4885 fill: resolve$4([custom.fill, dataset.fill, lineElementOptions.fill]),
4886 steppedLine: resolve$4([custom.steppedLine, dataset.steppedLine, lineElementOptions.stepped]),
4887 cubicInterpolationMode: resolve$4([custom.cubicInterpolationMode, dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode]),
4888 };
4889
4890 line.pivot();
4891 }
4892
4893 // Update Points
4894 for (i = 0, ilen = points.length; i < ilen; ++i) {
4895 me.updateElement(points[i], i, reset);
4896 }
4897
4898 if (showLine && line._model.tension !== 0) {
4899 me.updateBezierControlPoints();
4900 }
4901
4902 // Now pivot the point for animation
4903 for (i = 0, ilen = points.length; i < ilen; ++i) {
4904 points[i].pivot();
4905 }
4906 },
4907
4908 getPointBackgroundColor: function(point, index) {
4909 var dataset = this.getDataset();
4910 var custom = point.custom || {};
4911
4912 return resolve$4([
4913 custom.backgroundColor,
4914 dataset.pointBackgroundColor,
4915 dataset.backgroundColor,
4916 this.chart.options.elements.point.backgroundColor
4917 ], undefined, index);
4918 },
4919
4920 getPointBorderColor: function(point, index) {
4921 var dataset = this.getDataset();
4922 var custom = point.custom || {};
4923
4924 return resolve$4([
4925 custom.borderColor,
4926 dataset.pointBorderColor,
4927 dataset.borderColor,
4928 this.chart.options.elements.point.borderColor
4929 ], undefined, index);
4930 },
4931
4932 getPointBorderWidth: function(point, index) {
4933 var dataset = this.getDataset();
4934 var custom = point.custom || {};
4935
4936 return resolve$4([
4937 custom.borderWidth,
4938 dataset.pointBorderWidth,
4939 dataset.borderWidth,
4940 this.chart.options.elements.point.borderWidth
4941 ], undefined, index);
4942 },
4943
4944 getPointRotation: function(point, index) {
4945 var custom = point.custom || {};
4946
4947 return resolve$4([
4948 custom.rotation,
4949 this.getDataset().pointRotation,
4950 this.chart.options.elements.point.rotation
4951 ], undefined, index);
4952 },
4953
4954 updateElement: function(point, index, reset) {
4955 var me = this;
4956 var meta = me.getMeta();
4957 var custom = point.custom || {};
4958 var dataset = me.getDataset();
4959 var datasetIndex = me.index;
4960 var value = dataset.data[index];
4961 var yScale = me.getScaleForId(meta.yAxisID);
4962 var xScale = me.getScaleForId(meta.xAxisID);
4963 var pointOptions = me.chart.options.elements.point;
4964 var x, y;
4965
4966 // Compatibility: If the properties are defined with only the old name, use those values
4967 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
4968 dataset.pointRadius = dataset.radius;
4969 }
4970 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
4971 dataset.pointHitRadius = dataset.hitRadius;
4972 }
4973
4974 x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
4975 y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
4976
4977 // Utility
4978 point._xScale = xScale;
4979 point._yScale = yScale;
4980 point._datasetIndex = datasetIndex;
4981 point._index = index;
4982
4983 // Desired view properties
4984 point._model = {
4985 x: x,
4986 y: y,
4987 skip: custom.skip || isNaN(x) || isNaN(y),
4988 // Appearance
4989 radius: resolve$4([custom.radius, dataset.pointRadius, pointOptions.radius], undefined, index),
4990 pointStyle: resolve$4([custom.pointStyle, dataset.pointStyle, pointOptions.pointStyle], undefined, index),
4991 rotation: me.getPointRotation(point, index),
4992 backgroundColor: me.getPointBackgroundColor(point, index),
4993 borderColor: me.getPointBorderColor(point, index),
4994 borderWidth: me.getPointBorderWidth(point, index),
4995 tension: meta.dataset._model ? meta.dataset._model.tension : 0,
4996 steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
4997 // Tooltip
4998 hitRadius: resolve$4([custom.hitRadius, dataset.pointHitRadius, pointOptions.hitRadius], undefined, index)
4999 };
5000 },
5001
5002 calculatePointY: function(value, index, datasetIndex) {
5003 var me = this;
5004 var chart = me.chart;
5005 var meta = me.getMeta();
5006 var yScale = me.getScaleForId(meta.yAxisID);
5007 var sumPos = 0;
5008 var sumNeg = 0;
5009 var i, ds, dsMeta;
5010
5011 if (yScale.options.stacked) {
5012 for (i = 0; i < datasetIndex; i++) {
5013 ds = chart.data.datasets[i];
5014 dsMeta = chart.getDatasetMeta(i);
5015 if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
5016 var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
5017 if (stackedRightValue < 0) {
5018 sumNeg += stackedRightValue || 0;
5019 } else {
5020 sumPos += stackedRightValue || 0;
5021 }
5022 }
5023 }
5024
5025 var rightValue = Number(yScale.getRightValue(value));
5026 if (rightValue < 0) {
5027 return yScale.getPixelForValue(sumNeg + rightValue);
5028 }
5029 return yScale.getPixelForValue(sumPos + rightValue);
5030 }
5031
5032 return yScale.getPixelForValue(value);
5033 },
5034
5035 updateBezierControlPoints: function() {
5036 var me = this;
5037 var chart = me.chart;
5038 var meta = me.getMeta();
5039 var lineModel = meta.dataset._model;
5040 var area = chart.chartArea;
5041 var points = meta.data || [];
5042 var i, ilen, point, model, controlPoints;
5043
5044 // Only consider points that are drawn in case the spanGaps option is used
5045 if (lineModel.spanGaps) {
5046 points = points.filter(function(pt) {
5047 return !pt._model.skip;
5048 });
5049 }
5050
5051 function capControlPoint(pt, min, max) {
5052 return Math.max(Math.min(pt, max), min);
5053 }
5054
5055 if (lineModel.cubicInterpolationMode === 'monotone') {
5056 helpers$1.splineCurveMonotone(points);
5057 } else {
5058 for (i = 0, ilen = points.length; i < ilen; ++i) {
5059 point = points[i];
5060 model = point._model;
5061 controlPoints = helpers$1.splineCurve(
5062 helpers$1.previousItem(points, i)._model,
5063 model,
5064 helpers$1.nextItem(points, i)._model,
5065 lineModel.tension
5066 );
5067 model.controlPointPreviousX = controlPoints.previous.x;
5068 model.controlPointPreviousY = controlPoints.previous.y;
5069 model.controlPointNextX = controlPoints.next.x;
5070 model.controlPointNextY = controlPoints.next.y;
5071 }
5072 }
5073
5074 if (chart.options.elements.line.capBezierPoints) {
5075 for (i = 0, ilen = points.length; i < ilen; ++i) {
5076 model = points[i]._model;
5077 if (isPointInArea(model, area)) {
5078 if (i > 0 && isPointInArea(points[i - 1]._model, area)) {
5079 model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
5080 model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
5081 }
5082 if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) {
5083 model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
5084 model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
5085 }
5086 }
5087 }
5088 }
5089 },
5090
5091 draw: function() {
5092 var me = this;
5093 var chart = me.chart;
5094 var meta = me.getMeta();
5095 var points = meta.data || [];
5096 var area = chart.chartArea;
5097 var ilen = points.length;
5098 var halfBorderWidth;
5099 var i = 0;
5100
5101 if (lineEnabled(me.getDataset(), chart.options)) {
5102 halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2;
5103
5104 helpers$1.canvas.clipArea(chart.ctx, {
5105 left: area.left,
5106 right: area.right,
5107 top: area.top - halfBorderWidth,
5108 bottom: area.bottom + halfBorderWidth
5109 });
5110
5111 meta.dataset.draw();
5112
5113 helpers$1.canvas.unclipArea(chart.ctx);
5114 }
5115
5116 // Draw the points
5117 for (; i < ilen; ++i) {
5118 points[i].draw(area);
5119 }
5120 },
5121
5122 setHoverStyle: function(element) {
5123 // Point
5124 var dataset = this.chart.data.datasets[element._datasetIndex];
5125 var index = element._index;
5126 var custom = element.custom || {};
5127 var model = element._model;
5128 var getHoverColor = helpers$1.getHoverColor;
5129
5130 element.$previousStyle = {
5131 backgroundColor: model.backgroundColor,
5132 borderColor: model.borderColor,
5133 borderWidth: model.borderWidth,
5134 radius: model.radius
5135 };
5136
5137 model.backgroundColor = resolve$4([custom.hoverBackgroundColor, dataset.pointHoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index);
5138 model.borderColor = resolve$4([custom.hoverBorderColor, dataset.pointHoverBorderColor, getHoverColor(model.borderColor)], undefined, index);
5139 model.borderWidth = resolve$4([custom.hoverBorderWidth, dataset.pointHoverBorderWidth, model.borderWidth], undefined, index);
5140 model.radius = resolve$4([custom.hoverRadius, dataset.pointHoverRadius, this.chart.options.elements.point.hoverRadius], undefined, index);
5141 }
5142});
5143
5144var resolve$5 = helpers$1.options.resolve;
5145
5146core_defaults._set('polarArea', {
5147 scale: {
5148 type: 'radialLinear',
5149 angleLines: {
5150 display: false
5151 },
5152 gridLines: {
5153 circular: true
5154 },
5155 pointLabels: {
5156 display: false
5157 },
5158 ticks: {
5159 beginAtZero: true
5160 }
5161 },
5162
5163 // Boolean - Whether to animate the rotation of the chart
5164 animation: {
5165 animateRotate: true,
5166 animateScale: true
5167 },
5168
5169 startAngle: -0.5 * Math.PI,
5170 legendCallback: function(chart) {
5171 var text = [];
5172 text.push('<ul class="' + chart.id + '-legend">');
5173
5174 var data = chart.data;
5175 var datasets = data.datasets;
5176 var labels = data.labels;
5177
5178 if (datasets.length) {
5179 for (var i = 0; i < datasets[0].data.length; ++i) {
5180 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
5181 if (labels[i]) {
5182 text.push(labels[i]);
5183 }
5184 text.push('</li>');
5185 }
5186 }
5187
5188 text.push('</ul>');
5189 return text.join('');
5190 },
5191 legend: {
5192 labels: {
5193 generateLabels: function(chart) {
5194 var data = chart.data;
5195 if (data.labels.length && data.datasets.length) {
5196 return data.labels.map(function(label, i) {
5197 var meta = chart.getDatasetMeta(0);
5198 var ds = data.datasets[0];
5199 var arc = meta.data[i];
5200 var custom = arc.custom || {};
5201 var arcOpts = chart.options.elements.arc;
5202 var fill = resolve$5([custom.backgroundColor, ds.backgroundColor, arcOpts.backgroundColor], undefined, i);
5203 var stroke = resolve$5([custom.borderColor, ds.borderColor, arcOpts.borderColor], undefined, i);
5204 var bw = resolve$5([custom.borderWidth, ds.borderWidth, arcOpts.borderWidth], undefined, i);
5205
5206 return {
5207 text: label,
5208 fillStyle: fill,
5209 strokeStyle: stroke,
5210 lineWidth: bw,
5211 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
5212
5213 // Extra data used for toggling the correct item
5214 index: i
5215 };
5216 });
5217 }
5218 return [];
5219 }
5220 },
5221
5222 onClick: function(e, legendItem) {
5223 var index = legendItem.index;
5224 var chart = this.chart;
5225 var i, ilen, meta;
5226
5227 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
5228 meta = chart.getDatasetMeta(i);
5229 meta.data[index].hidden = !meta.data[index].hidden;
5230 }
5231
5232 chart.update();
5233 }
5234 },
5235
5236 // Need to override these to give a nice default
5237 tooltips: {
5238 callbacks: {
5239 title: function() {
5240 return '';
5241 },
5242 label: function(item, data) {
5243 return data.labels[item.index] + ': ' + item.yLabel;
5244 }
5245 }
5246 }
5247});
5248
5249var controller_polarArea = core_datasetController.extend({
5250
5251 dataElementType: elements.Arc,
5252
5253 linkScales: helpers$1.noop,
5254
5255 update: function(reset) {
5256 var me = this;
5257 var dataset = me.getDataset();
5258 var meta = me.getMeta();
5259 var start = me.chart.options.startAngle || 0;
5260 var starts = me._starts = [];
5261 var angles = me._angles = [];
5262 var i, ilen, angle;
5263
5264 me._updateRadius();
5265
5266 meta.count = me.countVisibleElements();
5267
5268 for (i = 0, ilen = dataset.data.length; i < ilen; i++) {
5269 starts[i] = start;
5270 angle = me._computeAngle(i);
5271 angles[i] = angle;
5272 start += angle;
5273 }
5274
5275 helpers$1.each(meta.data, function(arc, index) {
5276 me.updateElement(arc, index, reset);
5277 });
5278 },
5279
5280 /**
5281 * @private
5282 */
5283 _updateRadius: function() {
5284 var me = this;
5285 var chart = me.chart;
5286 var chartArea = chart.chartArea;
5287 var opts = chart.options;
5288 var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
5289
5290 chart.outerRadius = Math.max(minSize / 2, 0);
5291 chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
5292 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
5293
5294 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
5295 me.innerRadius = me.outerRadius - chart.radiusLength;
5296 },
5297
5298 updateElement: function(arc, index, reset) {
5299 var me = this;
5300 var chart = me.chart;
5301 var dataset = me.getDataset();
5302 var opts = chart.options;
5303 var animationOpts = opts.animation;
5304 var scale = chart.scale;
5305 var labels = chart.data.labels;
5306
5307 var centerX = scale.xCenter;
5308 var centerY = scale.yCenter;
5309
5310 // var negHalfPI = -0.5 * Math.PI;
5311 var datasetStartAngle = opts.startAngle;
5312 var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
5313 var startAngle = me._starts[index];
5314 var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]);
5315
5316 var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
5317
5318 helpers$1.extend(arc, {
5319 // Utility
5320 _datasetIndex: me.index,
5321 _index: index,
5322 _scale: scale,
5323
5324 // Desired view properties
5325 _model: {
5326 x: centerX,
5327 y: centerY,
5328 innerRadius: 0,
5329 outerRadius: reset ? resetRadius : distance,
5330 startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
5331 endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
5332 label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index])
5333 }
5334 });
5335
5336 // Apply border and fill style
5337 var elementOpts = this.chart.options.elements.arc;
5338 var custom = arc.custom || {};
5339 var model = arc._model;
5340
5341 model.backgroundColor = resolve$5([custom.backgroundColor, dataset.backgroundColor, elementOpts.backgroundColor], undefined, index);
5342 model.borderColor = resolve$5([custom.borderColor, dataset.borderColor, elementOpts.borderColor], undefined, index);
5343 model.borderWidth = resolve$5([custom.borderWidth, dataset.borderWidth, elementOpts.borderWidth], undefined, index);
5344 model.borderAlign = resolve$5([custom.borderAlign, dataset.borderAlign, elementOpts.borderAlign], undefined, index);
5345
5346 arc.pivot();
5347 },
5348
5349 countVisibleElements: function() {
5350 var dataset = this.getDataset();
5351 var meta = this.getMeta();
5352 var count = 0;
5353
5354 helpers$1.each(meta.data, function(element, index) {
5355 if (!isNaN(dataset.data[index]) && !element.hidden) {
5356 count++;
5357 }
5358 });
5359
5360 return count;
5361 },
5362
5363 /**
5364 * @private
5365 */
5366 _computeAngle: function(index) {
5367 var me = this;
5368 var count = this.getMeta().count;
5369 var dataset = me.getDataset();
5370 var meta = me.getMeta();
5371
5372 if (isNaN(dataset.data[index]) || meta.data[index].hidden) {
5373 return 0;
5374 }
5375
5376 // Scriptable options
5377 var context = {
5378 chart: me.chart,
5379 dataIndex: index,
5380 dataset: dataset,
5381 datasetIndex: me.index
5382 };
5383
5384 return resolve$5([
5385 me.chart.options.elements.arc.angle,
5386 (2 * Math.PI) / count
5387 ], context, index);
5388 }
5389});
5390
5391core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut));
5392core_defaults._set('pie', {
5393 cutoutPercentage: 0
5394});
5395
5396// Pie charts are Doughnut chart with different defaults
5397var controller_pie = controller_doughnut;
5398
5399var resolve$6 = helpers$1.options.resolve;
5400
5401core_defaults._set('radar', {
5402 scale: {
5403 type: 'radialLinear'
5404 },
5405 elements: {
5406 line: {
5407 tension: 0 // no bezier in radar
5408 }
5409 }
5410});
5411
5412var controller_radar = core_datasetController.extend({
5413
5414 datasetElementType: elements.Line,
5415
5416 dataElementType: elements.Point,
5417
5418 linkScales: helpers$1.noop,
5419
5420 update: function(reset) {
5421 var me = this;
5422 var meta = me.getMeta();
5423 var line = meta.dataset;
5424 var points = meta.data || [];
5425 var custom = line.custom || {};
5426 var dataset = me.getDataset();
5427 var lineElementOptions = me.chart.options.elements.line;
5428 var scale = me.chart.scale;
5429 var i, ilen;
5430
5431 // Compatibility: If the properties are defined with only the old name, use those values
5432 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
5433 dataset.lineTension = dataset.tension;
5434 }
5435
5436 helpers$1.extend(meta.dataset, {
5437 // Utility
5438 _datasetIndex: me.index,
5439 _scale: scale,
5440 // Data
5441 _children: points,
5442 _loop: true,
5443 // Model
5444 _model: {
5445 // Appearance
5446 tension: resolve$6([custom.tension, dataset.lineTension, lineElementOptions.tension]),
5447 backgroundColor: resolve$6([custom.backgroundColor, dataset.backgroundColor, lineElementOptions.backgroundColor]),
5448 borderWidth: resolve$6([custom.borderWidth, dataset.borderWidth, lineElementOptions.borderWidth]),
5449 borderColor: resolve$6([custom.borderColor, dataset.borderColor, lineElementOptions.borderColor]),
5450 fill: resolve$6([custom.fill, dataset.fill, lineElementOptions.fill]),
5451 borderCapStyle: resolve$6([custom.borderCapStyle, dataset.borderCapStyle, lineElementOptions.borderCapStyle]),
5452 borderDash: resolve$6([custom.borderDash, dataset.borderDash, lineElementOptions.borderDash]),
5453 borderDashOffset: resolve$6([custom.borderDashOffset, dataset.borderDashOffset, lineElementOptions.borderDashOffset]),
5454 borderJoinStyle: resolve$6([custom.borderJoinStyle, dataset.borderJoinStyle, lineElementOptions.borderJoinStyle]),
5455 }
5456 });
5457
5458 meta.dataset.pivot();
5459
5460 // Update Points
5461 for (i = 0, ilen = points.length; i < ilen; i++) {
5462 me.updateElement(points[i], i, reset);
5463 }
5464
5465 // Update bezier control points
5466 me.updateBezierControlPoints();
5467
5468 // Now pivot the point for animation
5469 for (i = 0, ilen = points.length; i < ilen; i++) {
5470 points[i].pivot();
5471 }
5472 },
5473
5474 updateElement: function(point, index, reset) {
5475 var me = this;
5476 var custom = point.custom || {};
5477 var dataset = me.getDataset();
5478 var scale = me.chart.scale;
5479 var pointElementOptions = me.chart.options.elements.point;
5480 var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
5481
5482 // Compatibility: If the properties are defined with only the old name, use those values
5483 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
5484 dataset.pointRadius = dataset.radius;
5485 }
5486 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
5487 dataset.pointHitRadius = dataset.hitRadius;
5488 }
5489
5490 helpers$1.extend(point, {
5491 // Utility
5492 _datasetIndex: me.index,
5493 _index: index,
5494 _scale: scale,
5495
5496 // Desired view properties
5497 _model: {
5498 x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
5499 y: reset ? scale.yCenter : pointPosition.y,
5500
5501 // Appearance
5502 tension: resolve$6([custom.tension, dataset.lineTension, me.chart.options.elements.line.tension]),
5503 radius: resolve$6([custom.radius, dataset.pointRadius, pointElementOptions.radius], undefined, index),
5504 backgroundColor: resolve$6([custom.backgroundColor, dataset.pointBackgroundColor, pointElementOptions.backgroundColor], undefined, index),
5505 borderColor: resolve$6([custom.borderColor, dataset.pointBorderColor, pointElementOptions.borderColor], undefined, index),
5506 borderWidth: resolve$6([custom.borderWidth, dataset.pointBorderWidth, pointElementOptions.borderWidth], undefined, index),
5507 pointStyle: resolve$6([custom.pointStyle, dataset.pointStyle, pointElementOptions.pointStyle], undefined, index),
5508 rotation: resolve$6([custom.rotation, dataset.pointRotation, pointElementOptions.rotation], undefined, index),
5509
5510 // Tooltip
5511 hitRadius: resolve$6([custom.hitRadius, dataset.pointHitRadius, pointElementOptions.hitRadius], undefined, index)
5512 }
5513 });
5514
5515 point._model.skip = custom.skip || isNaN(point._model.x) || isNaN(point._model.y);
5516 },
5517
5518 updateBezierControlPoints: function() {
5519 var me = this;
5520 var meta = me.getMeta();
5521 var area = me.chart.chartArea;
5522 var points = meta.data || [];
5523 var i, ilen, model, controlPoints;
5524
5525 function capControlPoint(pt, min, max) {
5526 return Math.max(Math.min(pt, max), min);
5527 }
5528
5529 for (i = 0, ilen = points.length; i < ilen; i++) {
5530 model = points[i]._model;
5531 controlPoints = helpers$1.splineCurve(
5532 helpers$1.previousItem(points, i, true)._model,
5533 model,
5534 helpers$1.nextItem(points, i, true)._model,
5535 model.tension
5536 );
5537
5538 // Prevent the bezier going outside of the bounds of the graph
5539 model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right);
5540 model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom);
5541 model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right);
5542 model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom);
5543 }
5544 },
5545
5546 setHoverStyle: function(point) {
5547 // Point
5548 var dataset = this.chart.data.datasets[point._datasetIndex];
5549 var custom = point.custom || {};
5550 var index = point._index;
5551 var model = point._model;
5552 var getHoverColor = helpers$1.getHoverColor;
5553
5554 point.$previousStyle = {
5555 backgroundColor: model.backgroundColor,
5556 borderColor: model.borderColor,
5557 borderWidth: model.borderWidth,
5558 radius: model.radius
5559 };
5560
5561 model.radius = resolve$6([custom.hoverRadius, dataset.pointHoverRadius, this.chart.options.elements.point.hoverRadius], undefined, index);
5562 model.backgroundColor = resolve$6([custom.hoverBackgroundColor, dataset.pointHoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index);
5563 model.borderColor = resolve$6([custom.hoverBorderColor, dataset.pointHoverBorderColor, getHoverColor(model.borderColor)], undefined, index);
5564 model.borderWidth = resolve$6([custom.hoverBorderWidth, dataset.pointHoverBorderWidth, model.borderWidth], undefined, index);
5565 }
5566});
5567
5568core_defaults._set('scatter', {
5569 hover: {
5570 mode: 'single'
5571 },
5572
5573 scales: {
5574 xAxes: [{
5575 id: 'x-axis-1', // need an ID so datasets can reference the scale
5576 type: 'linear', // scatter should not use a category axis
5577 position: 'bottom'
5578 }],
5579 yAxes: [{
5580 id: 'y-axis-1',
5581 type: 'linear',
5582 position: 'left'
5583 }]
5584 },
5585
5586 showLines: false,
5587
5588 tooltips: {
5589 callbacks: {
5590 title: function() {
5591 return ''; // doesn't make sense for scatter since data are formatted as a point
5592 },
5593 label: function(item) {
5594 return '(' + item.xLabel + ', ' + item.yLabel + ')';
5595 }
5596 }
5597 }
5598});
5599
5600// Scatter charts use line controllers
5601var controller_scatter = controller_line;
5602
5603// NOTE export a map in which the key represents the controller type, not
5604// the class, and so must be CamelCase in order to be correctly retrieved
5605// by the controller in core.controller.js (`controllers[meta.type]`).
5606
5607var controllers = {
5608 bar: controller_bar,
5609 bubble: controller_bubble,
5610 doughnut: controller_doughnut,
5611 horizontalBar: controller_horizontalBar,
5612 line: controller_line,
5613 polarArea: controller_polarArea,
5614 pie: controller_pie,
5615 radar: controller_radar,
5616 scatter: controller_scatter
5617};
5618
5619/**
5620 * Helper function to get relative position for an event
5621 * @param {Event|IEvent} event - The event to get the position for
5622 * @param {Chart} chart - The chart
5623 * @returns {Point} the event position
5624 */
5625function getRelativePosition(e, chart) {
5626 if (e.native) {
5627 return {
5628 x: e.x,
5629 y: e.y
5630 };
5631 }
5632
5633 return helpers$1.getRelativePosition(e, chart);
5634}
5635
5636/**
5637 * Helper function to traverse all of the visible elements in the chart
5638 * @param chart {chart} the chart
5639 * @param handler {Function} the callback to execute for each visible item
5640 */
5641function parseVisibleItems(chart, handler) {
5642 var datasets = chart.data.datasets;
5643 var meta, i, j, ilen, jlen;
5644
5645 for (i = 0, ilen = datasets.length; i < ilen; ++i) {
5646 if (!chart.isDatasetVisible(i)) {
5647 continue;
5648 }
5649
5650 meta = chart.getDatasetMeta(i);
5651 for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
5652 var element = meta.data[j];
5653 if (!element._view.skip) {
5654 handler(element);
5655 }
5656 }
5657 }
5658}
5659
5660/**
5661 * Helper function to get the items that intersect the event position
5662 * @param items {ChartElement[]} elements to filter
5663 * @param position {Point} the point to be nearest to
5664 * @return {ChartElement[]} the nearest items
5665 */
5666function getIntersectItems(chart, position) {
5667 var elements = [];
5668
5669 parseVisibleItems(chart, function(element) {
5670 if (element.inRange(position.x, position.y)) {
5671 elements.push(element);
5672 }
5673 });
5674
5675 return elements;
5676}
5677
5678/**
5679 * Helper function to get the items nearest to the event position considering all visible items in teh chart
5680 * @param chart {Chart} the chart to look at elements from
5681 * @param position {Point} the point to be nearest to
5682 * @param intersect {Boolean} if true, only consider items that intersect the position
5683 * @param distanceMetric {Function} function to provide the distance between points
5684 * @return {ChartElement[]} the nearest items
5685 */
5686function getNearestItems(chart, position, intersect, distanceMetric) {
5687 var minDistance = Number.POSITIVE_INFINITY;
5688 var nearestItems = [];
5689
5690 parseVisibleItems(chart, function(element) {
5691 if (intersect && !element.inRange(position.x, position.y)) {
5692 return;
5693 }
5694
5695 var center = element.getCenterPoint();
5696 var distance = distanceMetric(position, center);
5697
5698 if (distance < minDistance) {
5699 nearestItems = [element];
5700 minDistance = distance;
5701 } else if (distance === minDistance) {
5702 // Can have multiple items at the same distance in which case we sort by size
5703 nearestItems.push(element);
5704 }
5705 });
5706
5707 return nearestItems;
5708}
5709
5710/**
5711 * Get a distance metric function for two points based on the
5712 * axis mode setting
5713 * @param {String} axis the axis mode. x|y|xy
5714 */
5715function getDistanceMetricForAxis(axis) {
5716 var useX = axis.indexOf('x') !== -1;
5717 var useY = axis.indexOf('y') !== -1;
5718
5719 return function(pt1, pt2) {
5720 var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
5721 var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
5722 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
5723 };
5724}
5725
5726function indexMode(chart, e, options) {
5727 var position = getRelativePosition(e, chart);
5728 // Default axis for index mode is 'x' to match old behaviour
5729 options.axis = options.axis || 'x';
5730 var distanceMetric = getDistanceMetricForAxis(options.axis);
5731 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5732 var elements = [];
5733
5734 if (!items.length) {
5735 return [];
5736 }
5737
5738 chart.data.datasets.forEach(function(dataset, datasetIndex) {
5739 if (chart.isDatasetVisible(datasetIndex)) {
5740 var meta = chart.getDatasetMeta(datasetIndex);
5741 var element = meta.data[items[0]._index];
5742
5743 // don't count items that are skipped (null data)
5744 if (element && !element._view.skip) {
5745 elements.push(element);
5746 }
5747 }
5748 });
5749
5750 return elements;
5751}
5752
5753/**
5754 * @interface IInteractionOptions
5755 */
5756/**
5757 * If true, only consider items that intersect the point
5758 * @name IInterfaceOptions#boolean
5759 * @type Boolean
5760 */
5761
5762/**
5763 * Contains interaction related functions
5764 * @namespace Chart.Interaction
5765 */
5766var core_interaction = {
5767 // Helper function for different modes
5768 modes: {
5769 single: function(chart, e) {
5770 var position = getRelativePosition(e, chart);
5771 var elements = [];
5772
5773 parseVisibleItems(chart, function(element) {
5774 if (element.inRange(position.x, position.y)) {
5775 elements.push(element);
5776 return elements;
5777 }
5778 });
5779
5780 return elements.slice(0, 1);
5781 },
5782
5783 /**
5784 * @function Chart.Interaction.modes.label
5785 * @deprecated since version 2.4.0
5786 * @todo remove at version 3
5787 * @private
5788 */
5789 label: indexMode,
5790
5791 /**
5792 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
5793 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
5794 * @function Chart.Interaction.modes.index
5795 * @since v2.4.0
5796 * @param chart {chart} the chart we are returning items from
5797 * @param e {Event} the event we are find things at
5798 * @param options {IInteractionOptions} options to use during interaction
5799 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5800 */
5801 index: indexMode,
5802
5803 /**
5804 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
5805 * If the options.intersect is false, we find the nearest item and return the items in that dataset
5806 * @function Chart.Interaction.modes.dataset
5807 * @param chart {chart} the chart we are returning items from
5808 * @param e {Event} the event we are find things at
5809 * @param options {IInteractionOptions} options to use during interaction
5810 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5811 */
5812 dataset: function(chart, e, options) {
5813 var position = getRelativePosition(e, chart);
5814 options.axis = options.axis || 'xy';
5815 var distanceMetric = getDistanceMetricForAxis(options.axis);
5816 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5817
5818 if (items.length > 0) {
5819 items = chart.getDatasetMeta(items[0]._datasetIndex).data;
5820 }
5821
5822 return items;
5823 },
5824
5825 /**
5826 * @function Chart.Interaction.modes.x-axis
5827 * @deprecated since version 2.4.0. Use index mode and intersect == true
5828 * @todo remove at version 3
5829 * @private
5830 */
5831 'x-axis': function(chart, e) {
5832 return indexMode(chart, e, {intersect: false});
5833 },
5834
5835 /**
5836 * Point mode returns all elements that hit test based on the event position
5837 * of the event
5838 * @function Chart.Interaction.modes.intersect
5839 * @param chart {chart} the chart we are returning items from
5840 * @param e {Event} the event we are find things at
5841 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5842 */
5843 point: function(chart, e) {
5844 var position = getRelativePosition(e, chart);
5845 return getIntersectItems(chart, position);
5846 },
5847
5848 /**
5849 * nearest mode returns the element closest to the point
5850 * @function Chart.Interaction.modes.intersect
5851 * @param chart {chart} the chart we are returning items from
5852 * @param e {Event} the event we are find things at
5853 * @param options {IInteractionOptions} options to use
5854 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5855 */
5856 nearest: function(chart, e, options) {
5857 var position = getRelativePosition(e, chart);
5858 options.axis = options.axis || 'xy';
5859 var distanceMetric = getDistanceMetricForAxis(options.axis);
5860 return getNearestItems(chart, position, options.intersect, distanceMetric);
5861 },
5862
5863 /**
5864 * x mode returns the elements that hit-test at the current x coordinate
5865 * @function Chart.Interaction.modes.x
5866 * @param chart {chart} the chart we are returning items from
5867 * @param e {Event} the event we are find things at
5868 * @param options {IInteractionOptions} options to use
5869 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5870 */
5871 x: function(chart, e, options) {
5872 var position = getRelativePosition(e, chart);
5873 var items = [];
5874 var intersectsItem = false;
5875
5876 parseVisibleItems(chart, function(element) {
5877 if (element.inXRange(position.x)) {
5878 items.push(element);
5879 }
5880
5881 if (element.inRange(position.x, position.y)) {
5882 intersectsItem = true;
5883 }
5884 });
5885
5886 // If we want to trigger on an intersect and we don't have any items
5887 // that intersect the position, return nothing
5888 if (options.intersect && !intersectsItem) {
5889 items = [];
5890 }
5891 return items;
5892 },
5893
5894 /**
5895 * y mode returns the elements that hit-test at the current y coordinate
5896 * @function Chart.Interaction.modes.y
5897 * @param chart {chart} the chart we are returning items from
5898 * @param e {Event} the event we are find things at
5899 * @param options {IInteractionOptions} options to use
5900 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5901 */
5902 y: function(chart, e, options) {
5903 var position = getRelativePosition(e, chart);
5904 var items = [];
5905 var intersectsItem = false;
5906
5907 parseVisibleItems(chart, function(element) {
5908 if (element.inYRange(position.y)) {
5909 items.push(element);
5910 }
5911
5912 if (element.inRange(position.x, position.y)) {
5913 intersectsItem = true;
5914 }
5915 });
5916
5917 // If we want to trigger on an intersect and we don't have any items
5918 // that intersect the position, return nothing
5919 if (options.intersect && !intersectsItem) {
5920 items = [];
5921 }
5922 return items;
5923 }
5924 }
5925};
5926
5927function filterByPosition(array, position) {
5928 return helpers$1.where(array, function(v) {
5929 return v.position === position;
5930 });
5931}
5932
5933function sortByWeight(array, reverse) {
5934 array.forEach(function(v, i) {
5935 v._tmpIndex_ = i;
5936 return v;
5937 });
5938 array.sort(function(a, b) {
5939 var v0 = reverse ? b : a;
5940 var v1 = reverse ? a : b;
5941 return v0.weight === v1.weight ?
5942 v0._tmpIndex_ - v1._tmpIndex_ :
5943 v0.weight - v1.weight;
5944 });
5945 array.forEach(function(v) {
5946 delete v._tmpIndex_;
5947 });
5948}
5949
5950core_defaults._set('global', {
5951 layout: {
5952 padding: {
5953 top: 0,
5954 right: 0,
5955 bottom: 0,
5956 left: 0
5957 }
5958 }
5959});
5960
5961/**
5962 * @interface ILayoutItem
5963 * @prop {String} position - The position of the item in the chart layout. Possible values are
5964 * 'left', 'top', 'right', 'bottom', and 'chartArea'
5965 * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area
5966 * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
5967 * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
5968 * @prop {Function} update - Takes two parameters: width and height. Returns size of item
5969 * @prop {Function} getPadding - Returns an object with padding on the edges
5970 * @prop {Number} width - Width of item. Must be valid after update()
5971 * @prop {Number} height - Height of item. Must be valid after update()
5972 * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update
5973 * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update
5974 * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update
5975 * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
5976 */
5977
5978// The layout service is very self explanatory. It's responsible for the layout within a chart.
5979// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
5980// It is this service's responsibility of carrying out that layout.
5981var core_layouts = {
5982 defaults: {},
5983
5984 /**
5985 * Register a box to a chart.
5986 * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
5987 * @param {Chart} chart - the chart to use
5988 * @param {ILayoutItem} item - the item to add to be layed out
5989 */
5990 addBox: function(chart, item) {
5991 if (!chart.boxes) {
5992 chart.boxes = [];
5993 }
5994
5995 // initialize item with default values
5996 item.fullWidth = item.fullWidth || false;
5997 item.position = item.position || 'top';
5998 item.weight = item.weight || 0;
5999
6000 chart.boxes.push(item);
6001 },
6002
6003 /**
6004 * Remove a layoutItem from a chart
6005 * @param {Chart} chart - the chart to remove the box from
6006 * @param {Object} layoutItem - the item to remove from the layout
6007 */
6008 removeBox: function(chart, layoutItem) {
6009 var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
6010 if (index !== -1) {
6011 chart.boxes.splice(index, 1);
6012 }
6013 },
6014
6015 /**
6016 * Sets (or updates) options on the given `item`.
6017 * @param {Chart} chart - the chart in which the item lives (or will be added to)
6018 * @param {Object} item - the item to configure with the given options
6019 * @param {Object} options - the new item options.
6020 */
6021 configure: function(chart, item, options) {
6022 var props = ['fullWidth', 'position', 'weight'];
6023 var ilen = props.length;
6024 var i = 0;
6025 var prop;
6026
6027 for (; i < ilen; ++i) {
6028 prop = props[i];
6029 if (options.hasOwnProperty(prop)) {
6030 item[prop] = options[prop];
6031 }
6032 }
6033 },
6034
6035 /**
6036 * Fits boxes of the given chart into the given size by having each box measure itself
6037 * then running a fitting algorithm
6038 * @param {Chart} chart - the chart
6039 * @param {Number} width - the width to fit into
6040 * @param {Number} height - the height to fit into
6041 */
6042 update: function(chart, width, height) {
6043 if (!chart) {
6044 return;
6045 }
6046
6047 var layoutOptions = chart.options.layout || {};
6048 var padding = helpers$1.options.toPadding(layoutOptions.padding);
6049 var leftPadding = padding.left;
6050 var rightPadding = padding.right;
6051 var topPadding = padding.top;
6052 var bottomPadding = padding.bottom;
6053
6054 var leftBoxes = filterByPosition(chart.boxes, 'left');
6055 var rightBoxes = filterByPosition(chart.boxes, 'right');
6056 var topBoxes = filterByPosition(chart.boxes, 'top');
6057 var bottomBoxes = filterByPosition(chart.boxes, 'bottom');
6058 var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea');
6059
6060 // Sort boxes by weight. A higher weight is further away from the chart area
6061 sortByWeight(leftBoxes, true);
6062 sortByWeight(rightBoxes, false);
6063 sortByWeight(topBoxes, true);
6064 sortByWeight(bottomBoxes, false);
6065
6066 // Essentially we now have any number of boxes on each of the 4 sides.
6067 // Our canvas looks like the following.
6068 // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
6069 // B1 is the bottom axis
6070 // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
6071 // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
6072 // an error will be thrown.
6073 //
6074 // |----------------------------------------------------|
6075 // | T1 (Full Width) |
6076 // |----------------------------------------------------|
6077 // | | | T2 | |
6078 // | |----|-------------------------------------|----|
6079 // | | | C1 | | C2 | |
6080 // | | |----| |----| |
6081 // | | | | |
6082 // | L1 | L2 | ChartArea (C0) | R1 |
6083 // | | | | |
6084 // | | |----| |----| |
6085 // | | | C3 | | C4 | |
6086 // | |----|-------------------------------------|----|
6087 // | | | B1 | |
6088 // |----------------------------------------------------|
6089 // | B2 (Full Width) |
6090 // |----------------------------------------------------|
6091 //
6092 // What we do to find the best sizing, we do the following
6093 // 1. Determine the minimum size of the chart area.
6094 // 2. Split the remaining width equally between each vertical axis
6095 // 3. Split the remaining height equally between each horizontal axis
6096 // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
6097 // 5. Adjust the sizes of each axis based on it's minimum reported size.
6098 // 6. Refit each axis
6099 // 7. Position each axis in the final location
6100 // 8. Tell the chart the final location of the chart area
6101 // 9. Tell any axes that overlay the chart area the positions of the chart area
6102
6103 // Step 1
6104 var chartWidth = width - leftPadding - rightPadding;
6105 var chartHeight = height - topPadding - bottomPadding;
6106 var chartAreaWidth = chartWidth / 2; // min 50%
6107 var chartAreaHeight = chartHeight / 2; // min 50%
6108
6109 // Step 2
6110 var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
6111
6112 // Step 3
6113 var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
6114
6115 // Step 4
6116 var maxChartAreaWidth = chartWidth;
6117 var maxChartAreaHeight = chartHeight;
6118 var minBoxSizes = [];
6119
6120 function getMinimumBoxSize(box) {
6121 var minSize;
6122 var isHorizontal = box.isHorizontal();
6123
6124 if (isHorizontal) {
6125 minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
6126 maxChartAreaHeight -= minSize.height;
6127 } else {
6128 minSize = box.update(verticalBoxWidth, maxChartAreaHeight);
6129 maxChartAreaWidth -= minSize.width;
6130 }
6131
6132 minBoxSizes.push({
6133 horizontal: isHorizontal,
6134 minSize: minSize,
6135 box: box,
6136 });
6137 }
6138
6139 helpers$1.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
6140
6141 // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
6142 var maxHorizontalLeftPadding = 0;
6143 var maxHorizontalRightPadding = 0;
6144 var maxVerticalTopPadding = 0;
6145 var maxVerticalBottomPadding = 0;
6146
6147 helpers$1.each(topBoxes.concat(bottomBoxes), function(horizontalBox) {
6148 if (horizontalBox.getPadding) {
6149 var boxPadding = horizontalBox.getPadding();
6150 maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left);
6151 maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right);
6152 }
6153 });
6154
6155 helpers$1.each(leftBoxes.concat(rightBoxes), function(verticalBox) {
6156 if (verticalBox.getPadding) {
6157 var boxPadding = verticalBox.getPadding();
6158 maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top);
6159 maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom);
6160 }
6161 });
6162
6163 // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
6164 // be if the axes are drawn at their minimum sizes.
6165 // Steps 5 & 6
6166 var totalLeftBoxesWidth = leftPadding;
6167 var totalRightBoxesWidth = rightPadding;
6168 var totalTopBoxesHeight = topPadding;
6169 var totalBottomBoxesHeight = bottomPadding;
6170
6171 // Function to fit a box
6172 function fitBox(box) {
6173 var minBoxSize = helpers$1.findNextWhere(minBoxSizes, function(minBox) {
6174 return minBox.box === box;
6175 });
6176
6177 if (minBoxSize) {
6178 if (box.isHorizontal()) {
6179 var scaleMargin = {
6180 left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding),
6181 right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding),
6182 top: 0,
6183 bottom: 0
6184 };
6185
6186 // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
6187 // on the margin. Sometimes they need to increase in size slightly
6188 box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
6189 } else {
6190 box.update(minBoxSize.minSize.width, maxChartAreaHeight);
6191 }
6192 }
6193 }
6194
6195 // Update, and calculate the left and right margins for the horizontal boxes
6196 helpers$1.each(leftBoxes.concat(rightBoxes), fitBox);
6197
6198 helpers$1.each(leftBoxes, function(box) {
6199 totalLeftBoxesWidth += box.width;
6200 });
6201
6202 helpers$1.each(rightBoxes, function(box) {
6203 totalRightBoxesWidth += box.width;
6204 });
6205
6206 // Set the Left and Right margins for the horizontal boxes
6207 helpers$1.each(topBoxes.concat(bottomBoxes), fitBox);
6208
6209 // Figure out how much margin is on the top and bottom of the vertical boxes
6210 helpers$1.each(topBoxes, function(box) {
6211 totalTopBoxesHeight += box.height;
6212 });
6213
6214 helpers$1.each(bottomBoxes, function(box) {
6215 totalBottomBoxesHeight += box.height;
6216 });
6217
6218 function finalFitVerticalBox(box) {
6219 var minBoxSize = helpers$1.findNextWhere(minBoxSizes, function(minSize) {
6220 return minSize.box === box;
6221 });
6222
6223 var scaleMargin = {
6224 left: 0,
6225 right: 0,
6226 top: totalTopBoxesHeight,
6227 bottom: totalBottomBoxesHeight
6228 };
6229
6230 if (minBoxSize) {
6231 box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
6232 }
6233 }
6234
6235 // Let the left layout know the final margin
6236 helpers$1.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
6237
6238 // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
6239 totalLeftBoxesWidth = leftPadding;
6240 totalRightBoxesWidth = rightPadding;
6241 totalTopBoxesHeight = topPadding;
6242 totalBottomBoxesHeight = bottomPadding;
6243
6244 helpers$1.each(leftBoxes, function(box) {
6245 totalLeftBoxesWidth += box.width;
6246 });
6247
6248 helpers$1.each(rightBoxes, function(box) {
6249 totalRightBoxesWidth += box.width;
6250 });
6251
6252 helpers$1.each(topBoxes, function(box) {
6253 totalTopBoxesHeight += box.height;
6254 });
6255 helpers$1.each(bottomBoxes, function(box) {
6256 totalBottomBoxesHeight += box.height;
6257 });
6258
6259 // We may be adding some padding to account for rotated x axis labels
6260 var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0);
6261 totalLeftBoxesWidth += leftPaddingAddition;
6262 totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0);
6263
6264 var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0);
6265 totalTopBoxesHeight += topPaddingAddition;
6266 totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0);
6267
6268 // Figure out if our chart area changed. This would occur if the dataset layout label rotation
6269 // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
6270 // without calling `fit` again
6271 var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
6272 var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
6273
6274 if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
6275 helpers$1.each(leftBoxes, function(box) {
6276 box.height = newMaxChartAreaHeight;
6277 });
6278
6279 helpers$1.each(rightBoxes, function(box) {
6280 box.height = newMaxChartAreaHeight;
6281 });
6282
6283 helpers$1.each(topBoxes, function(box) {
6284 if (!box.fullWidth) {
6285 box.width = newMaxChartAreaWidth;
6286 }
6287 });
6288
6289 helpers$1.each(bottomBoxes, function(box) {
6290 if (!box.fullWidth) {
6291 box.width = newMaxChartAreaWidth;
6292 }
6293 });
6294
6295 maxChartAreaHeight = newMaxChartAreaHeight;
6296 maxChartAreaWidth = newMaxChartAreaWidth;
6297 }
6298
6299 // Step 7 - Position the boxes
6300 var left = leftPadding + leftPaddingAddition;
6301 var top = topPadding + topPaddingAddition;
6302
6303 function placeBox(box) {
6304 if (box.isHorizontal()) {
6305 box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth;
6306 box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;
6307 box.top = top;
6308 box.bottom = top + box.height;
6309
6310 // Move to next point
6311 top = box.bottom;
6312
6313 } else {
6314
6315 box.left = left;
6316 box.right = left + box.width;
6317 box.top = totalTopBoxesHeight;
6318 box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
6319
6320 // Move to next point
6321 left = box.right;
6322 }
6323 }
6324
6325 helpers$1.each(leftBoxes.concat(topBoxes), placeBox);
6326
6327 // Account for chart width and height
6328 left += maxChartAreaWidth;
6329 top += maxChartAreaHeight;
6330
6331 helpers$1.each(rightBoxes, placeBox);
6332 helpers$1.each(bottomBoxes, placeBox);
6333
6334 // Step 8
6335 chart.chartArea = {
6336 left: totalLeftBoxesWidth,
6337 top: totalTopBoxesHeight,
6338 right: totalLeftBoxesWidth + maxChartAreaWidth,
6339 bottom: totalTopBoxesHeight + maxChartAreaHeight
6340 };
6341
6342 // Step 9
6343 helpers$1.each(chartAreaBoxes, function(box) {
6344 box.left = chart.chartArea.left;
6345 box.top = chart.chartArea.top;
6346 box.right = chart.chartArea.right;
6347 box.bottom = chart.chartArea.bottom;
6348
6349 box.update(maxChartAreaWidth, maxChartAreaHeight);
6350 });
6351 }
6352};
6353
6354/**
6355 * Platform fallback implementation (minimal).
6356 * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
6357 */
6358
6359var platform_basic = {
6360 acquireContext: function(item) {
6361 if (item && item.canvas) {
6362 // Support for any object associated to a canvas (including a context2d)
6363 item = item.canvas;
6364 }
6365
6366 return item && item.getContext('2d') || null;
6367 }
6368};
6369
6370var EXPANDO_KEY = '$chartjs';
6371var CSS_PREFIX = 'chartjs-';
6372var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
6373var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
6374var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
6375
6376/**
6377 * DOM event types -> Chart.js event types.
6378 * Note: only events with different types are mapped.
6379 * @see https://developer.mozilla.org/en-US/docs/Web/Events
6380 */
6381var EVENT_TYPES = {
6382 touchstart: 'mousedown',
6383 touchmove: 'mousemove',
6384 touchend: 'mouseup',
6385 pointerenter: 'mouseenter',
6386 pointerdown: 'mousedown',
6387 pointermove: 'mousemove',
6388 pointerup: 'mouseup',
6389 pointerleave: 'mouseout',
6390 pointerout: 'mouseout'
6391};
6392
6393/**
6394 * The "used" size is the final value of a dimension property after all calculations have
6395 * been performed. This method uses the computed style of `element` but returns undefined
6396 * if the computed style is not expressed in pixels. That can happen in some cases where
6397 * `element` has a size relative to its parent and this last one is not yet displayed,
6398 * for example because of `display: none` on a parent node.
6399 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
6400 * @returns {Number} Size in pixels or undefined if unknown.
6401 */
6402function readUsedSize(element, property) {
6403 var value = helpers$1.getStyle(element, property);
6404 var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
6405 return matches ? Number(matches[1]) : undefined;
6406}
6407
6408/**
6409 * Initializes the canvas style and render size without modifying the canvas display size,
6410 * since responsiveness is handled by the controller.resize() method. The config is used
6411 * to determine the aspect ratio to apply in case no explicit height has been specified.
6412 */
6413function initCanvas(canvas, config) {
6414 var style = canvas.style;
6415
6416 // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
6417 // returns null or '' if no explicit value has been set to the canvas attribute.
6418 var renderHeight = canvas.getAttribute('height');
6419 var renderWidth = canvas.getAttribute('width');
6420
6421 // Chart.js modifies some canvas values that we want to restore on destroy
6422 canvas[EXPANDO_KEY] = {
6423 initial: {
6424 height: renderHeight,
6425 width: renderWidth,
6426 style: {
6427 display: style.display,
6428 height: style.height,
6429 width: style.width
6430 }
6431 }
6432 };
6433
6434 // Force canvas to display as block to avoid extra space caused by inline
6435 // elements, which would interfere with the responsive resize process.
6436 // https://github.com/chartjs/Chart.js/issues/2538
6437 style.display = style.display || 'block';
6438
6439 if (renderWidth === null || renderWidth === '') {
6440 var displayWidth = readUsedSize(canvas, 'width');
6441 if (displayWidth !== undefined) {
6442 canvas.width = displayWidth;
6443 }
6444 }
6445
6446 if (renderHeight === null || renderHeight === '') {
6447 if (canvas.style.height === '') {
6448 // If no explicit render height and style height, let's apply the aspect ratio,
6449 // which one can be specified by the user but also by charts as default option
6450 // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
6451 canvas.height = canvas.width / (config.options.aspectRatio || 2);
6452 } else {
6453 var displayHeight = readUsedSize(canvas, 'height');
6454 if (displayWidth !== undefined) {
6455 canvas.height = displayHeight;
6456 }
6457 }
6458 }
6459
6460 return canvas;
6461}
6462
6463/**
6464 * Detects support for options object argument in addEventListener.
6465 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
6466 * @private
6467 */
6468var supportsEventListenerOptions = (function() {
6469 var supports = false;
6470 try {
6471 var options = Object.defineProperty({}, 'passive', {
6472 // eslint-disable-next-line getter-return
6473 get: function() {
6474 supports = true;
6475 }
6476 });
6477 window.addEventListener('e', null, options);
6478 } catch (e) {
6479 // continue regardless of error
6480 }
6481 return supports;
6482}());
6483
6484// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
6485// https://github.com/chartjs/Chart.js/issues/4287
6486var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
6487
6488function addEventListener(node, type, listener) {
6489 node.addEventListener(type, listener, eventListenerOptions);
6490}
6491
6492function removeEventListener(node, type, listener) {
6493 node.removeEventListener(type, listener, eventListenerOptions);
6494}
6495
6496function createEvent(type, chart, x, y, nativeEvent) {
6497 return {
6498 type: type,
6499 chart: chart,
6500 native: nativeEvent || null,
6501 x: x !== undefined ? x : null,
6502 y: y !== undefined ? y : null,
6503 };
6504}
6505
6506function fromNativeEvent(event, chart) {
6507 var type = EVENT_TYPES[event.type] || event.type;
6508 var pos = helpers$1.getRelativePosition(event, chart);
6509 return createEvent(type, chart, pos.x, pos.y, event);
6510}
6511
6512function throttled(fn, thisArg) {
6513 var ticking = false;
6514 var args = [];
6515
6516 return function() {
6517 args = Array.prototype.slice.call(arguments);
6518 thisArg = thisArg || this;
6519
6520 if (!ticking) {
6521 ticking = true;
6522 helpers$1.requestAnimFrame.call(window, function() {
6523 ticking = false;
6524 fn.apply(thisArg, args);
6525 });
6526 }
6527 };
6528}
6529
6530function createDiv(cls, style) {
6531 var el = document.createElement('div');
6532 el.style.cssText = style || '';
6533 el.className = cls || '';
6534 return el;
6535}
6536
6537// Implementation based on https://github.com/marcj/css-element-queries
6538function createResizer(handler) {
6539 var cls = CSS_PREFIX + 'size-monitor';
6540 var maxSize = 1000000;
6541 var style =
6542 'position:absolute;' +
6543 'left:0;' +
6544 'top:0;' +
6545 'right:0;' +
6546 'bottom:0;' +
6547 'overflow:hidden;' +
6548 'pointer-events:none;' +
6549 'visibility:hidden;' +
6550 'z-index:-1;';
6551
6552 // NOTE(SB) Don't use innerHTML because it could be considered unsafe.
6553 // https://github.com/chartjs/Chart.js/issues/5902
6554 var resizer = createDiv(cls, style);
6555 var expand = createDiv(cls + '-expand', style);
6556 var shrink = createDiv(cls + '-shrink', style);
6557
6558 expand.appendChild(createDiv('',
6559 'position:absolute;' +
6560 'height:' + maxSize + 'px;' +
6561 'width:' + maxSize + 'px;' +
6562 'left:0;' +
6563 'top:0;'
6564 ));
6565 shrink.appendChild(createDiv('',
6566 'position:absolute;' +
6567 'height:200%;' +
6568 'width:200%;' +
6569 'left:0;' +
6570 'top:0;'
6571 ));
6572
6573 resizer.appendChild(expand);
6574 resizer.appendChild(shrink);
6575 resizer._reset = function() {
6576 expand.scrollLeft = maxSize;
6577 expand.scrollTop = maxSize;
6578 shrink.scrollLeft = maxSize;
6579 shrink.scrollTop = maxSize;
6580 };
6581
6582 var onScroll = function() {
6583 resizer._reset();
6584 handler();
6585 };
6586
6587 addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
6588 addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
6589
6590 return resizer;
6591}
6592
6593// https://davidwalsh.name/detect-node-insertion
6594function watchForRender(node, handler) {
6595 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
6596 var proxy = expando.renderProxy = function(e) {
6597 if (e.animationName === CSS_RENDER_ANIMATION) {
6598 handler();
6599 }
6600 };
6601
6602 helpers$1.each(ANIMATION_START_EVENTS, function(type) {
6603 addEventListener(node, type, proxy);
6604 });
6605
6606 // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
6607 // is removed then added back immediately (same animation frame?). Accessing the
6608 // `offsetParent` property will force a reflow and re-evaluate the CSS animation.
6609 // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
6610 // https://github.com/chartjs/Chart.js/issues/4737
6611 expando.reflow = !!node.offsetParent;
6612
6613 node.classList.add(CSS_RENDER_MONITOR);
6614}
6615
6616function unwatchForRender(node) {
6617 var expando = node[EXPANDO_KEY] || {};
6618 var proxy = expando.renderProxy;
6619
6620 if (proxy) {
6621 helpers$1.each(ANIMATION_START_EVENTS, function(type) {
6622 removeEventListener(node, type, proxy);
6623 });
6624
6625 delete expando.renderProxy;
6626 }
6627
6628 node.classList.remove(CSS_RENDER_MONITOR);
6629}
6630
6631function addResizeListener(node, listener, chart) {
6632 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
6633
6634 // Let's keep track of this added resizer and thus avoid DOM query when removing it.
6635 var resizer = expando.resizer = createResizer(throttled(function() {
6636 if (expando.resizer) {
6637 return listener(createEvent('resize', chart));
6638 }
6639 }));
6640
6641 // The resizer needs to be attached to the node parent, so we first need to be
6642 // sure that `node` is attached to the DOM before injecting the resizer element.
6643 watchForRender(node, function() {
6644 if (expando.resizer) {
6645 var container = node.parentNode;
6646 if (container && container !== resizer.parentNode) {
6647 container.insertBefore(resizer, container.firstChild);
6648 }
6649
6650 // The container size might have changed, let's reset the resizer state.
6651 resizer._reset();
6652 }
6653 });
6654}
6655
6656function removeResizeListener(node) {
6657 var expando = node[EXPANDO_KEY] || {};
6658 var resizer = expando.resizer;
6659
6660 delete expando.resizer;
6661 unwatchForRender(node);
6662
6663 if (resizer && resizer.parentNode) {
6664 resizer.parentNode.removeChild(resizer);
6665 }
6666}
6667
6668function injectCSS(platform, css) {
6669 // https://stackoverflow.com/q/3922139
6670 var style = platform._style || document.createElement('style');
6671 if (!platform._style) {
6672 platform._style = style;
6673 css = '/* Chart.js */\n' + css;
6674 style.setAttribute('type', 'text/css');
6675 document.getElementsByTagName('head')[0].appendChild(style);
6676 }
6677
6678 style.appendChild(document.createTextNode(css));
6679}
6680
6681var platform_dom = {
6682 /**
6683 * This property holds whether this platform is enabled for the current environment.
6684 * Currently used by platform.js to select the proper implementation.
6685 * @private
6686 */
6687 _enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
6688
6689 initialize: function() {
6690 var keyframes = 'from{opacity:0.99}to{opacity:1}';
6691
6692 injectCSS(this,
6693 // DOM rendering detection
6694 // https://davidwalsh.name/detect-node-insertion
6695 '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
6696 '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
6697 '.' + CSS_RENDER_MONITOR + '{' +
6698 '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
6699 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
6700 '}'
6701 );
6702 },
6703
6704 acquireContext: function(item, config) {
6705 if (typeof item === 'string') {
6706 item = document.getElementById(item);
6707 } else if (item.length) {
6708 // Support for array based queries (such as jQuery)
6709 item = item[0];
6710 }
6711
6712 if (item && item.canvas) {
6713 // Support for any object associated to a canvas (including a context2d)
6714 item = item.canvas;
6715 }
6716
6717 // To prevent canvas fingerprinting, some add-ons undefine the getContext
6718 // method, for example: https://github.com/kkapsner/CanvasBlocker
6719 // https://github.com/chartjs/Chart.js/issues/2807
6720 var context = item && item.getContext && item.getContext('2d');
6721
6722 // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
6723 // inside an iframe or when running in a protected environment. We could guess the
6724 // types from their toString() value but let's keep things flexible and assume it's
6725 // a sufficient condition if the item has a context2D which has item as `canvas`.
6726 // https://github.com/chartjs/Chart.js/issues/3887
6727 // https://github.com/chartjs/Chart.js/issues/4102
6728 // https://github.com/chartjs/Chart.js/issues/4152
6729 if (context && context.canvas === item) {
6730 initCanvas(item, config);
6731 return context;
6732 }
6733
6734 return null;
6735 },
6736
6737 releaseContext: function(context) {
6738 var canvas = context.canvas;
6739 if (!canvas[EXPANDO_KEY]) {
6740 return;
6741 }
6742
6743 var initial = canvas[EXPANDO_KEY].initial;
6744 ['height', 'width'].forEach(function(prop) {
6745 var value = initial[prop];
6746 if (helpers$1.isNullOrUndef(value)) {
6747 canvas.removeAttribute(prop);
6748 } else {
6749 canvas.setAttribute(prop, value);
6750 }
6751 });
6752
6753 helpers$1.each(initial.style || {}, function(value, key) {
6754 canvas.style[key] = value;
6755 });
6756
6757 // The canvas render size might have been changed (and thus the state stack discarded),
6758 // we can't use save() and restore() to restore the initial state. So make sure that at
6759 // least the canvas context is reset to the default state by setting the canvas width.
6760 // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
6761 // eslint-disable-next-line no-self-assign
6762 canvas.width = canvas.width;
6763
6764 delete canvas[EXPANDO_KEY];
6765 },
6766
6767 addEventListener: function(chart, type, listener) {
6768 var canvas = chart.canvas;
6769 if (type === 'resize') {
6770 // Note: the resize event is not supported on all browsers.
6771 addResizeListener(canvas, listener, chart);
6772 return;
6773 }
6774
6775 var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
6776 var proxies = expando.proxies || (expando.proxies = {});
6777 var proxy = proxies[chart.id + '_' + type] = function(event) {
6778 listener(fromNativeEvent(event, chart));
6779 };
6780
6781 addEventListener(canvas, type, proxy);
6782 },
6783
6784 removeEventListener: function(chart, type, listener) {
6785 var canvas = chart.canvas;
6786 if (type === 'resize') {
6787 // Note: the resize event is not supported on all browsers.
6788 removeResizeListener(canvas);
6789 return;
6790 }
6791
6792 var expando = listener[EXPANDO_KEY] || {};
6793 var proxies = expando.proxies || {};
6794 var proxy = proxies[chart.id + '_' + type];
6795 if (!proxy) {
6796 return;
6797 }
6798
6799 removeEventListener(canvas, type, proxy);
6800 }
6801};
6802
6803// DEPRECATIONS
6804
6805/**
6806 * Provided for backward compatibility, use EventTarget.addEventListener instead.
6807 * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
6808 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
6809 * @function Chart.helpers.addEvent
6810 * @deprecated since version 2.7.0
6811 * @todo remove at version 3
6812 * @private
6813 */
6814helpers$1.addEvent = addEventListener;
6815
6816/**
6817 * Provided for backward compatibility, use EventTarget.removeEventListener instead.
6818 * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
6819 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
6820 * @function Chart.helpers.removeEvent
6821 * @deprecated since version 2.7.0
6822 * @todo remove at version 3
6823 * @private
6824 */
6825helpers$1.removeEvent = removeEventListener;
6826
6827// @TODO Make possible to select another platform at build time.
6828var implementation = platform_dom._enabled ? platform_dom : platform_basic;
6829
6830/**
6831 * @namespace Chart.platform
6832 * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
6833 * @since 2.4.0
6834 */
6835var platform = helpers$1.extend({
6836 /**
6837 * @since 2.7.0
6838 */
6839 initialize: function() {},
6840
6841 /**
6842 * Called at chart construction time, returns a context2d instance implementing
6843 * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
6844 * @param {*} item - The native item from which to acquire context (platform specific)
6845 * @param {Object} options - The chart options
6846 * @returns {CanvasRenderingContext2D} context2d instance
6847 */
6848 acquireContext: function() {},
6849
6850 /**
6851 * Called at chart destruction time, releases any resources associated to the context
6852 * previously returned by the acquireContext() method.
6853 * @param {CanvasRenderingContext2D} context - The context2d instance
6854 * @returns {Boolean} true if the method succeeded, else false
6855 */
6856 releaseContext: function() {},
6857
6858 /**
6859 * Registers the specified listener on the given chart.
6860 * @param {Chart} chart - Chart from which to listen for event
6861 * @param {String} type - The ({@link IEvent}) type to listen for
6862 * @param {Function} listener - Receives a notification (an object that implements
6863 * the {@link IEvent} interface) when an event of the specified type occurs.
6864 */
6865 addEventListener: function() {},
6866
6867 /**
6868 * Removes the specified listener previously registered with addEventListener.
6869 * @param {Chart} chart -Chart from which to remove the listener
6870 * @param {String} type - The ({@link IEvent}) type to remove
6871 * @param {Function} listener - The listener function to remove from the event target.
6872 */
6873 removeEventListener: function() {}
6874
6875}, implementation);
6876
6877core_defaults._set('global', {
6878 plugins: {}
6879});
6880
6881/**
6882 * The plugin service singleton
6883 * @namespace Chart.plugins
6884 * @since 2.1.0
6885 */
6886var core_plugins = {
6887 /**
6888 * Globally registered plugins.
6889 * @private
6890 */
6891 _plugins: [],
6892
6893 /**
6894 * This identifier is used to invalidate the descriptors cache attached to each chart
6895 * when a global plugin is registered or unregistered. In this case, the cache ID is
6896 * incremented and descriptors are regenerated during following API calls.
6897 * @private
6898 */
6899 _cacheId: 0,
6900
6901 /**
6902 * Registers the given plugin(s) if not already registered.
6903 * @param {Array|Object} plugins plugin instance(s).
6904 */
6905 register: function(plugins) {
6906 var p = this._plugins;
6907 ([]).concat(plugins).forEach(function(plugin) {
6908 if (p.indexOf(plugin) === -1) {
6909 p.push(plugin);
6910 }
6911 });
6912
6913 this._cacheId++;
6914 },
6915
6916 /**
6917 * Unregisters the given plugin(s) only if registered.
6918 * @param {Array|Object} plugins plugin instance(s).
6919 */
6920 unregister: function(plugins) {
6921 var p = this._plugins;
6922 ([]).concat(plugins).forEach(function(plugin) {
6923 var idx = p.indexOf(plugin);
6924 if (idx !== -1) {
6925 p.splice(idx, 1);
6926 }
6927 });
6928
6929 this._cacheId++;
6930 },
6931
6932 /**
6933 * Remove all registered plugins.
6934 * @since 2.1.5
6935 */
6936 clear: function() {
6937 this._plugins = [];
6938 this._cacheId++;
6939 },
6940
6941 /**
6942 * Returns the number of registered plugins?
6943 * @returns {Number}
6944 * @since 2.1.5
6945 */
6946 count: function() {
6947 return this._plugins.length;
6948 },
6949
6950 /**
6951 * Returns all registered plugin instances.
6952 * @returns {Array} array of plugin objects.
6953 * @since 2.1.5
6954 */
6955 getAll: function() {
6956 return this._plugins;
6957 },
6958
6959 /**
6960 * Calls enabled plugins for `chart` on the specified hook and with the given args.
6961 * This method immediately returns as soon as a plugin explicitly returns false. The
6962 * returned value can be used, for instance, to interrupt the current action.
6963 * @param {Object} chart - The chart instance for which plugins should be called.
6964 * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
6965 * @param {Array} [args] - Extra arguments to apply to the hook call.
6966 * @returns {Boolean} false if any of the plugins return false, else returns true.
6967 */
6968 notify: function(chart, hook, args) {
6969 var descriptors = this.descriptors(chart);
6970 var ilen = descriptors.length;
6971 var i, descriptor, plugin, params, method;
6972
6973 for (i = 0; i < ilen; ++i) {
6974 descriptor = descriptors[i];
6975 plugin = descriptor.plugin;
6976 method = plugin[hook];
6977 if (typeof method === 'function') {
6978 params = [chart].concat(args || []);
6979 params.push(descriptor.options);
6980 if (method.apply(plugin, params) === false) {
6981 return false;
6982 }
6983 }
6984 }
6985
6986 return true;
6987 },
6988
6989 /**
6990 * Returns descriptors of enabled plugins for the given chart.
6991 * @returns {Array} [{ plugin, options }]
6992 * @private
6993 */
6994 descriptors: function(chart) {
6995 var cache = chart.$plugins || (chart.$plugins = {});
6996 if (cache.id === this._cacheId) {
6997 return cache.descriptors;
6998 }
6999
7000 var plugins = [];
7001 var descriptors = [];
7002 var config = (chart && chart.config) || {};
7003 var options = (config.options && config.options.plugins) || {};
7004
7005 this._plugins.concat(config.plugins || []).forEach(function(plugin) {
7006 var idx = plugins.indexOf(plugin);
7007 if (idx !== -1) {
7008 return;
7009 }
7010
7011 var id = plugin.id;
7012 var opts = options[id];
7013 if (opts === false) {
7014 return;
7015 }
7016
7017 if (opts === true) {
7018 opts = helpers$1.clone(core_defaults.global.plugins[id]);
7019 }
7020
7021 plugins.push(plugin);
7022 descriptors.push({
7023 plugin: plugin,
7024 options: opts || {}
7025 });
7026 });
7027
7028 cache.descriptors = descriptors;
7029 cache.id = this._cacheId;
7030 return descriptors;
7031 },
7032
7033 /**
7034 * Invalidates cache for the given chart: descriptors hold a reference on plugin option,
7035 * but in some cases, this reference can be changed by the user when updating options.
7036 * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
7037 * @private
7038 */
7039 _invalidate: function(chart) {
7040 delete chart.$plugins;
7041 }
7042};
7043
7044var core_scaleService = {
7045 // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
7046 // use the new chart options to grab the correct scale
7047 constructors: {},
7048 // Use a registration function so that we can move to an ES6 map when we no longer need to support
7049 // old browsers
7050
7051 // Scale config defaults
7052 defaults: {},
7053 registerScaleType: function(type, scaleConstructor, scaleDefaults) {
7054 this.constructors[type] = scaleConstructor;
7055 this.defaults[type] = helpers$1.clone(scaleDefaults);
7056 },
7057 getScaleConstructor: function(type) {
7058 return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
7059 },
7060 getScaleDefaults: function(type) {
7061 // Return the scale defaults merged with the global settings so that we always use the latest ones
7062 return this.defaults.hasOwnProperty(type) ? helpers$1.merge({}, [core_defaults.scale, this.defaults[type]]) : {};
7063 },
7064 updateScaleDefaults: function(type, additions) {
7065 var me = this;
7066 if (me.defaults.hasOwnProperty(type)) {
7067 me.defaults[type] = helpers$1.extend(me.defaults[type], additions);
7068 }
7069 },
7070 addScalesToLayout: function(chart) {
7071 // Adds each scale to the chart.boxes array to be sized accordingly
7072 helpers$1.each(chart.scales, function(scale) {
7073 // Set ILayoutItem parameters for backwards compatibility
7074 scale.fullWidth = scale.options.fullWidth;
7075 scale.position = scale.options.position;
7076 scale.weight = scale.options.weight;
7077 core_layouts.addBox(chart, scale);
7078 });
7079 }
7080};
7081
7082var valueOrDefault$5 = helpers$1.valueOrDefault;
7083
7084core_defaults._set('global', {
7085 tooltips: {
7086 enabled: true,
7087 custom: null,
7088 mode: 'nearest',
7089 position: 'average',
7090 intersect: true,
7091 backgroundColor: 'rgba(0,0,0,0.8)',
7092 titleFontStyle: 'bold',
7093 titleSpacing: 2,
7094 titleMarginBottom: 6,
7095 titleFontColor: '#fff',
7096 titleAlign: 'left',
7097 bodySpacing: 2,
7098 bodyFontColor: '#fff',
7099 bodyAlign: 'left',
7100 footerFontStyle: 'bold',
7101 footerSpacing: 2,
7102 footerMarginTop: 6,
7103 footerFontColor: '#fff',
7104 footerAlign: 'left',
7105 yPadding: 6,
7106 xPadding: 6,
7107 caretPadding: 2,
7108 caretSize: 5,
7109 cornerRadius: 6,
7110 multiKeyBackground: '#fff',
7111 displayColors: true,
7112 borderColor: 'rgba(0,0,0,0)',
7113 borderWidth: 0,
7114 callbacks: {
7115 // Args are: (tooltipItems, data)
7116 beforeTitle: helpers$1.noop,
7117 title: function(tooltipItems, data) {
7118 // Pick first xLabel for now
7119 var title = '';
7120 var labels = data.labels;
7121 var labelCount = labels ? labels.length : 0;
7122
7123 if (tooltipItems.length > 0) {
7124 var item = tooltipItems[0];
7125
7126 if (item.xLabel) {
7127 title = item.xLabel;
7128 } else if (labelCount > 0 && item.index < labelCount) {
7129 title = labels[item.index];
7130 }
7131 }
7132
7133 return title;
7134 },
7135 afterTitle: helpers$1.noop,
7136
7137 // Args are: (tooltipItems, data)
7138 beforeBody: helpers$1.noop,
7139
7140 // Args are: (tooltipItem, data)
7141 beforeLabel: helpers$1.noop,
7142 label: function(tooltipItem, data) {
7143 var label = data.datasets[tooltipItem.datasetIndex].label || '';
7144
7145 if (label) {
7146 label += ': ';
7147 }
7148 label += tooltipItem.yLabel;
7149 return label;
7150 },
7151 labelColor: function(tooltipItem, chart) {
7152 var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
7153 var activeElement = meta.data[tooltipItem.index];
7154 var view = activeElement._view;
7155 return {
7156 borderColor: view.borderColor,
7157 backgroundColor: view.backgroundColor
7158 };
7159 },
7160 labelTextColor: function() {
7161 return this._options.bodyFontColor;
7162 },
7163 afterLabel: helpers$1.noop,
7164
7165 // Args are: (tooltipItems, data)
7166 afterBody: helpers$1.noop,
7167
7168 // Args are: (tooltipItems, data)
7169 beforeFooter: helpers$1.noop,
7170 footer: helpers$1.noop,
7171 afterFooter: helpers$1.noop
7172 }
7173 }
7174});
7175
7176var positioners = {
7177 /**
7178 * Average mode places the tooltip at the average position of the elements shown
7179 * @function Chart.Tooltip.positioners.average
7180 * @param elements {ChartElement[]} the elements being displayed in the tooltip
7181 * @returns {Point} tooltip position
7182 */
7183 average: function(elements) {
7184 if (!elements.length) {
7185 return false;
7186 }
7187
7188 var i, len;
7189 var x = 0;
7190 var y = 0;
7191 var count = 0;
7192
7193 for (i = 0, len = elements.length; i < len; ++i) {
7194 var el = elements[i];
7195 if (el && el.hasValue()) {
7196 var pos = el.tooltipPosition();
7197 x += pos.x;
7198 y += pos.y;
7199 ++count;
7200 }
7201 }
7202
7203 return {
7204 x: x / count,
7205 y: y / count
7206 };
7207 },
7208
7209 /**
7210 * Gets the tooltip position nearest of the item nearest to the event position
7211 * @function Chart.Tooltip.positioners.nearest
7212 * @param elements {Chart.Element[]} the tooltip elements
7213 * @param eventPosition {Point} the position of the event in canvas coordinates
7214 * @returns {Point} the tooltip position
7215 */
7216 nearest: function(elements, eventPosition) {
7217 var x = eventPosition.x;
7218 var y = eventPosition.y;
7219 var minDistance = Number.POSITIVE_INFINITY;
7220 var i, len, nearestElement;
7221
7222 for (i = 0, len = elements.length; i < len; ++i) {
7223 var el = elements[i];
7224 if (el && el.hasValue()) {
7225 var center = el.getCenterPoint();
7226 var d = helpers$1.distanceBetweenPoints(eventPosition, center);
7227
7228 if (d < minDistance) {
7229 minDistance = d;
7230 nearestElement = el;
7231 }
7232 }
7233 }
7234
7235 if (nearestElement) {
7236 var tp = nearestElement.tooltipPosition();
7237 x = tp.x;
7238 y = tp.y;
7239 }
7240
7241 return {
7242 x: x,
7243 y: y
7244 };
7245 }
7246};
7247
7248// Helper to push or concat based on if the 2nd parameter is an array or not
7249function pushOrConcat(base, toPush) {
7250 if (toPush) {
7251 if (helpers$1.isArray(toPush)) {
7252 // base = base.concat(toPush);
7253 Array.prototype.push.apply(base, toPush);
7254 } else {
7255 base.push(toPush);
7256 }
7257 }
7258
7259 return base;
7260}
7261
7262/**
7263 * Returns array of strings split by newline
7264 * @param {String} value - The value to split by newline.
7265 * @returns {Array} value if newline present - Returned from String split() method
7266 * @function
7267 */
7268function splitNewlines(str) {
7269 if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
7270 return str.split('\n');
7271 }
7272 return str;
7273}
7274
7275
7276// Private helper to create a tooltip item model
7277// @param element : the chart element (point, arc, bar) to create the tooltip item for
7278// @return : new tooltip item
7279function createTooltipItem(element) {
7280 var xScale = element._xScale;
7281 var yScale = element._yScale || element._scale; // handle radar || polarArea charts
7282 var index = element._index;
7283 var datasetIndex = element._datasetIndex;
7284
7285 return {
7286 xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
7287 yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
7288 index: index,
7289 datasetIndex: datasetIndex,
7290 x: element._model.x,
7291 y: element._model.y
7292 };
7293}
7294
7295/**
7296 * Helper to get the reset model for the tooltip
7297 * @param tooltipOpts {Object} the tooltip options
7298 */
7299function getBaseModel(tooltipOpts) {
7300 var globalDefaults = core_defaults.global;
7301
7302 return {
7303 // Positioning
7304 xPadding: tooltipOpts.xPadding,
7305 yPadding: tooltipOpts.yPadding,
7306 xAlign: tooltipOpts.xAlign,
7307 yAlign: tooltipOpts.yAlign,
7308
7309 // Body
7310 bodyFontColor: tooltipOpts.bodyFontColor,
7311 _bodyFontFamily: valueOrDefault$5(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
7312 _bodyFontStyle: valueOrDefault$5(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
7313 _bodyAlign: tooltipOpts.bodyAlign,
7314 bodyFontSize: valueOrDefault$5(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
7315 bodySpacing: tooltipOpts.bodySpacing,
7316
7317 // Title
7318 titleFontColor: tooltipOpts.titleFontColor,
7319 _titleFontFamily: valueOrDefault$5(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
7320 _titleFontStyle: valueOrDefault$5(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
7321 titleFontSize: valueOrDefault$5(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
7322 _titleAlign: tooltipOpts.titleAlign,
7323 titleSpacing: tooltipOpts.titleSpacing,
7324 titleMarginBottom: tooltipOpts.titleMarginBottom,
7325
7326 // Footer
7327 footerFontColor: tooltipOpts.footerFontColor,
7328 _footerFontFamily: valueOrDefault$5(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
7329 _footerFontStyle: valueOrDefault$5(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
7330 footerFontSize: valueOrDefault$5(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
7331 _footerAlign: tooltipOpts.footerAlign,
7332 footerSpacing: tooltipOpts.footerSpacing,
7333 footerMarginTop: tooltipOpts.footerMarginTop,
7334
7335 // Appearance
7336 caretSize: tooltipOpts.caretSize,
7337 cornerRadius: tooltipOpts.cornerRadius,
7338 backgroundColor: tooltipOpts.backgroundColor,
7339 opacity: 0,
7340 legendColorBackground: tooltipOpts.multiKeyBackground,
7341 displayColors: tooltipOpts.displayColors,
7342 borderColor: tooltipOpts.borderColor,
7343 borderWidth: tooltipOpts.borderWidth
7344 };
7345}
7346
7347/**
7348 * Get the size of the tooltip
7349 */
7350function getTooltipSize(tooltip, model) {
7351 var ctx = tooltip._chart.ctx;
7352
7353 var height = model.yPadding * 2; // Tooltip Padding
7354 var width = 0;
7355
7356 // Count of all lines in the body
7357 var body = model.body;
7358 var combinedBodyLength = body.reduce(function(count, bodyItem) {
7359 return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
7360 }, 0);
7361 combinedBodyLength += model.beforeBody.length + model.afterBody.length;
7362
7363 var titleLineCount = model.title.length;
7364 var footerLineCount = model.footer.length;
7365 var titleFontSize = model.titleFontSize;
7366 var bodyFontSize = model.bodyFontSize;
7367 var footerFontSize = model.footerFontSize;
7368
7369 height += titleLineCount * titleFontSize; // Title Lines
7370 height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
7371 height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
7372 height += combinedBodyLength * bodyFontSize; // Body Lines
7373 height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
7374 height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
7375 height += footerLineCount * (footerFontSize); // Footer Lines
7376 height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
7377
7378 // Title width
7379 var widthPadding = 0;
7380 var maxLineWidth = function(line) {
7381 width = Math.max(width, ctx.measureText(line).width + widthPadding);
7382 };
7383
7384 ctx.font = helpers$1.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
7385 helpers$1.each(model.title, maxLineWidth);
7386
7387 // Body width
7388 ctx.font = helpers$1.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
7389 helpers$1.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
7390
7391 // Body lines may include some extra width due to the color box
7392 widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
7393 helpers$1.each(body, function(bodyItem) {
7394 helpers$1.each(bodyItem.before, maxLineWidth);
7395 helpers$1.each(bodyItem.lines, maxLineWidth);
7396 helpers$1.each(bodyItem.after, maxLineWidth);
7397 });
7398
7399 // Reset back to 0
7400 widthPadding = 0;
7401
7402 // Footer width
7403 ctx.font = helpers$1.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
7404 helpers$1.each(model.footer, maxLineWidth);
7405
7406 // Add padding
7407 width += 2 * model.xPadding;
7408
7409 return {
7410 width: width,
7411 height: height
7412 };
7413}
7414
7415/**
7416 * Helper to get the alignment of a tooltip given the size
7417 */
7418function determineAlignment(tooltip, size) {
7419 var model = tooltip._model;
7420 var chart = tooltip._chart;
7421 var chartArea = tooltip._chart.chartArea;
7422 var xAlign = 'center';
7423 var yAlign = 'center';
7424
7425 if (model.y < size.height) {
7426 yAlign = 'top';
7427 } else if (model.y > (chart.height - size.height)) {
7428 yAlign = 'bottom';
7429 }
7430
7431 var lf, rf; // functions to determine left, right alignment
7432 var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
7433 var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
7434 var midX = (chartArea.left + chartArea.right) / 2;
7435 var midY = (chartArea.top + chartArea.bottom) / 2;
7436
7437 if (yAlign === 'center') {
7438 lf = function(x) {
7439 return x <= midX;
7440 };
7441 rf = function(x) {
7442 return x > midX;
7443 };
7444 } else {
7445 lf = function(x) {
7446 return x <= (size.width / 2);
7447 };
7448 rf = function(x) {
7449 return x >= (chart.width - (size.width / 2));
7450 };
7451 }
7452
7453 olf = function(x) {
7454 return x + size.width + model.caretSize + model.caretPadding > chart.width;
7455 };
7456 orf = function(x) {
7457 return x - size.width - model.caretSize - model.caretPadding < 0;
7458 };
7459 yf = function(y) {
7460 return y <= midY ? 'top' : 'bottom';
7461 };
7462
7463 if (lf(model.x)) {
7464 xAlign = 'left';
7465
7466 // Is tooltip too wide and goes over the right side of the chart.?
7467 if (olf(model.x)) {
7468 xAlign = 'center';
7469 yAlign = yf(model.y);
7470 }
7471 } else if (rf(model.x)) {
7472 xAlign = 'right';
7473
7474 // Is tooltip too wide and goes outside left edge of canvas?
7475 if (orf(model.x)) {
7476 xAlign = 'center';
7477 yAlign = yf(model.y);
7478 }
7479 }
7480
7481 var opts = tooltip._options;
7482 return {
7483 xAlign: opts.xAlign ? opts.xAlign : xAlign,
7484 yAlign: opts.yAlign ? opts.yAlign : yAlign
7485 };
7486}
7487
7488/**
7489 * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
7490 */
7491function getBackgroundPoint(vm, size, alignment, chart) {
7492 // Background Position
7493 var x = vm.x;
7494 var y = vm.y;
7495
7496 var caretSize = vm.caretSize;
7497 var caretPadding = vm.caretPadding;
7498 var cornerRadius = vm.cornerRadius;
7499 var xAlign = alignment.xAlign;
7500 var yAlign = alignment.yAlign;
7501 var paddingAndSize = caretSize + caretPadding;
7502 var radiusAndPadding = cornerRadius + caretPadding;
7503
7504 if (xAlign === 'right') {
7505 x -= size.width;
7506 } else if (xAlign === 'center') {
7507 x -= (size.width / 2);
7508 if (x + size.width > chart.width) {
7509 x = chart.width - size.width;
7510 }
7511 if (x < 0) {
7512 x = 0;
7513 }
7514 }
7515
7516 if (yAlign === 'top') {
7517 y += paddingAndSize;
7518 } else if (yAlign === 'bottom') {
7519 y -= size.height + paddingAndSize;
7520 } else {
7521 y -= (size.height / 2);
7522 }
7523
7524 if (yAlign === 'center') {
7525 if (xAlign === 'left') {
7526 x += paddingAndSize;
7527 } else if (xAlign === 'right') {
7528 x -= paddingAndSize;
7529 }
7530 } else if (xAlign === 'left') {
7531 x -= radiusAndPadding;
7532 } else if (xAlign === 'right') {
7533 x += radiusAndPadding;
7534 }
7535
7536 return {
7537 x: x,
7538 y: y
7539 };
7540}
7541
7542function getAlignedX(vm, align) {
7543 return align === 'center'
7544 ? vm.x + vm.width / 2
7545 : align === 'right'
7546 ? vm.x + vm.width - vm.xPadding
7547 : vm.x + vm.xPadding;
7548}
7549
7550/**
7551 * Helper to build before and after body lines
7552 */
7553function getBeforeAfterBodyLines(callback) {
7554 return pushOrConcat([], splitNewlines(callback));
7555}
7556
7557var exports$3 = core_element.extend({
7558 initialize: function() {
7559 this._model = getBaseModel(this._options);
7560 this._lastActive = [];
7561 },
7562
7563 // Get the title
7564 // Args are: (tooltipItem, data)
7565 getTitle: function() {
7566 var me = this;
7567 var opts = me._options;
7568 var callbacks = opts.callbacks;
7569
7570 var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
7571 var title = callbacks.title.apply(me, arguments);
7572 var afterTitle = callbacks.afterTitle.apply(me, arguments);
7573
7574 var lines = [];
7575 lines = pushOrConcat(lines, splitNewlines(beforeTitle));
7576 lines = pushOrConcat(lines, splitNewlines(title));
7577 lines = pushOrConcat(lines, splitNewlines(afterTitle));
7578
7579 return lines;
7580 },
7581
7582 // Args are: (tooltipItem, data)
7583 getBeforeBody: function() {
7584 return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments));
7585 },
7586
7587 // Args are: (tooltipItem, data)
7588 getBody: function(tooltipItems, data) {
7589 var me = this;
7590 var callbacks = me._options.callbacks;
7591 var bodyItems = [];
7592
7593 helpers$1.each(tooltipItems, function(tooltipItem) {
7594 var bodyItem = {
7595 before: [],
7596 lines: [],
7597 after: []
7598 };
7599 pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data)));
7600 pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
7601 pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data)));
7602
7603 bodyItems.push(bodyItem);
7604 });
7605
7606 return bodyItems;
7607 },
7608
7609 // Args are: (tooltipItem, data)
7610 getAfterBody: function() {
7611 return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments));
7612 },
7613
7614 // Get the footer and beforeFooter and afterFooter lines
7615 // Args are: (tooltipItem, data)
7616 getFooter: function() {
7617 var me = this;
7618 var callbacks = me._options.callbacks;
7619
7620 var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
7621 var footer = callbacks.footer.apply(me, arguments);
7622 var afterFooter = callbacks.afterFooter.apply(me, arguments);
7623
7624 var lines = [];
7625 lines = pushOrConcat(lines, splitNewlines(beforeFooter));
7626 lines = pushOrConcat(lines, splitNewlines(footer));
7627 lines = pushOrConcat(lines, splitNewlines(afterFooter));
7628
7629 return lines;
7630 },
7631
7632 update: function(changed) {
7633 var me = this;
7634 var opts = me._options;
7635
7636 // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
7637 // 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
7638 // which breaks any animations.
7639 var existingModel = me._model;
7640 var model = me._model = getBaseModel(opts);
7641 var active = me._active;
7642
7643 var data = me._data;
7644
7645 // In the case where active.length === 0 we need to keep these at existing values for good animations
7646 var alignment = {
7647 xAlign: existingModel.xAlign,
7648 yAlign: existingModel.yAlign
7649 };
7650 var backgroundPoint = {
7651 x: existingModel.x,
7652 y: existingModel.y
7653 };
7654 var tooltipSize = {
7655 width: existingModel.width,
7656 height: existingModel.height
7657 };
7658 var tooltipPosition = {
7659 x: existingModel.caretX,
7660 y: existingModel.caretY
7661 };
7662
7663 var i, len;
7664
7665 if (active.length) {
7666 model.opacity = 1;
7667
7668 var labelColors = [];
7669 var labelTextColors = [];
7670 tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition);
7671
7672 var tooltipItems = [];
7673 for (i = 0, len = active.length; i < len; ++i) {
7674 tooltipItems.push(createTooltipItem(active[i]));
7675 }
7676
7677 // If the user provided a filter function, use it to modify the tooltip items
7678 if (opts.filter) {
7679 tooltipItems = tooltipItems.filter(function(a) {
7680 return opts.filter(a, data);
7681 });
7682 }
7683
7684 // If the user provided a sorting function, use it to modify the tooltip items
7685 if (opts.itemSort) {
7686 tooltipItems = tooltipItems.sort(function(a, b) {
7687 return opts.itemSort(a, b, data);
7688 });
7689 }
7690
7691 // Determine colors for boxes
7692 helpers$1.each(tooltipItems, function(tooltipItem) {
7693 labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
7694 labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
7695 });
7696
7697
7698 // Build the Text Lines
7699 model.title = me.getTitle(tooltipItems, data);
7700 model.beforeBody = me.getBeforeBody(tooltipItems, data);
7701 model.body = me.getBody(tooltipItems, data);
7702 model.afterBody = me.getAfterBody(tooltipItems, data);
7703 model.footer = me.getFooter(tooltipItems, data);
7704
7705 // Initial positioning and colors
7706 model.x = tooltipPosition.x;
7707 model.y = tooltipPosition.y;
7708 model.caretPadding = opts.caretPadding;
7709 model.labelColors = labelColors;
7710 model.labelTextColors = labelTextColors;
7711
7712 // data points
7713 model.dataPoints = tooltipItems;
7714
7715 // We need to determine alignment of the tooltip
7716 tooltipSize = getTooltipSize(this, model);
7717 alignment = determineAlignment(this, tooltipSize);
7718 // Final Size and Position
7719 backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart);
7720 } else {
7721 model.opacity = 0;
7722 }
7723
7724 model.xAlign = alignment.xAlign;
7725 model.yAlign = alignment.yAlign;
7726 model.x = backgroundPoint.x;
7727 model.y = backgroundPoint.y;
7728 model.width = tooltipSize.width;
7729 model.height = tooltipSize.height;
7730
7731 // Point where the caret on the tooltip points to
7732 model.caretX = tooltipPosition.x;
7733 model.caretY = tooltipPosition.y;
7734
7735 me._model = model;
7736
7737 if (changed && opts.custom) {
7738 opts.custom.call(me, model);
7739 }
7740
7741 return me;
7742 },
7743
7744 drawCaret: function(tooltipPoint, size) {
7745 var ctx = this._chart.ctx;
7746 var vm = this._view;
7747 var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
7748
7749 ctx.lineTo(caretPosition.x1, caretPosition.y1);
7750 ctx.lineTo(caretPosition.x2, caretPosition.y2);
7751 ctx.lineTo(caretPosition.x3, caretPosition.y3);
7752 },
7753 getCaretPosition: function(tooltipPoint, size, vm) {
7754 var x1, x2, x3, y1, y2, y3;
7755 var caretSize = vm.caretSize;
7756 var cornerRadius = vm.cornerRadius;
7757 var xAlign = vm.xAlign;
7758 var yAlign = vm.yAlign;
7759 var ptX = tooltipPoint.x;
7760 var ptY = tooltipPoint.y;
7761 var width = size.width;
7762 var height = size.height;
7763
7764 if (yAlign === 'center') {
7765 y2 = ptY + (height / 2);
7766
7767 if (xAlign === 'left') {
7768 x1 = ptX;
7769 x2 = x1 - caretSize;
7770 x3 = x1;
7771
7772 y1 = y2 + caretSize;
7773 y3 = y2 - caretSize;
7774 } else {
7775 x1 = ptX + width;
7776 x2 = x1 + caretSize;
7777 x3 = x1;
7778
7779 y1 = y2 - caretSize;
7780 y3 = y2 + caretSize;
7781 }
7782 } else {
7783 if (xAlign === 'left') {
7784 x2 = ptX + cornerRadius + (caretSize);
7785 x1 = x2 - caretSize;
7786 x3 = x2 + caretSize;
7787 } else if (xAlign === 'right') {
7788 x2 = ptX + width - cornerRadius - caretSize;
7789 x1 = x2 - caretSize;
7790 x3 = x2 + caretSize;
7791 } else {
7792 x2 = vm.caretX;
7793 x1 = x2 - caretSize;
7794 x3 = x2 + caretSize;
7795 }
7796 if (yAlign === 'top') {
7797 y1 = ptY;
7798 y2 = y1 - caretSize;
7799 y3 = y1;
7800 } else {
7801 y1 = ptY + height;
7802 y2 = y1 + caretSize;
7803 y3 = y1;
7804 // invert drawing order
7805 var tmp = x3;
7806 x3 = x1;
7807 x1 = tmp;
7808 }
7809 }
7810 return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
7811 },
7812
7813 drawTitle: function(pt, vm, ctx) {
7814 var title = vm.title;
7815
7816 if (title.length) {
7817 pt.x = getAlignedX(vm, vm._titleAlign);
7818
7819 ctx.textAlign = vm._titleAlign;
7820 ctx.textBaseline = 'top';
7821
7822 var titleFontSize = vm.titleFontSize;
7823 var titleSpacing = vm.titleSpacing;
7824
7825 ctx.fillStyle = vm.titleFontColor;
7826 ctx.font = helpers$1.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
7827
7828 var i, len;
7829 for (i = 0, len = title.length; i < len; ++i) {
7830 ctx.fillText(title[i], pt.x, pt.y);
7831 pt.y += titleFontSize + titleSpacing; // Line Height and spacing
7832
7833 if (i + 1 === title.length) {
7834 pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
7835 }
7836 }
7837 }
7838 },
7839
7840 drawBody: function(pt, vm, ctx) {
7841 var bodyFontSize = vm.bodyFontSize;
7842 var bodySpacing = vm.bodySpacing;
7843 var bodyAlign = vm._bodyAlign;
7844 var body = vm.body;
7845 var drawColorBoxes = vm.displayColors;
7846 var labelColors = vm.labelColors;
7847 var xLinePadding = 0;
7848 var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0;
7849 var textColor;
7850
7851 ctx.textAlign = bodyAlign;
7852 ctx.textBaseline = 'top';
7853 ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
7854
7855 pt.x = getAlignedX(vm, bodyAlign);
7856
7857 // Before Body
7858 var fillLineOfText = function(line) {
7859 ctx.fillText(line, pt.x + xLinePadding, pt.y);
7860 pt.y += bodyFontSize + bodySpacing;
7861 };
7862
7863 // Before body lines
7864 ctx.fillStyle = vm.bodyFontColor;
7865 helpers$1.each(vm.beforeBody, fillLineOfText);
7866
7867 xLinePadding = drawColorBoxes && bodyAlign !== 'right'
7868 ? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2)
7869 : 0;
7870
7871 // Draw body lines now
7872 helpers$1.each(body, function(bodyItem, i) {
7873 textColor = vm.labelTextColors[i];
7874 ctx.fillStyle = textColor;
7875 helpers$1.each(bodyItem.before, fillLineOfText);
7876
7877 helpers$1.each(bodyItem.lines, function(line) {
7878 // Draw Legend-like boxes if needed
7879 if (drawColorBoxes) {
7880 // Fill a white rect so that colours merge nicely if the opacity is < 1
7881 ctx.fillStyle = vm.legendColorBackground;
7882 ctx.fillRect(colorX, pt.y, bodyFontSize, bodyFontSize);
7883
7884 // Border
7885 ctx.lineWidth = 1;
7886 ctx.strokeStyle = labelColors[i].borderColor;
7887 ctx.strokeRect(colorX, pt.y, bodyFontSize, bodyFontSize);
7888
7889 // Inner square
7890 ctx.fillStyle = labelColors[i].backgroundColor;
7891 ctx.fillRect(colorX + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
7892 ctx.fillStyle = textColor;
7893 }
7894
7895 fillLineOfText(line);
7896 });
7897
7898 helpers$1.each(bodyItem.after, fillLineOfText);
7899 });
7900
7901 // Reset back to 0 for after body
7902 xLinePadding = 0;
7903
7904 // After body lines
7905 helpers$1.each(vm.afterBody, fillLineOfText);
7906 pt.y -= bodySpacing; // Remove last body spacing
7907 },
7908
7909 drawFooter: function(pt, vm, ctx) {
7910 var footer = vm.footer;
7911
7912 if (footer.length) {
7913 pt.x = getAlignedX(vm, vm._footerAlign);
7914 pt.y += vm.footerMarginTop;
7915
7916 ctx.textAlign = vm._footerAlign;
7917 ctx.textBaseline = 'top';
7918
7919 ctx.fillStyle = vm.footerFontColor;
7920 ctx.font = helpers$1.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
7921
7922 helpers$1.each(footer, function(line) {
7923 ctx.fillText(line, pt.x, pt.y);
7924 pt.y += vm.footerFontSize + vm.footerSpacing;
7925 });
7926 }
7927 },
7928
7929 drawBackground: function(pt, vm, ctx, tooltipSize) {
7930 ctx.fillStyle = vm.backgroundColor;
7931 ctx.strokeStyle = vm.borderColor;
7932 ctx.lineWidth = vm.borderWidth;
7933 var xAlign = vm.xAlign;
7934 var yAlign = vm.yAlign;
7935 var x = pt.x;
7936 var y = pt.y;
7937 var width = tooltipSize.width;
7938 var height = tooltipSize.height;
7939 var radius = vm.cornerRadius;
7940
7941 ctx.beginPath();
7942 ctx.moveTo(x + radius, y);
7943 if (yAlign === 'top') {
7944 this.drawCaret(pt, tooltipSize);
7945 }
7946 ctx.lineTo(x + width - radius, y);
7947 ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
7948 if (yAlign === 'center' && xAlign === 'right') {
7949 this.drawCaret(pt, tooltipSize);
7950 }
7951 ctx.lineTo(x + width, y + height - radius);
7952 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
7953 if (yAlign === 'bottom') {
7954 this.drawCaret(pt, tooltipSize);
7955 }
7956 ctx.lineTo(x + radius, y + height);
7957 ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
7958 if (yAlign === 'center' && xAlign === 'left') {
7959 this.drawCaret(pt, tooltipSize);
7960 }
7961 ctx.lineTo(x, y + radius);
7962 ctx.quadraticCurveTo(x, y, x + radius, y);
7963 ctx.closePath();
7964
7965 ctx.fill();
7966
7967 if (vm.borderWidth > 0) {
7968 ctx.stroke();
7969 }
7970 },
7971
7972 draw: function() {
7973 var ctx = this._chart.ctx;
7974 var vm = this._view;
7975
7976 if (vm.opacity === 0) {
7977 return;
7978 }
7979
7980 var tooltipSize = {
7981 width: vm.width,
7982 height: vm.height
7983 };
7984 var pt = {
7985 x: vm.x,
7986 y: vm.y
7987 };
7988
7989 // IE11/Edge does not like very small opacities, so snap to 0
7990 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
7991
7992 // Truthy/falsey value for empty tooltip
7993 var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
7994
7995 if (this._options.enabled && hasTooltipContent) {
7996 ctx.save();
7997 ctx.globalAlpha = opacity;
7998
7999 // Draw Background
8000 this.drawBackground(pt, vm, ctx, tooltipSize);
8001
8002 // Draw Title, Body, and Footer
8003 pt.y += vm.yPadding;
8004
8005 // Titles
8006 this.drawTitle(pt, vm, ctx);
8007
8008 // Body
8009 this.drawBody(pt, vm, ctx);
8010
8011 // Footer
8012 this.drawFooter(pt, vm, ctx);
8013
8014 ctx.restore();
8015 }
8016 },
8017
8018 /**
8019 * Handle an event
8020 * @private
8021 * @param {IEvent} event - The event to handle
8022 * @returns {Boolean} true if the tooltip changed
8023 */
8024 handleEvent: function(e) {
8025 var me = this;
8026 var options = me._options;
8027 var changed = false;
8028
8029 me._lastActive = me._lastActive || [];
8030
8031 // Find Active Elements for tooltips
8032 if (e.type === 'mouseout') {
8033 me._active = [];
8034 } else {
8035 me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
8036 }
8037
8038 // Remember Last Actives
8039 changed = !helpers$1.arrayEquals(me._active, me._lastActive);
8040
8041 // Only handle target event on tooltip change
8042 if (changed) {
8043 me._lastActive = me._active;
8044
8045 if (options.enabled || options.custom) {
8046 me._eventPosition = {
8047 x: e.x,
8048 y: e.y
8049 };
8050
8051 me.update(true);
8052 me.pivot();
8053 }
8054 }
8055
8056 return changed;
8057 }
8058});
8059
8060/**
8061 * @namespace Chart.Tooltip.positioners
8062 */
8063var positioners_1 = positioners;
8064
8065var core_tooltip = exports$3;
8066core_tooltip.positioners = positioners_1;
8067
8068var valueOrDefault$6 = helpers$1.valueOrDefault;
8069
8070core_defaults._set('global', {
8071 elements: {},
8072 events: [
8073 'mousemove',
8074 'mouseout',
8075 'click',
8076 'touchstart',
8077 'touchmove'
8078 ],
8079 hover: {
8080 onHover: null,
8081 mode: 'nearest',
8082 intersect: true,
8083 animationDuration: 400
8084 },
8085 onClick: null,
8086 maintainAspectRatio: true,
8087 responsive: true,
8088 responsiveAnimationDuration: 0
8089});
8090
8091function initConfig(config) {
8092 config = config || {};
8093
8094 // Do NOT use configMerge() for the data object because this method merges arrays
8095 // and so would change references to labels and datasets, preventing data updates.
8096 var data = config.data = config.data || {};
8097 data.datasets = data.datasets || [];
8098 data.labels = data.labels || [];
8099
8100 config.options = helpers$1.configMerge(
8101 core_defaults.global,
8102 core_defaults[config.type],
8103 config.options || {});
8104
8105 return config;
8106}
8107
8108function updateConfig(chart) {
8109 var newOptions = chart.options;
8110
8111 helpers$1.each(chart.scales, function(scale) {
8112 core_layouts.removeBox(chart, scale);
8113 });
8114
8115 newOptions = helpers$1.configMerge(
8116 core_defaults.global,
8117 core_defaults[chart.config.type],
8118 newOptions);
8119
8120 chart.options = chart.config.options = newOptions;
8121 chart.ensureScalesHaveIDs();
8122 chart.buildOrUpdateScales();
8123
8124 // Tooltip
8125 chart.tooltip._options = newOptions.tooltips;
8126 chart.tooltip.initialize();
8127}
8128
8129function positionIsHorizontal(position) {
8130 return position === 'top' || position === 'bottom';
8131}
8132
8133var Chart = function(item, config) {
8134 this.construct(item, config);
8135 return this;
8136};
8137
8138helpers$1.extend(Chart.prototype, /** @lends Chart */ {
8139 /**
8140 * @private
8141 */
8142 construct: function(item, config) {
8143 var me = this;
8144
8145 config = initConfig(config);
8146
8147 var context = platform.acquireContext(item, config);
8148 var canvas = context && context.canvas;
8149 var height = canvas && canvas.height;
8150 var width = canvas && canvas.width;
8151
8152 me.id = helpers$1.uid();
8153 me.ctx = context;
8154 me.canvas = canvas;
8155 me.config = config;
8156 me.width = width;
8157 me.height = height;
8158 me.aspectRatio = height ? width / height : null;
8159 me.options = config.options;
8160 me._bufferedRender = false;
8161
8162 /**
8163 * Provided for backward compatibility, Chart and Chart.Controller have been merged,
8164 * the "instance" still need to be defined since it might be called from plugins.
8165 * @prop Chart#chart
8166 * @deprecated since version 2.6.0
8167 * @todo remove at version 3
8168 * @private
8169 */
8170 me.chart = me;
8171 me.controller = me; // chart.chart.controller #inception
8172
8173 // Add the chart instance to the global namespace
8174 Chart.instances[me.id] = me;
8175
8176 // Define alias to the config data: `chart.data === chart.config.data`
8177 Object.defineProperty(me, 'data', {
8178 get: function() {
8179 return me.config.data;
8180 },
8181 set: function(value) {
8182 me.config.data = value;
8183 }
8184 });
8185
8186 if (!context || !canvas) {
8187 // The given item is not a compatible context2d element, let's return before finalizing
8188 // the chart initialization but after setting basic chart / controller properties that
8189 // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
8190 // https://github.com/chartjs/Chart.js/issues/2807
8191 console.error("Failed to create chart: can't acquire context from the given item");
8192 return;
8193 }
8194
8195 me.initialize();
8196 me.update();
8197 },
8198
8199 /**
8200 * @private
8201 */
8202 initialize: function() {
8203 var me = this;
8204
8205 // Before init plugin notification
8206 core_plugins.notify(me, 'beforeInit');
8207
8208 helpers$1.retinaScale(me, me.options.devicePixelRatio);
8209
8210 me.bindEvents();
8211
8212 if (me.options.responsive) {
8213 // Initial resize before chart draws (must be silent to preserve initial animations).
8214 me.resize(true);
8215 }
8216
8217 // Make sure scales have IDs and are built before we build any controllers.
8218 me.ensureScalesHaveIDs();
8219 me.buildOrUpdateScales();
8220 me.initToolTip();
8221
8222 // After init plugin notification
8223 core_plugins.notify(me, 'afterInit');
8224
8225 return me;
8226 },
8227
8228 clear: function() {
8229 helpers$1.canvas.clear(this);
8230 return this;
8231 },
8232
8233 stop: function() {
8234 // Stops any current animation loop occurring
8235 core_animations.cancelAnimation(this);
8236 return this;
8237 },
8238
8239 resize: function(silent) {
8240 var me = this;
8241 var options = me.options;
8242 var canvas = me.canvas;
8243 var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
8244
8245 // the canvas render width and height will be casted to integers so make sure that
8246 // the canvas display style uses the same integer values to avoid blurring effect.
8247
8248 // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed
8249 var newWidth = Math.max(0, Math.floor(helpers$1.getMaximumWidth(canvas)));
8250 var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers$1.getMaximumHeight(canvas)));
8251
8252 if (me.width === newWidth && me.height === newHeight) {
8253 return;
8254 }
8255
8256 canvas.width = me.width = newWidth;
8257 canvas.height = me.height = newHeight;
8258 canvas.style.width = newWidth + 'px';
8259 canvas.style.height = newHeight + 'px';
8260
8261 helpers$1.retinaScale(me, options.devicePixelRatio);
8262
8263 if (!silent) {
8264 // Notify any plugins about the resize
8265 var newSize = {width: newWidth, height: newHeight};
8266 core_plugins.notify(me, 'resize', [newSize]);
8267
8268 // Notify of resize
8269 if (me.options.onResize) {
8270 me.options.onResize(me, newSize);
8271 }
8272
8273 me.stop();
8274 me.update({
8275 duration: me.options.responsiveAnimationDuration
8276 });
8277 }
8278 },
8279
8280 ensureScalesHaveIDs: function() {
8281 var options = this.options;
8282 var scalesOptions = options.scales || {};
8283 var scaleOptions = options.scale;
8284
8285 helpers$1.each(scalesOptions.xAxes, function(xAxisOptions, index) {
8286 xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
8287 });
8288
8289 helpers$1.each(scalesOptions.yAxes, function(yAxisOptions, index) {
8290 yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
8291 });
8292
8293 if (scaleOptions) {
8294 scaleOptions.id = scaleOptions.id || 'scale';
8295 }
8296 },
8297
8298 /**
8299 * Builds a map of scale ID to scale object for future lookup.
8300 */
8301 buildOrUpdateScales: function() {
8302 var me = this;
8303 var options = me.options;
8304 var scales = me.scales || {};
8305 var items = [];
8306 var updated = Object.keys(scales).reduce(function(obj, id) {
8307 obj[id] = false;
8308 return obj;
8309 }, {});
8310
8311 if (options.scales) {
8312 items = items.concat(
8313 (options.scales.xAxes || []).map(function(xAxisOptions) {
8314 return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
8315 }),
8316 (options.scales.yAxes || []).map(function(yAxisOptions) {
8317 return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
8318 })
8319 );
8320 }
8321
8322 if (options.scale) {
8323 items.push({
8324 options: options.scale,
8325 dtype: 'radialLinear',
8326 isDefault: true,
8327 dposition: 'chartArea'
8328 });
8329 }
8330
8331 helpers$1.each(items, function(item) {
8332 var scaleOptions = item.options;
8333 var id = scaleOptions.id;
8334 var scaleType = valueOrDefault$6(scaleOptions.type, item.dtype);
8335
8336 if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
8337 scaleOptions.position = item.dposition;
8338 }
8339
8340 updated[id] = true;
8341 var scale = null;
8342 if (id in scales && scales[id].type === scaleType) {
8343 scale = scales[id];
8344 scale.options = scaleOptions;
8345 scale.ctx = me.ctx;
8346 scale.chart = me;
8347 } else {
8348 var scaleClass = core_scaleService.getScaleConstructor(scaleType);
8349 if (!scaleClass) {
8350 return;
8351 }
8352 scale = new scaleClass({
8353 id: id,
8354 type: scaleType,
8355 options: scaleOptions,
8356 ctx: me.ctx,
8357 chart: me
8358 });
8359 scales[scale.id] = scale;
8360 }
8361
8362 scale.mergeTicksOptions();
8363
8364 // TODO(SB): I think we should be able to remove this custom case (options.scale)
8365 // and consider it as a regular scale part of the "scales"" map only! This would
8366 // make the logic easier and remove some useless? custom code.
8367 if (item.isDefault) {
8368 me.scale = scale;
8369 }
8370 });
8371 // clear up discarded scales
8372 helpers$1.each(updated, function(hasUpdated, id) {
8373 if (!hasUpdated) {
8374 delete scales[id];
8375 }
8376 });
8377
8378 me.scales = scales;
8379
8380 core_scaleService.addScalesToLayout(this);
8381 },
8382
8383 buildOrUpdateControllers: function() {
8384 var me = this;
8385 var newControllers = [];
8386
8387 helpers$1.each(me.data.datasets, function(dataset, datasetIndex) {
8388 var meta = me.getDatasetMeta(datasetIndex);
8389 var type = dataset.type || me.config.type;
8390
8391 if (meta.type && meta.type !== type) {
8392 me.destroyDatasetMeta(datasetIndex);
8393 meta = me.getDatasetMeta(datasetIndex);
8394 }
8395 meta.type = type;
8396
8397 if (meta.controller) {
8398 meta.controller.updateIndex(datasetIndex);
8399 meta.controller.linkScales();
8400 } else {
8401 var ControllerClass = controllers[meta.type];
8402 if (ControllerClass === undefined) {
8403 throw new Error('"' + meta.type + '" is not a chart type.');
8404 }
8405
8406 meta.controller = new ControllerClass(me, datasetIndex);
8407 newControllers.push(meta.controller);
8408 }
8409 }, me);
8410
8411 return newControllers;
8412 },
8413
8414 /**
8415 * Reset the elements of all datasets
8416 * @private
8417 */
8418 resetElements: function() {
8419 var me = this;
8420 helpers$1.each(me.data.datasets, function(dataset, datasetIndex) {
8421 me.getDatasetMeta(datasetIndex).controller.reset();
8422 }, me);
8423 },
8424
8425 /**
8426 * Resets the chart back to it's state before the initial animation
8427 */
8428 reset: function() {
8429 this.resetElements();
8430 this.tooltip.initialize();
8431 },
8432
8433 update: function(config) {
8434 var me = this;
8435
8436 if (!config || typeof config !== 'object') {
8437 // backwards compatibility
8438 config = {
8439 duration: config,
8440 lazy: arguments[1]
8441 };
8442 }
8443
8444 updateConfig(me);
8445
8446 // plugins options references might have change, let's invalidate the cache
8447 // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
8448 core_plugins._invalidate(me);
8449
8450 if (core_plugins.notify(me, 'beforeUpdate') === false) {
8451 return;
8452 }
8453
8454 // In case the entire data object changed
8455 me.tooltip._data = me.data;
8456
8457 // Make sure dataset controllers are updated and new controllers are reset
8458 var newControllers = me.buildOrUpdateControllers();
8459
8460 // Make sure all dataset controllers have correct meta data counts
8461 helpers$1.each(me.data.datasets, function(dataset, datasetIndex) {
8462 me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
8463 }, me);
8464
8465 me.updateLayout();
8466
8467 // Can only reset the new controllers after the scales have been updated
8468 if (me.options.animation && me.options.animation.duration) {
8469 helpers$1.each(newControllers, function(controller) {
8470 controller.reset();
8471 });
8472 }
8473
8474 me.updateDatasets();
8475
8476 // Need to reset tooltip in case it is displayed with elements that are removed
8477 // after update.
8478 me.tooltip.initialize();
8479
8480 // Last active contains items that were previously in the tooltip.
8481 // When we reset the tooltip, we need to clear it
8482 me.lastActive = [];
8483
8484 // Do this before render so that any plugins that need final scale updates can use it
8485 core_plugins.notify(me, 'afterUpdate');
8486
8487 if (me._bufferedRender) {
8488 me._bufferedRequest = {
8489 duration: config.duration,
8490 easing: config.easing,
8491 lazy: config.lazy
8492 };
8493 } else {
8494 me.render(config);
8495 }
8496 },
8497
8498 /**
8499 * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
8500 * hook, in which case, plugins will not be called on `afterLayout`.
8501 * @private
8502 */
8503 updateLayout: function() {
8504 var me = this;
8505
8506 if (core_plugins.notify(me, 'beforeLayout') === false) {
8507 return;
8508 }
8509
8510 core_layouts.update(this, this.width, this.height);
8511
8512 /**
8513 * Provided for backward compatibility, use `afterLayout` instead.
8514 * @method IPlugin#afterScaleUpdate
8515 * @deprecated since version 2.5.0
8516 * @todo remove at version 3
8517 * @private
8518 */
8519 core_plugins.notify(me, 'afterScaleUpdate');
8520 core_plugins.notify(me, 'afterLayout');
8521 },
8522
8523 /**
8524 * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
8525 * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
8526 * @private
8527 */
8528 updateDatasets: function() {
8529 var me = this;
8530
8531 if (core_plugins.notify(me, 'beforeDatasetsUpdate') === false) {
8532 return;
8533 }
8534
8535 for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
8536 me.updateDataset(i);
8537 }
8538
8539 core_plugins.notify(me, 'afterDatasetsUpdate');
8540 },
8541
8542 /**
8543 * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
8544 * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
8545 * @private
8546 */
8547 updateDataset: function(index) {
8548 var me = this;
8549 var meta = me.getDatasetMeta(index);
8550 var args = {
8551 meta: meta,
8552 index: index
8553 };
8554
8555 if (core_plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
8556 return;
8557 }
8558
8559 meta.controller.update();
8560
8561 core_plugins.notify(me, 'afterDatasetUpdate', [args]);
8562 },
8563
8564 render: function(config) {
8565 var me = this;
8566
8567 if (!config || typeof config !== 'object') {
8568 // backwards compatibility
8569 config = {
8570 duration: config,
8571 lazy: arguments[1]
8572 };
8573 }
8574
8575 var animationOptions = me.options.animation;
8576 var duration = valueOrDefault$6(config.duration, animationOptions && animationOptions.duration);
8577 var lazy = config.lazy;
8578
8579 if (core_plugins.notify(me, 'beforeRender') === false) {
8580 return;
8581 }
8582
8583 var onComplete = function(animation) {
8584 core_plugins.notify(me, 'afterRender');
8585 helpers$1.callback(animationOptions && animationOptions.onComplete, [animation], me);
8586 };
8587
8588 if (animationOptions && duration) {
8589 var animation = new core_animation({
8590 numSteps: duration / 16.66, // 60 fps
8591 easing: config.easing || animationOptions.easing,
8592
8593 render: function(chart, animationObject) {
8594 var easingFunction = helpers$1.easing.effects[animationObject.easing];
8595 var currentStep = animationObject.currentStep;
8596 var stepDecimal = currentStep / animationObject.numSteps;
8597
8598 chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
8599 },
8600
8601 onAnimationProgress: animationOptions.onProgress,
8602 onAnimationComplete: onComplete
8603 });
8604
8605 core_animations.addAnimation(me, animation, duration, lazy);
8606 } else {
8607 me.draw();
8608
8609 // See https://github.com/chartjs/Chart.js/issues/3781
8610 onComplete(new core_animation({numSteps: 0, chart: me}));
8611 }
8612
8613 return me;
8614 },
8615
8616 draw: function(easingValue) {
8617 var me = this;
8618
8619 me.clear();
8620
8621 if (helpers$1.isNullOrUndef(easingValue)) {
8622 easingValue = 1;
8623 }
8624
8625 me.transition(easingValue);
8626
8627 if (me.width <= 0 || me.height <= 0) {
8628 return;
8629 }
8630
8631 if (core_plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
8632 return;
8633 }
8634
8635 // Draw all the scales
8636 helpers$1.each(me.boxes, function(box) {
8637 box.draw(me.chartArea);
8638 }, me);
8639
8640 if (me.scale) {
8641 me.scale.draw();
8642 }
8643
8644 me.drawDatasets(easingValue);
8645 me._drawTooltip(easingValue);
8646
8647 core_plugins.notify(me, 'afterDraw', [easingValue]);
8648 },
8649
8650 /**
8651 * @private
8652 */
8653 transition: function(easingValue) {
8654 var me = this;
8655
8656 for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
8657 if (me.isDatasetVisible(i)) {
8658 me.getDatasetMeta(i).controller.transition(easingValue);
8659 }
8660 }
8661
8662 me.tooltip.transition(easingValue);
8663 },
8664
8665 /**
8666 * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
8667 * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
8668 * @private
8669 */
8670 drawDatasets: function(easingValue) {
8671 var me = this;
8672
8673 if (core_plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
8674 return;
8675 }
8676
8677 // Draw datasets reversed to support proper line stacking
8678 for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
8679 if (me.isDatasetVisible(i)) {
8680 me.drawDataset(i, easingValue);
8681 }
8682 }
8683
8684 core_plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
8685 },
8686
8687 /**
8688 * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
8689 * hook, in which case, plugins will not be called on `afterDatasetDraw`.
8690 * @private
8691 */
8692 drawDataset: function(index, easingValue) {
8693 var me = this;
8694 var meta = me.getDatasetMeta(index);
8695 var args = {
8696 meta: meta,
8697 index: index,
8698 easingValue: easingValue
8699 };
8700
8701 if (core_plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
8702 return;
8703 }
8704
8705 meta.controller.draw(easingValue);
8706
8707 core_plugins.notify(me, 'afterDatasetDraw', [args]);
8708 },
8709
8710 /**
8711 * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
8712 * hook, in which case, plugins will not be called on `afterTooltipDraw`.
8713 * @private
8714 */
8715 _drawTooltip: function(easingValue) {
8716 var me = this;
8717 var tooltip = me.tooltip;
8718 var args = {
8719 tooltip: tooltip,
8720 easingValue: easingValue
8721 };
8722
8723 if (core_plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
8724 return;
8725 }
8726
8727 tooltip.draw();
8728
8729 core_plugins.notify(me, 'afterTooltipDraw', [args]);
8730 },
8731
8732 // Get the single element that was clicked on
8733 // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
8734 getElementAtEvent: function(e) {
8735 return core_interaction.modes.single(this, e);
8736 },
8737
8738 getElementsAtEvent: function(e) {
8739 return core_interaction.modes.label(this, e, {intersect: true});
8740 },
8741
8742 getElementsAtXAxis: function(e) {
8743 return core_interaction.modes['x-axis'](this, e, {intersect: true});
8744 },
8745
8746 getElementsAtEventForMode: function(e, mode, options) {
8747 var method = core_interaction.modes[mode];
8748 if (typeof method === 'function') {
8749 return method(this, e, options);
8750 }
8751
8752 return [];
8753 },
8754
8755 getDatasetAtEvent: function(e) {
8756 return core_interaction.modes.dataset(this, e, {intersect: true});
8757 },
8758
8759 getDatasetMeta: function(datasetIndex) {
8760 var me = this;
8761 var dataset = me.data.datasets[datasetIndex];
8762 if (!dataset._meta) {
8763 dataset._meta = {};
8764 }
8765
8766 var meta = dataset._meta[me.id];
8767 if (!meta) {
8768 meta = dataset._meta[me.id] = {
8769 type: null,
8770 data: [],
8771 dataset: null,
8772 controller: null,
8773 hidden: null, // See isDatasetVisible() comment
8774 xAxisID: null,
8775 yAxisID: null
8776 };
8777 }
8778
8779 return meta;
8780 },
8781
8782 getVisibleDatasetCount: function() {
8783 var count = 0;
8784 for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
8785 if (this.isDatasetVisible(i)) {
8786 count++;
8787 }
8788 }
8789 return count;
8790 },
8791
8792 isDatasetVisible: function(datasetIndex) {
8793 var meta = this.getDatasetMeta(datasetIndex);
8794
8795 // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
8796 // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
8797 return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
8798 },
8799
8800 generateLegend: function() {
8801 return this.options.legendCallback(this);
8802 },
8803
8804 /**
8805 * @private
8806 */
8807 destroyDatasetMeta: function(datasetIndex) {
8808 var id = this.id;
8809 var dataset = this.data.datasets[datasetIndex];
8810 var meta = dataset._meta && dataset._meta[id];
8811
8812 if (meta) {
8813 meta.controller.destroy();
8814 delete dataset._meta[id];
8815 }
8816 },
8817
8818 destroy: function() {
8819 var me = this;
8820 var canvas = me.canvas;
8821 var i, ilen;
8822
8823 me.stop();
8824
8825 // dataset controllers need to cleanup associated data
8826 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
8827 me.destroyDatasetMeta(i);
8828 }
8829
8830 if (canvas) {
8831 me.unbindEvents();
8832 helpers$1.canvas.clear(me);
8833 platform.releaseContext(me.ctx);
8834 me.canvas = null;
8835 me.ctx = null;
8836 }
8837
8838 core_plugins.notify(me, 'destroy');
8839
8840 delete Chart.instances[me.id];
8841 },
8842
8843 toBase64Image: function() {
8844 return this.canvas.toDataURL.apply(this.canvas, arguments);
8845 },
8846
8847 initToolTip: function() {
8848 var me = this;
8849 me.tooltip = new core_tooltip({
8850 _chart: me,
8851 _chartInstance: me, // deprecated, backward compatibility
8852 _data: me.data,
8853 _options: me.options.tooltips
8854 }, me);
8855 },
8856
8857 /**
8858 * @private
8859 */
8860 bindEvents: function() {
8861 var me = this;
8862 var listeners = me._listeners = {};
8863 var listener = function() {
8864 me.eventHandler.apply(me, arguments);
8865 };
8866
8867 helpers$1.each(me.options.events, function(type) {
8868 platform.addEventListener(me, type, listener);
8869 listeners[type] = listener;
8870 });
8871
8872 // Elements used to detect size change should not be injected for non responsive charts.
8873 // See https://github.com/chartjs/Chart.js/issues/2210
8874 if (me.options.responsive) {
8875 listener = function() {
8876 me.resize();
8877 };
8878
8879 platform.addEventListener(me, 'resize', listener);
8880 listeners.resize = listener;
8881 }
8882 },
8883
8884 /**
8885 * @private
8886 */
8887 unbindEvents: function() {
8888 var me = this;
8889 var listeners = me._listeners;
8890 if (!listeners) {
8891 return;
8892 }
8893
8894 delete me._listeners;
8895 helpers$1.each(listeners, function(listener, type) {
8896 platform.removeEventListener(me, type, listener);
8897 });
8898 },
8899
8900 updateHoverStyle: function(elements, mode, enabled) {
8901 var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
8902 var element, i, ilen;
8903
8904 for (i = 0, ilen = elements.length; i < ilen; ++i) {
8905 element = elements[i];
8906 if (element) {
8907 this.getDatasetMeta(element._datasetIndex).controller[method](element);
8908 }
8909 }
8910 },
8911
8912 /**
8913 * @private
8914 */
8915 eventHandler: function(e) {
8916 var me = this;
8917 var tooltip = me.tooltip;
8918
8919 if (core_plugins.notify(me, 'beforeEvent', [e]) === false) {
8920 return;
8921 }
8922
8923 // Buffer any update calls so that renders do not occur
8924 me._bufferedRender = true;
8925 me._bufferedRequest = null;
8926
8927 var changed = me.handleEvent(e);
8928 // for smooth tooltip animations issue #4989
8929 // the tooltip should be the source of change
8930 // Animation check workaround:
8931 // tooltip._start will be null when tooltip isn't animating
8932 if (tooltip) {
8933 changed = tooltip._start
8934 ? tooltip.handleEvent(e)
8935 : changed | tooltip.handleEvent(e);
8936 }
8937
8938 core_plugins.notify(me, 'afterEvent', [e]);
8939
8940 var bufferedRequest = me._bufferedRequest;
8941 if (bufferedRequest) {
8942 // If we have an update that was triggered, we need to do a normal render
8943 me.render(bufferedRequest);
8944 } else if (changed && !me.animating) {
8945 // If entering, leaving, or changing elements, animate the change via pivot
8946 me.stop();
8947
8948 // We only need to render at this point. Updating will cause scales to be
8949 // recomputed generating flicker & using more memory than necessary.
8950 me.render({
8951 duration: me.options.hover.animationDuration,
8952 lazy: true
8953 });
8954 }
8955
8956 me._bufferedRender = false;
8957 me._bufferedRequest = null;
8958
8959 return me;
8960 },
8961
8962 /**
8963 * Handle an event
8964 * @private
8965 * @param {IEvent} event the event to handle
8966 * @return {Boolean} true if the chart needs to re-render
8967 */
8968 handleEvent: function(e) {
8969 var me = this;
8970 var options = me.options || {};
8971 var hoverOptions = options.hover;
8972 var changed = false;
8973
8974 me.lastActive = me.lastActive || [];
8975
8976 // Find Active Elements for hover and tooltips
8977 if (e.type === 'mouseout') {
8978 me.active = [];
8979 } else {
8980 me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
8981 }
8982
8983 // Invoke onHover hook
8984 // Need to call with native event here to not break backwards compatibility
8985 helpers$1.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
8986
8987 if (e.type === 'mouseup' || e.type === 'click') {
8988 if (options.onClick) {
8989 // Use e.native here for backwards compatibility
8990 options.onClick.call(me, e.native, me.active);
8991 }
8992 }
8993
8994 // Remove styling for last active (even if it may still be active)
8995 if (me.lastActive.length) {
8996 me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
8997 }
8998
8999 // Built in hover styling
9000 if (me.active.length && hoverOptions.mode) {
9001 me.updateHoverStyle(me.active, hoverOptions.mode, true);
9002 }
9003
9004 changed = !helpers$1.arrayEquals(me.active, me.lastActive);
9005
9006 // Remember Last Actives
9007 me.lastActive = me.active;
9008
9009 return changed;
9010 }
9011});
9012
9013/**
9014 * NOTE(SB) We actually don't use this container anymore but we need to keep it
9015 * for backward compatibility. Though, it can still be useful for plugins that
9016 * would need to work on multiple charts?!
9017 */
9018Chart.instances = {};
9019
9020var core_controller = Chart;
9021
9022// DEPRECATIONS
9023
9024/**
9025 * Provided for backward compatibility, use Chart instead.
9026 * @class Chart.Controller
9027 * @deprecated since version 2.6
9028 * @todo remove at version 3
9029 * @private
9030 */
9031Chart.Controller = Chart;
9032
9033/**
9034 * Provided for backward compatibility, not available anymore.
9035 * @namespace Chart
9036 * @deprecated since version 2.8
9037 * @todo remove at version 3
9038 * @private
9039 */
9040Chart.types = {};
9041
9042var core_helpers = function() {
9043
9044 // -- Basic js utility methods
9045
9046 helpers$1.configMerge = function(/* objects ... */) {
9047 return helpers$1.merge(helpers$1.clone(arguments[0]), [].slice.call(arguments, 1), {
9048 merger: function(key, target, source, options) {
9049 var tval = target[key] || {};
9050 var sval = source[key];
9051
9052 if (key === 'scales') {
9053 // scale config merging is complex. Add our own function here for that
9054 target[key] = helpers$1.scaleMerge(tval, sval);
9055 } else if (key === 'scale') {
9056 // used in polar area & radar charts since there is only one scale
9057 target[key] = helpers$1.merge(tval, [core_scaleService.getScaleDefaults(sval.type), sval]);
9058 } else {
9059 helpers$1._merger(key, target, source, options);
9060 }
9061 }
9062 });
9063 };
9064
9065 helpers$1.scaleMerge = function(/* objects ... */) {
9066 return helpers$1.merge(helpers$1.clone(arguments[0]), [].slice.call(arguments, 1), {
9067 merger: function(key, target, source, options) {
9068 if (key === 'xAxes' || key === 'yAxes') {
9069 var slen = source[key].length;
9070 var i, type, scale;
9071
9072 if (!target[key]) {
9073 target[key] = [];
9074 }
9075
9076 for (i = 0; i < slen; ++i) {
9077 scale = source[key][i];
9078 type = helpers$1.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear');
9079
9080 if (i >= target[key].length) {
9081 target[key].push({});
9082 }
9083
9084 if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
9085 // new/untyped scale or type changed: let's apply the new defaults
9086 // then merge source scale to correctly overwrite the defaults.
9087 helpers$1.merge(target[key][i], [core_scaleService.getScaleDefaults(type), scale]);
9088 } else {
9089 // scales type are the same
9090 helpers$1.merge(target[key][i], scale);
9091 }
9092 }
9093 } else {
9094 helpers$1._merger(key, target, source, options);
9095 }
9096 }
9097 });
9098 };
9099
9100 helpers$1.where = function(collection, filterCallback) {
9101 if (helpers$1.isArray(collection) && Array.prototype.filter) {
9102 return collection.filter(filterCallback);
9103 }
9104 var filtered = [];
9105
9106 helpers$1.each(collection, function(item) {
9107 if (filterCallback(item)) {
9108 filtered.push(item);
9109 }
9110 });
9111
9112 return filtered;
9113 };
9114 helpers$1.findIndex = Array.prototype.findIndex ?
9115 function(array, callback, scope) {
9116 return array.findIndex(callback, scope);
9117 } :
9118 function(array, callback, scope) {
9119 scope = scope === undefined ? array : scope;
9120 for (var i = 0, ilen = array.length; i < ilen; ++i) {
9121 if (callback.call(scope, array[i], i, array)) {
9122 return i;
9123 }
9124 }
9125 return -1;
9126 };
9127 helpers$1.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
9128 // Default to start of the array
9129 if (helpers$1.isNullOrUndef(startIndex)) {
9130 startIndex = -1;
9131 }
9132 for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
9133 var currentItem = arrayToSearch[i];
9134 if (filterCallback(currentItem)) {
9135 return currentItem;
9136 }
9137 }
9138 };
9139 helpers$1.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
9140 // Default to end of the array
9141 if (helpers$1.isNullOrUndef(startIndex)) {
9142 startIndex = arrayToSearch.length;
9143 }
9144 for (var i = startIndex - 1; i >= 0; i--) {
9145 var currentItem = arrayToSearch[i];
9146 if (filterCallback(currentItem)) {
9147 return currentItem;
9148 }
9149 }
9150 };
9151
9152 // -- Math methods
9153 helpers$1.isNumber = function(n) {
9154 return !isNaN(parseFloat(n)) && isFinite(n);
9155 };
9156 helpers$1.almostEquals = function(x, y, epsilon) {
9157 return Math.abs(x - y) < epsilon;
9158 };
9159 helpers$1.almostWhole = function(x, epsilon) {
9160 var rounded = Math.round(x);
9161 return (((rounded - epsilon) < x) && ((rounded + epsilon) > x));
9162 };
9163 helpers$1.max = function(array) {
9164 return array.reduce(function(max, value) {
9165 if (!isNaN(value)) {
9166 return Math.max(max, value);
9167 }
9168 return max;
9169 }, Number.NEGATIVE_INFINITY);
9170 };
9171 helpers$1.min = function(array) {
9172 return array.reduce(function(min, value) {
9173 if (!isNaN(value)) {
9174 return Math.min(min, value);
9175 }
9176 return min;
9177 }, Number.POSITIVE_INFINITY);
9178 };
9179 helpers$1.sign = Math.sign ?
9180 function(x) {
9181 return Math.sign(x);
9182 } :
9183 function(x) {
9184 x = +x; // convert to a number
9185 if (x === 0 || isNaN(x)) {
9186 return x;
9187 }
9188 return x > 0 ? 1 : -1;
9189 };
9190 helpers$1.log10 = Math.log10 ?
9191 function(x) {
9192 return Math.log10(x);
9193 } :
9194 function(x) {
9195 var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10.
9196 // Check for whole powers of 10,
9197 // which due to floating point rounding error should be corrected.
9198 var powerOf10 = Math.round(exponent);
9199 var isPowerOf10 = x === Math.pow(10, powerOf10);
9200
9201 return isPowerOf10 ? powerOf10 : exponent;
9202 };
9203 helpers$1.toRadians = function(degrees) {
9204 return degrees * (Math.PI / 180);
9205 };
9206 helpers$1.toDegrees = function(radians) {
9207 return radians * (180 / Math.PI);
9208 };
9209
9210 /**
9211 * Returns the number of decimal places
9212 * i.e. the number of digits after the decimal point, of the value of this Number.
9213 * @param {Number} x - A number.
9214 * @returns {Number} The number of decimal places.
9215 */
9216 helpers$1.decimalPlaces = function(x) {
9217 if (!helpers$1.isFinite(x)) {
9218 return;
9219 }
9220 var e = 1;
9221 var p = 0;
9222 while (Math.round(x * e) / e !== x) {
9223 e *= 10;
9224 p++;
9225 }
9226 return p;
9227 };
9228
9229 // Gets the angle from vertical upright to the point about a centre.
9230 helpers$1.getAngleFromPoint = function(centrePoint, anglePoint) {
9231 var distanceFromXCenter = anglePoint.x - centrePoint.x;
9232 var distanceFromYCenter = anglePoint.y - centrePoint.y;
9233 var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
9234
9235 var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
9236
9237 if (angle < (-0.5 * Math.PI)) {
9238 angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
9239 }
9240
9241 return {
9242 angle: angle,
9243 distance: radialDistanceFromCenter
9244 };
9245 };
9246 helpers$1.distanceBetweenPoints = function(pt1, pt2) {
9247 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
9248 };
9249
9250 /**
9251 * Provided for backward compatibility, not available anymore
9252 * @function Chart.helpers.aliasPixel
9253 * @deprecated since version 2.8.0
9254 * @todo remove at version 3
9255 */
9256 helpers$1.aliasPixel = function(pixelWidth) {
9257 return (pixelWidth % 2 === 0) ? 0 : 0.5;
9258 };
9259
9260 /**
9261 * Returns the aligned pixel value to avoid anti-aliasing blur
9262 * @param {Chart} chart - The chart instance.
9263 * @param {Number} pixel - A pixel value.
9264 * @param {Number} width - The width of the element.
9265 * @returns {Number} The aligned pixel value.
9266 * @private
9267 */
9268 helpers$1._alignPixel = function(chart, pixel, width) {
9269 var devicePixelRatio = chart.currentDevicePixelRatio;
9270 var halfWidth = width / 2;
9271 return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
9272 };
9273
9274 helpers$1.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
9275 // Props to Rob Spencer at scaled innovation for his post on splining between points
9276 // http://scaledinnovation.com/analytics/splines/aboutSplines.html
9277
9278 // This function must also respect "skipped" points
9279
9280 var previous = firstPoint.skip ? middlePoint : firstPoint;
9281 var current = middlePoint;
9282 var next = afterPoint.skip ? middlePoint : afterPoint;
9283
9284 var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
9285 var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
9286
9287 var s01 = d01 / (d01 + d12);
9288 var s12 = d12 / (d01 + d12);
9289
9290 // If all points are the same, s01 & s02 will be inf
9291 s01 = isNaN(s01) ? 0 : s01;
9292 s12 = isNaN(s12) ? 0 : s12;
9293
9294 var fa = t * s01; // scaling factor for triangle Ta
9295 var fb = t * s12;
9296
9297 return {
9298 previous: {
9299 x: current.x - fa * (next.x - previous.x),
9300 y: current.y - fa * (next.y - previous.y)
9301 },
9302 next: {
9303 x: current.x + fb * (next.x - previous.x),
9304 y: current.y + fb * (next.y - previous.y)
9305 }
9306 };
9307 };
9308 helpers$1.EPSILON = Number.EPSILON || 1e-14;
9309 helpers$1.splineCurveMonotone = function(points) {
9310 // This function calculates Bézier control points in a similar way than |splineCurve|,
9311 // but preserves monotonicity of the provided data and ensures no local extremums are added
9312 // between the dataset discrete points due to the interpolation.
9313 // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
9314
9315 var pointsWithTangents = (points || []).map(function(point) {
9316 return {
9317 model: point._model,
9318 deltaK: 0,
9319 mK: 0
9320 };
9321 });
9322
9323 // Calculate slopes (deltaK) and initialize tangents (mK)
9324 var pointsLen = pointsWithTangents.length;
9325 var i, pointBefore, pointCurrent, pointAfter;
9326 for (i = 0; i < pointsLen; ++i) {
9327 pointCurrent = pointsWithTangents[i];
9328 if (pointCurrent.model.skip) {
9329 continue;
9330 }
9331
9332 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
9333 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
9334 if (pointAfter && !pointAfter.model.skip) {
9335 var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
9336
9337 // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
9338 pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
9339 }
9340
9341 if (!pointBefore || pointBefore.model.skip) {
9342 pointCurrent.mK = pointCurrent.deltaK;
9343 } else if (!pointAfter || pointAfter.model.skip) {
9344 pointCurrent.mK = pointBefore.deltaK;
9345 } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
9346 pointCurrent.mK = 0;
9347 } else {
9348 pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
9349 }
9350 }
9351
9352 // Adjust tangents to ensure monotonic properties
9353 var alphaK, betaK, tauK, squaredMagnitude;
9354 for (i = 0; i < pointsLen - 1; ++i) {
9355 pointCurrent = pointsWithTangents[i];
9356 pointAfter = pointsWithTangents[i + 1];
9357 if (pointCurrent.model.skip || pointAfter.model.skip) {
9358 continue;
9359 }
9360
9361 if (helpers$1.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
9362 pointCurrent.mK = pointAfter.mK = 0;
9363 continue;
9364 }
9365
9366 alphaK = pointCurrent.mK / pointCurrent.deltaK;
9367 betaK = pointAfter.mK / pointCurrent.deltaK;
9368 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
9369 if (squaredMagnitude <= 9) {
9370 continue;
9371 }
9372
9373 tauK = 3 / Math.sqrt(squaredMagnitude);
9374 pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
9375 pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
9376 }
9377
9378 // Compute control points
9379 var deltaX;
9380 for (i = 0; i < pointsLen; ++i) {
9381 pointCurrent = pointsWithTangents[i];
9382 if (pointCurrent.model.skip) {
9383 continue;
9384 }
9385
9386 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
9387 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
9388 if (pointBefore && !pointBefore.model.skip) {
9389 deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
9390 pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
9391 pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
9392 }
9393 if (pointAfter && !pointAfter.model.skip) {
9394 deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
9395 pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
9396 pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
9397 }
9398 }
9399 };
9400 helpers$1.nextItem = function(collection, index, loop) {
9401 if (loop) {
9402 return index >= collection.length - 1 ? collection[0] : collection[index + 1];
9403 }
9404 return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
9405 };
9406 helpers$1.previousItem = function(collection, index, loop) {
9407 if (loop) {
9408 return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
9409 }
9410 return index <= 0 ? collection[0] : collection[index - 1];
9411 };
9412 // Implementation of the nice number algorithm used in determining where axis labels will go
9413 helpers$1.niceNum = function(range, round) {
9414 var exponent = Math.floor(helpers$1.log10(range));
9415 var fraction = range / Math.pow(10, exponent);
9416 var niceFraction;
9417
9418 if (round) {
9419 if (fraction < 1.5) {
9420 niceFraction = 1;
9421 } else if (fraction < 3) {
9422 niceFraction = 2;
9423 } else if (fraction < 7) {
9424 niceFraction = 5;
9425 } else {
9426 niceFraction = 10;
9427 }
9428 } else if (fraction <= 1.0) {
9429 niceFraction = 1;
9430 } else if (fraction <= 2) {
9431 niceFraction = 2;
9432 } else if (fraction <= 5) {
9433 niceFraction = 5;
9434 } else {
9435 niceFraction = 10;
9436 }
9437
9438 return niceFraction * Math.pow(10, exponent);
9439 };
9440 // Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
9441 helpers$1.requestAnimFrame = (function() {
9442 if (typeof window === 'undefined') {
9443 return function(callback) {
9444 callback();
9445 };
9446 }
9447 return window.requestAnimationFrame ||
9448 window.webkitRequestAnimationFrame ||
9449 window.mozRequestAnimationFrame ||
9450 window.oRequestAnimationFrame ||
9451 window.msRequestAnimationFrame ||
9452 function(callback) {
9453 return window.setTimeout(callback, 1000 / 60);
9454 };
9455 }());
9456 // -- DOM methods
9457 helpers$1.getRelativePosition = function(evt, chart) {
9458 var mouseX, mouseY;
9459 var e = evt.originalEvent || evt;
9460 var canvas = evt.target || evt.srcElement;
9461 var boundingRect = canvas.getBoundingClientRect();
9462
9463 var touches = e.touches;
9464 if (touches && touches.length > 0) {
9465 mouseX = touches[0].clientX;
9466 mouseY = touches[0].clientY;
9467
9468 } else {
9469 mouseX = e.clientX;
9470 mouseY = e.clientY;
9471 }
9472
9473 // Scale mouse coordinates into canvas coordinates
9474 // by following the pattern laid out by 'jerryj' in the comments of
9475 // https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
9476 var paddingLeft = parseFloat(helpers$1.getStyle(canvas, 'padding-left'));
9477 var paddingTop = parseFloat(helpers$1.getStyle(canvas, 'padding-top'));
9478 var paddingRight = parseFloat(helpers$1.getStyle(canvas, 'padding-right'));
9479 var paddingBottom = parseFloat(helpers$1.getStyle(canvas, 'padding-bottom'));
9480 var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
9481 var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
9482
9483 // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
9484 // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
9485 mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
9486 mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
9487
9488 return {
9489 x: mouseX,
9490 y: mouseY
9491 };
9492
9493 };
9494
9495 // Private helper function to convert max-width/max-height values that may be percentages into a number
9496 function parseMaxStyle(styleValue, node, parentProperty) {
9497 var valueInPixels;
9498 if (typeof styleValue === 'string') {
9499 valueInPixels = parseInt(styleValue, 10);
9500
9501 if (styleValue.indexOf('%') !== -1) {
9502 // percentage * size in dimension
9503 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
9504 }
9505 } else {
9506 valueInPixels = styleValue;
9507 }
9508
9509 return valueInPixels;
9510 }
9511
9512 /**
9513 * Returns if the given value contains an effective constraint.
9514 * @private
9515 */
9516 function isConstrainedValue(value) {
9517 return value !== undefined && value !== null && value !== 'none';
9518 }
9519
9520 // Private helper to get a constraint dimension
9521 // @param domNode : the node to check the constraint on
9522 // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
9523 // @param percentageProperty : property of parent to use when calculating width as a percentage
9524 // @see https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
9525 function getConstraintDimension(domNode, maxStyle, percentageProperty) {
9526 var view = document.defaultView;
9527 var parentNode = helpers$1._getParentNode(domNode);
9528 var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
9529 var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
9530 var hasCNode = isConstrainedValue(constrainedNode);
9531 var hasCContainer = isConstrainedValue(constrainedContainer);
9532 var infinity = Number.POSITIVE_INFINITY;
9533
9534 if (hasCNode || hasCContainer) {
9535 return Math.min(
9536 hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
9537 hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
9538 }
9539
9540 return 'none';
9541 }
9542 // returns Number or undefined if no constraint
9543 helpers$1.getConstraintWidth = function(domNode) {
9544 return getConstraintDimension(domNode, 'max-width', 'clientWidth');
9545 };
9546 // returns Number or undefined if no constraint
9547 helpers$1.getConstraintHeight = function(domNode) {
9548 return getConstraintDimension(domNode, 'max-height', 'clientHeight');
9549 };
9550 /**
9551 * @private
9552 */
9553 helpers$1._calculatePadding = function(container, padding, parentDimension) {
9554 padding = helpers$1.getStyle(container, padding);
9555
9556 return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10);
9557 };
9558 /**
9559 * @private
9560 */
9561 helpers$1._getParentNode = function(domNode) {
9562 var parent = domNode.parentNode;
9563 if (parent && parent.toString() === '[object ShadowRoot]') {
9564 parent = parent.host;
9565 }
9566 return parent;
9567 };
9568 helpers$1.getMaximumWidth = function(domNode) {
9569 var container = helpers$1._getParentNode(domNode);
9570 if (!container) {
9571 return domNode.clientWidth;
9572 }
9573
9574 var clientWidth = container.clientWidth;
9575 var paddingLeft = helpers$1._calculatePadding(container, 'padding-left', clientWidth);
9576 var paddingRight = helpers$1._calculatePadding(container, 'padding-right', clientWidth);
9577
9578 var w = clientWidth - paddingLeft - paddingRight;
9579 var cw = helpers$1.getConstraintWidth(domNode);
9580 return isNaN(cw) ? w : Math.min(w, cw);
9581 };
9582 helpers$1.getMaximumHeight = function(domNode) {
9583 var container = helpers$1._getParentNode(domNode);
9584 if (!container) {
9585 return domNode.clientHeight;
9586 }
9587
9588 var clientHeight = container.clientHeight;
9589 var paddingTop = helpers$1._calculatePadding(container, 'padding-top', clientHeight);
9590 var paddingBottom = helpers$1._calculatePadding(container, 'padding-bottom', clientHeight);
9591
9592 var h = clientHeight - paddingTop - paddingBottom;
9593 var ch = helpers$1.getConstraintHeight(domNode);
9594 return isNaN(ch) ? h : Math.min(h, ch);
9595 };
9596 helpers$1.getStyle = function(el, property) {
9597 return el.currentStyle ?
9598 el.currentStyle[property] :
9599 document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
9600 };
9601 helpers$1.retinaScale = function(chart, forceRatio) {
9602 var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1;
9603 if (pixelRatio === 1) {
9604 return;
9605 }
9606
9607 var canvas = chart.canvas;
9608 var height = chart.height;
9609 var width = chart.width;
9610
9611 canvas.height = height * pixelRatio;
9612 canvas.width = width * pixelRatio;
9613 chart.ctx.scale(pixelRatio, pixelRatio);
9614
9615 // If no style has been set on the canvas, the render size is used as display size,
9616 // making the chart visually bigger, so let's enforce it to the "correct" values.
9617 // See https://github.com/chartjs/Chart.js/issues/3575
9618 if (!canvas.style.height && !canvas.style.width) {
9619 canvas.style.height = height + 'px';
9620 canvas.style.width = width + 'px';
9621 }
9622 };
9623 // -- Canvas methods
9624 helpers$1.fontString = function(pixelSize, fontStyle, fontFamily) {
9625 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
9626 };
9627 helpers$1.longestText = function(ctx, font, arrayOfThings, cache) {
9628 cache = cache || {};
9629 var data = cache.data = cache.data || {};
9630 var gc = cache.garbageCollect = cache.garbageCollect || [];
9631
9632 if (cache.font !== font) {
9633 data = cache.data = {};
9634 gc = cache.garbageCollect = [];
9635 cache.font = font;
9636 }
9637
9638 ctx.font = font;
9639 var longest = 0;
9640 helpers$1.each(arrayOfThings, function(thing) {
9641 // Undefined strings and arrays should not be measured
9642 if (thing !== undefined && thing !== null && helpers$1.isArray(thing) !== true) {
9643 longest = helpers$1.measureText(ctx, data, gc, longest, thing);
9644 } else if (helpers$1.isArray(thing)) {
9645 // if it is an array lets measure each element
9646 // to do maybe simplify this function a bit so we can do this more recursively?
9647 helpers$1.each(thing, function(nestedThing) {
9648 // Undefined strings and arrays should not be measured
9649 if (nestedThing !== undefined && nestedThing !== null && !helpers$1.isArray(nestedThing)) {
9650 longest = helpers$1.measureText(ctx, data, gc, longest, nestedThing);
9651 }
9652 });
9653 }
9654 });
9655
9656 var gcLen = gc.length / 2;
9657 if (gcLen > arrayOfThings.length) {
9658 for (var i = 0; i < gcLen; i++) {
9659 delete data[gc[i]];
9660 }
9661 gc.splice(0, gcLen);
9662 }
9663 return longest;
9664 };
9665 helpers$1.measureText = function(ctx, data, gc, longest, string) {
9666 var textWidth = data[string];
9667 if (!textWidth) {
9668 textWidth = data[string] = ctx.measureText(string).width;
9669 gc.push(string);
9670 }
9671 if (textWidth > longest) {
9672 longest = textWidth;
9673 }
9674 return longest;
9675 };
9676 helpers$1.numberOfLabelLines = function(arrayOfThings) {
9677 var numberOfLines = 1;
9678 helpers$1.each(arrayOfThings, function(thing) {
9679 if (helpers$1.isArray(thing)) {
9680 if (thing.length > numberOfLines) {
9681 numberOfLines = thing.length;
9682 }
9683 }
9684 });
9685 return numberOfLines;
9686 };
9687
9688 helpers$1.color = !chartjsColor ?
9689 function(value) {
9690 console.error('Color.js not found!');
9691 return value;
9692 } :
9693 function(value) {
9694 /* global CanvasGradient */
9695 if (value instanceof CanvasGradient) {
9696 value = core_defaults.global.defaultColor;
9697 }
9698
9699 return chartjsColor(value);
9700 };
9701
9702 helpers$1.getHoverColor = function(colorValue) {
9703 /* global CanvasPattern */
9704 return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ?
9705 colorValue :
9706 helpers$1.color(colorValue).saturate(0.5).darken(0.1).rgbString();
9707 };
9708};
9709
9710/**
9711 * Namespace to hold static tick generation functions
9712 * @namespace Chart.Ticks
9713 */
9714var core_ticks = {
9715 /**
9716 * Namespace to hold formatters for different types of ticks
9717 * @namespace Chart.Ticks.formatters
9718 */
9719 formatters: {
9720 /**
9721 * Formatter for value labels
9722 * @method Chart.Ticks.formatters.values
9723 * @param value the value to display
9724 * @return {String|Array} the label to display
9725 */
9726 values: function(value) {
9727 return helpers$1.isArray(value) ? value : '' + value;
9728 },
9729
9730 /**
9731 * Formatter for linear numeric ticks
9732 * @method Chart.Ticks.formatters.linear
9733 * @param tickValue {Number} the value to be formatted
9734 * @param index {Number} the position of the tickValue parameter in the ticks array
9735 * @param ticks {Array<Number>} the list of ticks being converted
9736 * @return {String} string representation of the tickValue parameter
9737 */
9738 linear: function(tickValue, index, ticks) {
9739 // If we have lots of ticks, don't use the ones
9740 var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
9741
9742 // If we have a number like 2.5 as the delta, figure out how many decimal places we need
9743 if (Math.abs(delta) > 1) {
9744 if (tickValue !== Math.floor(tickValue)) {
9745 // not an integer
9746 delta = tickValue - Math.floor(tickValue);
9747 }
9748 }
9749
9750 var logDelta = helpers$1.log10(Math.abs(delta));
9751 var tickString = '';
9752
9753 if (tickValue !== 0) {
9754 var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1]));
9755 if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation
9756 var logTick = helpers$1.log10(Math.abs(tickValue));
9757 tickString = tickValue.toExponential(Math.floor(logTick) - Math.floor(logDelta));
9758 } else {
9759 var numDecimal = -1 * Math.floor(logDelta);
9760 numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
9761 tickString = tickValue.toFixed(numDecimal);
9762 }
9763 } else {
9764 tickString = '0'; // never show decimal places for 0
9765 }
9766
9767 return tickString;
9768 },
9769
9770 logarithmic: function(tickValue, index, ticks) {
9771 var remain = tickValue / (Math.pow(10, Math.floor(helpers$1.log10(tickValue))));
9772
9773 if (tickValue === 0) {
9774 return '0';
9775 } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
9776 return tickValue.toExponential();
9777 }
9778 return '';
9779 }
9780 }
9781};
9782
9783var valueOrDefault$7 = helpers$1.valueOrDefault;
9784var valueAtIndexOrDefault = helpers$1.valueAtIndexOrDefault;
9785
9786core_defaults._set('scale', {
9787 display: true,
9788 position: 'left',
9789 offset: false,
9790
9791 // grid line settings
9792 gridLines: {
9793 display: true,
9794 color: 'rgba(0, 0, 0, 0.1)',
9795 lineWidth: 1,
9796 drawBorder: true,
9797 drawOnChartArea: true,
9798 drawTicks: true,
9799 tickMarkLength: 10,
9800 zeroLineWidth: 1,
9801 zeroLineColor: 'rgba(0,0,0,0.25)',
9802 zeroLineBorderDash: [],
9803 zeroLineBorderDashOffset: 0.0,
9804 offsetGridLines: false,
9805 borderDash: [],
9806 borderDashOffset: 0.0
9807 },
9808
9809 // scale label
9810 scaleLabel: {
9811 // display property
9812 display: false,
9813
9814 // actual label
9815 labelString: '',
9816
9817 // top/bottom padding
9818 padding: {
9819 top: 4,
9820 bottom: 4
9821 }
9822 },
9823
9824 // label settings
9825 ticks: {
9826 beginAtZero: false,
9827 minRotation: 0,
9828 maxRotation: 50,
9829 mirror: false,
9830 padding: 0,
9831 reverse: false,
9832 display: true,
9833 autoSkip: true,
9834 autoSkipPadding: 0,
9835 labelOffset: 0,
9836 // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
9837 callback: core_ticks.formatters.values,
9838 minor: {},
9839 major: {}
9840 }
9841});
9842
9843function labelsFromTicks(ticks) {
9844 var labels = [];
9845 var i, ilen;
9846
9847 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
9848 labels.push(ticks[i].label);
9849 }
9850
9851 return labels;
9852}
9853
9854function getPixelForGridLine(scale, index, offsetGridLines) {
9855 var lineValue = scale.getPixelForTick(index);
9856
9857 if (offsetGridLines) {
9858 if (scale.getTicks().length === 1) {
9859 lineValue -= scale.isHorizontal() ?
9860 Math.max(lineValue - scale.left, scale.right - lineValue) :
9861 Math.max(lineValue - scale.top, scale.bottom - lineValue);
9862 } else if (index === 0) {
9863 lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
9864 } else {
9865 lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
9866 }
9867 }
9868 return lineValue;
9869}
9870
9871function computeTextSize(context, tick, font) {
9872 return helpers$1.isArray(tick) ?
9873 helpers$1.longestText(context, font, tick) :
9874 context.measureText(tick).width;
9875}
9876
9877var core_scale = core_element.extend({
9878 /**
9879 * Get the padding needed for the scale
9880 * @method getPadding
9881 * @private
9882 * @returns {Padding} the necessary padding
9883 */
9884 getPadding: function() {
9885 var me = this;
9886 return {
9887 left: me.paddingLeft || 0,
9888 top: me.paddingTop || 0,
9889 right: me.paddingRight || 0,
9890 bottom: me.paddingBottom || 0
9891 };
9892 },
9893
9894 /**
9895 * Returns the scale tick objects ({label, major})
9896 * @since 2.7
9897 */
9898 getTicks: function() {
9899 return this._ticks;
9900 },
9901
9902 // These methods are ordered by lifecyle. Utilities then follow.
9903 // Any function defined here is inherited by all scale types.
9904 // Any function can be extended by the scale type
9905
9906 mergeTicksOptions: function() {
9907 var ticks = this.options.ticks;
9908 if (ticks.minor === false) {
9909 ticks.minor = {
9910 display: false
9911 };
9912 }
9913 if (ticks.major === false) {
9914 ticks.major = {
9915 display: false
9916 };
9917 }
9918 for (var key in ticks) {
9919 if (key !== 'major' && key !== 'minor') {
9920 if (typeof ticks.minor[key] === 'undefined') {
9921 ticks.minor[key] = ticks[key];
9922 }
9923 if (typeof ticks.major[key] === 'undefined') {
9924 ticks.major[key] = ticks[key];
9925 }
9926 }
9927 }
9928 },
9929 beforeUpdate: function() {
9930 helpers$1.callback(this.options.beforeUpdate, [this]);
9931 },
9932
9933 update: function(maxWidth, maxHeight, margins) {
9934 var me = this;
9935 var i, ilen, labels, label, ticks, tick;
9936
9937 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
9938 me.beforeUpdate();
9939
9940 // Absorb the master measurements
9941 me.maxWidth = maxWidth;
9942 me.maxHeight = maxHeight;
9943 me.margins = helpers$1.extend({
9944 left: 0,
9945 right: 0,
9946 top: 0,
9947 bottom: 0
9948 }, margins);
9949 me.longestTextCache = me.longestTextCache || {};
9950
9951 // Dimensions
9952 me.beforeSetDimensions();
9953 me.setDimensions();
9954 me.afterSetDimensions();
9955
9956 // Data min/max
9957 me.beforeDataLimits();
9958 me.determineDataLimits();
9959 me.afterDataLimits();
9960
9961 // Ticks - `this.ticks` is now DEPRECATED!
9962 // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
9963 // and must not be accessed directly from outside this class. `this.ticks` being
9964 // around for long time and not marked as private, we can't change its structure
9965 // without unexpected breaking changes. If you need to access the scale ticks,
9966 // use scale.getTicks() instead.
9967
9968 me.beforeBuildTicks();
9969
9970 // New implementations should return an array of objects but for BACKWARD COMPAT,
9971 // we still support no return (`this.ticks` internally set by calling this method).
9972 ticks = me.buildTicks() || [];
9973
9974 me.afterBuildTicks();
9975
9976 me.beforeTickToLabelConversion();
9977
9978 // New implementations should return the formatted tick labels but for BACKWARD
9979 // COMPAT, we still support no return (`this.ticks` internally changed by calling
9980 // this method and supposed to contain only string values).
9981 labels = me.convertTicksToLabels(ticks) || me.ticks;
9982
9983 me.afterTickToLabelConversion();
9984
9985 me.ticks = labels; // BACKWARD COMPATIBILITY
9986
9987 // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change!
9988
9989 // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
9990 for (i = 0, ilen = labels.length; i < ilen; ++i) {
9991 label = labels[i];
9992 tick = ticks[i];
9993 if (!tick) {
9994 ticks.push(tick = {
9995 label: label,
9996 major: false
9997 });
9998 } else {
9999 tick.label = label;
10000 }
10001 }
10002
10003 me._ticks = ticks;
10004
10005 // Tick Rotation
10006 me.beforeCalculateTickRotation();
10007 me.calculateTickRotation();
10008 me.afterCalculateTickRotation();
10009 // Fit
10010 me.beforeFit();
10011 me.fit();
10012 me.afterFit();
10013 //
10014 me.afterUpdate();
10015
10016 return me.minSize;
10017
10018 },
10019 afterUpdate: function() {
10020 helpers$1.callback(this.options.afterUpdate, [this]);
10021 },
10022
10023 //
10024
10025 beforeSetDimensions: function() {
10026 helpers$1.callback(this.options.beforeSetDimensions, [this]);
10027 },
10028 setDimensions: function() {
10029 var me = this;
10030 // Set the unconstrained dimension before label rotation
10031 if (me.isHorizontal()) {
10032 // Reset position before calculating rotation
10033 me.width = me.maxWidth;
10034 me.left = 0;
10035 me.right = me.width;
10036 } else {
10037 me.height = me.maxHeight;
10038
10039 // Reset position before calculating rotation
10040 me.top = 0;
10041 me.bottom = me.height;
10042 }
10043
10044 // Reset padding
10045 me.paddingLeft = 0;
10046 me.paddingTop = 0;
10047 me.paddingRight = 0;
10048 me.paddingBottom = 0;
10049 },
10050 afterSetDimensions: function() {
10051 helpers$1.callback(this.options.afterSetDimensions, [this]);
10052 },
10053
10054 // Data limits
10055 beforeDataLimits: function() {
10056 helpers$1.callback(this.options.beforeDataLimits, [this]);
10057 },
10058 determineDataLimits: helpers$1.noop,
10059 afterDataLimits: function() {
10060 helpers$1.callback(this.options.afterDataLimits, [this]);
10061 },
10062
10063 //
10064 beforeBuildTicks: function() {
10065 helpers$1.callback(this.options.beforeBuildTicks, [this]);
10066 },
10067 buildTicks: helpers$1.noop,
10068 afterBuildTicks: function() {
10069 helpers$1.callback(this.options.afterBuildTicks, [this]);
10070 },
10071
10072 beforeTickToLabelConversion: function() {
10073 helpers$1.callback(this.options.beforeTickToLabelConversion, [this]);
10074 },
10075 convertTicksToLabels: function() {
10076 var me = this;
10077 // Convert ticks to strings
10078 var tickOpts = me.options.ticks;
10079 me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
10080 },
10081 afterTickToLabelConversion: function() {
10082 helpers$1.callback(this.options.afterTickToLabelConversion, [this]);
10083 },
10084
10085 //
10086
10087 beforeCalculateTickRotation: function() {
10088 helpers$1.callback(this.options.beforeCalculateTickRotation, [this]);
10089 },
10090 calculateTickRotation: function() {
10091 var me = this;
10092 var context = me.ctx;
10093 var tickOpts = me.options.ticks;
10094 var labels = labelsFromTicks(me._ticks);
10095
10096 // Get the width of each grid by calculating the difference
10097 // between x offsets between 0 and 1.
10098 var tickFont = helpers$1.options._parseFont(tickOpts);
10099 context.font = tickFont.string;
10100
10101 var labelRotation = tickOpts.minRotation || 0;
10102
10103 if (labels.length && me.options.display && me.isHorizontal()) {
10104 var originalLabelWidth = helpers$1.longestText(context, tickFont.string, labels, me.longestTextCache);
10105 var labelWidth = originalLabelWidth;
10106 var cosRotation, sinRotation;
10107
10108 // Allow 3 pixels x2 padding either side for label readability
10109 var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
10110
10111 // Max label rotation can be set or default to 90 - also act as a loop counter
10112 while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) {
10113 var angleRadians = helpers$1.toRadians(labelRotation);
10114 cosRotation = Math.cos(angleRadians);
10115 sinRotation = Math.sin(angleRadians);
10116
10117 if (sinRotation * originalLabelWidth > me.maxHeight) {
10118 // go back one step
10119 labelRotation--;
10120 break;
10121 }
10122
10123 labelRotation++;
10124 labelWidth = cosRotation * originalLabelWidth;
10125 }
10126 }
10127
10128 me.labelRotation = labelRotation;
10129 },
10130 afterCalculateTickRotation: function() {
10131 helpers$1.callback(this.options.afterCalculateTickRotation, [this]);
10132 },
10133
10134 //
10135
10136 beforeFit: function() {
10137 helpers$1.callback(this.options.beforeFit, [this]);
10138 },
10139 fit: function() {
10140 var me = this;
10141 // Reset
10142 var minSize = me.minSize = {
10143 width: 0,
10144 height: 0
10145 };
10146
10147 var labels = labelsFromTicks(me._ticks);
10148
10149 var opts = me.options;
10150 var tickOpts = opts.ticks;
10151 var scaleLabelOpts = opts.scaleLabel;
10152 var gridLineOpts = opts.gridLines;
10153 var display = me._isVisible();
10154 var position = opts.position;
10155 var isHorizontal = me.isHorizontal();
10156
10157 var parseFont = helpers$1.options._parseFont;
10158 var tickFont = parseFont(tickOpts);
10159 var tickMarkLength = opts.gridLines.tickMarkLength;
10160
10161 // Width
10162 if (isHorizontal) {
10163 // subtract the margins to line up with the chartArea if we are a full width scale
10164 minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
10165 } else {
10166 minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
10167 }
10168
10169 // height
10170 if (isHorizontal) {
10171 minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
10172 } else {
10173 minSize.height = me.maxHeight; // fill all the height
10174 }
10175
10176 // Are we showing a title for the scale?
10177 if (scaleLabelOpts.display && display) {
10178 var scaleLabelFont = parseFont(scaleLabelOpts);
10179 var scaleLabelPadding = helpers$1.options.toPadding(scaleLabelOpts.padding);
10180 var deltaHeight = scaleLabelFont.lineHeight + scaleLabelPadding.height;
10181
10182 if (isHorizontal) {
10183 minSize.height += deltaHeight;
10184 } else {
10185 minSize.width += deltaHeight;
10186 }
10187 }
10188
10189 // Don't bother fitting the ticks if we are not showing them
10190 if (tickOpts.display && display) {
10191 var largestTextWidth = helpers$1.longestText(me.ctx, tickFont.string, labels, me.longestTextCache);
10192 var tallestLabelHeightInLines = helpers$1.numberOfLabelLines(labels);
10193 var lineSpace = tickFont.size * 0.5;
10194 var tickPadding = me.options.ticks.padding;
10195
10196 // Store max number of lines used in labels for _autoSkip
10197 me._maxLabelLines = tallestLabelHeightInLines;
10198
10199 if (isHorizontal) {
10200 // A horizontal axis is more constrained by the height.
10201 me.longestLabelWidth = largestTextWidth;
10202
10203 var angleRadians = helpers$1.toRadians(me.labelRotation);
10204 var cosRotation = Math.cos(angleRadians);
10205 var sinRotation = Math.sin(angleRadians);
10206
10207 // TODO - improve this calculation
10208 var labelHeight = (sinRotation * largestTextWidth)
10209 + (tickFont.lineHeight * tallestLabelHeightInLines)
10210 + lineSpace; // padding
10211
10212 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
10213
10214 me.ctx.font = tickFont.string;
10215 var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.string);
10216 var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.string);
10217 var offsetLeft = me.getPixelForTick(0) - me.left;
10218 var offsetRight = me.right - me.getPixelForTick(labels.length - 1);
10219 var paddingLeft, paddingRight;
10220
10221 // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
10222 // which means that the right padding is dominated by the font height
10223 if (me.labelRotation !== 0) {
10224 paddingLeft = position === 'bottom' ? (cosRotation * firstLabelWidth) : (cosRotation * lineSpace);
10225 paddingRight = position === 'bottom' ? (cosRotation * lineSpace) : (cosRotation * lastLabelWidth);
10226 } else {
10227 paddingLeft = firstLabelWidth / 2;
10228 paddingRight = lastLabelWidth / 2;
10229 }
10230 me.paddingLeft = Math.max(paddingLeft - offsetLeft, 0) + 3; // add 3 px to move away from canvas edges
10231 me.paddingRight = Math.max(paddingRight - offsetRight, 0) + 3;
10232 } else {
10233 // A vertical axis is more constrained by the width. Labels are the
10234 // dominant factor here, so get that length first and account for padding
10235 if (tickOpts.mirror) {
10236 largestTextWidth = 0;
10237 } else {
10238 // use lineSpace for consistency with horizontal axis
10239 // tickPadding is not implemented for horizontal
10240 largestTextWidth += tickPadding + lineSpace;
10241 }
10242
10243 minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth);
10244
10245 me.paddingTop = tickFont.size / 2;
10246 me.paddingBottom = tickFont.size / 2;
10247 }
10248 }
10249
10250 me.handleMargins();
10251
10252 me.width = minSize.width;
10253 me.height = minSize.height;
10254 },
10255
10256 /**
10257 * Handle margins and padding interactions
10258 * @private
10259 */
10260 handleMargins: function() {
10261 var me = this;
10262 if (me.margins) {
10263 me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
10264 me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
10265 me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
10266 me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
10267 }
10268 },
10269
10270 afterFit: function() {
10271 helpers$1.callback(this.options.afterFit, [this]);
10272 },
10273
10274 // Shared Methods
10275 isHorizontal: function() {
10276 return this.options.position === 'top' || this.options.position === 'bottom';
10277 },
10278 isFullWidth: function() {
10279 return (this.options.fullWidth);
10280 },
10281
10282 // 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
10283 getRightValue: function(rawValue) {
10284 // Null and undefined values first
10285 if (helpers$1.isNullOrUndef(rawValue)) {
10286 return NaN;
10287 }
10288 // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
10289 if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) {
10290 return NaN;
10291 }
10292 // If it is in fact an object, dive in one more level
10293 if (rawValue) {
10294 if (this.isHorizontal()) {
10295 if (rawValue.x !== undefined) {
10296 return this.getRightValue(rawValue.x);
10297 }
10298 } else if (rawValue.y !== undefined) {
10299 return this.getRightValue(rawValue.y);
10300 }
10301 }
10302
10303 // Value is good, return it
10304 return rawValue;
10305 },
10306
10307 /**
10308 * Used to get the value to display in the tooltip for the data at the given index
10309 * @param index
10310 * @param datasetIndex
10311 */
10312 getLabelForIndex: helpers$1.noop,
10313
10314 /**
10315 * Returns the location of the given data point. Value can either be an index or a numerical value
10316 * The coordinate (0, 0) is at the upper-left corner of the canvas
10317 * @param value
10318 * @param index
10319 * @param datasetIndex
10320 */
10321 getPixelForValue: helpers$1.noop,
10322
10323 /**
10324 * Used to get the data value from a given pixel. This is the inverse of getPixelForValue
10325 * The coordinate (0, 0) is at the upper-left corner of the canvas
10326 * @param pixel
10327 */
10328 getValueForPixel: helpers$1.noop,
10329
10330 /**
10331 * Returns the location of the tick at the given index
10332 * The coordinate (0, 0) is at the upper-left corner of the canvas
10333 */
10334 getPixelForTick: function(index) {
10335 var me = this;
10336 var offset = me.options.offset;
10337 if (me.isHorizontal()) {
10338 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
10339 var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
10340 var pixel = (tickWidth * index) + me.paddingLeft;
10341
10342 if (offset) {
10343 pixel += tickWidth / 2;
10344 }
10345
10346 var finalVal = me.left + pixel;
10347 finalVal += me.isFullWidth() ? me.margins.left : 0;
10348 return finalVal;
10349 }
10350 var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
10351 return me.top + (index * (innerHeight / (me._ticks.length - 1)));
10352 },
10353
10354 /**
10355 * Utility for getting the pixel location of a percentage of scale
10356 * The coordinate (0, 0) is at the upper-left corner of the canvas
10357 */
10358 getPixelForDecimal: function(decimal) {
10359 var me = this;
10360 if (me.isHorizontal()) {
10361 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
10362 var valueOffset = (innerWidth * decimal) + me.paddingLeft;
10363
10364 var finalVal = me.left + valueOffset;
10365 finalVal += me.isFullWidth() ? me.margins.left : 0;
10366 return finalVal;
10367 }
10368 return me.top + (decimal * me.height);
10369 },
10370
10371 /**
10372 * Returns the pixel for the minimum chart value
10373 * The coordinate (0, 0) is at the upper-left corner of the canvas
10374 */
10375 getBasePixel: function() {
10376 return this.getPixelForValue(this.getBaseValue());
10377 },
10378
10379 getBaseValue: function() {
10380 var me = this;
10381 var min = me.min;
10382 var max = me.max;
10383
10384 return me.beginAtZero ? 0 :
10385 min < 0 && max < 0 ? max :
10386 min > 0 && max > 0 ? min :
10387 0;
10388 },
10389
10390 /**
10391 * Returns a subset of ticks to be plotted to avoid overlapping labels.
10392 * @private
10393 */
10394 _autoSkip: function(ticks) {
10395 var skipRatio;
10396 var me = this;
10397 var isHorizontal = me.isHorizontal();
10398 var optionTicks = me.options.ticks.minor;
10399 var tickCount = ticks.length;
10400
10401 // Calculate space needed by label in axis direction.
10402 var rot = helpers$1.toRadians(me.labelRotation);
10403 var cos = Math.abs(Math.cos(rot));
10404 var sin = Math.abs(Math.sin(rot));
10405
10406 var padding = optionTicks.autoSkipPadding;
10407 var w = me.longestLabelWidth + padding || 0;
10408
10409 var tickFont = helpers$1.options._parseFont(optionTicks);
10410 var h = me._maxLabelLines * tickFont.lineHeight + padding;
10411
10412 // Calculate space needed for 1 tick in axis direction.
10413 var tickSize = isHorizontal
10414 ? h * cos > w * sin ? w / cos : h / sin
10415 : h * sin < w * cos ? h / cos : w / sin;
10416
10417 // Total space needed to display all ticks. First and last ticks are
10418 // drawn as their center at end of axis, so tickCount-1
10419 var ticksLength = tickSize * (tickCount - 1);
10420
10421 // Axis length
10422 var axisLength = isHorizontal
10423 ? me.width - (me.paddingLeft + me.paddingRight)
10424 : me.height - (me.paddingTop + me.PaddingBottom);
10425
10426 var result = [];
10427 var i, tick;
10428
10429 // figure out the maximum number of gridlines to show
10430 var maxTicks;
10431 if (optionTicks.maxTicksLimit) {
10432 maxTicks = optionTicks.maxTicksLimit;
10433 }
10434
10435 skipRatio = false;
10436
10437 if (ticksLength > axisLength) {
10438 skipRatio = 1 + Math.floor(ticksLength / axisLength);
10439 }
10440
10441 // if they defined a max number of optionTicks,
10442 // increase skipRatio until that number is met
10443 if (maxTicks && tickCount > maxTicks) {
10444 skipRatio = Math.max(skipRatio, 1 + Math.floor(tickCount / maxTicks));
10445 }
10446
10447 for (i = 0; i < tickCount; i++) {
10448 tick = ticks[i];
10449
10450 if (skipRatio > 1 && i % skipRatio > 0) {
10451 // leave tick in place but make sure it's not displayed (#4635)
10452 delete tick.label;
10453 }
10454 result.push(tick);
10455 }
10456 return result;
10457 },
10458
10459 /**
10460 * @private
10461 */
10462 _isVisible: function() {
10463 var me = this;
10464 var chart = me.chart;
10465 var display = me.options.display;
10466 var i, ilen, meta;
10467
10468 if (display !== 'auto') {
10469 return !!display;
10470 }
10471
10472 // When 'auto', the scale is visible if at least one associated dataset is visible.
10473 for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
10474 if (chart.isDatasetVisible(i)) {
10475 meta = chart.getDatasetMeta(i);
10476 if (meta.xAxisID === me.id || meta.yAxisID === me.id) {
10477 return true;
10478 }
10479 }
10480 }
10481
10482 return false;
10483 },
10484
10485 // Actually draw the scale on the canvas
10486 // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
10487 draw: function(chartArea) {
10488 var me = this;
10489 var options = me.options;
10490
10491 if (!me._isVisible()) {
10492 return;
10493 }
10494
10495 var chart = me.chart;
10496 var context = me.ctx;
10497 var globalDefaults = core_defaults.global;
10498 var defaultFontColor = globalDefaults.defaultFontColor;
10499 var optionTicks = options.ticks.minor;
10500 var optionMajorTicks = options.ticks.major || optionTicks;
10501 var gridLines = options.gridLines;
10502 var scaleLabel = options.scaleLabel;
10503 var position = options.position;
10504
10505 var isRotated = me.labelRotation !== 0;
10506 var isMirrored = optionTicks.mirror;
10507 var isHorizontal = me.isHorizontal();
10508
10509 var parseFont = helpers$1.options._parseFont;
10510 var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
10511 var tickFontColor = valueOrDefault$7(optionTicks.fontColor, defaultFontColor);
10512 var tickFont = parseFont(optionTicks);
10513 var lineHeight = tickFont.lineHeight;
10514 var majorTickFontColor = valueOrDefault$7(optionMajorTicks.fontColor, defaultFontColor);
10515 var majorTickFont = parseFont(optionMajorTicks);
10516 var tickPadding = optionTicks.padding;
10517 var labelOffset = optionTicks.labelOffset;
10518
10519 var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
10520
10521 var scaleLabelFontColor = valueOrDefault$7(scaleLabel.fontColor, defaultFontColor);
10522 var scaleLabelFont = parseFont(scaleLabel);
10523 var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding);
10524 var labelRotationRadians = helpers$1.toRadians(me.labelRotation);
10525
10526 var itemsToDraw = [];
10527
10528 var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
10529 var alignPixel = helpers$1._alignPixel;
10530 var borderValue, tickStart, tickEnd;
10531
10532 if (position === 'top') {
10533 borderValue = alignPixel(chart, me.bottom, axisWidth);
10534 tickStart = me.bottom - tl;
10535 tickEnd = borderValue - axisWidth / 2;
10536 } else if (position === 'bottom') {
10537 borderValue = alignPixel(chart, me.top, axisWidth);
10538 tickStart = borderValue + axisWidth / 2;
10539 tickEnd = me.top + tl;
10540 } else if (position === 'left') {
10541 borderValue = alignPixel(chart, me.right, axisWidth);
10542 tickStart = me.right - tl;
10543 tickEnd = borderValue - axisWidth / 2;
10544 } else {
10545 borderValue = alignPixel(chart, me.left, axisWidth);
10546 tickStart = borderValue + axisWidth / 2;
10547 tickEnd = me.left + tl;
10548 }
10549
10550 var epsilon = 0.0000001; // 0.0000001 is margin in pixels for Accumulated error.
10551
10552 helpers$1.each(ticks, function(tick, index) {
10553 // autoskipper skipped this tick (#4635)
10554 if (helpers$1.isNullOrUndef(tick.label)) {
10555 return;
10556 }
10557
10558 var label = tick.label;
10559 var lineWidth, lineColor, borderDash, borderDashOffset;
10560 if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) {
10561 // Draw the first index specially
10562 lineWidth = gridLines.zeroLineWidth;
10563 lineColor = gridLines.zeroLineColor;
10564 borderDash = gridLines.zeroLineBorderDash || [];
10565 borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0;
10566 } else {
10567 lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, index);
10568 lineColor = valueAtIndexOrDefault(gridLines.color, index);
10569 borderDash = gridLines.borderDash || [];
10570 borderDashOffset = gridLines.borderDashOffset || 0.0;
10571 }
10572
10573 // Common properties
10574 var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY, textOffset, textAlign;
10575 var labelCount = helpers$1.isArray(label) ? label.length : 1;
10576 var lineValue = getPixelForGridLine(me, index, gridLines.offsetGridLines);
10577
10578 if (isHorizontal) {
10579 var labelYOffset = tl + tickPadding;
10580
10581 if (lineValue < me.left - epsilon) {
10582 lineColor = 'rgba(0,0,0,0)';
10583 }
10584
10585 tx1 = tx2 = x1 = x2 = alignPixel(chart, lineValue, lineWidth);
10586 ty1 = tickStart;
10587 ty2 = tickEnd;
10588 labelX = me.getPixelForTick(index) + labelOffset; // x values for optionTicks (need to consider offsetLabel option)
10589
10590 if (position === 'top') {
10591 y1 = alignPixel(chart, chartArea.top, axisWidth) + axisWidth / 2;
10592 y2 = chartArea.bottom;
10593 textOffset = ((!isRotated ? 0.5 : 1) - labelCount) * lineHeight;
10594 textAlign = !isRotated ? 'center' : 'left';
10595 labelY = me.bottom - labelYOffset;
10596 } else {
10597 y1 = chartArea.top;
10598 y2 = alignPixel(chart, chartArea.bottom, axisWidth) - axisWidth / 2;
10599 textOffset = (!isRotated ? 0.5 : 0) * lineHeight;
10600 textAlign = !isRotated ? 'center' : 'right';
10601 labelY = me.top + labelYOffset;
10602 }
10603 } else {
10604 var labelXOffset = (isMirrored ? 0 : tl) + tickPadding;
10605
10606 if (lineValue < me.top - epsilon) {
10607 lineColor = 'rgba(0,0,0,0)';
10608 }
10609
10610 tx1 = tickStart;
10611 tx2 = tickEnd;
10612 ty1 = ty2 = y1 = y2 = alignPixel(chart, lineValue, lineWidth);
10613 labelY = me.getPixelForTick(index) + labelOffset;
10614 textOffset = (1 - labelCount) * lineHeight / 2;
10615
10616 if (position === 'left') {
10617 x1 = alignPixel(chart, chartArea.left, axisWidth) + axisWidth / 2;
10618 x2 = chartArea.right;
10619 textAlign = isMirrored ? 'left' : 'right';
10620 labelX = me.right - labelXOffset;
10621 } else {
10622 x1 = chartArea.left;
10623 x2 = alignPixel(chart, chartArea.right, axisWidth) - axisWidth / 2;
10624 textAlign = isMirrored ? 'right' : 'left';
10625 labelX = me.left + labelXOffset;
10626 }
10627 }
10628
10629 itemsToDraw.push({
10630 tx1: tx1,
10631 ty1: ty1,
10632 tx2: tx2,
10633 ty2: ty2,
10634 x1: x1,
10635 y1: y1,
10636 x2: x2,
10637 y2: y2,
10638 labelX: labelX,
10639 labelY: labelY,
10640 glWidth: lineWidth,
10641 glColor: lineColor,
10642 glBorderDash: borderDash,
10643 glBorderDashOffset: borderDashOffset,
10644 rotation: -1 * labelRotationRadians,
10645 label: label,
10646 major: tick.major,
10647 textOffset: textOffset,
10648 textAlign: textAlign
10649 });
10650 });
10651
10652 // Draw all of the tick labels, tick marks, and grid lines at the correct places
10653 helpers$1.each(itemsToDraw, function(itemToDraw) {
10654 var glWidth = itemToDraw.glWidth;
10655 var glColor = itemToDraw.glColor;
10656
10657 if (gridLines.display && glWidth && glColor) {
10658 context.save();
10659 context.lineWidth = glWidth;
10660 context.strokeStyle = glColor;
10661 if (context.setLineDash) {
10662 context.setLineDash(itemToDraw.glBorderDash);
10663 context.lineDashOffset = itemToDraw.glBorderDashOffset;
10664 }
10665
10666 context.beginPath();
10667
10668 if (gridLines.drawTicks) {
10669 context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
10670 context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
10671 }
10672
10673 if (gridLines.drawOnChartArea) {
10674 context.moveTo(itemToDraw.x1, itemToDraw.y1);
10675 context.lineTo(itemToDraw.x2, itemToDraw.y2);
10676 }
10677
10678 context.stroke();
10679 context.restore();
10680 }
10681
10682 if (optionTicks.display) {
10683 // Make sure we draw text in the correct color and font
10684 context.save();
10685 context.translate(itemToDraw.labelX, itemToDraw.labelY);
10686 context.rotate(itemToDraw.rotation);
10687 context.font = itemToDraw.major ? majorTickFont.string : tickFont.string;
10688 context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
10689 context.textBaseline = 'middle';
10690 context.textAlign = itemToDraw.textAlign;
10691
10692 var label = itemToDraw.label;
10693 var y = itemToDraw.textOffset;
10694 if (helpers$1.isArray(label)) {
10695 for (var i = 0; i < label.length; ++i) {
10696 // We just make sure the multiline element is a string here..
10697 context.fillText('' + label[i], 0, y);
10698 y += lineHeight;
10699 }
10700 } else {
10701 context.fillText(label, 0, y);
10702 }
10703 context.restore();
10704 }
10705 });
10706
10707 if (scaleLabel.display) {
10708 // Draw the scale label
10709 var scaleLabelX;
10710 var scaleLabelY;
10711 var rotation = 0;
10712 var halfLineHeight = scaleLabelFont.lineHeight / 2;
10713
10714 if (isHorizontal) {
10715 scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
10716 scaleLabelY = position === 'bottom'
10717 ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
10718 : me.top + halfLineHeight + scaleLabelPadding.top;
10719 } else {
10720 var isLeft = position === 'left';
10721 scaleLabelX = isLeft
10722 ? me.left + halfLineHeight + scaleLabelPadding.top
10723 : me.right - halfLineHeight - scaleLabelPadding.top;
10724 scaleLabelY = me.top + ((me.bottom - me.top) / 2);
10725 rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
10726 }
10727
10728 context.save();
10729 context.translate(scaleLabelX, scaleLabelY);
10730 context.rotate(rotation);
10731 context.textAlign = 'center';
10732 context.textBaseline = 'middle';
10733 context.fillStyle = scaleLabelFontColor; // render in correct colour
10734 context.font = scaleLabelFont.string;
10735 context.fillText(scaleLabel.labelString, 0, 0);
10736 context.restore();
10737 }
10738
10739 if (axisWidth) {
10740 // Draw the line at the edge of the axis
10741 var firstLineWidth = axisWidth;
10742 var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, ticks.length - 1, 0);
10743 var x1, x2, y1, y2;
10744
10745 if (isHorizontal) {
10746 x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2;
10747 x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2;
10748 y1 = y2 = borderValue;
10749 } else {
10750 y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2;
10751 y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2;
10752 x1 = x2 = borderValue;
10753 }
10754
10755 context.lineWidth = axisWidth;
10756 context.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0);
10757 context.beginPath();
10758 context.moveTo(x1, y1);
10759 context.lineTo(x2, y2);
10760 context.stroke();
10761 }
10762 }
10763});
10764
10765var defaultConfig = {
10766 position: 'bottom'
10767};
10768
10769var scale_category = core_scale.extend({
10770 /**
10771 * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
10772 * else fall back to data.labels
10773 * @private
10774 */
10775 getLabels: function() {
10776 var data = this.chart.data;
10777 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
10778 },
10779
10780 determineDataLimits: function() {
10781 var me = this;
10782 var labels = me.getLabels();
10783 me.minIndex = 0;
10784 me.maxIndex = labels.length - 1;
10785 var findIndex;
10786
10787 if (me.options.ticks.min !== undefined) {
10788 // user specified min value
10789 findIndex = labels.indexOf(me.options.ticks.min);
10790 me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
10791 }
10792
10793 if (me.options.ticks.max !== undefined) {
10794 // user specified max value
10795 findIndex = labels.indexOf(me.options.ticks.max);
10796 me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
10797 }
10798
10799 me.min = labels[me.minIndex];
10800 me.max = labels[me.maxIndex];
10801 },
10802
10803 buildTicks: function() {
10804 var me = this;
10805 var labels = me.getLabels();
10806 // If we are viewing some subset of labels, slice the original array
10807 me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
10808 },
10809
10810 getLabelForIndex: function(index, datasetIndex) {
10811 var me = this;
10812 var data = me.chart.data;
10813 var isHorizontal = me.isHorizontal();
10814
10815 if (data.yLabels && !isHorizontal) {
10816 return me.getRightValue(data.datasets[datasetIndex].data[index]);
10817 }
10818 return me.ticks[index - me.minIndex];
10819 },
10820
10821 // Used to get data value locations. Value can either be an index or a numerical value
10822 getPixelForValue: function(value, index) {
10823 var me = this;
10824 var offset = me.options.offset;
10825 // 1 is added because we need the length but we have the indexes
10826 var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1);
10827
10828 // If value is a data object, then index is the index in the data array,
10829 // not the index of the scale. We need to change that.
10830 var valueCategory;
10831 if (value !== undefined && value !== null) {
10832 valueCategory = me.isHorizontal() ? value.x : value.y;
10833 }
10834 if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
10835 var labels = me.getLabels();
10836 value = valueCategory || value;
10837 var idx = labels.indexOf(value);
10838 index = idx !== -1 ? idx : index;
10839 }
10840
10841 if (me.isHorizontal()) {
10842 var valueWidth = me.width / offsetAmt;
10843 var widthOffset = (valueWidth * (index - me.minIndex));
10844
10845 if (offset) {
10846 widthOffset += (valueWidth / 2);
10847 }
10848
10849 return me.left + widthOffset;
10850 }
10851 var valueHeight = me.height / offsetAmt;
10852 var heightOffset = (valueHeight * (index - me.minIndex));
10853
10854 if (offset) {
10855 heightOffset += (valueHeight / 2);
10856 }
10857
10858 return me.top + heightOffset;
10859 },
10860
10861 getPixelForTick: function(index) {
10862 return this.getPixelForValue(this.ticks[index], index + this.minIndex, null);
10863 },
10864
10865 getValueForPixel: function(pixel) {
10866 var me = this;
10867 var offset = me.options.offset;
10868 var value;
10869 var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
10870 var horz = me.isHorizontal();
10871 var valueDimension = (horz ? me.width : me.height) / offsetAmt;
10872
10873 pixel -= horz ? me.left : me.top;
10874
10875 if (offset) {
10876 pixel -= (valueDimension / 2);
10877 }
10878
10879 if (pixel <= 0) {
10880 value = 0;
10881 } else {
10882 value = Math.round(pixel / valueDimension);
10883 }
10884
10885 return value + me.minIndex;
10886 },
10887
10888 getBasePixel: function() {
10889 return this.bottom;
10890 }
10891});
10892
10893// INTERNAL: static default options, registered in src/chart.js
10894var _defaults = defaultConfig;
10895scale_category._defaults = _defaults;
10896
10897var noop = helpers$1.noop;
10898var isNullOrUndef = helpers$1.isNullOrUndef;
10899
10900/**
10901 * Generate a set of linear ticks
10902 * @param generationOptions the options used to generate the ticks
10903 * @param dataRange the range of the data
10904 * @returns {Array<Number>} array of tick values
10905 */
10906function generateTicks(generationOptions, dataRange) {
10907 var ticks = [];
10908 // To get a "nice" value for the tick spacing, we will use the appropriately named
10909 // "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
10910 // for details.
10911
10912 var MIN_SPACING = 1e-14;
10913 var stepSize = generationOptions.stepSize;
10914 var unit = stepSize || 1;
10915 var maxNumSpaces = generationOptions.maxTicks - 1;
10916 var min = generationOptions.min;
10917 var max = generationOptions.max;
10918 var precision = generationOptions.precision;
10919 var rmin = dataRange.min;
10920 var rmax = dataRange.max;
10921 var spacing = helpers$1.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit;
10922 var factor, niceMin, niceMax, numSpaces;
10923
10924 // Beyond MIN_SPACING floating point numbers being to lose precision
10925 // such that we can't do the math necessary to generate ticks
10926 if (spacing < MIN_SPACING && isNullOrUndef(min) && isNullOrUndef(max)) {
10927 return [rmin, rmax];
10928 }
10929
10930 numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
10931 if (numSpaces > maxNumSpaces) {
10932 // If the calculated num of spaces exceeds maxNumSpaces, recalculate it
10933 spacing = helpers$1.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit;
10934 }
10935
10936 if (stepSize || isNullOrUndef(precision)) {
10937 // If a precision is not specified, calculate factor based on spacing
10938 factor = Math.pow(10, helpers$1.decimalPlaces(spacing));
10939 } else {
10940 // If the user specified a precision, round to that number of decimal places
10941 factor = Math.pow(10, precision);
10942 spacing = Math.ceil(spacing * factor) / factor;
10943 }
10944
10945 niceMin = Math.floor(rmin / spacing) * spacing;
10946 niceMax = Math.ceil(rmax / spacing) * spacing;
10947
10948 // If min, max and stepSize is set and they make an evenly spaced scale use it.
10949 if (stepSize) {
10950 // If very close to our whole number, use it.
10951 if (!isNullOrUndef(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) {
10952 niceMin = min;
10953 }
10954 if (!isNullOrUndef(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) {
10955 niceMax = max;
10956 }
10957 }
10958
10959 numSpaces = (niceMax - niceMin) / spacing;
10960 // If very close to our rounded value, use it.
10961 if (helpers$1.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
10962 numSpaces = Math.round(numSpaces);
10963 } else {
10964 numSpaces = Math.ceil(numSpaces);
10965 }
10966
10967 niceMin = Math.round(niceMin * factor) / factor;
10968 niceMax = Math.round(niceMax * factor) / factor;
10969 ticks.push(isNullOrUndef(min) ? niceMin : min);
10970 for (var j = 1; j < numSpaces; ++j) {
10971 ticks.push(Math.round((niceMin + j * spacing) * factor) / factor);
10972 }
10973 ticks.push(isNullOrUndef(max) ? niceMax : max);
10974
10975 return ticks;
10976}
10977
10978var scale_linearbase = core_scale.extend({
10979 getRightValue: function(value) {
10980 if (typeof value === 'string') {
10981 return +value;
10982 }
10983 return core_scale.prototype.getRightValue.call(this, value);
10984 },
10985
10986 handleTickRangeOptions: function() {
10987 var me = this;
10988 var opts = me.options;
10989 var tickOpts = opts.ticks;
10990
10991 // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
10992 // do nothing since that would make the chart weird. If the user really wants a weird chart
10993 // axis, they can manually override it
10994 if (tickOpts.beginAtZero) {
10995 var minSign = helpers$1.sign(me.min);
10996 var maxSign = helpers$1.sign(me.max);
10997
10998 if (minSign < 0 && maxSign < 0) {
10999 // move the top up to 0
11000 me.max = 0;
11001 } else if (minSign > 0 && maxSign > 0) {
11002 // move the bottom down to 0
11003 me.min = 0;
11004 }
11005 }
11006
11007 var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
11008 var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
11009
11010 if (tickOpts.min !== undefined) {
11011 me.min = tickOpts.min;
11012 } else if (tickOpts.suggestedMin !== undefined) {
11013 if (me.min === null) {
11014 me.min = tickOpts.suggestedMin;
11015 } else {
11016 me.min = Math.min(me.min, tickOpts.suggestedMin);
11017 }
11018 }
11019
11020 if (tickOpts.max !== undefined) {
11021 me.max = tickOpts.max;
11022 } else if (tickOpts.suggestedMax !== undefined) {
11023 if (me.max === null) {
11024 me.max = tickOpts.suggestedMax;
11025 } else {
11026 me.max = Math.max(me.max, tickOpts.suggestedMax);
11027 }
11028 }
11029
11030 if (setMin !== setMax) {
11031 // We set the min or the max but not both.
11032 // So ensure that our range is good
11033 // Inverted or 0 length range can happen when
11034 // ticks.min is set, and no datasets are visible
11035 if (me.min >= me.max) {
11036 if (setMin) {
11037 me.max = me.min + 1;
11038 } else {
11039 me.min = me.max - 1;
11040 }
11041 }
11042 }
11043
11044 if (me.min === me.max) {
11045 me.max++;
11046
11047 if (!tickOpts.beginAtZero) {
11048 me.min--;
11049 }
11050 }
11051 },
11052
11053 getTickLimit: function() {
11054 var me = this;
11055 var tickOpts = me.options.ticks;
11056 var stepSize = tickOpts.stepSize;
11057 var maxTicksLimit = tickOpts.maxTicksLimit;
11058 var maxTicks;
11059
11060 if (stepSize) {
11061 maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1;
11062 } else {
11063 maxTicks = me._computeTickLimit();
11064 maxTicksLimit = maxTicksLimit || 11;
11065 }
11066
11067 if (maxTicksLimit) {
11068 maxTicks = Math.min(maxTicksLimit, maxTicks);
11069 }
11070
11071 return maxTicks;
11072 },
11073
11074 _computeTickLimit: function() {
11075 return Number.POSITIVE_INFINITY;
11076 },
11077
11078 handleDirectionalChanges: noop,
11079
11080 buildTicks: function() {
11081 var me = this;
11082 var opts = me.options;
11083 var tickOpts = opts.ticks;
11084
11085 // Figure out what the max number of ticks we can support it is based on the size of
11086 // the axis area. For now, we say that the minimum tick spacing in pixels must be 40
11087 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
11088 // the graph. Make sure we always have at least 2 ticks
11089 var maxTicks = me.getTickLimit();
11090 maxTicks = Math.max(2, maxTicks);
11091
11092 var numericGeneratorOptions = {
11093 maxTicks: maxTicks,
11094 min: tickOpts.min,
11095 max: tickOpts.max,
11096 precision: tickOpts.precision,
11097 stepSize: helpers$1.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
11098 };
11099 var ticks = me.ticks = generateTicks(numericGeneratorOptions, me);
11100
11101 me.handleDirectionalChanges();
11102
11103 // At this point, we need to update our max and min given the tick values since we have expanded the
11104 // range of the scale
11105 me.max = helpers$1.max(ticks);
11106 me.min = helpers$1.min(ticks);
11107
11108 if (tickOpts.reverse) {
11109 ticks.reverse();
11110
11111 me.start = me.max;
11112 me.end = me.min;
11113 } else {
11114 me.start = me.min;
11115 me.end = me.max;
11116 }
11117 },
11118
11119 convertTicksToLabels: function() {
11120 var me = this;
11121 me.ticksAsNumbers = me.ticks.slice();
11122 me.zeroLineIndex = me.ticks.indexOf(0);
11123
11124 core_scale.prototype.convertTicksToLabels.call(me);
11125 }
11126});
11127
11128var defaultConfig$1 = {
11129 position: 'left',
11130 ticks: {
11131 callback: core_ticks.formatters.linear
11132 }
11133};
11134
11135var scale_linear = scale_linearbase.extend({
11136 determineDataLimits: function() {
11137 var me = this;
11138 var opts = me.options;
11139 var chart = me.chart;
11140 var data = chart.data;
11141 var datasets = data.datasets;
11142 var isHorizontal = me.isHorizontal();
11143 var DEFAULT_MIN = 0;
11144 var DEFAULT_MAX = 1;
11145
11146 function IDMatches(meta) {
11147 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
11148 }
11149
11150 // First Calculate the range
11151 me.min = null;
11152 me.max = null;
11153
11154 var hasStacks = opts.stacked;
11155 if (hasStacks === undefined) {
11156 helpers$1.each(datasets, function(dataset, datasetIndex) {
11157 if (hasStacks) {
11158 return;
11159 }
11160
11161 var meta = chart.getDatasetMeta(datasetIndex);
11162 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
11163 meta.stack !== undefined) {
11164 hasStacks = true;
11165 }
11166 });
11167 }
11168
11169 if (opts.stacked || hasStacks) {
11170 var valuesPerStack = {};
11171
11172 helpers$1.each(datasets, function(dataset, datasetIndex) {
11173 var meta = chart.getDatasetMeta(datasetIndex);
11174 var key = [
11175 meta.type,
11176 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
11177 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
11178 meta.stack
11179 ].join('.');
11180
11181 if (valuesPerStack[key] === undefined) {
11182 valuesPerStack[key] = {
11183 positiveValues: [],
11184 negativeValues: []
11185 };
11186 }
11187
11188 // Store these per type
11189 var positiveValues = valuesPerStack[key].positiveValues;
11190 var negativeValues = valuesPerStack[key].negativeValues;
11191
11192 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
11193 helpers$1.each(dataset.data, function(rawValue, index) {
11194 var value = +me.getRightValue(rawValue);
11195 if (isNaN(value) || meta.data[index].hidden) {
11196 return;
11197 }
11198
11199 positiveValues[index] = positiveValues[index] || 0;
11200 negativeValues[index] = negativeValues[index] || 0;
11201
11202 if (opts.relativePoints) {
11203 positiveValues[index] = 100;
11204 } else if (value < 0) {
11205 negativeValues[index] += value;
11206 } else {
11207 positiveValues[index] += value;
11208 }
11209 });
11210 }
11211 });
11212
11213 helpers$1.each(valuesPerStack, function(valuesForType) {
11214 var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
11215 var minVal = helpers$1.min(values);
11216 var maxVal = helpers$1.max(values);
11217 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
11218 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
11219 });
11220
11221 } else {
11222 helpers$1.each(datasets, function(dataset, datasetIndex) {
11223 var meta = chart.getDatasetMeta(datasetIndex);
11224 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
11225 helpers$1.each(dataset.data, function(rawValue, index) {
11226 var value = +me.getRightValue(rawValue);
11227 if (isNaN(value) || meta.data[index].hidden) {
11228 return;
11229 }
11230
11231 if (me.min === null) {
11232 me.min = value;
11233 } else if (value < me.min) {
11234 me.min = value;
11235 }
11236
11237 if (me.max === null) {
11238 me.max = value;
11239 } else if (value > me.max) {
11240 me.max = value;
11241 }
11242 });
11243 }
11244 });
11245 }
11246
11247 me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
11248 me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
11249
11250 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
11251 this.handleTickRangeOptions();
11252 },
11253
11254 // Returns the maximum number of ticks based on the scale dimension
11255 _computeTickLimit: function() {
11256 var me = this;
11257 var tickFont;
11258
11259 if (me.isHorizontal()) {
11260 return Math.ceil(me.width / 40);
11261 }
11262 tickFont = helpers$1.options._parseFont(me.options.ticks);
11263 return Math.ceil(me.height / tickFont.lineHeight);
11264 },
11265
11266 // Called after the ticks are built. We need
11267 handleDirectionalChanges: function() {
11268 if (!this.isHorizontal()) {
11269 // We are in a vertical orientation. The top value is the highest. So reverse the array
11270 this.ticks.reverse();
11271 }
11272 },
11273
11274 getLabelForIndex: function(index, datasetIndex) {
11275 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
11276 },
11277
11278 // Utils
11279 getPixelForValue: function(value) {
11280 // This must be called after fit has been run so that
11281 // this.left, this.top, this.right, and this.bottom have been defined
11282 var me = this;
11283 var start = me.start;
11284
11285 var rightValue = +me.getRightValue(value);
11286 var pixel;
11287 var range = me.end - start;
11288
11289 if (me.isHorizontal()) {
11290 pixel = me.left + (me.width / range * (rightValue - start));
11291 } else {
11292 pixel = me.bottom - (me.height / range * (rightValue - start));
11293 }
11294 return pixel;
11295 },
11296
11297 getValueForPixel: function(pixel) {
11298 var me = this;
11299 var isHorizontal = me.isHorizontal();
11300 var innerDimension = isHorizontal ? me.width : me.height;
11301 var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;
11302 return me.start + ((me.end - me.start) * offset);
11303 },
11304
11305 getPixelForTick: function(index) {
11306 return this.getPixelForValue(this.ticksAsNumbers[index]);
11307 }
11308});
11309
11310// INTERNAL: static default options, registered in src/chart.js
11311var _defaults$1 = defaultConfig$1;
11312scale_linear._defaults = _defaults$1;
11313
11314var valueOrDefault$8 = helpers$1.valueOrDefault;
11315
11316/**
11317 * Generate a set of logarithmic ticks
11318 * @param generationOptions the options used to generate the ticks
11319 * @param dataRange the range of the data
11320 * @returns {Array<Number>} array of tick values
11321 */
11322function generateTicks$1(generationOptions, dataRange) {
11323 var ticks = [];
11324
11325 var tickVal = valueOrDefault$8(generationOptions.min, Math.pow(10, Math.floor(helpers$1.log10(dataRange.min))));
11326
11327 var endExp = Math.floor(helpers$1.log10(dataRange.max));
11328 var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
11329 var exp, significand;
11330
11331 if (tickVal === 0) {
11332 exp = Math.floor(helpers$1.log10(dataRange.minNotZero));
11333 significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
11334
11335 ticks.push(tickVal);
11336 tickVal = significand * Math.pow(10, exp);
11337 } else {
11338 exp = Math.floor(helpers$1.log10(tickVal));
11339 significand = Math.floor(tickVal / Math.pow(10, exp));
11340 }
11341 var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
11342
11343 do {
11344 ticks.push(tickVal);
11345
11346 ++significand;
11347 if (significand === 10) {
11348 significand = 1;
11349 ++exp;
11350 precision = exp >= 0 ? 1 : precision;
11351 }
11352
11353 tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
11354 } while (exp < endExp || (exp === endExp && significand < endSignificand));
11355
11356 var lastTick = valueOrDefault$8(generationOptions.max, tickVal);
11357 ticks.push(lastTick);
11358
11359 return ticks;
11360}
11361
11362var defaultConfig$2 = {
11363 position: 'left',
11364
11365 // label settings
11366 ticks: {
11367 callback: core_ticks.formatters.logarithmic
11368 }
11369};
11370
11371var scale_logarithmic = core_scale.extend({
11372 determineDataLimits: function() {
11373 var me = this;
11374 var opts = me.options;
11375 var chart = me.chart;
11376 var data = chart.data;
11377 var datasets = data.datasets;
11378 var isHorizontal = me.isHorizontal();
11379 function IDMatches(meta) {
11380 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
11381 }
11382
11383 // Calculate Range
11384 me.min = null;
11385 me.max = null;
11386 me.minNotZero = null;
11387
11388 var hasStacks = opts.stacked;
11389 if (hasStacks === undefined) {
11390 helpers$1.each(datasets, function(dataset, datasetIndex) {
11391 if (hasStacks) {
11392 return;
11393 }
11394
11395 var meta = chart.getDatasetMeta(datasetIndex);
11396 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
11397 meta.stack !== undefined) {
11398 hasStacks = true;
11399 }
11400 });
11401 }
11402
11403 if (opts.stacked || hasStacks) {
11404 var valuesPerStack = {};
11405
11406 helpers$1.each(datasets, function(dataset, datasetIndex) {
11407 var meta = chart.getDatasetMeta(datasetIndex);
11408 var key = [
11409 meta.type,
11410 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
11411 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
11412 meta.stack
11413 ].join('.');
11414
11415 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
11416 if (valuesPerStack[key] === undefined) {
11417 valuesPerStack[key] = [];
11418 }
11419
11420 helpers$1.each(dataset.data, function(rawValue, index) {
11421 var values = valuesPerStack[key];
11422 var value = +me.getRightValue(rawValue);
11423 // invalid, hidden and negative values are ignored
11424 if (isNaN(value) || meta.data[index].hidden || value < 0) {
11425 return;
11426 }
11427 values[index] = values[index] || 0;
11428 values[index] += value;
11429 });
11430 }
11431 });
11432
11433 helpers$1.each(valuesPerStack, function(valuesForType) {
11434 if (valuesForType.length > 0) {
11435 var minVal = helpers$1.min(valuesForType);
11436 var maxVal = helpers$1.max(valuesForType);
11437 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
11438 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
11439 }
11440 });
11441
11442 } else {
11443 helpers$1.each(datasets, function(dataset, datasetIndex) {
11444 var meta = chart.getDatasetMeta(datasetIndex);
11445 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
11446 helpers$1.each(dataset.data, function(rawValue, index) {
11447 var value = +me.getRightValue(rawValue);
11448 // invalid, hidden and negative values are ignored
11449 if (isNaN(value) || meta.data[index].hidden || value < 0) {
11450 return;
11451 }
11452
11453 if (me.min === null) {
11454 me.min = value;
11455 } else if (value < me.min) {
11456 me.min = value;
11457 }
11458
11459 if (me.max === null) {
11460 me.max = value;
11461 } else if (value > me.max) {
11462 me.max = value;
11463 }
11464
11465 if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
11466 me.minNotZero = value;
11467 }
11468 });
11469 }
11470 });
11471 }
11472
11473 // Common base implementation to handle ticks.min, ticks.max
11474 this.handleTickRangeOptions();
11475 },
11476
11477 handleTickRangeOptions: function() {
11478 var me = this;
11479 var tickOpts = me.options.ticks;
11480 var DEFAULT_MIN = 1;
11481 var DEFAULT_MAX = 10;
11482
11483 me.min = valueOrDefault$8(tickOpts.min, me.min);
11484 me.max = valueOrDefault$8(tickOpts.max, me.max);
11485
11486 if (me.min === me.max) {
11487 if (me.min !== 0 && me.min !== null) {
11488 me.min = Math.pow(10, Math.floor(helpers$1.log10(me.min)) - 1);
11489 me.max = Math.pow(10, Math.floor(helpers$1.log10(me.max)) + 1);
11490 } else {
11491 me.min = DEFAULT_MIN;
11492 me.max = DEFAULT_MAX;
11493 }
11494 }
11495 if (me.min === null) {
11496 me.min = Math.pow(10, Math.floor(helpers$1.log10(me.max)) - 1);
11497 }
11498 if (me.max === null) {
11499 me.max = me.min !== 0
11500 ? Math.pow(10, Math.floor(helpers$1.log10(me.min)) + 1)
11501 : DEFAULT_MAX;
11502 }
11503 if (me.minNotZero === null) {
11504 if (me.min > 0) {
11505 me.minNotZero = me.min;
11506 } else if (me.max < 1) {
11507 me.minNotZero = Math.pow(10, Math.floor(helpers$1.log10(me.max)));
11508 } else {
11509 me.minNotZero = DEFAULT_MIN;
11510 }
11511 }
11512 },
11513
11514 buildTicks: function() {
11515 var me = this;
11516 var tickOpts = me.options.ticks;
11517 var reverse = !me.isHorizontal();
11518
11519 var generationOptions = {
11520 min: tickOpts.min,
11521 max: tickOpts.max
11522 };
11523 var ticks = me.ticks = generateTicks$1(generationOptions, me);
11524
11525 // At this point, we need to update our max and min given the tick values since we have expanded the
11526 // range of the scale
11527 me.max = helpers$1.max(ticks);
11528 me.min = helpers$1.min(ticks);
11529
11530 if (tickOpts.reverse) {
11531 reverse = !reverse;
11532 me.start = me.max;
11533 me.end = me.min;
11534 } else {
11535 me.start = me.min;
11536 me.end = me.max;
11537 }
11538 if (reverse) {
11539 ticks.reverse();
11540 }
11541 },
11542
11543 convertTicksToLabels: function() {
11544 this.tickValues = this.ticks.slice();
11545
11546 core_scale.prototype.convertTicksToLabels.call(this);
11547 },
11548
11549 // Get the correct tooltip label
11550 getLabelForIndex: function(index, datasetIndex) {
11551 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
11552 },
11553
11554 getPixelForTick: function(index) {
11555 return this.getPixelForValue(this.tickValues[index]);
11556 },
11557
11558 /**
11559 * Returns the value of the first tick.
11560 * @param {Number} value - The minimum not zero value.
11561 * @return {Number} The first tick value.
11562 * @private
11563 */
11564 _getFirstTickValue: function(value) {
11565 var exp = Math.floor(helpers$1.log10(value));
11566 var significand = Math.floor(value / Math.pow(10, exp));
11567
11568 return significand * Math.pow(10, exp);
11569 },
11570
11571 getPixelForValue: function(value) {
11572 var me = this;
11573 var tickOpts = me.options.ticks;
11574 var reverse = tickOpts.reverse;
11575 var log10 = helpers$1.log10;
11576 var firstTickValue = me._getFirstTickValue(me.minNotZero);
11577 var offset = 0;
11578 var innerDimension, pixel, start, end, sign;
11579
11580 value = +me.getRightValue(value);
11581 if (reverse) {
11582 start = me.end;
11583 end = me.start;
11584 sign = -1;
11585 } else {
11586 start = me.start;
11587 end = me.end;
11588 sign = 1;
11589 }
11590 if (me.isHorizontal()) {
11591 innerDimension = me.width;
11592 pixel = reverse ? me.right : me.left;
11593 } else {
11594 innerDimension = me.height;
11595 sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0)
11596 pixel = reverse ? me.top : me.bottom;
11597 }
11598 if (value !== start) {
11599 if (start === 0) { // include zero tick
11600 offset = valueOrDefault$8(tickOpts.fontSize, core_defaults.global.defaultFontSize);
11601 innerDimension -= offset;
11602 start = firstTickValue;
11603 }
11604 if (value !== 0) {
11605 offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start));
11606 }
11607 pixel += sign * offset;
11608 }
11609 return pixel;
11610 },
11611
11612 getValueForPixel: function(pixel) {
11613 var me = this;
11614 var tickOpts = me.options.ticks;
11615 var reverse = tickOpts.reverse;
11616 var log10 = helpers$1.log10;
11617 var firstTickValue = me._getFirstTickValue(me.minNotZero);
11618 var innerDimension, start, end, value;
11619
11620 if (reverse) {
11621 start = me.end;
11622 end = me.start;
11623 } else {
11624 start = me.start;
11625 end = me.end;
11626 }
11627 if (me.isHorizontal()) {
11628 innerDimension = me.width;
11629 value = reverse ? me.right - pixel : pixel - me.left;
11630 } else {
11631 innerDimension = me.height;
11632 value = reverse ? pixel - me.top : me.bottom - pixel;
11633 }
11634 if (value !== start) {
11635 if (start === 0) { // include zero tick
11636 var offset = valueOrDefault$8(tickOpts.fontSize, core_defaults.global.defaultFontSize);
11637 value -= offset;
11638 innerDimension -= offset;
11639 start = firstTickValue;
11640 }
11641 value *= log10(end) - log10(start);
11642 value /= innerDimension;
11643 value = Math.pow(10, log10(start) + value);
11644 }
11645 return value;
11646 }
11647});
11648
11649// INTERNAL: static default options, registered in src/chart.js
11650var _defaults$2 = defaultConfig$2;
11651scale_logarithmic._defaults = _defaults$2;
11652
11653var valueOrDefault$9 = helpers$1.valueOrDefault;
11654var valueAtIndexOrDefault$1 = helpers$1.valueAtIndexOrDefault;
11655var resolve$7 = helpers$1.options.resolve;
11656
11657var defaultConfig$3 = {
11658 display: true,
11659
11660 // Boolean - Whether to animate scaling the chart from the centre
11661 animate: true,
11662 position: 'chartArea',
11663
11664 angleLines: {
11665 display: true,
11666 color: 'rgba(0, 0, 0, 0.1)',
11667 lineWidth: 1,
11668 borderDash: [],
11669 borderDashOffset: 0.0
11670 },
11671
11672 gridLines: {
11673 circular: false
11674 },
11675
11676 // label settings
11677 ticks: {
11678 // Boolean - Show a backdrop to the scale label
11679 showLabelBackdrop: true,
11680
11681 // String - The colour of the label backdrop
11682 backdropColor: 'rgba(255,255,255,0.75)',
11683
11684 // Number - The backdrop padding above & below the label in pixels
11685 backdropPaddingY: 2,
11686
11687 // Number - The backdrop padding to the side of the label in pixels
11688 backdropPaddingX: 2,
11689
11690 callback: core_ticks.formatters.linear
11691 },
11692
11693 pointLabels: {
11694 // Boolean - if true, show point labels
11695 display: true,
11696
11697 // Number - Point label font size in pixels
11698 fontSize: 10,
11699
11700 // Function - Used to convert point labels
11701 callback: function(label) {
11702 return label;
11703 }
11704 }
11705};
11706
11707function getValueCount(scale) {
11708 var opts = scale.options;
11709 return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0;
11710}
11711
11712function getTickBackdropHeight(opts) {
11713 var tickOpts = opts.ticks;
11714
11715 if (tickOpts.display && opts.display) {
11716 return valueOrDefault$9(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2;
11717 }
11718 return 0;
11719}
11720
11721function measureLabelSize(ctx, lineHeight, label) {
11722 if (helpers$1.isArray(label)) {
11723 return {
11724 w: helpers$1.longestText(ctx, ctx.font, label),
11725 h: label.length * lineHeight
11726 };
11727 }
11728
11729 return {
11730 w: ctx.measureText(label).width,
11731 h: lineHeight
11732 };
11733}
11734
11735function determineLimits(angle, pos, size, min, max) {
11736 if (angle === min || angle === max) {
11737 return {
11738 start: pos - (size / 2),
11739 end: pos + (size / 2)
11740 };
11741 } else if (angle < min || angle > max) {
11742 return {
11743 start: pos - size,
11744 end: pos
11745 };
11746 }
11747
11748 return {
11749 start: pos,
11750 end: pos + size
11751 };
11752}
11753
11754/**
11755 * Helper function to fit a radial linear scale with point labels
11756 */
11757function fitWithPointLabels(scale) {
11758
11759 // Right, this is really confusing and there is a lot of maths going on here
11760 // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
11761 //
11762 // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
11763 //
11764 // Solution:
11765 //
11766 // We assume the radius of the polygon is half the size of the canvas at first
11767 // at each index we check if the text overlaps.
11768 //
11769 // Where it does, we store that angle and that index.
11770 //
11771 // After finding the largest index and angle we calculate how much we need to remove
11772 // from the shape radius to move the point inwards by that x.
11773 //
11774 // We average the left and right distances to get the maximum shape radius that can fit in the box
11775 // along with labels.
11776 //
11777 // Once we have that, we can find the centre point for the chart, by taking the x text protrusion
11778 // on each side, removing that from the size, halving it and adding the left x protrusion width.
11779 //
11780 // This will mean we have a shape fitted to the canvas, as large as it can be with the labels
11781 // and position it in the most space efficient manner
11782 //
11783 // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
11784
11785 var plFont = helpers$1.options._parseFont(scale.options.pointLabels);
11786
11787 // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
11788 // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
11789 var furthestLimits = {
11790 l: 0,
11791 r: scale.width,
11792 t: 0,
11793 b: scale.height - scale.paddingTop
11794 };
11795 var furthestAngles = {};
11796 var i, textSize, pointPosition;
11797
11798 scale.ctx.font = plFont.string;
11799 scale._pointLabelSizes = [];
11800
11801 var valueCount = getValueCount(scale);
11802 for (i = 0; i < valueCount; i++) {
11803 pointPosition = scale.getPointPosition(i, scale.drawingArea + 5);
11804 textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i] || '');
11805 scale._pointLabelSizes[i] = textSize;
11806
11807 // Add quarter circle to make degree 0 mean top of circle
11808 var angleRadians = scale.getIndexAngle(i);
11809 var angle = helpers$1.toDegrees(angleRadians) % 360;
11810 var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
11811 var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
11812
11813 if (hLimits.start < furthestLimits.l) {
11814 furthestLimits.l = hLimits.start;
11815 furthestAngles.l = angleRadians;
11816 }
11817
11818 if (hLimits.end > furthestLimits.r) {
11819 furthestLimits.r = hLimits.end;
11820 furthestAngles.r = angleRadians;
11821 }
11822
11823 if (vLimits.start < furthestLimits.t) {
11824 furthestLimits.t = vLimits.start;
11825 furthestAngles.t = angleRadians;
11826 }
11827
11828 if (vLimits.end > furthestLimits.b) {
11829 furthestLimits.b = vLimits.end;
11830 furthestAngles.b = angleRadians;
11831 }
11832 }
11833
11834 scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles);
11835}
11836
11837function getTextAlignForAngle(angle) {
11838 if (angle === 0 || angle === 180) {
11839 return 'center';
11840 } else if (angle < 180) {
11841 return 'left';
11842 }
11843
11844 return 'right';
11845}
11846
11847function fillText(ctx, text, position, lineHeight) {
11848 var y = position.y + lineHeight / 2;
11849 var i, ilen;
11850
11851 if (helpers$1.isArray(text)) {
11852 for (i = 0, ilen = text.length; i < ilen; ++i) {
11853 ctx.fillText(text[i], position.x, y);
11854 y += lineHeight;
11855 }
11856 } else {
11857 ctx.fillText(text, position.x, y);
11858 }
11859}
11860
11861function adjustPointPositionForLabelHeight(angle, textSize, position) {
11862 if (angle === 90 || angle === 270) {
11863 position.y -= (textSize.h / 2);
11864 } else if (angle > 270 || angle < 90) {
11865 position.y -= textSize.h;
11866 }
11867}
11868
11869function drawPointLabels(scale) {
11870 var ctx = scale.ctx;
11871 var opts = scale.options;
11872 var angleLineOpts = opts.angleLines;
11873 var gridLineOpts = opts.gridLines;
11874 var pointLabelOpts = opts.pointLabels;
11875 var lineWidth = valueOrDefault$9(angleLineOpts.lineWidth, gridLineOpts.lineWidth);
11876 var lineColor = valueOrDefault$9(angleLineOpts.color, gridLineOpts.color);
11877 var tickBackdropHeight = getTickBackdropHeight(opts);
11878
11879 ctx.save();
11880 ctx.lineWidth = lineWidth;
11881 ctx.strokeStyle = lineColor;
11882 if (ctx.setLineDash) {
11883 ctx.setLineDash(resolve$7([angleLineOpts.borderDash, gridLineOpts.borderDash, []]));
11884 ctx.lineDashOffset = resolve$7([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]);
11885 }
11886
11887 var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
11888
11889 // Point Label Font
11890 var plFont = helpers$1.options._parseFont(pointLabelOpts);
11891
11892 ctx.font = plFont.string;
11893 ctx.textBaseline = 'middle';
11894
11895 for (var i = getValueCount(scale) - 1; i >= 0; i--) {
11896 if (angleLineOpts.display && lineWidth && lineColor) {
11897 var outerPosition = scale.getPointPosition(i, outerDistance);
11898 ctx.beginPath();
11899 ctx.moveTo(scale.xCenter, scale.yCenter);
11900 ctx.lineTo(outerPosition.x, outerPosition.y);
11901 ctx.stroke();
11902 }
11903
11904 if (pointLabelOpts.display) {
11905 // Extra pixels out for some label spacing
11906 var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
11907 var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
11908
11909 // Keep this in loop since we may support array properties here
11910 var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor);
11911 ctx.fillStyle = pointLabelFontColor;
11912
11913 var angleRadians = scale.getIndexAngle(i);
11914 var angle = helpers$1.toDegrees(angleRadians);
11915 ctx.textAlign = getTextAlignForAngle(angle);
11916 adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
11917 fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.lineHeight);
11918 }
11919 }
11920 ctx.restore();
11921}
11922
11923function drawRadiusLine(scale, gridLineOpts, radius, index) {
11924 var ctx = scale.ctx;
11925 var circular = gridLineOpts.circular;
11926 var valueCount = getValueCount(scale);
11927 var lineColor = valueAtIndexOrDefault$1(gridLineOpts.color, index - 1);
11928 var lineWidth = valueAtIndexOrDefault$1(gridLineOpts.lineWidth, index - 1);
11929 var pointPosition;
11930
11931 if ((!circular && !valueCount) || !lineColor || !lineWidth) {
11932 return;
11933 }
11934
11935 ctx.save();
11936 ctx.strokeStyle = lineColor;
11937 ctx.lineWidth = lineWidth;
11938 if (ctx.setLineDash) {
11939 ctx.setLineDash(gridLineOpts.borderDash || []);
11940 ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0;
11941 }
11942
11943 ctx.beginPath();
11944 if (circular) {
11945 // Draw circular arcs between the points
11946 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
11947 } else {
11948 // Draw straight lines connecting each index
11949 pointPosition = scale.getPointPosition(0, radius);
11950 ctx.moveTo(pointPosition.x, pointPosition.y);
11951
11952 for (var i = 1; i < valueCount; i++) {
11953 pointPosition = scale.getPointPosition(i, radius);
11954 ctx.lineTo(pointPosition.x, pointPosition.y);
11955 }
11956 }
11957 ctx.closePath();
11958 ctx.stroke();
11959 ctx.restore();
11960}
11961
11962function numberOrZero(param) {
11963 return helpers$1.isNumber(param) ? param : 0;
11964}
11965
11966var scale_radialLinear = scale_linearbase.extend({
11967 setDimensions: function() {
11968 var me = this;
11969
11970 // Set the unconstrained dimension before label rotation
11971 me.width = me.maxWidth;
11972 me.height = me.maxHeight;
11973 me.paddingTop = getTickBackdropHeight(me.options) / 2;
11974 me.xCenter = Math.floor(me.width / 2);
11975 me.yCenter = Math.floor((me.height - me.paddingTop) / 2);
11976 me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2;
11977 },
11978
11979 determineDataLimits: function() {
11980 var me = this;
11981 var chart = me.chart;
11982 var min = Number.POSITIVE_INFINITY;
11983 var max = Number.NEGATIVE_INFINITY;
11984
11985 helpers$1.each(chart.data.datasets, function(dataset, datasetIndex) {
11986 if (chart.isDatasetVisible(datasetIndex)) {
11987 var meta = chart.getDatasetMeta(datasetIndex);
11988
11989 helpers$1.each(dataset.data, function(rawValue, index) {
11990 var value = +me.getRightValue(rawValue);
11991 if (isNaN(value) || meta.data[index].hidden) {
11992 return;
11993 }
11994
11995 min = Math.min(value, min);
11996 max = Math.max(value, max);
11997 });
11998 }
11999 });
12000
12001 me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
12002 me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
12003
12004 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
12005 me.handleTickRangeOptions();
12006 },
12007
12008 // Returns the maximum number of ticks based on the scale dimension
12009 _computeTickLimit: function() {
12010 return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
12011 },
12012
12013 convertTicksToLabels: function() {
12014 var me = this;
12015
12016 scale_linearbase.prototype.convertTicksToLabels.call(me);
12017
12018 // Point labels
12019 me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
12020 },
12021
12022 getLabelForIndex: function(index, datasetIndex) {
12023 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12024 },
12025
12026 fit: function() {
12027 var me = this;
12028 var opts = me.options;
12029
12030 if (opts.display && opts.pointLabels.display) {
12031 fitWithPointLabels(me);
12032 } else {
12033 me.setCenterPoint(0, 0, 0, 0);
12034 }
12035 },
12036
12037 /**
12038 * Set radius reductions and determine new radius and center point
12039 * @private
12040 */
12041 setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
12042 var me = this;
12043 var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
12044 var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
12045 var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
12046 var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b);
12047
12048 radiusReductionLeft = numberOrZero(radiusReductionLeft);
12049 radiusReductionRight = numberOrZero(radiusReductionRight);
12050 radiusReductionTop = numberOrZero(radiusReductionTop);
12051 radiusReductionBottom = numberOrZero(radiusReductionBottom);
12052
12053 me.drawingArea = Math.min(
12054 Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
12055 Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
12056 me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
12057 },
12058
12059 setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
12060 var me = this;
12061 var maxRight = me.width - rightMovement - me.drawingArea;
12062 var maxLeft = leftMovement + me.drawingArea;
12063 var maxTop = topMovement + me.drawingArea;
12064 var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea;
12065
12066 me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left);
12067 me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop);
12068 },
12069
12070 getIndexAngle: function(index) {
12071 var angleMultiplier = (Math.PI * 2) / getValueCount(this);
12072 var startAngle = this.chart.options && this.chart.options.startAngle ?
12073 this.chart.options.startAngle :
12074 0;
12075
12076 var startAngleRadians = startAngle * Math.PI * 2 / 360;
12077
12078 // Start from the top instead of right, so remove a quarter of the circle
12079 return index * angleMultiplier + startAngleRadians;
12080 },
12081
12082 getDistanceFromCenterForValue: function(value) {
12083 var me = this;
12084
12085 if (value === null) {
12086 return 0; // null always in center
12087 }
12088
12089 // Take into account half font size + the yPadding of the top value
12090 var scalingFactor = me.drawingArea / (me.max - me.min);
12091 if (me.options.ticks.reverse) {
12092 return (me.max - value) * scalingFactor;
12093 }
12094 return (value - me.min) * scalingFactor;
12095 },
12096
12097 getPointPosition: function(index, distanceFromCenter) {
12098 var me = this;
12099 var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
12100 return {
12101 x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter,
12102 y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter
12103 };
12104 },
12105
12106 getPointPositionForValue: function(index, value) {
12107 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
12108 },
12109
12110 getBasePosition: function() {
12111 var me = this;
12112 var min = me.min;
12113 var max = me.max;
12114
12115 return me.getPointPositionForValue(0,
12116 me.beginAtZero ? 0 :
12117 min < 0 && max < 0 ? max :
12118 min > 0 && max > 0 ? min :
12119 0);
12120 },
12121
12122 draw: function() {
12123 var me = this;
12124 var opts = me.options;
12125 var gridLineOpts = opts.gridLines;
12126 var tickOpts = opts.ticks;
12127
12128 if (opts.display) {
12129 var ctx = me.ctx;
12130 var startAngle = this.getIndexAngle(0);
12131 var tickFont = helpers$1.options._parseFont(tickOpts);
12132
12133 if (opts.angleLines.display || opts.pointLabels.display) {
12134 drawPointLabels(me);
12135 }
12136
12137 helpers$1.each(me.ticks, function(label, index) {
12138 // Don't draw a centre value (if it is minimum)
12139 if (index > 0 || tickOpts.reverse) {
12140 var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
12141
12142 // Draw circular lines around the scale
12143 if (gridLineOpts.display && index !== 0) {
12144 drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
12145 }
12146
12147 if (tickOpts.display) {
12148 var tickFontColor = valueOrDefault$9(tickOpts.fontColor, core_defaults.global.defaultFontColor);
12149 ctx.font = tickFont.string;
12150
12151 ctx.save();
12152 ctx.translate(me.xCenter, me.yCenter);
12153 ctx.rotate(startAngle);
12154
12155 if (tickOpts.showLabelBackdrop) {
12156 var labelWidth = ctx.measureText(label).width;
12157 ctx.fillStyle = tickOpts.backdropColor;
12158 ctx.fillRect(
12159 -labelWidth / 2 - tickOpts.backdropPaddingX,
12160 -yCenterOffset - tickFont.size / 2 - tickOpts.backdropPaddingY,
12161 labelWidth + tickOpts.backdropPaddingX * 2,
12162 tickFont.size + tickOpts.backdropPaddingY * 2
12163 );
12164 }
12165
12166 ctx.textAlign = 'center';
12167 ctx.textBaseline = 'middle';
12168 ctx.fillStyle = tickFontColor;
12169 ctx.fillText(label, 0, -yCenterOffset);
12170 ctx.restore();
12171 }
12172 }
12173 });
12174 }
12175 }
12176});
12177
12178// INTERNAL: static default options, registered in src/chart.js
12179var _defaults$3 = defaultConfig$3;
12180scale_radialLinear._defaults = _defaults$3;
12181
12182var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
12183
12184function commonjsRequire () {
12185 throw new Error('Dynamic requires are not currently supported by rollup-plugin-commonjs');
12186}
12187
12188function createCommonjsModule(fn, module) {
12189 return module = { exports: {} }, fn(module, module.exports), module.exports;
12190}
12191
12192var moment = createCommonjsModule(function (module, exports) {
12193(function (global, factory) {
12194 module.exports = factory();
12195}(commonjsGlobal, (function () {
12196 var hookCallback;
12197
12198 function hooks () {
12199 return hookCallback.apply(null, arguments);
12200 }
12201
12202 // This is done to register the method called with moment()
12203 // without creating circular dependencies.
12204 function setHookCallback (callback) {
12205 hookCallback = callback;
12206 }
12207
12208 function isArray(input) {
12209 return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
12210 }
12211
12212 function isObject(input) {
12213 // IE8 will treat undefined and null as object if it wasn't for
12214 // input != null
12215 return input != null && Object.prototype.toString.call(input) === '[object Object]';
12216 }
12217
12218 function isObjectEmpty(obj) {
12219 if (Object.getOwnPropertyNames) {
12220 return (Object.getOwnPropertyNames(obj).length === 0);
12221 } else {
12222 var k;
12223 for (k in obj) {
12224 if (obj.hasOwnProperty(k)) {
12225 return false;
12226 }
12227 }
12228 return true;
12229 }
12230 }
12231
12232 function isUndefined(input) {
12233 return input === void 0;
12234 }
12235
12236 function isNumber(input) {
12237 return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]';
12238 }
12239
12240 function isDate(input) {
12241 return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
12242 }
12243
12244 function map(arr, fn) {
12245 var res = [], i;
12246 for (i = 0; i < arr.length; ++i) {
12247 res.push(fn(arr[i], i));
12248 }
12249 return res;
12250 }
12251
12252 function hasOwnProp(a, b) {
12253 return Object.prototype.hasOwnProperty.call(a, b);
12254 }
12255
12256 function extend(a, b) {
12257 for (var i in b) {
12258 if (hasOwnProp(b, i)) {
12259 a[i] = b[i];
12260 }
12261 }
12262
12263 if (hasOwnProp(b, 'toString')) {
12264 a.toString = b.toString;
12265 }
12266
12267 if (hasOwnProp(b, 'valueOf')) {
12268 a.valueOf = b.valueOf;
12269 }
12270
12271 return a;
12272 }
12273
12274 function createUTC (input, format, locale, strict) {
12275 return createLocalOrUTC(input, format, locale, strict, true).utc();
12276 }
12277
12278 function defaultParsingFlags() {
12279 // We need to deep clone this object.
12280 return {
12281 empty : false,
12282 unusedTokens : [],
12283 unusedInput : [],
12284 overflow : -2,
12285 charsLeftOver : 0,
12286 nullInput : false,
12287 invalidMonth : null,
12288 invalidFormat : false,
12289 userInvalidated : false,
12290 iso : false,
12291 parsedDateParts : [],
12292 meridiem : null,
12293 rfc2822 : false,
12294 weekdayMismatch : false
12295 };
12296 }
12297
12298 function getParsingFlags(m) {
12299 if (m._pf == null) {
12300 m._pf = defaultParsingFlags();
12301 }
12302 return m._pf;
12303 }
12304
12305 var some;
12306 if (Array.prototype.some) {
12307 some = Array.prototype.some;
12308 } else {
12309 some = function (fun) {
12310 var t = Object(this);
12311 var len = t.length >>> 0;
12312
12313 for (var i = 0; i < len; i++) {
12314 if (i in t && fun.call(this, t[i], i, t)) {
12315 return true;
12316 }
12317 }
12318
12319 return false;
12320 };
12321 }
12322
12323 function isValid(m) {
12324 if (m._isValid == null) {
12325 var flags = getParsingFlags(m);
12326 var parsedParts = some.call(flags.parsedDateParts, function (i) {
12327 return i != null;
12328 });
12329 var isNowValid = !isNaN(m._d.getTime()) &&
12330 flags.overflow < 0 &&
12331 !flags.empty &&
12332 !flags.invalidMonth &&
12333 !flags.invalidWeekday &&
12334 !flags.weekdayMismatch &&
12335 !flags.nullInput &&
12336 !flags.invalidFormat &&
12337 !flags.userInvalidated &&
12338 (!flags.meridiem || (flags.meridiem && parsedParts));
12339
12340 if (m._strict) {
12341 isNowValid = isNowValid &&
12342 flags.charsLeftOver === 0 &&
12343 flags.unusedTokens.length === 0 &&
12344 flags.bigHour === undefined;
12345 }
12346
12347 if (Object.isFrozen == null || !Object.isFrozen(m)) {
12348 m._isValid = isNowValid;
12349 }
12350 else {
12351 return isNowValid;
12352 }
12353 }
12354 return m._isValid;
12355 }
12356
12357 function createInvalid (flags) {
12358 var m = createUTC(NaN);
12359 if (flags != null) {
12360 extend(getParsingFlags(m), flags);
12361 }
12362 else {
12363 getParsingFlags(m).userInvalidated = true;
12364 }
12365
12366 return m;
12367 }
12368
12369 // Plugins that add properties should also add the key here (null value),
12370 // so we can properly clone ourselves.
12371 var momentProperties = hooks.momentProperties = [];
12372
12373 function copyConfig(to, from) {
12374 var i, prop, val;
12375
12376 if (!isUndefined(from._isAMomentObject)) {
12377 to._isAMomentObject = from._isAMomentObject;
12378 }
12379 if (!isUndefined(from._i)) {
12380 to._i = from._i;
12381 }
12382 if (!isUndefined(from._f)) {
12383 to._f = from._f;
12384 }
12385 if (!isUndefined(from._l)) {
12386 to._l = from._l;
12387 }
12388 if (!isUndefined(from._strict)) {
12389 to._strict = from._strict;
12390 }
12391 if (!isUndefined(from._tzm)) {
12392 to._tzm = from._tzm;
12393 }
12394 if (!isUndefined(from._isUTC)) {
12395 to._isUTC = from._isUTC;
12396 }
12397 if (!isUndefined(from._offset)) {
12398 to._offset = from._offset;
12399 }
12400 if (!isUndefined(from._pf)) {
12401 to._pf = getParsingFlags(from);
12402 }
12403 if (!isUndefined(from._locale)) {
12404 to._locale = from._locale;
12405 }
12406
12407 if (momentProperties.length > 0) {
12408 for (i = 0; i < momentProperties.length; i++) {
12409 prop = momentProperties[i];
12410 val = from[prop];
12411 if (!isUndefined(val)) {
12412 to[prop] = val;
12413 }
12414 }
12415 }
12416
12417 return to;
12418 }
12419
12420 var updateInProgress = false;
12421
12422 // Moment prototype object
12423 function Moment(config) {
12424 copyConfig(this, config);
12425 this._d = new Date(config._d != null ? config._d.getTime() : NaN);
12426 if (!this.isValid()) {
12427 this._d = new Date(NaN);
12428 }
12429 // Prevent infinite loop in case updateOffset creates new moment
12430 // objects.
12431 if (updateInProgress === false) {
12432 updateInProgress = true;
12433 hooks.updateOffset(this);
12434 updateInProgress = false;
12435 }
12436 }
12437
12438 function isMoment (obj) {
12439 return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
12440 }
12441
12442 function absFloor (number) {
12443 if (number < 0) {
12444 // -0 -> 0
12445 return Math.ceil(number) || 0;
12446 } else {
12447 return Math.floor(number);
12448 }
12449 }
12450
12451 function toInt(argumentForCoercion) {
12452 var coercedNumber = +argumentForCoercion,
12453 value = 0;
12454
12455 if (coercedNumber !== 0 && isFinite(coercedNumber)) {
12456 value = absFloor(coercedNumber);
12457 }
12458
12459 return value;
12460 }
12461
12462 // compare two arrays, return the number of differences
12463 function compareArrays(array1, array2, dontConvert) {
12464 var len = Math.min(array1.length, array2.length),
12465 lengthDiff = Math.abs(array1.length - array2.length),
12466 diffs = 0,
12467 i;
12468 for (i = 0; i < len; i++) {
12469 if ((dontConvert && array1[i] !== array2[i]) ||
12470 (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
12471 diffs++;
12472 }
12473 }
12474 return diffs + lengthDiff;
12475 }
12476
12477 function warn(msg) {
12478 if (hooks.suppressDeprecationWarnings === false &&
12479 (typeof console !== 'undefined') && console.warn) {
12480 console.warn('Deprecation warning: ' + msg);
12481 }
12482 }
12483
12484 function deprecate(msg, fn) {
12485 var firstTime = true;
12486
12487 return extend(function () {
12488 if (hooks.deprecationHandler != null) {
12489 hooks.deprecationHandler(null, msg);
12490 }
12491 if (firstTime) {
12492 var args = [];
12493 var arg;
12494 for (var i = 0; i < arguments.length; i++) {
12495 arg = '';
12496 if (typeof arguments[i] === 'object') {
12497 arg += '\n[' + i + '] ';
12498 for (var key in arguments[0]) {
12499 arg += key + ': ' + arguments[0][key] + ', ';
12500 }
12501 arg = arg.slice(0, -2); // Remove trailing comma and space
12502 } else {
12503 arg = arguments[i];
12504 }
12505 args.push(arg);
12506 }
12507 warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack);
12508 firstTime = false;
12509 }
12510 return fn.apply(this, arguments);
12511 }, fn);
12512 }
12513
12514 var deprecations = {};
12515
12516 function deprecateSimple(name, msg) {
12517 if (hooks.deprecationHandler != null) {
12518 hooks.deprecationHandler(name, msg);
12519 }
12520 if (!deprecations[name]) {
12521 warn(msg);
12522 deprecations[name] = true;
12523 }
12524 }
12525
12526 hooks.suppressDeprecationWarnings = false;
12527 hooks.deprecationHandler = null;
12528
12529 function isFunction(input) {
12530 return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
12531 }
12532
12533 function set (config) {
12534 var prop, i;
12535 for (i in config) {
12536 prop = config[i];
12537 if (isFunction(prop)) {
12538 this[i] = prop;
12539 } else {
12540 this['_' + i] = prop;
12541 }
12542 }
12543 this._config = config;
12544 // Lenient ordinal parsing accepts just a number in addition to
12545 // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
12546 // TODO: Remove "ordinalParse" fallback in next major release.
12547 this._dayOfMonthOrdinalParseLenient = new RegExp(
12548 (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
12549 '|' + (/\d{1,2}/).source);
12550 }
12551
12552 function mergeConfigs(parentConfig, childConfig) {
12553 var res = extend({}, parentConfig), prop;
12554 for (prop in childConfig) {
12555 if (hasOwnProp(childConfig, prop)) {
12556 if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
12557 res[prop] = {};
12558 extend(res[prop], parentConfig[prop]);
12559 extend(res[prop], childConfig[prop]);
12560 } else if (childConfig[prop] != null) {
12561 res[prop] = childConfig[prop];
12562 } else {
12563 delete res[prop];
12564 }
12565 }
12566 }
12567 for (prop in parentConfig) {
12568 if (hasOwnProp(parentConfig, prop) &&
12569 !hasOwnProp(childConfig, prop) &&
12570 isObject(parentConfig[prop])) {
12571 // make sure changes to properties don't modify parent config
12572 res[prop] = extend({}, res[prop]);
12573 }
12574 }
12575 return res;
12576 }
12577
12578 function Locale(config) {
12579 if (config != null) {
12580 this.set(config);
12581 }
12582 }
12583
12584 var keys;
12585
12586 if (Object.keys) {
12587 keys = Object.keys;
12588 } else {
12589 keys = function (obj) {
12590 var i, res = [];
12591 for (i in obj) {
12592 if (hasOwnProp(obj, i)) {
12593 res.push(i);
12594 }
12595 }
12596 return res;
12597 };
12598 }
12599
12600 var defaultCalendar = {
12601 sameDay : '[Today at] LT',
12602 nextDay : '[Tomorrow at] LT',
12603 nextWeek : 'dddd [at] LT',
12604 lastDay : '[Yesterday at] LT',
12605 lastWeek : '[Last] dddd [at] LT',
12606 sameElse : 'L'
12607 };
12608
12609 function calendar (key, mom, now) {
12610 var output = this._calendar[key] || this._calendar['sameElse'];
12611 return isFunction(output) ? output.call(mom, now) : output;
12612 }
12613
12614 var defaultLongDateFormat = {
12615 LTS : 'h:mm:ss A',
12616 LT : 'h:mm A',
12617 L : 'MM/DD/YYYY',
12618 LL : 'MMMM D, YYYY',
12619 LLL : 'MMMM D, YYYY h:mm A',
12620 LLLL : 'dddd, MMMM D, YYYY h:mm A'
12621 };
12622
12623 function longDateFormat (key) {
12624 var format = this._longDateFormat[key],
12625 formatUpper = this._longDateFormat[key.toUpperCase()];
12626
12627 if (format || !formatUpper) {
12628 return format;
12629 }
12630
12631 this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
12632 return val.slice(1);
12633 });
12634
12635 return this._longDateFormat[key];
12636 }
12637
12638 var defaultInvalidDate = 'Invalid date';
12639
12640 function invalidDate () {
12641 return this._invalidDate;
12642 }
12643
12644 var defaultOrdinal = '%d';
12645 var defaultDayOfMonthOrdinalParse = /\d{1,2}/;
12646
12647 function ordinal (number) {
12648 return this._ordinal.replace('%d', number);
12649 }
12650
12651 var defaultRelativeTime = {
12652 future : 'in %s',
12653 past : '%s ago',
12654 s : 'a few seconds',
12655 ss : '%d seconds',
12656 m : 'a minute',
12657 mm : '%d minutes',
12658 h : 'an hour',
12659 hh : '%d hours',
12660 d : 'a day',
12661 dd : '%d days',
12662 M : 'a month',
12663 MM : '%d months',
12664 y : 'a year',
12665 yy : '%d years'
12666 };
12667
12668 function relativeTime (number, withoutSuffix, string, isFuture) {
12669 var output = this._relativeTime[string];
12670 return (isFunction(output)) ?
12671 output(number, withoutSuffix, string, isFuture) :
12672 output.replace(/%d/i, number);
12673 }
12674
12675 function pastFuture (diff, output) {
12676 var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
12677 return isFunction(format) ? format(output) : format.replace(/%s/i, output);
12678 }
12679
12680 var aliases = {};
12681
12682 function addUnitAlias (unit, shorthand) {
12683 var lowerCase = unit.toLowerCase();
12684 aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
12685 }
12686
12687 function normalizeUnits(units) {
12688 return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
12689 }
12690
12691 function normalizeObjectUnits(inputObject) {
12692 var normalizedInput = {},
12693 normalizedProp,
12694 prop;
12695
12696 for (prop in inputObject) {
12697 if (hasOwnProp(inputObject, prop)) {
12698 normalizedProp = normalizeUnits(prop);
12699 if (normalizedProp) {
12700 normalizedInput[normalizedProp] = inputObject[prop];
12701 }
12702 }
12703 }
12704
12705 return normalizedInput;
12706 }
12707
12708 var priorities = {};
12709
12710 function addUnitPriority(unit, priority) {
12711 priorities[unit] = priority;
12712 }
12713
12714 function getPrioritizedUnits(unitsObj) {
12715 var units = [];
12716 for (var u in unitsObj) {
12717 units.push({unit: u, priority: priorities[u]});
12718 }
12719 units.sort(function (a, b) {
12720 return a.priority - b.priority;
12721 });
12722 return units;
12723 }
12724
12725 function zeroFill(number, targetLength, forceSign) {
12726 var absNumber = '' + Math.abs(number),
12727 zerosToFill = targetLength - absNumber.length,
12728 sign = number >= 0;
12729 return (sign ? (forceSign ? '+' : '') : '-') +
12730 Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
12731 }
12732
12733 var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
12734
12735 var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
12736
12737 var formatFunctions = {};
12738
12739 var formatTokenFunctions = {};
12740
12741 // token: 'M'
12742 // padded: ['MM', 2]
12743 // ordinal: 'Mo'
12744 // callback: function () { this.month() + 1 }
12745 function addFormatToken (token, padded, ordinal, callback) {
12746 var func = callback;
12747 if (typeof callback === 'string') {
12748 func = function () {
12749 return this[callback]();
12750 };
12751 }
12752 if (token) {
12753 formatTokenFunctions[token] = func;
12754 }
12755 if (padded) {
12756 formatTokenFunctions[padded[0]] = function () {
12757 return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
12758 };
12759 }
12760 if (ordinal) {
12761 formatTokenFunctions[ordinal] = function () {
12762 return this.localeData().ordinal(func.apply(this, arguments), token);
12763 };
12764 }
12765 }
12766
12767 function removeFormattingTokens(input) {
12768 if (input.match(/\[[\s\S]/)) {
12769 return input.replace(/^\[|\]$/g, '');
12770 }
12771 return input.replace(/\\/g, '');
12772 }
12773
12774 function makeFormatFunction(format) {
12775 var array = format.match(formattingTokens), i, length;
12776
12777 for (i = 0, length = array.length; i < length; i++) {
12778 if (formatTokenFunctions[array[i]]) {
12779 array[i] = formatTokenFunctions[array[i]];
12780 } else {
12781 array[i] = removeFormattingTokens(array[i]);
12782 }
12783 }
12784
12785 return function (mom) {
12786 var output = '', i;
12787 for (i = 0; i < length; i++) {
12788 output += isFunction(array[i]) ? array[i].call(mom, format) : array[i];
12789 }
12790 return output;
12791 };
12792 }
12793
12794 // format date using native date object
12795 function formatMoment(m, format) {
12796 if (!m.isValid()) {
12797 return m.localeData().invalidDate();
12798 }
12799
12800 format = expandFormat(format, m.localeData());
12801 formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
12802
12803 return formatFunctions[format](m);
12804 }
12805
12806 function expandFormat(format, locale) {
12807 var i = 5;
12808
12809 function replaceLongDateFormatTokens(input) {
12810 return locale.longDateFormat(input) || input;
12811 }
12812
12813 localFormattingTokens.lastIndex = 0;
12814 while (i >= 0 && localFormattingTokens.test(format)) {
12815 format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
12816 localFormattingTokens.lastIndex = 0;
12817 i -= 1;
12818 }
12819
12820 return format;
12821 }
12822
12823 var match1 = /\d/; // 0 - 9
12824 var match2 = /\d\d/; // 00 - 99
12825 var match3 = /\d{3}/; // 000 - 999
12826 var match4 = /\d{4}/; // 0000 - 9999
12827 var match6 = /[+-]?\d{6}/; // -999999 - 999999
12828 var match1to2 = /\d\d?/; // 0 - 99
12829 var match3to4 = /\d\d\d\d?/; // 999 - 9999
12830 var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999
12831 var match1to3 = /\d{1,3}/; // 0 - 999
12832 var match1to4 = /\d{1,4}/; // 0 - 9999
12833 var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999
12834
12835 var matchUnsigned = /\d+/; // 0 - inf
12836 var matchSigned = /[+-]?\d+/; // -inf - inf
12837
12838 var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
12839 var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
12840
12841 var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
12842
12843 // any word (or two) characters or numbers including two/three word month in arabic.
12844 // includes scottish gaelic two word and hyphenated months
12845 var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;
12846
12847 var regexes = {};
12848
12849 function addRegexToken (token, regex, strictRegex) {
12850 regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
12851 return (isStrict && strictRegex) ? strictRegex : regex;
12852 };
12853 }
12854
12855 function getParseRegexForToken (token, config) {
12856 if (!hasOwnProp(regexes, token)) {
12857 return new RegExp(unescapeFormat(token));
12858 }
12859
12860 return regexes[token](config._strict, config._locale);
12861 }
12862
12863 // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
12864 function unescapeFormat(s) {
12865 return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
12866 return p1 || p2 || p3 || p4;
12867 }));
12868 }
12869
12870 function regexEscape(s) {
12871 return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
12872 }
12873
12874 var tokens = {};
12875
12876 function addParseToken (token, callback) {
12877 var i, func = callback;
12878 if (typeof token === 'string') {
12879 token = [token];
12880 }
12881 if (isNumber(callback)) {
12882 func = function (input, array) {
12883 array[callback] = toInt(input);
12884 };
12885 }
12886 for (i = 0; i < token.length; i++) {
12887 tokens[token[i]] = func;
12888 }
12889 }
12890
12891 function addWeekParseToken (token, callback) {
12892 addParseToken(token, function (input, array, config, token) {
12893 config._w = config._w || {};
12894 callback(input, config._w, config, token);
12895 });
12896 }
12897
12898 function addTimeToArrayFromToken(token, input, config) {
12899 if (input != null && hasOwnProp(tokens, token)) {
12900 tokens[token](input, config._a, config, token);
12901 }
12902 }
12903
12904 var YEAR = 0;
12905 var MONTH = 1;
12906 var DATE = 2;
12907 var HOUR = 3;
12908 var MINUTE = 4;
12909 var SECOND = 5;
12910 var MILLISECOND = 6;
12911 var WEEK = 7;
12912 var WEEKDAY = 8;
12913
12914 // FORMATTING
12915
12916 addFormatToken('Y', 0, 0, function () {
12917 var y = this.year();
12918 return y <= 9999 ? '' + y : '+' + y;
12919 });
12920
12921 addFormatToken(0, ['YY', 2], 0, function () {
12922 return this.year() % 100;
12923 });
12924
12925 addFormatToken(0, ['YYYY', 4], 0, 'year');
12926 addFormatToken(0, ['YYYYY', 5], 0, 'year');
12927 addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
12928
12929 // ALIASES
12930
12931 addUnitAlias('year', 'y');
12932
12933 // PRIORITIES
12934
12935 addUnitPriority('year', 1);
12936
12937 // PARSING
12938
12939 addRegexToken('Y', matchSigned);
12940 addRegexToken('YY', match1to2, match2);
12941 addRegexToken('YYYY', match1to4, match4);
12942 addRegexToken('YYYYY', match1to6, match6);
12943 addRegexToken('YYYYYY', match1to6, match6);
12944
12945 addParseToken(['YYYYY', 'YYYYYY'], YEAR);
12946 addParseToken('YYYY', function (input, array) {
12947 array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
12948 });
12949 addParseToken('YY', function (input, array) {
12950 array[YEAR] = hooks.parseTwoDigitYear(input);
12951 });
12952 addParseToken('Y', function (input, array) {
12953 array[YEAR] = parseInt(input, 10);
12954 });
12955
12956 // HELPERS
12957
12958 function daysInYear(year) {
12959 return isLeapYear(year) ? 366 : 365;
12960 }
12961
12962 function isLeapYear(year) {
12963 return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
12964 }
12965
12966 // HOOKS
12967
12968 hooks.parseTwoDigitYear = function (input) {
12969 return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
12970 };
12971
12972 // MOMENTS
12973
12974 var getSetYear = makeGetSet('FullYear', true);
12975
12976 function getIsLeapYear () {
12977 return isLeapYear(this.year());
12978 }
12979
12980 function makeGetSet (unit, keepTime) {
12981 return function (value) {
12982 if (value != null) {
12983 set$1(this, unit, value);
12984 hooks.updateOffset(this, keepTime);
12985 return this;
12986 } else {
12987 return get(this, unit);
12988 }
12989 };
12990 }
12991
12992 function get (mom, unit) {
12993 return mom.isValid() ?
12994 mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
12995 }
12996
12997 function set$1 (mom, unit, value) {
12998 if (mom.isValid() && !isNaN(value)) {
12999 if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) {
13000 mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month()));
13001 }
13002 else {
13003 mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
13004 }
13005 }
13006 }
13007
13008 // MOMENTS
13009
13010 function stringGet (units) {
13011 units = normalizeUnits(units);
13012 if (isFunction(this[units])) {
13013 return this[units]();
13014 }
13015 return this;
13016 }
13017
13018
13019 function stringSet (units, value) {
13020 if (typeof units === 'object') {
13021 units = normalizeObjectUnits(units);
13022 var prioritized = getPrioritizedUnits(units);
13023 for (var i = 0; i < prioritized.length; i++) {
13024 this[prioritized[i].unit](units[prioritized[i].unit]);
13025 }
13026 } else {
13027 units = normalizeUnits(units);
13028 if (isFunction(this[units])) {
13029 return this[units](value);
13030 }
13031 }
13032 return this;
13033 }
13034
13035 function mod(n, x) {
13036 return ((n % x) + x) % x;
13037 }
13038
13039 var indexOf;
13040
13041 if (Array.prototype.indexOf) {
13042 indexOf = Array.prototype.indexOf;
13043 } else {
13044 indexOf = function (o) {
13045 // I know
13046 var i;
13047 for (i = 0; i < this.length; ++i) {
13048 if (this[i] === o) {
13049 return i;
13050 }
13051 }
13052 return -1;
13053 };
13054 }
13055
13056 function daysInMonth(year, month) {
13057 if (isNaN(year) || isNaN(month)) {
13058 return NaN;
13059 }
13060 var modMonth = mod(month, 12);
13061 year += (month - modMonth) / 12;
13062 return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2);
13063 }
13064
13065 // FORMATTING
13066
13067 addFormatToken('M', ['MM', 2], 'Mo', function () {
13068 return this.month() + 1;
13069 });
13070
13071 addFormatToken('MMM', 0, 0, function (format) {
13072 return this.localeData().monthsShort(this, format);
13073 });
13074
13075 addFormatToken('MMMM', 0, 0, function (format) {
13076 return this.localeData().months(this, format);
13077 });
13078
13079 // ALIASES
13080
13081 addUnitAlias('month', 'M');
13082
13083 // PRIORITY
13084
13085 addUnitPriority('month', 8);
13086
13087 // PARSING
13088
13089 addRegexToken('M', match1to2);
13090 addRegexToken('MM', match1to2, match2);
13091 addRegexToken('MMM', function (isStrict, locale) {
13092 return locale.monthsShortRegex(isStrict);
13093 });
13094 addRegexToken('MMMM', function (isStrict, locale) {
13095 return locale.monthsRegex(isStrict);
13096 });
13097
13098 addParseToken(['M', 'MM'], function (input, array) {
13099 array[MONTH] = toInt(input) - 1;
13100 });
13101
13102 addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
13103 var month = config._locale.monthsParse(input, token, config._strict);
13104 // if we didn't find a month name, mark the date as invalid.
13105 if (month != null) {
13106 array[MONTH] = month;
13107 } else {
13108 getParsingFlags(config).invalidMonth = input;
13109 }
13110 });
13111
13112 // LOCALES
13113
13114 var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
13115 var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
13116 function localeMonths (m, format) {
13117 if (!m) {
13118 return isArray(this._months) ? this._months :
13119 this._months['standalone'];
13120 }
13121 return isArray(this._months) ? this._months[m.month()] :
13122 this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()];
13123 }
13124
13125 var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
13126 function localeMonthsShort (m, format) {
13127 if (!m) {
13128 return isArray(this._monthsShort) ? this._monthsShort :
13129 this._monthsShort['standalone'];
13130 }
13131 return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
13132 this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
13133 }
13134
13135 function handleStrictParse(monthName, format, strict) {
13136 var i, ii, mom, llc = monthName.toLocaleLowerCase();
13137 if (!this._monthsParse) {
13138 // this is not used
13139 this._monthsParse = [];
13140 this._longMonthsParse = [];
13141 this._shortMonthsParse = [];
13142 for (i = 0; i < 12; ++i) {
13143 mom = createUTC([2000, i]);
13144 this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
13145 this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
13146 }
13147 }
13148
13149 if (strict) {
13150 if (format === 'MMM') {
13151 ii = indexOf.call(this._shortMonthsParse, llc);
13152 return ii !== -1 ? ii : null;
13153 } else {
13154 ii = indexOf.call(this._longMonthsParse, llc);
13155 return ii !== -1 ? ii : null;
13156 }
13157 } else {
13158 if (format === 'MMM') {
13159 ii = indexOf.call(this._shortMonthsParse, llc);
13160 if (ii !== -1) {
13161 return ii;
13162 }
13163 ii = indexOf.call(this._longMonthsParse, llc);
13164 return ii !== -1 ? ii : null;
13165 } else {
13166 ii = indexOf.call(this._longMonthsParse, llc);
13167 if (ii !== -1) {
13168 return ii;
13169 }
13170 ii = indexOf.call(this._shortMonthsParse, llc);
13171 return ii !== -1 ? ii : null;
13172 }
13173 }
13174 }
13175
13176 function localeMonthsParse (monthName, format, strict) {
13177 var i, mom, regex;
13178
13179 if (this._monthsParseExact) {
13180 return handleStrictParse.call(this, monthName, format, strict);
13181 }
13182
13183 if (!this._monthsParse) {
13184 this._monthsParse = [];
13185 this._longMonthsParse = [];
13186 this._shortMonthsParse = [];
13187 }
13188
13189 // TODO: add sorting
13190 // Sorting makes sure if one month (or abbr) is a prefix of another
13191 // see sorting in computeMonthsParse
13192 for (i = 0; i < 12; i++) {
13193 // make the regex if we don't have it already
13194 mom = createUTC([2000, i]);
13195 if (strict && !this._longMonthsParse[i]) {
13196 this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
13197 this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
13198 }
13199 if (!strict && !this._monthsParse[i]) {
13200 regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
13201 this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
13202 }
13203 // test the regex
13204 if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
13205 return i;
13206 } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
13207 return i;
13208 } else if (!strict && this._monthsParse[i].test(monthName)) {
13209 return i;
13210 }
13211 }
13212 }
13213
13214 // MOMENTS
13215
13216 function setMonth (mom, value) {
13217 var dayOfMonth;
13218
13219 if (!mom.isValid()) {
13220 // No op
13221 return mom;
13222 }
13223
13224 if (typeof value === 'string') {
13225 if (/^\d+$/.test(value)) {
13226 value = toInt(value);
13227 } else {
13228 value = mom.localeData().monthsParse(value);
13229 // TODO: Another silent failure?
13230 if (!isNumber(value)) {
13231 return mom;
13232 }
13233 }
13234 }
13235
13236 dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
13237 mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
13238 return mom;
13239 }
13240
13241 function getSetMonth (value) {
13242 if (value != null) {
13243 setMonth(this, value);
13244 hooks.updateOffset(this, true);
13245 return this;
13246 } else {
13247 return get(this, 'Month');
13248 }
13249 }
13250
13251 function getDaysInMonth () {
13252 return daysInMonth(this.year(), this.month());
13253 }
13254
13255 var defaultMonthsShortRegex = matchWord;
13256 function monthsShortRegex (isStrict) {
13257 if (this._monthsParseExact) {
13258 if (!hasOwnProp(this, '_monthsRegex')) {
13259 computeMonthsParse.call(this);
13260 }
13261 if (isStrict) {
13262 return this._monthsShortStrictRegex;
13263 } else {
13264 return this._monthsShortRegex;
13265 }
13266 } else {
13267 if (!hasOwnProp(this, '_monthsShortRegex')) {
13268 this._monthsShortRegex = defaultMonthsShortRegex;
13269 }
13270 return this._monthsShortStrictRegex && isStrict ?
13271 this._monthsShortStrictRegex : this._monthsShortRegex;
13272 }
13273 }
13274
13275 var defaultMonthsRegex = matchWord;
13276 function monthsRegex (isStrict) {
13277 if (this._monthsParseExact) {
13278 if (!hasOwnProp(this, '_monthsRegex')) {
13279 computeMonthsParse.call(this);
13280 }
13281 if (isStrict) {
13282 return this._monthsStrictRegex;
13283 } else {
13284 return this._monthsRegex;
13285 }
13286 } else {
13287 if (!hasOwnProp(this, '_monthsRegex')) {
13288 this._monthsRegex = defaultMonthsRegex;
13289 }
13290 return this._monthsStrictRegex && isStrict ?
13291 this._monthsStrictRegex : this._monthsRegex;
13292 }
13293 }
13294
13295 function computeMonthsParse () {
13296 function cmpLenRev(a, b) {
13297 return b.length - a.length;
13298 }
13299
13300 var shortPieces = [], longPieces = [], mixedPieces = [],
13301 i, mom;
13302 for (i = 0; i < 12; i++) {
13303 // make the regex if we don't have it already
13304 mom = createUTC([2000, i]);
13305 shortPieces.push(this.monthsShort(mom, ''));
13306 longPieces.push(this.months(mom, ''));
13307 mixedPieces.push(this.months(mom, ''));
13308 mixedPieces.push(this.monthsShort(mom, ''));
13309 }
13310 // Sorting makes sure if one month (or abbr) is a prefix of another it
13311 // will match the longer piece.
13312 shortPieces.sort(cmpLenRev);
13313 longPieces.sort(cmpLenRev);
13314 mixedPieces.sort(cmpLenRev);
13315 for (i = 0; i < 12; i++) {
13316 shortPieces[i] = regexEscape(shortPieces[i]);
13317 longPieces[i] = regexEscape(longPieces[i]);
13318 }
13319 for (i = 0; i < 24; i++) {
13320 mixedPieces[i] = regexEscape(mixedPieces[i]);
13321 }
13322
13323 this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
13324 this._monthsShortRegex = this._monthsRegex;
13325 this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
13326 this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
13327 }
13328
13329 function createDate (y, m, d, h, M, s, ms) {
13330 // can't just apply() to create a date:
13331 // https://stackoverflow.com/q/181348
13332 var date = new Date(y, m, d, h, M, s, ms);
13333
13334 // the date constructor remaps years 0-99 to 1900-1999
13335 if (y < 100 && y >= 0 && isFinite(date.getFullYear())) {
13336 date.setFullYear(y);
13337 }
13338 return date;
13339 }
13340
13341 function createUTCDate (y) {
13342 var date = new Date(Date.UTC.apply(null, arguments));
13343
13344 // the Date.UTC function remaps years 0-99 to 1900-1999
13345 if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
13346 date.setUTCFullYear(y);
13347 }
13348 return date;
13349 }
13350
13351 // start-of-first-week - start-of-year
13352 function firstWeekOffset(year, dow, doy) {
13353 var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
13354 fwd = 7 + dow - doy,
13355 // first-week day local weekday -- which local weekday is fwd
13356 fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
13357
13358 return -fwdlw + fwd - 1;
13359 }
13360
13361 // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
13362 function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
13363 var localWeekday = (7 + weekday - dow) % 7,
13364 weekOffset = firstWeekOffset(year, dow, doy),
13365 dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
13366 resYear, resDayOfYear;
13367
13368 if (dayOfYear <= 0) {
13369 resYear = year - 1;
13370 resDayOfYear = daysInYear(resYear) + dayOfYear;
13371 } else if (dayOfYear > daysInYear(year)) {
13372 resYear = year + 1;
13373 resDayOfYear = dayOfYear - daysInYear(year);
13374 } else {
13375 resYear = year;
13376 resDayOfYear = dayOfYear;
13377 }
13378
13379 return {
13380 year: resYear,
13381 dayOfYear: resDayOfYear
13382 };
13383 }
13384
13385 function weekOfYear(mom, dow, doy) {
13386 var weekOffset = firstWeekOffset(mom.year(), dow, doy),
13387 week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
13388 resWeek, resYear;
13389
13390 if (week < 1) {
13391 resYear = mom.year() - 1;
13392 resWeek = week + weeksInYear(resYear, dow, doy);
13393 } else if (week > weeksInYear(mom.year(), dow, doy)) {
13394 resWeek = week - weeksInYear(mom.year(), dow, doy);
13395 resYear = mom.year() + 1;
13396 } else {
13397 resYear = mom.year();
13398 resWeek = week;
13399 }
13400
13401 return {
13402 week: resWeek,
13403 year: resYear
13404 };
13405 }
13406
13407 function weeksInYear(year, dow, doy) {
13408 var weekOffset = firstWeekOffset(year, dow, doy),
13409 weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
13410 return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
13411 }
13412
13413 // FORMATTING
13414
13415 addFormatToken('w', ['ww', 2], 'wo', 'week');
13416 addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
13417
13418 // ALIASES
13419
13420 addUnitAlias('week', 'w');
13421 addUnitAlias('isoWeek', 'W');
13422
13423 // PRIORITIES
13424
13425 addUnitPriority('week', 5);
13426 addUnitPriority('isoWeek', 5);
13427
13428 // PARSING
13429
13430 addRegexToken('w', match1to2);
13431 addRegexToken('ww', match1to2, match2);
13432 addRegexToken('W', match1to2);
13433 addRegexToken('WW', match1to2, match2);
13434
13435 addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
13436 week[token.substr(0, 1)] = toInt(input);
13437 });
13438
13439 // HELPERS
13440
13441 // LOCALES
13442
13443 function localeWeek (mom) {
13444 return weekOfYear(mom, this._week.dow, this._week.doy).week;
13445 }
13446
13447 var defaultLocaleWeek = {
13448 dow : 0, // Sunday is the first day of the week.
13449 doy : 6 // The week that contains Jan 6th is the first week of the year.
13450 };
13451
13452 function localeFirstDayOfWeek () {
13453 return this._week.dow;
13454 }
13455
13456 function localeFirstDayOfYear () {
13457 return this._week.doy;
13458 }
13459
13460 // MOMENTS
13461
13462 function getSetWeek (input) {
13463 var week = this.localeData().week(this);
13464 return input == null ? week : this.add((input - week) * 7, 'd');
13465 }
13466
13467 function getSetISOWeek (input) {
13468 var week = weekOfYear(this, 1, 4).week;
13469 return input == null ? week : this.add((input - week) * 7, 'd');
13470 }
13471
13472 // FORMATTING
13473
13474 addFormatToken('d', 0, 'do', 'day');
13475
13476 addFormatToken('dd', 0, 0, function (format) {
13477 return this.localeData().weekdaysMin(this, format);
13478 });
13479
13480 addFormatToken('ddd', 0, 0, function (format) {
13481 return this.localeData().weekdaysShort(this, format);
13482 });
13483
13484 addFormatToken('dddd', 0, 0, function (format) {
13485 return this.localeData().weekdays(this, format);
13486 });
13487
13488 addFormatToken('e', 0, 0, 'weekday');
13489 addFormatToken('E', 0, 0, 'isoWeekday');
13490
13491 // ALIASES
13492
13493 addUnitAlias('day', 'd');
13494 addUnitAlias('weekday', 'e');
13495 addUnitAlias('isoWeekday', 'E');
13496
13497 // PRIORITY
13498 addUnitPriority('day', 11);
13499 addUnitPriority('weekday', 11);
13500 addUnitPriority('isoWeekday', 11);
13501
13502 // PARSING
13503
13504 addRegexToken('d', match1to2);
13505 addRegexToken('e', match1to2);
13506 addRegexToken('E', match1to2);
13507 addRegexToken('dd', function (isStrict, locale) {
13508 return locale.weekdaysMinRegex(isStrict);
13509 });
13510 addRegexToken('ddd', function (isStrict, locale) {
13511 return locale.weekdaysShortRegex(isStrict);
13512 });
13513 addRegexToken('dddd', function (isStrict, locale) {
13514 return locale.weekdaysRegex(isStrict);
13515 });
13516
13517 addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
13518 var weekday = config._locale.weekdaysParse(input, token, config._strict);
13519 // if we didn't get a weekday name, mark the date as invalid
13520 if (weekday != null) {
13521 week.d = weekday;
13522 } else {
13523 getParsingFlags(config).invalidWeekday = input;
13524 }
13525 });
13526
13527 addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
13528 week[token] = toInt(input);
13529 });
13530
13531 // HELPERS
13532
13533 function parseWeekday(input, locale) {
13534 if (typeof input !== 'string') {
13535 return input;
13536 }
13537
13538 if (!isNaN(input)) {
13539 return parseInt(input, 10);
13540 }
13541
13542 input = locale.weekdaysParse(input);
13543 if (typeof input === 'number') {
13544 return input;
13545 }
13546
13547 return null;
13548 }
13549
13550 function parseIsoWeekday(input, locale) {
13551 if (typeof input === 'string') {
13552 return locale.weekdaysParse(input) % 7 || 7;
13553 }
13554 return isNaN(input) ? null : input;
13555 }
13556
13557 // LOCALES
13558
13559 var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
13560 function localeWeekdays (m, format) {
13561 if (!m) {
13562 return isArray(this._weekdays) ? this._weekdays :
13563 this._weekdays['standalone'];
13564 }
13565 return isArray(this._weekdays) ? this._weekdays[m.day()] :
13566 this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()];
13567 }
13568
13569 var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
13570 function localeWeekdaysShort (m) {
13571 return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort;
13572 }
13573
13574 var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
13575 function localeWeekdaysMin (m) {
13576 return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin;
13577 }
13578
13579 function handleStrictParse$1(weekdayName, format, strict) {
13580 var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
13581 if (!this._weekdaysParse) {
13582 this._weekdaysParse = [];
13583 this._shortWeekdaysParse = [];
13584 this._minWeekdaysParse = [];
13585
13586 for (i = 0; i < 7; ++i) {
13587 mom = createUTC([2000, 1]).day(i);
13588 this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
13589 this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
13590 this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
13591 }
13592 }
13593
13594 if (strict) {
13595 if (format === 'dddd') {
13596 ii = indexOf.call(this._weekdaysParse, llc);
13597 return ii !== -1 ? ii : null;
13598 } else if (format === 'ddd') {
13599 ii = indexOf.call(this._shortWeekdaysParse, llc);
13600 return ii !== -1 ? ii : null;
13601 } else {
13602 ii = indexOf.call(this._minWeekdaysParse, llc);
13603 return ii !== -1 ? ii : null;
13604 }
13605 } else {
13606 if (format === 'dddd') {
13607 ii = indexOf.call(this._weekdaysParse, llc);
13608 if (ii !== -1) {
13609 return ii;
13610 }
13611 ii = indexOf.call(this._shortWeekdaysParse, llc);
13612 if (ii !== -1) {
13613 return ii;
13614 }
13615 ii = indexOf.call(this._minWeekdaysParse, llc);
13616 return ii !== -1 ? ii : null;
13617 } else if (format === 'ddd') {
13618 ii = indexOf.call(this._shortWeekdaysParse, llc);
13619 if (ii !== -1) {
13620 return ii;
13621 }
13622 ii = indexOf.call(this._weekdaysParse, llc);
13623 if (ii !== -1) {
13624 return ii;
13625 }
13626 ii = indexOf.call(this._minWeekdaysParse, llc);
13627 return ii !== -1 ? ii : null;
13628 } else {
13629 ii = indexOf.call(this._minWeekdaysParse, llc);
13630 if (ii !== -1) {
13631 return ii;
13632 }
13633 ii = indexOf.call(this._weekdaysParse, llc);
13634 if (ii !== -1) {
13635 return ii;
13636 }
13637 ii = indexOf.call(this._shortWeekdaysParse, llc);
13638 return ii !== -1 ? ii : null;
13639 }
13640 }
13641 }
13642
13643 function localeWeekdaysParse (weekdayName, format, strict) {
13644 var i, mom, regex;
13645
13646 if (this._weekdaysParseExact) {
13647 return handleStrictParse$1.call(this, weekdayName, format, strict);
13648 }
13649
13650 if (!this._weekdaysParse) {
13651 this._weekdaysParse = [];
13652 this._minWeekdaysParse = [];
13653 this._shortWeekdaysParse = [];
13654 this._fullWeekdaysParse = [];
13655 }
13656
13657 for (i = 0; i < 7; i++) {
13658 // make the regex if we don't have it already
13659
13660 mom = createUTC([2000, 1]).day(i);
13661 if (strict && !this._fullWeekdaysParse[i]) {
13662 this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\\.?') + '$', 'i');
13663 this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$', 'i');
13664 this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$', 'i');
13665 }
13666 if (!this._weekdaysParse[i]) {
13667 regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
13668 this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
13669 }
13670 // test the regex
13671 if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
13672 return i;
13673 } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
13674 return i;
13675 } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
13676 return i;
13677 } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
13678 return i;
13679 }
13680 }
13681 }
13682
13683 // MOMENTS
13684
13685 function getSetDayOfWeek (input) {
13686 if (!this.isValid()) {
13687 return input != null ? this : NaN;
13688 }
13689 var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
13690 if (input != null) {
13691 input = parseWeekday(input, this.localeData());
13692 return this.add(input - day, 'd');
13693 } else {
13694 return day;
13695 }
13696 }
13697
13698 function getSetLocaleDayOfWeek (input) {
13699 if (!this.isValid()) {
13700 return input != null ? this : NaN;
13701 }
13702 var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
13703 return input == null ? weekday : this.add(input - weekday, 'd');
13704 }
13705
13706 function getSetISODayOfWeek (input) {
13707 if (!this.isValid()) {
13708 return input != null ? this : NaN;
13709 }
13710
13711 // behaves the same as moment#day except
13712 // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
13713 // as a setter, sunday should belong to the previous week.
13714
13715 if (input != null) {
13716 var weekday = parseIsoWeekday(input, this.localeData());
13717 return this.day(this.day() % 7 ? weekday : weekday - 7);
13718 } else {
13719 return this.day() || 7;
13720 }
13721 }
13722
13723 var defaultWeekdaysRegex = matchWord;
13724 function weekdaysRegex (isStrict) {
13725 if (this._weekdaysParseExact) {
13726 if (!hasOwnProp(this, '_weekdaysRegex')) {
13727 computeWeekdaysParse.call(this);
13728 }
13729 if (isStrict) {
13730 return this._weekdaysStrictRegex;
13731 } else {
13732 return this._weekdaysRegex;
13733 }
13734 } else {
13735 if (!hasOwnProp(this, '_weekdaysRegex')) {
13736 this._weekdaysRegex = defaultWeekdaysRegex;
13737 }
13738 return this._weekdaysStrictRegex && isStrict ?
13739 this._weekdaysStrictRegex : this._weekdaysRegex;
13740 }
13741 }
13742
13743 var defaultWeekdaysShortRegex = matchWord;
13744 function weekdaysShortRegex (isStrict) {
13745 if (this._weekdaysParseExact) {
13746 if (!hasOwnProp(this, '_weekdaysRegex')) {
13747 computeWeekdaysParse.call(this);
13748 }
13749 if (isStrict) {
13750 return this._weekdaysShortStrictRegex;
13751 } else {
13752 return this._weekdaysShortRegex;
13753 }
13754 } else {
13755 if (!hasOwnProp(this, '_weekdaysShortRegex')) {
13756 this._weekdaysShortRegex = defaultWeekdaysShortRegex;
13757 }
13758 return this._weekdaysShortStrictRegex && isStrict ?
13759 this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
13760 }
13761 }
13762
13763 var defaultWeekdaysMinRegex = matchWord;
13764 function weekdaysMinRegex (isStrict) {
13765 if (this._weekdaysParseExact) {
13766 if (!hasOwnProp(this, '_weekdaysRegex')) {
13767 computeWeekdaysParse.call(this);
13768 }
13769 if (isStrict) {
13770 return this._weekdaysMinStrictRegex;
13771 } else {
13772 return this._weekdaysMinRegex;
13773 }
13774 } else {
13775 if (!hasOwnProp(this, '_weekdaysMinRegex')) {
13776 this._weekdaysMinRegex = defaultWeekdaysMinRegex;
13777 }
13778 return this._weekdaysMinStrictRegex && isStrict ?
13779 this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
13780 }
13781 }
13782
13783
13784 function computeWeekdaysParse () {
13785 function cmpLenRev(a, b) {
13786 return b.length - a.length;
13787 }
13788
13789 var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
13790 i, mom, minp, shortp, longp;
13791 for (i = 0; i < 7; i++) {
13792 // make the regex if we don't have it already
13793 mom = createUTC([2000, 1]).day(i);
13794 minp = this.weekdaysMin(mom, '');
13795 shortp = this.weekdaysShort(mom, '');
13796 longp = this.weekdays(mom, '');
13797 minPieces.push(minp);
13798 shortPieces.push(shortp);
13799 longPieces.push(longp);
13800 mixedPieces.push(minp);
13801 mixedPieces.push(shortp);
13802 mixedPieces.push(longp);
13803 }
13804 // Sorting makes sure if one weekday (or abbr) is a prefix of another it
13805 // will match the longer piece.
13806 minPieces.sort(cmpLenRev);
13807 shortPieces.sort(cmpLenRev);
13808 longPieces.sort(cmpLenRev);
13809 mixedPieces.sort(cmpLenRev);
13810 for (i = 0; i < 7; i++) {
13811 shortPieces[i] = regexEscape(shortPieces[i]);
13812 longPieces[i] = regexEscape(longPieces[i]);
13813 mixedPieces[i] = regexEscape(mixedPieces[i]);
13814 }
13815
13816 this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
13817 this._weekdaysShortRegex = this._weekdaysRegex;
13818 this._weekdaysMinRegex = this._weekdaysRegex;
13819
13820 this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
13821 this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
13822 this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
13823 }
13824
13825 // FORMATTING
13826
13827 function hFormat() {
13828 return this.hours() % 12 || 12;
13829 }
13830
13831 function kFormat() {
13832 return this.hours() || 24;
13833 }
13834
13835 addFormatToken('H', ['HH', 2], 0, 'hour');
13836 addFormatToken('h', ['hh', 2], 0, hFormat);
13837 addFormatToken('k', ['kk', 2], 0, kFormat);
13838
13839 addFormatToken('hmm', 0, 0, function () {
13840 return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
13841 });
13842
13843 addFormatToken('hmmss', 0, 0, function () {
13844 return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
13845 zeroFill(this.seconds(), 2);
13846 });
13847
13848 addFormatToken('Hmm', 0, 0, function () {
13849 return '' + this.hours() + zeroFill(this.minutes(), 2);
13850 });
13851
13852 addFormatToken('Hmmss', 0, 0, function () {
13853 return '' + this.hours() + zeroFill(this.minutes(), 2) +
13854 zeroFill(this.seconds(), 2);
13855 });
13856
13857 function meridiem (token, lowercase) {
13858 addFormatToken(token, 0, 0, function () {
13859 return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
13860 });
13861 }
13862
13863 meridiem('a', true);
13864 meridiem('A', false);
13865
13866 // ALIASES
13867
13868 addUnitAlias('hour', 'h');
13869
13870 // PRIORITY
13871 addUnitPriority('hour', 13);
13872
13873 // PARSING
13874
13875 function matchMeridiem (isStrict, locale) {
13876 return locale._meridiemParse;
13877 }
13878
13879 addRegexToken('a', matchMeridiem);
13880 addRegexToken('A', matchMeridiem);
13881 addRegexToken('H', match1to2);
13882 addRegexToken('h', match1to2);
13883 addRegexToken('k', match1to2);
13884 addRegexToken('HH', match1to2, match2);
13885 addRegexToken('hh', match1to2, match2);
13886 addRegexToken('kk', match1to2, match2);
13887
13888 addRegexToken('hmm', match3to4);
13889 addRegexToken('hmmss', match5to6);
13890 addRegexToken('Hmm', match3to4);
13891 addRegexToken('Hmmss', match5to6);
13892
13893 addParseToken(['H', 'HH'], HOUR);
13894 addParseToken(['k', 'kk'], function (input, array, config) {
13895 var kInput = toInt(input);
13896 array[HOUR] = kInput === 24 ? 0 : kInput;
13897 });
13898 addParseToken(['a', 'A'], function (input, array, config) {
13899 config._isPm = config._locale.isPM(input);
13900 config._meridiem = input;
13901 });
13902 addParseToken(['h', 'hh'], function (input, array, config) {
13903 array[HOUR] = toInt(input);
13904 getParsingFlags(config).bigHour = true;
13905 });
13906 addParseToken('hmm', function (input, array, config) {
13907 var pos = input.length - 2;
13908 array[HOUR] = toInt(input.substr(0, pos));
13909 array[MINUTE] = toInt(input.substr(pos));
13910 getParsingFlags(config).bigHour = true;
13911 });
13912 addParseToken('hmmss', function (input, array, config) {
13913 var pos1 = input.length - 4;
13914 var pos2 = input.length - 2;
13915 array[HOUR] = toInt(input.substr(0, pos1));
13916 array[MINUTE] = toInt(input.substr(pos1, 2));
13917 array[SECOND] = toInt(input.substr(pos2));
13918 getParsingFlags(config).bigHour = true;
13919 });
13920 addParseToken('Hmm', function (input, array, config) {
13921 var pos = input.length - 2;
13922 array[HOUR] = toInt(input.substr(0, pos));
13923 array[MINUTE] = toInt(input.substr(pos));
13924 });
13925 addParseToken('Hmmss', function (input, array, config) {
13926 var pos1 = input.length - 4;
13927 var pos2 = input.length - 2;
13928 array[HOUR] = toInt(input.substr(0, pos1));
13929 array[MINUTE] = toInt(input.substr(pos1, 2));
13930 array[SECOND] = toInt(input.substr(pos2));
13931 });
13932
13933 // LOCALES
13934
13935 function localeIsPM (input) {
13936 // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
13937 // Using charAt should be more compatible.
13938 return ((input + '').toLowerCase().charAt(0) === 'p');
13939 }
13940
13941 var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
13942 function localeMeridiem (hours, minutes, isLower) {
13943 if (hours > 11) {
13944 return isLower ? 'pm' : 'PM';
13945 } else {
13946 return isLower ? 'am' : 'AM';
13947 }
13948 }
13949
13950
13951 // MOMENTS
13952
13953 // Setting the hour should keep the time, because the user explicitly
13954 // specified which hour they want. So trying to maintain the same hour (in
13955 // a new timezone) makes sense. Adding/subtracting hours does not follow
13956 // this rule.
13957 var getSetHour = makeGetSet('Hours', true);
13958
13959 var baseConfig = {
13960 calendar: defaultCalendar,
13961 longDateFormat: defaultLongDateFormat,
13962 invalidDate: defaultInvalidDate,
13963 ordinal: defaultOrdinal,
13964 dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
13965 relativeTime: defaultRelativeTime,
13966
13967 months: defaultLocaleMonths,
13968 monthsShort: defaultLocaleMonthsShort,
13969
13970 week: defaultLocaleWeek,
13971
13972 weekdays: defaultLocaleWeekdays,
13973 weekdaysMin: defaultLocaleWeekdaysMin,
13974 weekdaysShort: defaultLocaleWeekdaysShort,
13975
13976 meridiemParse: defaultLocaleMeridiemParse
13977 };
13978
13979 // internal storage for locale config files
13980 var locales = {};
13981 var localeFamilies = {};
13982 var globalLocale;
13983
13984 function normalizeLocale(key) {
13985 return key ? key.toLowerCase().replace('_', '-') : key;
13986 }
13987
13988 // pick the locale from the array
13989 // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
13990 // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
13991 function chooseLocale(names) {
13992 var i = 0, j, next, locale, split;
13993
13994 while (i < names.length) {
13995 split = normalizeLocale(names[i]).split('-');
13996 j = split.length;
13997 next = normalizeLocale(names[i + 1]);
13998 next = next ? next.split('-') : null;
13999 while (j > 0) {
14000 locale = loadLocale(split.slice(0, j).join('-'));
14001 if (locale) {
14002 return locale;
14003 }
14004 if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
14005 //the next array item is better than a shallower substring of this one
14006 break;
14007 }
14008 j--;
14009 }
14010 i++;
14011 }
14012 return globalLocale;
14013 }
14014
14015 function loadLocale(name) {
14016 var oldLocale = null;
14017 // TODO: Find a better way to register and load all the locales in Node
14018 if (!locales[name] && ('object' !== 'undefined') &&
14019 module && module.exports) {
14020 try {
14021 oldLocale = globalLocale._abbr;
14022 var aliasedRequire = commonjsRequire;
14023 aliasedRequire('./locale/' + name);
14024 getSetGlobalLocale(oldLocale);
14025 } catch (e) {}
14026 }
14027 return locales[name];
14028 }
14029
14030 // This function will load locale and then set the global locale. If
14031 // no arguments are passed in, it will simply return the current global
14032 // locale key.
14033 function getSetGlobalLocale (key, values) {
14034 var data;
14035 if (key) {
14036 if (isUndefined(values)) {
14037 data = getLocale(key);
14038 }
14039 else {
14040 data = defineLocale(key, values);
14041 }
14042
14043 if (data) {
14044 // moment.duration._locale = moment._locale = data;
14045 globalLocale = data;
14046 }
14047 else {
14048 if ((typeof console !== 'undefined') && console.warn) {
14049 //warn user if arguments are passed but the locale could not be set
14050 console.warn('Locale ' + key + ' not found. Did you forget to load it?');
14051 }
14052 }
14053 }
14054
14055 return globalLocale._abbr;
14056 }
14057
14058 function defineLocale (name, config) {
14059 if (config !== null) {
14060 var locale, parentConfig = baseConfig;
14061 config.abbr = name;
14062 if (locales[name] != null) {
14063 deprecateSimple('defineLocaleOverride',
14064 'use moment.updateLocale(localeName, config) to change ' +
14065 'an existing locale. moment.defineLocale(localeName, ' +
14066 'config) should only be used for creating a new locale ' +
14067 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
14068 parentConfig = locales[name]._config;
14069 } else if (config.parentLocale != null) {
14070 if (locales[config.parentLocale] != null) {
14071 parentConfig = locales[config.parentLocale]._config;
14072 } else {
14073 locale = loadLocale(config.parentLocale);
14074 if (locale != null) {
14075 parentConfig = locale._config;
14076 } else {
14077 if (!localeFamilies[config.parentLocale]) {
14078 localeFamilies[config.parentLocale] = [];
14079 }
14080 localeFamilies[config.parentLocale].push({
14081 name: name,
14082 config: config
14083 });
14084 return null;
14085 }
14086 }
14087 }
14088 locales[name] = new Locale(mergeConfigs(parentConfig, config));
14089
14090 if (localeFamilies[name]) {
14091 localeFamilies[name].forEach(function (x) {
14092 defineLocale(x.name, x.config);
14093 });
14094 }
14095
14096 // backwards compat for now: also set the locale
14097 // make sure we set the locale AFTER all child locales have been
14098 // created, so we won't end up with the child locale set.
14099 getSetGlobalLocale(name);
14100
14101
14102 return locales[name];
14103 } else {
14104 // useful for testing
14105 delete locales[name];
14106 return null;
14107 }
14108 }
14109
14110 function updateLocale(name, config) {
14111 if (config != null) {
14112 var locale, tmpLocale, parentConfig = baseConfig;
14113 // MERGE
14114 tmpLocale = loadLocale(name);
14115 if (tmpLocale != null) {
14116 parentConfig = tmpLocale._config;
14117 }
14118 config = mergeConfigs(parentConfig, config);
14119 locale = new Locale(config);
14120 locale.parentLocale = locales[name];
14121 locales[name] = locale;
14122
14123 // backwards compat for now: also set the locale
14124 getSetGlobalLocale(name);
14125 } else {
14126 // pass null for config to unupdate, useful for tests
14127 if (locales[name] != null) {
14128 if (locales[name].parentLocale != null) {
14129 locales[name] = locales[name].parentLocale;
14130 } else if (locales[name] != null) {
14131 delete locales[name];
14132 }
14133 }
14134 }
14135 return locales[name];
14136 }
14137
14138 // returns locale data
14139 function getLocale (key) {
14140 var locale;
14141
14142 if (key && key._locale && key._locale._abbr) {
14143 key = key._locale._abbr;
14144 }
14145
14146 if (!key) {
14147 return globalLocale;
14148 }
14149
14150 if (!isArray(key)) {
14151 //short-circuit everything else
14152 locale = loadLocale(key);
14153 if (locale) {
14154 return locale;
14155 }
14156 key = [key];
14157 }
14158
14159 return chooseLocale(key);
14160 }
14161
14162 function listLocales() {
14163 return keys(locales);
14164 }
14165
14166 function checkOverflow (m) {
14167 var overflow;
14168 var a = m._a;
14169
14170 if (a && getParsingFlags(m).overflow === -2) {
14171 overflow =
14172 a[MONTH] < 0 || a[MONTH] > 11 ? MONTH :
14173 a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
14174 a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
14175 a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE :
14176 a[SECOND] < 0 || a[SECOND] > 59 ? SECOND :
14177 a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
14178 -1;
14179
14180 if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
14181 overflow = DATE;
14182 }
14183 if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
14184 overflow = WEEK;
14185 }
14186 if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
14187 overflow = WEEKDAY;
14188 }
14189
14190 getParsingFlags(m).overflow = overflow;
14191 }
14192
14193 return m;
14194 }
14195
14196 // Pick the first defined of two or three arguments.
14197 function defaults(a, b, c) {
14198 if (a != null) {
14199 return a;
14200 }
14201 if (b != null) {
14202 return b;
14203 }
14204 return c;
14205 }
14206
14207 function currentDateArray(config) {
14208 // hooks is actually the exported moment object
14209 var nowValue = new Date(hooks.now());
14210 if (config._useUTC) {
14211 return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
14212 }
14213 return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
14214 }
14215
14216 // convert an array to a date.
14217 // the array should mirror the parameters below
14218 // note: all values past the year are optional and will default to the lowest possible value.
14219 // [year, month, day , hour, minute, second, millisecond]
14220 function configFromArray (config) {
14221 var i, date, input = [], currentDate, expectedWeekday, yearToUse;
14222
14223 if (config._d) {
14224 return;
14225 }
14226
14227 currentDate = currentDateArray(config);
14228
14229 //compute day of the year from weeks and weekdays
14230 if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
14231 dayOfYearFromWeekInfo(config);
14232 }
14233
14234 //if the day of the year is set, figure out what it is
14235 if (config._dayOfYear != null) {
14236 yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
14237
14238 if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) {
14239 getParsingFlags(config)._overflowDayOfYear = true;
14240 }
14241
14242 date = createUTCDate(yearToUse, 0, config._dayOfYear);
14243 config._a[MONTH] = date.getUTCMonth();
14244 config._a[DATE] = date.getUTCDate();
14245 }
14246
14247 // Default to current date.
14248 // * if no year, month, day of month are given, default to today
14249 // * if day of month is given, default month and year
14250 // * if month is given, default only year
14251 // * if year is given, don't default anything
14252 for (i = 0; i < 3 && config._a[i] == null; ++i) {
14253 config._a[i] = input[i] = currentDate[i];
14254 }
14255
14256 // Zero out whatever was not defaulted, including time
14257 for (; i < 7; i++) {
14258 config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
14259 }
14260
14261 // Check for 24:00:00.000
14262 if (config._a[HOUR] === 24 &&
14263 config._a[MINUTE] === 0 &&
14264 config._a[SECOND] === 0 &&
14265 config._a[MILLISECOND] === 0) {
14266 config._nextDay = true;
14267 config._a[HOUR] = 0;
14268 }
14269
14270 config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
14271 expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay();
14272
14273 // Apply timezone offset from input. The actual utcOffset can be changed
14274 // with parseZone.
14275 if (config._tzm != null) {
14276 config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
14277 }
14278
14279 if (config._nextDay) {
14280 config._a[HOUR] = 24;
14281 }
14282
14283 // check for mismatching day of week
14284 if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) {
14285 getParsingFlags(config).weekdayMismatch = true;
14286 }
14287 }
14288
14289 function dayOfYearFromWeekInfo(config) {
14290 var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
14291
14292 w = config._w;
14293 if (w.GG != null || w.W != null || w.E != null) {
14294 dow = 1;
14295 doy = 4;
14296
14297 // TODO: We need to take the current isoWeekYear, but that depends on
14298 // how we interpret now (local, utc, fixed offset). So create
14299 // a now version of current config (take local/utc/offset flags, and
14300 // create now).
14301 weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year);
14302 week = defaults(w.W, 1);
14303 weekday = defaults(w.E, 1);
14304 if (weekday < 1 || weekday > 7) {
14305 weekdayOverflow = true;
14306 }
14307 } else {
14308 dow = config._locale._week.dow;
14309 doy = config._locale._week.doy;
14310
14311 var curWeek = weekOfYear(createLocal(), dow, doy);
14312
14313 weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
14314
14315 // Default to current week.
14316 week = defaults(w.w, curWeek.week);
14317
14318 if (w.d != null) {
14319 // weekday -- low day numbers are considered next week
14320 weekday = w.d;
14321 if (weekday < 0 || weekday > 6) {
14322 weekdayOverflow = true;
14323 }
14324 } else if (w.e != null) {
14325 // local weekday -- counting starts from beginning of week
14326 weekday = w.e + dow;
14327 if (w.e < 0 || w.e > 6) {
14328 weekdayOverflow = true;
14329 }
14330 } else {
14331 // default to beginning of week
14332 weekday = dow;
14333 }
14334 }
14335 if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
14336 getParsingFlags(config)._overflowWeeks = true;
14337 } else if (weekdayOverflow != null) {
14338 getParsingFlags(config)._overflowWeekday = true;
14339 } else {
14340 temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
14341 config._a[YEAR] = temp.year;
14342 config._dayOfYear = temp.dayOfYear;
14343 }
14344 }
14345
14346 // iso 8601 regex
14347 // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
14348 var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
14349 var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
14350
14351 var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
14352
14353 var isoDates = [
14354 ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
14355 ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
14356 ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
14357 ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
14358 ['YYYY-DDD', /\d{4}-\d{3}/],
14359 ['YYYY-MM', /\d{4}-\d\d/, false],
14360 ['YYYYYYMMDD', /[+-]\d{10}/],
14361 ['YYYYMMDD', /\d{8}/],
14362 // YYYYMM is NOT allowed by the standard
14363 ['GGGG[W]WWE', /\d{4}W\d{3}/],
14364 ['GGGG[W]WW', /\d{4}W\d{2}/, false],
14365 ['YYYYDDD', /\d{7}/]
14366 ];
14367
14368 // iso time formats and regexes
14369 var isoTimes = [
14370 ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
14371 ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
14372 ['HH:mm:ss', /\d\d:\d\d:\d\d/],
14373 ['HH:mm', /\d\d:\d\d/],
14374 ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
14375 ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
14376 ['HHmmss', /\d\d\d\d\d\d/],
14377 ['HHmm', /\d\d\d\d/],
14378 ['HH', /\d\d/]
14379 ];
14380
14381 var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
14382
14383 // date from iso format
14384 function configFromISO(config) {
14385 var i, l,
14386 string = config._i,
14387 match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
14388 allowTime, dateFormat, timeFormat, tzFormat;
14389
14390 if (match) {
14391 getParsingFlags(config).iso = true;
14392
14393 for (i = 0, l = isoDates.length; i < l; i++) {
14394 if (isoDates[i][1].exec(match[1])) {
14395 dateFormat = isoDates[i][0];
14396 allowTime = isoDates[i][2] !== false;
14397 break;
14398 }
14399 }
14400 if (dateFormat == null) {
14401 config._isValid = false;
14402 return;
14403 }
14404 if (match[3]) {
14405 for (i = 0, l = isoTimes.length; i < l; i++) {
14406 if (isoTimes[i][1].exec(match[3])) {
14407 // match[2] should be 'T' or space
14408 timeFormat = (match[2] || ' ') + isoTimes[i][0];
14409 break;
14410 }
14411 }
14412 if (timeFormat == null) {
14413 config._isValid = false;
14414 return;
14415 }
14416 }
14417 if (!allowTime && timeFormat != null) {
14418 config._isValid = false;
14419 return;
14420 }
14421 if (match[4]) {
14422 if (tzRegex.exec(match[4])) {
14423 tzFormat = 'Z';
14424 } else {
14425 config._isValid = false;
14426 return;
14427 }
14428 }
14429 config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
14430 configFromStringAndFormat(config);
14431 } else {
14432 config._isValid = false;
14433 }
14434 }
14435
14436 // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
14437 var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
14438
14439 function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
14440 var result = [
14441 untruncateYear(yearStr),
14442 defaultLocaleMonthsShort.indexOf(monthStr),
14443 parseInt(dayStr, 10),
14444 parseInt(hourStr, 10),
14445 parseInt(minuteStr, 10)
14446 ];
14447
14448 if (secondStr) {
14449 result.push(parseInt(secondStr, 10));
14450 }
14451
14452 return result;
14453 }
14454
14455 function untruncateYear(yearStr) {
14456 var year = parseInt(yearStr, 10);
14457 if (year <= 49) {
14458 return 2000 + year;
14459 } else if (year <= 999) {
14460 return 1900 + year;
14461 }
14462 return year;
14463 }
14464
14465 function preprocessRFC2822(s) {
14466 // Remove comments and folding whitespace and replace multiple-spaces with a single space
14467 return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, '');
14468 }
14469
14470 function checkWeekday(weekdayStr, parsedInput, config) {
14471 if (weekdayStr) {
14472 // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
14473 var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
14474 weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay();
14475 if (weekdayProvided !== weekdayActual) {
14476 getParsingFlags(config).weekdayMismatch = true;
14477 config._isValid = false;
14478 return false;
14479 }
14480 }
14481 return true;
14482 }
14483
14484 var obsOffsets = {
14485 UT: 0,
14486 GMT: 0,
14487 EDT: -4 * 60,
14488 EST: -5 * 60,
14489 CDT: -5 * 60,
14490 CST: -6 * 60,
14491 MDT: -6 * 60,
14492 MST: -7 * 60,
14493 PDT: -7 * 60,
14494 PST: -8 * 60
14495 };
14496
14497 function calculateOffset(obsOffset, militaryOffset, numOffset) {
14498 if (obsOffset) {
14499 return obsOffsets[obsOffset];
14500 } else if (militaryOffset) {
14501 // the only allowed military tz is Z
14502 return 0;
14503 } else {
14504 var hm = parseInt(numOffset, 10);
14505 var m = hm % 100, h = (hm - m) / 100;
14506 return h * 60 + m;
14507 }
14508 }
14509
14510 // date and time from ref 2822 format
14511 function configFromRFC2822(config) {
14512 var match = rfc2822.exec(preprocessRFC2822(config._i));
14513 if (match) {
14514 var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]);
14515 if (!checkWeekday(match[1], parsedArray, config)) {
14516 return;
14517 }
14518
14519 config._a = parsedArray;
14520 config._tzm = calculateOffset(match[8], match[9], match[10]);
14521
14522 config._d = createUTCDate.apply(null, config._a);
14523 config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
14524
14525 getParsingFlags(config).rfc2822 = true;
14526 } else {
14527 config._isValid = false;
14528 }
14529 }
14530
14531 // date from iso format or fallback
14532 function configFromString(config) {
14533 var matched = aspNetJsonRegex.exec(config._i);
14534
14535 if (matched !== null) {
14536 config._d = new Date(+matched[1]);
14537 return;
14538 }
14539
14540 configFromISO(config);
14541 if (config._isValid === false) {
14542 delete config._isValid;
14543 } else {
14544 return;
14545 }
14546
14547 configFromRFC2822(config);
14548 if (config._isValid === false) {
14549 delete config._isValid;
14550 } else {
14551 return;
14552 }
14553
14554 // Final attempt, use Input Fallback
14555 hooks.createFromInputFallback(config);
14556 }
14557
14558 hooks.createFromInputFallback = deprecate(
14559 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
14560 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
14561 'discouraged and will be removed in an upcoming major release. Please refer to ' +
14562 'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
14563 function (config) {
14564 config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
14565 }
14566 );
14567
14568 // constant that refers to the ISO standard
14569 hooks.ISO_8601 = function () {};
14570
14571 // constant that refers to the RFC 2822 form
14572 hooks.RFC_2822 = function () {};
14573
14574 // date from string and format string
14575 function configFromStringAndFormat(config) {
14576 // TODO: Move this to another part of the creation flow to prevent circular deps
14577 if (config._f === hooks.ISO_8601) {
14578 configFromISO(config);
14579 return;
14580 }
14581 if (config._f === hooks.RFC_2822) {
14582 configFromRFC2822(config);
14583 return;
14584 }
14585 config._a = [];
14586 getParsingFlags(config).empty = true;
14587
14588 // This array is used to make a Date, either with `new Date` or `Date.UTC`
14589 var string = '' + config._i,
14590 i, parsedInput, tokens, token, skipped,
14591 stringLength = string.length,
14592 totalParsedInputLength = 0;
14593
14594 tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
14595
14596 for (i = 0; i < tokens.length; i++) {
14597 token = tokens[i];
14598 parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
14599 // console.log('token', token, 'parsedInput', parsedInput,
14600 // 'regex', getParseRegexForToken(token, config));
14601 if (parsedInput) {
14602 skipped = string.substr(0, string.indexOf(parsedInput));
14603 if (skipped.length > 0) {
14604 getParsingFlags(config).unusedInput.push(skipped);
14605 }
14606 string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
14607 totalParsedInputLength += parsedInput.length;
14608 }
14609 // don't parse if it's not a known token
14610 if (formatTokenFunctions[token]) {
14611 if (parsedInput) {
14612 getParsingFlags(config).empty = false;
14613 }
14614 else {
14615 getParsingFlags(config).unusedTokens.push(token);
14616 }
14617 addTimeToArrayFromToken(token, parsedInput, config);
14618 }
14619 else if (config._strict && !parsedInput) {
14620 getParsingFlags(config).unusedTokens.push(token);
14621 }
14622 }
14623
14624 // add remaining unparsed input length to the string
14625 getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
14626 if (string.length > 0) {
14627 getParsingFlags(config).unusedInput.push(string);
14628 }
14629
14630 // clear _12h flag if hour is <= 12
14631 if (config._a[HOUR] <= 12 &&
14632 getParsingFlags(config).bigHour === true &&
14633 config._a[HOUR] > 0) {
14634 getParsingFlags(config).bigHour = undefined;
14635 }
14636
14637 getParsingFlags(config).parsedDateParts = config._a.slice(0);
14638 getParsingFlags(config).meridiem = config._meridiem;
14639 // handle meridiem
14640 config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
14641
14642 configFromArray(config);
14643 checkOverflow(config);
14644 }
14645
14646
14647 function meridiemFixWrap (locale, hour, meridiem) {
14648 var isPm;
14649
14650 if (meridiem == null) {
14651 // nothing to do
14652 return hour;
14653 }
14654 if (locale.meridiemHour != null) {
14655 return locale.meridiemHour(hour, meridiem);
14656 } else if (locale.isPM != null) {
14657 // Fallback
14658 isPm = locale.isPM(meridiem);
14659 if (isPm && hour < 12) {
14660 hour += 12;
14661 }
14662 if (!isPm && hour === 12) {
14663 hour = 0;
14664 }
14665 return hour;
14666 } else {
14667 // this is not supposed to happen
14668 return hour;
14669 }
14670 }
14671
14672 // date from string and array of format strings
14673 function configFromStringAndArray(config) {
14674 var tempConfig,
14675 bestMoment,
14676
14677 scoreToBeat,
14678 i,
14679 currentScore;
14680
14681 if (config._f.length === 0) {
14682 getParsingFlags(config).invalidFormat = true;
14683 config._d = new Date(NaN);
14684 return;
14685 }
14686
14687 for (i = 0; i < config._f.length; i++) {
14688 currentScore = 0;
14689 tempConfig = copyConfig({}, config);
14690 if (config._useUTC != null) {
14691 tempConfig._useUTC = config._useUTC;
14692 }
14693 tempConfig._f = config._f[i];
14694 configFromStringAndFormat(tempConfig);
14695
14696 if (!isValid(tempConfig)) {
14697 continue;
14698 }
14699
14700 // if there is any input that was not parsed add a penalty for that format
14701 currentScore += getParsingFlags(tempConfig).charsLeftOver;
14702
14703 //or tokens
14704 currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
14705
14706 getParsingFlags(tempConfig).score = currentScore;
14707
14708 if (scoreToBeat == null || currentScore < scoreToBeat) {
14709 scoreToBeat = currentScore;
14710 bestMoment = tempConfig;
14711 }
14712 }
14713
14714 extend(config, bestMoment || tempConfig);
14715 }
14716
14717 function configFromObject(config) {
14718 if (config._d) {
14719 return;
14720 }
14721
14722 var i = normalizeObjectUnits(config._i);
14723 config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
14724 return obj && parseInt(obj, 10);
14725 });
14726
14727 configFromArray(config);
14728 }
14729
14730 function createFromConfig (config) {
14731 var res = new Moment(checkOverflow(prepareConfig(config)));
14732 if (res._nextDay) {
14733 // Adding is smart enough around DST
14734 res.add(1, 'd');
14735 res._nextDay = undefined;
14736 }
14737
14738 return res;
14739 }
14740
14741 function prepareConfig (config) {
14742 var input = config._i,
14743 format = config._f;
14744
14745 config._locale = config._locale || getLocale(config._l);
14746
14747 if (input === null || (format === undefined && input === '')) {
14748 return createInvalid({nullInput: true});
14749 }
14750
14751 if (typeof input === 'string') {
14752 config._i = input = config._locale.preparse(input);
14753 }
14754
14755 if (isMoment(input)) {
14756 return new Moment(checkOverflow(input));
14757 } else if (isDate(input)) {
14758 config._d = input;
14759 } else if (isArray(format)) {
14760 configFromStringAndArray(config);
14761 } else if (format) {
14762 configFromStringAndFormat(config);
14763 } else {
14764 configFromInput(config);
14765 }
14766
14767 if (!isValid(config)) {
14768 config._d = null;
14769 }
14770
14771 return config;
14772 }
14773
14774 function configFromInput(config) {
14775 var input = config._i;
14776 if (isUndefined(input)) {
14777 config._d = new Date(hooks.now());
14778 } else if (isDate(input)) {
14779 config._d = new Date(input.valueOf());
14780 } else if (typeof input === 'string') {
14781 configFromString(config);
14782 } else if (isArray(input)) {
14783 config._a = map(input.slice(0), function (obj) {
14784 return parseInt(obj, 10);
14785 });
14786 configFromArray(config);
14787 } else if (isObject(input)) {
14788 configFromObject(config);
14789 } else if (isNumber(input)) {
14790 // from milliseconds
14791 config._d = new Date(input);
14792 } else {
14793 hooks.createFromInputFallback(config);
14794 }
14795 }
14796
14797 function createLocalOrUTC (input, format, locale, strict, isUTC) {
14798 var c = {};
14799
14800 if (locale === true || locale === false) {
14801 strict = locale;
14802 locale = undefined;
14803 }
14804
14805 if ((isObject(input) && isObjectEmpty(input)) ||
14806 (isArray(input) && input.length === 0)) {
14807 input = undefined;
14808 }
14809 // object construction must be done this way.
14810 // https://github.com/moment/moment/issues/1423
14811 c._isAMomentObject = true;
14812 c._useUTC = c._isUTC = isUTC;
14813 c._l = locale;
14814 c._i = input;
14815 c._f = format;
14816 c._strict = strict;
14817
14818 return createFromConfig(c);
14819 }
14820
14821 function createLocal (input, format, locale, strict) {
14822 return createLocalOrUTC(input, format, locale, strict, false);
14823 }
14824
14825 var prototypeMin = deprecate(
14826 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
14827 function () {
14828 var other = createLocal.apply(null, arguments);
14829 if (this.isValid() && other.isValid()) {
14830 return other < this ? this : other;
14831 } else {
14832 return createInvalid();
14833 }
14834 }
14835 );
14836
14837 var prototypeMax = deprecate(
14838 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
14839 function () {
14840 var other = createLocal.apply(null, arguments);
14841 if (this.isValid() && other.isValid()) {
14842 return other > this ? this : other;
14843 } else {
14844 return createInvalid();
14845 }
14846 }
14847 );
14848
14849 // Pick a moment m from moments so that m[fn](other) is true for all
14850 // other. This relies on the function fn to be transitive.
14851 //
14852 // moments should either be an array of moment objects or an array, whose
14853 // first element is an array of moment objects.
14854 function pickBy(fn, moments) {
14855 var res, i;
14856 if (moments.length === 1 && isArray(moments[0])) {
14857 moments = moments[0];
14858 }
14859 if (!moments.length) {
14860 return createLocal();
14861 }
14862 res = moments[0];
14863 for (i = 1; i < moments.length; ++i) {
14864 if (!moments[i].isValid() || moments[i][fn](res)) {
14865 res = moments[i];
14866 }
14867 }
14868 return res;
14869 }
14870
14871 // TODO: Use [].sort instead?
14872 function min () {
14873 var args = [].slice.call(arguments, 0);
14874
14875 return pickBy('isBefore', args);
14876 }
14877
14878 function max () {
14879 var args = [].slice.call(arguments, 0);
14880
14881 return pickBy('isAfter', args);
14882 }
14883
14884 var now = function () {
14885 return Date.now ? Date.now() : +(new Date());
14886 };
14887
14888 var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond'];
14889
14890 function isDurationValid(m) {
14891 for (var key in m) {
14892 if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) {
14893 return false;
14894 }
14895 }
14896
14897 var unitHasDecimal = false;
14898 for (var i = 0; i < ordering.length; ++i) {
14899 if (m[ordering[i]]) {
14900 if (unitHasDecimal) {
14901 return false; // only allow non-integers for smallest unit
14902 }
14903 if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
14904 unitHasDecimal = true;
14905 }
14906 }
14907 }
14908
14909 return true;
14910 }
14911
14912 function isValid$1() {
14913 return this._isValid;
14914 }
14915
14916 function createInvalid$1() {
14917 return createDuration(NaN);
14918 }
14919
14920 function Duration (duration) {
14921 var normalizedInput = normalizeObjectUnits(duration),
14922 years = normalizedInput.year || 0,
14923 quarters = normalizedInput.quarter || 0,
14924 months = normalizedInput.month || 0,
14925 weeks = normalizedInput.week || normalizedInput.isoWeek || 0,
14926 days = normalizedInput.day || 0,
14927 hours = normalizedInput.hour || 0,
14928 minutes = normalizedInput.minute || 0,
14929 seconds = normalizedInput.second || 0,
14930 milliseconds = normalizedInput.millisecond || 0;
14931
14932 this._isValid = isDurationValid(normalizedInput);
14933
14934 // representation for dateAddRemove
14935 this._milliseconds = +milliseconds +
14936 seconds * 1e3 + // 1000
14937 minutes * 6e4 + // 1000 * 60
14938 hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
14939 // Because of dateAddRemove treats 24 hours as different from a
14940 // day when working around DST, we need to store them separately
14941 this._days = +days +
14942 weeks * 7;
14943 // It is impossible to translate months into days without knowing
14944 // which months you are are talking about, so we have to store
14945 // it separately.
14946 this._months = +months +
14947 quarters * 3 +
14948 years * 12;
14949
14950 this._data = {};
14951
14952 this._locale = getLocale();
14953
14954 this._bubble();
14955 }
14956
14957 function isDuration (obj) {
14958 return obj instanceof Duration;
14959 }
14960
14961 function absRound (number) {
14962 if (number < 0) {
14963 return Math.round(-1 * number) * -1;
14964 } else {
14965 return Math.round(number);
14966 }
14967 }
14968
14969 // FORMATTING
14970
14971 function offset (token, separator) {
14972 addFormatToken(token, 0, 0, function () {
14973 var offset = this.utcOffset();
14974 var sign = '+';
14975 if (offset < 0) {
14976 offset = -offset;
14977 sign = '-';
14978 }
14979 return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
14980 });
14981 }
14982
14983 offset('Z', ':');
14984 offset('ZZ', '');
14985
14986 // PARSING
14987
14988 addRegexToken('Z', matchShortOffset);
14989 addRegexToken('ZZ', matchShortOffset);
14990 addParseToken(['Z', 'ZZ'], function (input, array, config) {
14991 config._useUTC = true;
14992 config._tzm = offsetFromString(matchShortOffset, input);
14993 });
14994
14995 // HELPERS
14996
14997 // timezone chunker
14998 // '+10:00' > ['10', '00']
14999 // '-1530' > ['-15', '30']
15000 var chunkOffset = /([\+\-]|\d\d)/gi;
15001
15002 function offsetFromString(matcher, string) {
15003 var matches = (string || '').match(matcher);
15004
15005 if (matches === null) {
15006 return null;
15007 }
15008
15009 var chunk = matches[matches.length - 1] || [];
15010 var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
15011 var minutes = +(parts[1] * 60) + toInt(parts[2]);
15012
15013 return minutes === 0 ?
15014 0 :
15015 parts[0] === '+' ? minutes : -minutes;
15016 }
15017
15018 // Return a moment from input, that is local/utc/zone equivalent to model.
15019 function cloneWithOffset(input, model) {
15020 var res, diff;
15021 if (model._isUTC) {
15022 res = model.clone();
15023 diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf();
15024 // Use low-level api, because this fn is low-level api.
15025 res._d.setTime(res._d.valueOf() + diff);
15026 hooks.updateOffset(res, false);
15027 return res;
15028 } else {
15029 return createLocal(input).local();
15030 }
15031 }
15032
15033 function getDateOffset (m) {
15034 // On Firefox.24 Date#getTimezoneOffset returns a floating point.
15035 // https://github.com/moment/moment/pull/1871
15036 return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
15037 }
15038
15039 // HOOKS
15040
15041 // This function will be called whenever a moment is mutated.
15042 // It is intended to keep the offset in sync with the timezone.
15043 hooks.updateOffset = function () {};
15044
15045 // MOMENTS
15046
15047 // keepLocalTime = true means only change the timezone, without
15048 // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
15049 // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
15050 // +0200, so we adjust the time as needed, to be valid.
15051 //
15052 // Keeping the time actually adds/subtracts (one hour)
15053 // from the actual represented time. That is why we call updateOffset
15054 // a second time. In case it wants us to change the offset again
15055 // _changeInProgress == true case, then we have to adjust, because
15056 // there is no such time in the given timezone.
15057 function getSetOffset (input, keepLocalTime, keepMinutes) {
15058 var offset = this._offset || 0,
15059 localAdjust;
15060 if (!this.isValid()) {
15061 return input != null ? this : NaN;
15062 }
15063 if (input != null) {
15064 if (typeof input === 'string') {
15065 input = offsetFromString(matchShortOffset, input);
15066 if (input === null) {
15067 return this;
15068 }
15069 } else if (Math.abs(input) < 16 && !keepMinutes) {
15070 input = input * 60;
15071 }
15072 if (!this._isUTC && keepLocalTime) {
15073 localAdjust = getDateOffset(this);
15074 }
15075 this._offset = input;
15076 this._isUTC = true;
15077 if (localAdjust != null) {
15078 this.add(localAdjust, 'm');
15079 }
15080 if (offset !== input) {
15081 if (!keepLocalTime || this._changeInProgress) {
15082 addSubtract(this, createDuration(input - offset, 'm'), 1, false);
15083 } else if (!this._changeInProgress) {
15084 this._changeInProgress = true;
15085 hooks.updateOffset(this, true);
15086 this._changeInProgress = null;
15087 }
15088 }
15089 return this;
15090 } else {
15091 return this._isUTC ? offset : getDateOffset(this);
15092 }
15093 }
15094
15095 function getSetZone (input, keepLocalTime) {
15096 if (input != null) {
15097 if (typeof input !== 'string') {
15098 input = -input;
15099 }
15100
15101 this.utcOffset(input, keepLocalTime);
15102
15103 return this;
15104 } else {
15105 return -this.utcOffset();
15106 }
15107 }
15108
15109 function setOffsetToUTC (keepLocalTime) {
15110 return this.utcOffset(0, keepLocalTime);
15111 }
15112
15113 function setOffsetToLocal (keepLocalTime) {
15114 if (this._isUTC) {
15115 this.utcOffset(0, keepLocalTime);
15116 this._isUTC = false;
15117
15118 if (keepLocalTime) {
15119 this.subtract(getDateOffset(this), 'm');
15120 }
15121 }
15122 return this;
15123 }
15124
15125 function setOffsetToParsedOffset () {
15126 if (this._tzm != null) {
15127 this.utcOffset(this._tzm, false, true);
15128 } else if (typeof this._i === 'string') {
15129 var tZone = offsetFromString(matchOffset, this._i);
15130 if (tZone != null) {
15131 this.utcOffset(tZone);
15132 }
15133 else {
15134 this.utcOffset(0, true);
15135 }
15136 }
15137 return this;
15138 }
15139
15140 function hasAlignedHourOffset (input) {
15141 if (!this.isValid()) {
15142 return false;
15143 }
15144 input = input ? createLocal(input).utcOffset() : 0;
15145
15146 return (this.utcOffset() - input) % 60 === 0;
15147 }
15148
15149 function isDaylightSavingTime () {
15150 return (
15151 this.utcOffset() > this.clone().month(0).utcOffset() ||
15152 this.utcOffset() > this.clone().month(5).utcOffset()
15153 );
15154 }
15155
15156 function isDaylightSavingTimeShifted () {
15157 if (!isUndefined(this._isDSTShifted)) {
15158 return this._isDSTShifted;
15159 }
15160
15161 var c = {};
15162
15163 copyConfig(c, this);
15164 c = prepareConfig(c);
15165
15166 if (c._a) {
15167 var other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
15168 this._isDSTShifted = this.isValid() &&
15169 compareArrays(c._a, other.toArray()) > 0;
15170 } else {
15171 this._isDSTShifted = false;
15172 }
15173
15174 return this._isDSTShifted;
15175 }
15176
15177 function isLocal () {
15178 return this.isValid() ? !this._isUTC : false;
15179 }
15180
15181 function isUtcOffset () {
15182 return this.isValid() ? this._isUTC : false;
15183 }
15184
15185 function isUtc () {
15186 return this.isValid() ? this._isUTC && this._offset === 0 : false;
15187 }
15188
15189 // ASP.NET json date format regex
15190 var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/;
15191
15192 // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
15193 // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
15194 // and further modified to allow for strings containing both week and day
15195 var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
15196
15197 function createDuration (input, key) {
15198 var duration = input,
15199 // matching against regexp is expensive, do it on demand
15200 match = null,
15201 sign,
15202 ret,
15203 diffRes;
15204
15205 if (isDuration(input)) {
15206 duration = {
15207 ms : input._milliseconds,
15208 d : input._days,
15209 M : input._months
15210 };
15211 } else if (isNumber(input)) {
15212 duration = {};
15213 if (key) {
15214 duration[key] = input;
15215 } else {
15216 duration.milliseconds = input;
15217 }
15218 } else if (!!(match = aspNetRegex.exec(input))) {
15219 sign = (match[1] === '-') ? -1 : 1;
15220 duration = {
15221 y : 0,
15222 d : toInt(match[DATE]) * sign,
15223 h : toInt(match[HOUR]) * sign,
15224 m : toInt(match[MINUTE]) * sign,
15225 s : toInt(match[SECOND]) * sign,
15226 ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match
15227 };
15228 } else if (!!(match = isoRegex.exec(input))) {
15229 sign = (match[1] === '-') ? -1 : 1;
15230 duration = {
15231 y : parseIso(match[2], sign),
15232 M : parseIso(match[3], sign),
15233 w : parseIso(match[4], sign),
15234 d : parseIso(match[5], sign),
15235 h : parseIso(match[6], sign),
15236 m : parseIso(match[7], sign),
15237 s : parseIso(match[8], sign)
15238 };
15239 } else if (duration == null) {// checks for null or undefined
15240 duration = {};
15241 } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
15242 diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to));
15243
15244 duration = {};
15245 duration.ms = diffRes.milliseconds;
15246 duration.M = diffRes.months;
15247 }
15248
15249 ret = new Duration(duration);
15250
15251 if (isDuration(input) && hasOwnProp(input, '_locale')) {
15252 ret._locale = input._locale;
15253 }
15254
15255 return ret;
15256 }
15257
15258 createDuration.fn = Duration.prototype;
15259 createDuration.invalid = createInvalid$1;
15260
15261 function parseIso (inp, sign) {
15262 // We'd normally use ~~inp for this, but unfortunately it also
15263 // converts floats to ints.
15264 // inp may be undefined, so careful calling replace on it.
15265 var res = inp && parseFloat(inp.replace(',', '.'));
15266 // apply sign while we're at it
15267 return (isNaN(res) ? 0 : res) * sign;
15268 }
15269
15270 function positiveMomentsDifference(base, other) {
15271 var res = {milliseconds: 0, months: 0};
15272
15273 res.months = other.month() - base.month() +
15274 (other.year() - base.year()) * 12;
15275 if (base.clone().add(res.months, 'M').isAfter(other)) {
15276 --res.months;
15277 }
15278
15279 res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
15280
15281 return res;
15282 }
15283
15284 function momentsDifference(base, other) {
15285 var res;
15286 if (!(base.isValid() && other.isValid())) {
15287 return {milliseconds: 0, months: 0};
15288 }
15289
15290 other = cloneWithOffset(other, base);
15291 if (base.isBefore(other)) {
15292 res = positiveMomentsDifference(base, other);
15293 } else {
15294 res = positiveMomentsDifference(other, base);
15295 res.milliseconds = -res.milliseconds;
15296 res.months = -res.months;
15297 }
15298
15299 return res;
15300 }
15301
15302 // TODO: remove 'name' arg after deprecation is removed
15303 function createAdder(direction, name) {
15304 return function (val, period) {
15305 var dur, tmp;
15306 //invert the arguments, but complain about it
15307 if (period !== null && !isNaN(+period)) {
15308 deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' +
15309 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.');
15310 tmp = val; val = period; period = tmp;
15311 }
15312
15313 val = typeof val === 'string' ? +val : val;
15314 dur = createDuration(val, period);
15315 addSubtract(this, dur, direction);
15316 return this;
15317 };
15318 }
15319
15320 function addSubtract (mom, duration, isAdding, updateOffset) {
15321 var milliseconds = duration._milliseconds,
15322 days = absRound(duration._days),
15323 months = absRound(duration._months);
15324
15325 if (!mom.isValid()) {
15326 // No op
15327 return;
15328 }
15329
15330 updateOffset = updateOffset == null ? true : updateOffset;
15331
15332 if (months) {
15333 setMonth(mom, get(mom, 'Month') + months * isAdding);
15334 }
15335 if (days) {
15336 set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
15337 }
15338 if (milliseconds) {
15339 mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
15340 }
15341 if (updateOffset) {
15342 hooks.updateOffset(mom, days || months);
15343 }
15344 }
15345
15346 var add = createAdder(1, 'add');
15347 var subtract = createAdder(-1, 'subtract');
15348
15349 function getCalendarFormat(myMoment, now) {
15350 var diff = myMoment.diff(now, 'days', true);
15351 return diff < -6 ? 'sameElse' :
15352 diff < -1 ? 'lastWeek' :
15353 diff < 0 ? 'lastDay' :
15354 diff < 1 ? 'sameDay' :
15355 diff < 2 ? 'nextDay' :
15356 diff < 7 ? 'nextWeek' : 'sameElse';
15357 }
15358
15359 function calendar$1 (time, formats) {
15360 // We want to compare the start of today, vs this.
15361 // Getting start-of-today depends on whether we're local/utc/offset or not.
15362 var now = time || createLocal(),
15363 sod = cloneWithOffset(now, this).startOf('day'),
15364 format = hooks.calendarFormat(this, sod) || 'sameElse';
15365
15366 var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]);
15367
15368 return this.format(output || this.localeData().calendar(format, this, createLocal(now)));
15369 }
15370
15371 function clone () {
15372 return new Moment(this);
15373 }
15374
15375 function isAfter (input, units) {
15376 var localInput = isMoment(input) ? input : createLocal(input);
15377 if (!(this.isValid() && localInput.isValid())) {
15378 return false;
15379 }
15380 units = normalizeUnits(units) || 'millisecond';
15381 if (units === 'millisecond') {
15382 return this.valueOf() > localInput.valueOf();
15383 } else {
15384 return localInput.valueOf() < this.clone().startOf(units).valueOf();
15385 }
15386 }
15387
15388 function isBefore (input, units) {
15389 var localInput = isMoment(input) ? input : createLocal(input);
15390 if (!(this.isValid() && localInput.isValid())) {
15391 return false;
15392 }
15393 units = normalizeUnits(units) || 'millisecond';
15394 if (units === 'millisecond') {
15395 return this.valueOf() < localInput.valueOf();
15396 } else {
15397 return this.clone().endOf(units).valueOf() < localInput.valueOf();
15398 }
15399 }
15400
15401 function isBetween (from, to, units, inclusivity) {
15402 var localFrom = isMoment(from) ? from : createLocal(from),
15403 localTo = isMoment(to) ? to : createLocal(to);
15404 if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) {
15405 return false;
15406 }
15407 inclusivity = inclusivity || '()';
15408 return (inclusivity[0] === '(' ? this.isAfter(localFrom, units) : !this.isBefore(localFrom, units)) &&
15409 (inclusivity[1] === ')' ? this.isBefore(localTo, units) : !this.isAfter(localTo, units));
15410 }
15411
15412 function isSame (input, units) {
15413 var localInput = isMoment(input) ? input : createLocal(input),
15414 inputMs;
15415 if (!(this.isValid() && localInput.isValid())) {
15416 return false;
15417 }
15418 units = normalizeUnits(units) || 'millisecond';
15419 if (units === 'millisecond') {
15420 return this.valueOf() === localInput.valueOf();
15421 } else {
15422 inputMs = localInput.valueOf();
15423 return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
15424 }
15425 }
15426
15427 function isSameOrAfter (input, units) {
15428 return this.isSame(input, units) || this.isAfter(input, units);
15429 }
15430
15431 function isSameOrBefore (input, units) {
15432 return this.isSame(input, units) || this.isBefore(input, units);
15433 }
15434
15435 function diff (input, units, asFloat) {
15436 var that,
15437 zoneDelta,
15438 output;
15439
15440 if (!this.isValid()) {
15441 return NaN;
15442 }
15443
15444 that = cloneWithOffset(input, this);
15445
15446 if (!that.isValid()) {
15447 return NaN;
15448 }
15449
15450 zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
15451
15452 units = normalizeUnits(units);
15453
15454 switch (units) {
15455 case 'year': output = monthDiff(this, that) / 12; break;
15456 case 'month': output = monthDiff(this, that); break;
15457 case 'quarter': output = monthDiff(this, that) / 3; break;
15458 case 'second': output = (this - that) / 1e3; break; // 1000
15459 case 'minute': output = (this - that) / 6e4; break; // 1000 * 60
15460 case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60
15461 case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst
15462 case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst
15463 default: output = this - that;
15464 }
15465
15466 return asFloat ? output : absFloor(output);
15467 }
15468
15469 function monthDiff (a, b) {
15470 // difference in months
15471 var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
15472 // b is in (anchor - 1 month, anchor + 1 month)
15473 anchor = a.clone().add(wholeMonthDiff, 'months'),
15474 anchor2, adjust;
15475
15476 if (b - anchor < 0) {
15477 anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
15478 // linear across the month
15479 adjust = (b - anchor) / (anchor - anchor2);
15480 } else {
15481 anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
15482 // linear across the month
15483 adjust = (b - anchor) / (anchor2 - anchor);
15484 }
15485
15486 //check for negative zero, return zero if negative zero
15487 return -(wholeMonthDiff + adjust) || 0;
15488 }
15489
15490 hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
15491 hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
15492
15493 function toString () {
15494 return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
15495 }
15496
15497 function toISOString(keepOffset) {
15498 if (!this.isValid()) {
15499 return null;
15500 }
15501 var utc = keepOffset !== true;
15502 var m = utc ? this.clone().utc() : this;
15503 if (m.year() < 0 || m.year() > 9999) {
15504 return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ');
15505 }
15506 if (isFunction(Date.prototype.toISOString)) {
15507 // native implementation is ~50x faster, use it when we can
15508 if (utc) {
15509 return this.toDate().toISOString();
15510 } else {
15511 return new Date(this.valueOf() + this.utcOffset() * 60 * 1000).toISOString().replace('Z', formatMoment(m, 'Z'));
15512 }
15513 }
15514 return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ');
15515 }
15516
15517 /**
15518 * Return a human readable representation of a moment that can
15519 * also be evaluated to get a new moment which is the same
15520 *
15521 * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
15522 */
15523 function inspect () {
15524 if (!this.isValid()) {
15525 return 'moment.invalid(/* ' + this._i + ' */)';
15526 }
15527 var func = 'moment';
15528 var zone = '';
15529 if (!this.isLocal()) {
15530 func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
15531 zone = 'Z';
15532 }
15533 var prefix = '[' + func + '("]';
15534 var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY';
15535 var datetime = '-MM-DD[T]HH:mm:ss.SSS';
15536 var suffix = zone + '[")]';
15537
15538 return this.format(prefix + year + datetime + suffix);
15539 }
15540
15541 function format (inputString) {
15542 if (!inputString) {
15543 inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat;
15544 }
15545 var output = formatMoment(this, inputString);
15546 return this.localeData().postformat(output);
15547 }
15548
15549 function from (time, withoutSuffix) {
15550 if (this.isValid() &&
15551 ((isMoment(time) && time.isValid()) ||
15552 createLocal(time).isValid())) {
15553 return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
15554 } else {
15555 return this.localeData().invalidDate();
15556 }
15557 }
15558
15559 function fromNow (withoutSuffix) {
15560 return this.from(createLocal(), withoutSuffix);
15561 }
15562
15563 function to (time, withoutSuffix) {
15564 if (this.isValid() &&
15565 ((isMoment(time) && time.isValid()) ||
15566 createLocal(time).isValid())) {
15567 return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
15568 } else {
15569 return this.localeData().invalidDate();
15570 }
15571 }
15572
15573 function toNow (withoutSuffix) {
15574 return this.to(createLocal(), withoutSuffix);
15575 }
15576
15577 // If passed a locale key, it will set the locale for this
15578 // instance. Otherwise, it will return the locale configuration
15579 // variables for this instance.
15580 function locale (key) {
15581 var newLocaleData;
15582
15583 if (key === undefined) {
15584 return this._locale._abbr;
15585 } else {
15586 newLocaleData = getLocale(key);
15587 if (newLocaleData != null) {
15588 this._locale = newLocaleData;
15589 }
15590 return this;
15591 }
15592 }
15593
15594 var lang = deprecate(
15595 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
15596 function (key) {
15597 if (key === undefined) {
15598 return this.localeData();
15599 } else {
15600 return this.locale(key);
15601 }
15602 }
15603 );
15604
15605 function localeData () {
15606 return this._locale;
15607 }
15608
15609 function startOf (units) {
15610 units = normalizeUnits(units);
15611 // the following switch intentionally omits break keywords
15612 // to utilize falling through the cases.
15613 switch (units) {
15614 case 'year':
15615 this.month(0);
15616 /* falls through */
15617 case 'quarter':
15618 case 'month':
15619 this.date(1);
15620 /* falls through */
15621 case 'week':
15622 case 'isoWeek':
15623 case 'day':
15624 case 'date':
15625 this.hours(0);
15626 /* falls through */
15627 case 'hour':
15628 this.minutes(0);
15629 /* falls through */
15630 case 'minute':
15631 this.seconds(0);
15632 /* falls through */
15633 case 'second':
15634 this.milliseconds(0);
15635 }
15636
15637 // weeks are a special case
15638 if (units === 'week') {
15639 this.weekday(0);
15640 }
15641 if (units === 'isoWeek') {
15642 this.isoWeekday(1);
15643 }
15644
15645 // quarters are also special
15646 if (units === 'quarter') {
15647 this.month(Math.floor(this.month() / 3) * 3);
15648 }
15649
15650 return this;
15651 }
15652
15653 function endOf (units) {
15654 units = normalizeUnits(units);
15655 if (units === undefined || units === 'millisecond') {
15656 return this;
15657 }
15658
15659 // 'date' is an alias for 'day', so it should be considered as such.
15660 if (units === 'date') {
15661 units = 'day';
15662 }
15663
15664 return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
15665 }
15666
15667 function valueOf () {
15668 return this._d.valueOf() - ((this._offset || 0) * 60000);
15669 }
15670
15671 function unix () {
15672 return Math.floor(this.valueOf() / 1000);
15673 }
15674
15675 function toDate () {
15676 return new Date(this.valueOf());
15677 }
15678
15679 function toArray () {
15680 var m = this;
15681 return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
15682 }
15683
15684 function toObject () {
15685 var m = this;
15686 return {
15687 years: m.year(),
15688 months: m.month(),
15689 date: m.date(),
15690 hours: m.hours(),
15691 minutes: m.minutes(),
15692 seconds: m.seconds(),
15693 milliseconds: m.milliseconds()
15694 };
15695 }
15696
15697 function toJSON () {
15698 // new Date(NaN).toJSON() === null
15699 return this.isValid() ? this.toISOString() : null;
15700 }
15701
15702 function isValid$2 () {
15703 return isValid(this);
15704 }
15705
15706 function parsingFlags () {
15707 return extend({}, getParsingFlags(this));
15708 }
15709
15710 function invalidAt () {
15711 return getParsingFlags(this).overflow;
15712 }
15713
15714 function creationData() {
15715 return {
15716 input: this._i,
15717 format: this._f,
15718 locale: this._locale,
15719 isUTC: this._isUTC,
15720 strict: this._strict
15721 };
15722 }
15723
15724 // FORMATTING
15725
15726 addFormatToken(0, ['gg', 2], 0, function () {
15727 return this.weekYear() % 100;
15728 });
15729
15730 addFormatToken(0, ['GG', 2], 0, function () {
15731 return this.isoWeekYear() % 100;
15732 });
15733
15734 function addWeekYearFormatToken (token, getter) {
15735 addFormatToken(0, [token, token.length], 0, getter);
15736 }
15737
15738 addWeekYearFormatToken('gggg', 'weekYear');
15739 addWeekYearFormatToken('ggggg', 'weekYear');
15740 addWeekYearFormatToken('GGGG', 'isoWeekYear');
15741 addWeekYearFormatToken('GGGGG', 'isoWeekYear');
15742
15743 // ALIASES
15744
15745 addUnitAlias('weekYear', 'gg');
15746 addUnitAlias('isoWeekYear', 'GG');
15747
15748 // PRIORITY
15749
15750 addUnitPriority('weekYear', 1);
15751 addUnitPriority('isoWeekYear', 1);
15752
15753
15754 // PARSING
15755
15756 addRegexToken('G', matchSigned);
15757 addRegexToken('g', matchSigned);
15758 addRegexToken('GG', match1to2, match2);
15759 addRegexToken('gg', match1to2, match2);
15760 addRegexToken('GGGG', match1to4, match4);
15761 addRegexToken('gggg', match1to4, match4);
15762 addRegexToken('GGGGG', match1to6, match6);
15763 addRegexToken('ggggg', match1to6, match6);
15764
15765 addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
15766 week[token.substr(0, 2)] = toInt(input);
15767 });
15768
15769 addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
15770 week[token] = hooks.parseTwoDigitYear(input);
15771 });
15772
15773 // MOMENTS
15774
15775 function getSetWeekYear (input) {
15776 return getSetWeekYearHelper.call(this,
15777 input,
15778 this.week(),
15779 this.weekday(),
15780 this.localeData()._week.dow,
15781 this.localeData()._week.doy);
15782 }
15783
15784 function getSetISOWeekYear (input) {
15785 return getSetWeekYearHelper.call(this,
15786 input, this.isoWeek(), this.isoWeekday(), 1, 4);
15787 }
15788
15789 function getISOWeeksInYear () {
15790 return weeksInYear(this.year(), 1, 4);
15791 }
15792
15793 function getWeeksInYear () {
15794 var weekInfo = this.localeData()._week;
15795 return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
15796 }
15797
15798 function getSetWeekYearHelper(input, week, weekday, dow, doy) {
15799 var weeksTarget;
15800 if (input == null) {
15801 return weekOfYear(this, dow, doy).year;
15802 } else {
15803 weeksTarget = weeksInYear(input, dow, doy);
15804 if (week > weeksTarget) {
15805 week = weeksTarget;
15806 }
15807 return setWeekAll.call(this, input, week, weekday, dow, doy);
15808 }
15809 }
15810
15811 function setWeekAll(weekYear, week, weekday, dow, doy) {
15812 var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
15813 date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
15814
15815 this.year(date.getUTCFullYear());
15816 this.month(date.getUTCMonth());
15817 this.date(date.getUTCDate());
15818 return this;
15819 }
15820
15821 // FORMATTING
15822
15823 addFormatToken('Q', 0, 'Qo', 'quarter');
15824
15825 // ALIASES
15826
15827 addUnitAlias('quarter', 'Q');
15828
15829 // PRIORITY
15830
15831 addUnitPriority('quarter', 7);
15832
15833 // PARSING
15834
15835 addRegexToken('Q', match1);
15836 addParseToken('Q', function (input, array) {
15837 array[MONTH] = (toInt(input) - 1) * 3;
15838 });
15839
15840 // MOMENTS
15841
15842 function getSetQuarter (input) {
15843 return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
15844 }
15845
15846 // FORMATTING
15847
15848 addFormatToken('D', ['DD', 2], 'Do', 'date');
15849
15850 // ALIASES
15851
15852 addUnitAlias('date', 'D');
15853
15854 // PRIORITY
15855 addUnitPriority('date', 9);
15856
15857 // PARSING
15858
15859 addRegexToken('D', match1to2);
15860 addRegexToken('DD', match1to2, match2);
15861 addRegexToken('Do', function (isStrict, locale) {
15862 // TODO: Remove "ordinalParse" fallback in next major release.
15863 return isStrict ?
15864 (locale._dayOfMonthOrdinalParse || locale._ordinalParse) :
15865 locale._dayOfMonthOrdinalParseLenient;
15866 });
15867
15868 addParseToken(['D', 'DD'], DATE);
15869 addParseToken('Do', function (input, array) {
15870 array[DATE] = toInt(input.match(match1to2)[0]);
15871 });
15872
15873 // MOMENTS
15874
15875 var getSetDayOfMonth = makeGetSet('Date', true);
15876
15877 // FORMATTING
15878
15879 addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
15880
15881 // ALIASES
15882
15883 addUnitAlias('dayOfYear', 'DDD');
15884
15885 // PRIORITY
15886 addUnitPriority('dayOfYear', 4);
15887
15888 // PARSING
15889
15890 addRegexToken('DDD', match1to3);
15891 addRegexToken('DDDD', match3);
15892 addParseToken(['DDD', 'DDDD'], function (input, array, config) {
15893 config._dayOfYear = toInt(input);
15894 });
15895
15896 // HELPERS
15897
15898 // MOMENTS
15899
15900 function getSetDayOfYear (input) {
15901 var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
15902 return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
15903 }
15904
15905 // FORMATTING
15906
15907 addFormatToken('m', ['mm', 2], 0, 'minute');
15908
15909 // ALIASES
15910
15911 addUnitAlias('minute', 'm');
15912
15913 // PRIORITY
15914
15915 addUnitPriority('minute', 14);
15916
15917 // PARSING
15918
15919 addRegexToken('m', match1to2);
15920 addRegexToken('mm', match1to2, match2);
15921 addParseToken(['m', 'mm'], MINUTE);
15922
15923 // MOMENTS
15924
15925 var getSetMinute = makeGetSet('Minutes', false);
15926
15927 // FORMATTING
15928
15929 addFormatToken('s', ['ss', 2], 0, 'second');
15930
15931 // ALIASES
15932
15933 addUnitAlias('second', 's');
15934
15935 // PRIORITY
15936
15937 addUnitPriority('second', 15);
15938
15939 // PARSING
15940
15941 addRegexToken('s', match1to2);
15942 addRegexToken('ss', match1to2, match2);
15943 addParseToken(['s', 'ss'], SECOND);
15944
15945 // MOMENTS
15946
15947 var getSetSecond = makeGetSet('Seconds', false);
15948
15949 // FORMATTING
15950
15951 addFormatToken('S', 0, 0, function () {
15952 return ~~(this.millisecond() / 100);
15953 });
15954
15955 addFormatToken(0, ['SS', 2], 0, function () {
15956 return ~~(this.millisecond() / 10);
15957 });
15958
15959 addFormatToken(0, ['SSS', 3], 0, 'millisecond');
15960 addFormatToken(0, ['SSSS', 4], 0, function () {
15961 return this.millisecond() * 10;
15962 });
15963 addFormatToken(0, ['SSSSS', 5], 0, function () {
15964 return this.millisecond() * 100;
15965 });
15966 addFormatToken(0, ['SSSSSS', 6], 0, function () {
15967 return this.millisecond() * 1000;
15968 });
15969 addFormatToken(0, ['SSSSSSS', 7], 0, function () {
15970 return this.millisecond() * 10000;
15971 });
15972 addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
15973 return this.millisecond() * 100000;
15974 });
15975 addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
15976 return this.millisecond() * 1000000;
15977 });
15978
15979
15980 // ALIASES
15981
15982 addUnitAlias('millisecond', 'ms');
15983
15984 // PRIORITY
15985
15986 addUnitPriority('millisecond', 16);
15987
15988 // PARSING
15989
15990 addRegexToken('S', match1to3, match1);
15991 addRegexToken('SS', match1to3, match2);
15992 addRegexToken('SSS', match1to3, match3);
15993
15994 var token;
15995 for (token = 'SSSS'; token.length <= 9; token += 'S') {
15996 addRegexToken(token, matchUnsigned);
15997 }
15998
15999 function parseMs(input, array) {
16000 array[MILLISECOND] = toInt(('0.' + input) * 1000);
16001 }
16002
16003 for (token = 'S'; token.length <= 9; token += 'S') {
16004 addParseToken(token, parseMs);
16005 }
16006 // MOMENTS
16007
16008 var getSetMillisecond = makeGetSet('Milliseconds', false);
16009
16010 // FORMATTING
16011
16012 addFormatToken('z', 0, 0, 'zoneAbbr');
16013 addFormatToken('zz', 0, 0, 'zoneName');
16014
16015 // MOMENTS
16016
16017 function getZoneAbbr () {
16018 return this._isUTC ? 'UTC' : '';
16019 }
16020
16021 function getZoneName () {
16022 return this._isUTC ? 'Coordinated Universal Time' : '';
16023 }
16024
16025 var proto = Moment.prototype;
16026
16027 proto.add = add;
16028 proto.calendar = calendar$1;
16029 proto.clone = clone;
16030 proto.diff = diff;
16031 proto.endOf = endOf;
16032 proto.format = format;
16033 proto.from = from;
16034 proto.fromNow = fromNow;
16035 proto.to = to;
16036 proto.toNow = toNow;
16037 proto.get = stringGet;
16038 proto.invalidAt = invalidAt;
16039 proto.isAfter = isAfter;
16040 proto.isBefore = isBefore;
16041 proto.isBetween = isBetween;
16042 proto.isSame = isSame;
16043 proto.isSameOrAfter = isSameOrAfter;
16044 proto.isSameOrBefore = isSameOrBefore;
16045 proto.isValid = isValid$2;
16046 proto.lang = lang;
16047 proto.locale = locale;
16048 proto.localeData = localeData;
16049 proto.max = prototypeMax;
16050 proto.min = prototypeMin;
16051 proto.parsingFlags = parsingFlags;
16052 proto.set = stringSet;
16053 proto.startOf = startOf;
16054 proto.subtract = subtract;
16055 proto.toArray = toArray;
16056 proto.toObject = toObject;
16057 proto.toDate = toDate;
16058 proto.toISOString = toISOString;
16059 proto.inspect = inspect;
16060 proto.toJSON = toJSON;
16061 proto.toString = toString;
16062 proto.unix = unix;
16063 proto.valueOf = valueOf;
16064 proto.creationData = creationData;
16065 proto.year = getSetYear;
16066 proto.isLeapYear = getIsLeapYear;
16067 proto.weekYear = getSetWeekYear;
16068 proto.isoWeekYear = getSetISOWeekYear;
16069 proto.quarter = proto.quarters = getSetQuarter;
16070 proto.month = getSetMonth;
16071 proto.daysInMonth = getDaysInMonth;
16072 proto.week = proto.weeks = getSetWeek;
16073 proto.isoWeek = proto.isoWeeks = getSetISOWeek;
16074 proto.weeksInYear = getWeeksInYear;
16075 proto.isoWeeksInYear = getISOWeeksInYear;
16076 proto.date = getSetDayOfMonth;
16077 proto.day = proto.days = getSetDayOfWeek;
16078 proto.weekday = getSetLocaleDayOfWeek;
16079 proto.isoWeekday = getSetISODayOfWeek;
16080 proto.dayOfYear = getSetDayOfYear;
16081 proto.hour = proto.hours = getSetHour;
16082 proto.minute = proto.minutes = getSetMinute;
16083 proto.second = proto.seconds = getSetSecond;
16084 proto.millisecond = proto.milliseconds = getSetMillisecond;
16085 proto.utcOffset = getSetOffset;
16086 proto.utc = setOffsetToUTC;
16087 proto.local = setOffsetToLocal;
16088 proto.parseZone = setOffsetToParsedOffset;
16089 proto.hasAlignedHourOffset = hasAlignedHourOffset;
16090 proto.isDST = isDaylightSavingTime;
16091 proto.isLocal = isLocal;
16092 proto.isUtcOffset = isUtcOffset;
16093 proto.isUtc = isUtc;
16094 proto.isUTC = isUtc;
16095 proto.zoneAbbr = getZoneAbbr;
16096 proto.zoneName = getZoneName;
16097 proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
16098 proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
16099 proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear);
16100 proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone);
16101 proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted);
16102
16103 function createUnix (input) {
16104 return createLocal(input * 1000);
16105 }
16106
16107 function createInZone () {
16108 return createLocal.apply(null, arguments).parseZone();
16109 }
16110
16111 function preParsePostFormat (string) {
16112 return string;
16113 }
16114
16115 var proto$1 = Locale.prototype;
16116
16117 proto$1.calendar = calendar;
16118 proto$1.longDateFormat = longDateFormat;
16119 proto$1.invalidDate = invalidDate;
16120 proto$1.ordinal = ordinal;
16121 proto$1.preparse = preParsePostFormat;
16122 proto$1.postformat = preParsePostFormat;
16123 proto$1.relativeTime = relativeTime;
16124 proto$1.pastFuture = pastFuture;
16125 proto$1.set = set;
16126
16127 proto$1.months = localeMonths;
16128 proto$1.monthsShort = localeMonthsShort;
16129 proto$1.monthsParse = localeMonthsParse;
16130 proto$1.monthsRegex = monthsRegex;
16131 proto$1.monthsShortRegex = monthsShortRegex;
16132 proto$1.week = localeWeek;
16133 proto$1.firstDayOfYear = localeFirstDayOfYear;
16134 proto$1.firstDayOfWeek = localeFirstDayOfWeek;
16135
16136 proto$1.weekdays = localeWeekdays;
16137 proto$1.weekdaysMin = localeWeekdaysMin;
16138 proto$1.weekdaysShort = localeWeekdaysShort;
16139 proto$1.weekdaysParse = localeWeekdaysParse;
16140
16141 proto$1.weekdaysRegex = weekdaysRegex;
16142 proto$1.weekdaysShortRegex = weekdaysShortRegex;
16143 proto$1.weekdaysMinRegex = weekdaysMinRegex;
16144
16145 proto$1.isPM = localeIsPM;
16146 proto$1.meridiem = localeMeridiem;
16147
16148 function get$1 (format, index, field, setter) {
16149 var locale = getLocale();
16150 var utc = createUTC().set(setter, index);
16151 return locale[field](utc, format);
16152 }
16153
16154 function listMonthsImpl (format, index, field) {
16155 if (isNumber(format)) {
16156 index = format;
16157 format = undefined;
16158 }
16159
16160 format = format || '';
16161
16162 if (index != null) {
16163 return get$1(format, index, field, 'month');
16164 }
16165
16166 var i;
16167 var out = [];
16168 for (i = 0; i < 12; i++) {
16169 out[i] = get$1(format, i, field, 'month');
16170 }
16171 return out;
16172 }
16173
16174 // ()
16175 // (5)
16176 // (fmt, 5)
16177 // (fmt)
16178 // (true)
16179 // (true, 5)
16180 // (true, fmt, 5)
16181 // (true, fmt)
16182 function listWeekdaysImpl (localeSorted, format, index, field) {
16183 if (typeof localeSorted === 'boolean') {
16184 if (isNumber(format)) {
16185 index = format;
16186 format = undefined;
16187 }
16188
16189 format = format || '';
16190 } else {
16191 format = localeSorted;
16192 index = format;
16193 localeSorted = false;
16194
16195 if (isNumber(format)) {
16196 index = format;
16197 format = undefined;
16198 }
16199
16200 format = format || '';
16201 }
16202
16203 var locale = getLocale(),
16204 shift = localeSorted ? locale._week.dow : 0;
16205
16206 if (index != null) {
16207 return get$1(format, (index + shift) % 7, field, 'day');
16208 }
16209
16210 var i;
16211 var out = [];
16212 for (i = 0; i < 7; i++) {
16213 out[i] = get$1(format, (i + shift) % 7, field, 'day');
16214 }
16215 return out;
16216 }
16217
16218 function listMonths (format, index) {
16219 return listMonthsImpl(format, index, 'months');
16220 }
16221
16222 function listMonthsShort (format, index) {
16223 return listMonthsImpl(format, index, 'monthsShort');
16224 }
16225
16226 function listWeekdays (localeSorted, format, index) {
16227 return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
16228 }
16229
16230 function listWeekdaysShort (localeSorted, format, index) {
16231 return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
16232 }
16233
16234 function listWeekdaysMin (localeSorted, format, index) {
16235 return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
16236 }
16237
16238 getSetGlobalLocale('en', {
16239 dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
16240 ordinal : function (number) {
16241 var b = number % 10,
16242 output = (toInt(number % 100 / 10) === 1) ? 'th' :
16243 (b === 1) ? 'st' :
16244 (b === 2) ? 'nd' :
16245 (b === 3) ? 'rd' : 'th';
16246 return number + output;
16247 }
16248 });
16249
16250 // Side effect imports
16251
16252 hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale);
16253 hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale);
16254
16255 var mathAbs = Math.abs;
16256
16257 function abs () {
16258 var data = this._data;
16259
16260 this._milliseconds = mathAbs(this._milliseconds);
16261 this._days = mathAbs(this._days);
16262 this._months = mathAbs(this._months);
16263
16264 data.milliseconds = mathAbs(data.milliseconds);
16265 data.seconds = mathAbs(data.seconds);
16266 data.minutes = mathAbs(data.minutes);
16267 data.hours = mathAbs(data.hours);
16268 data.months = mathAbs(data.months);
16269 data.years = mathAbs(data.years);
16270
16271 return this;
16272 }
16273
16274 function addSubtract$1 (duration, input, value, direction) {
16275 var other = createDuration(input, value);
16276
16277 duration._milliseconds += direction * other._milliseconds;
16278 duration._days += direction * other._days;
16279 duration._months += direction * other._months;
16280
16281 return duration._bubble();
16282 }
16283
16284 // supports only 2.0-style add(1, 's') or add(duration)
16285 function add$1 (input, value) {
16286 return addSubtract$1(this, input, value, 1);
16287 }
16288
16289 // supports only 2.0-style subtract(1, 's') or subtract(duration)
16290 function subtract$1 (input, value) {
16291 return addSubtract$1(this, input, value, -1);
16292 }
16293
16294 function absCeil (number) {
16295 if (number < 0) {
16296 return Math.floor(number);
16297 } else {
16298 return Math.ceil(number);
16299 }
16300 }
16301
16302 function bubble () {
16303 var milliseconds = this._milliseconds;
16304 var days = this._days;
16305 var months = this._months;
16306 var data = this._data;
16307 var seconds, minutes, hours, years, monthsFromDays;
16308
16309 // if we have a mix of positive and negative values, bubble down first
16310 // check: https://github.com/moment/moment/issues/2166
16311 if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
16312 (milliseconds <= 0 && days <= 0 && months <= 0))) {
16313 milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
16314 days = 0;
16315 months = 0;
16316 }
16317
16318 // The following code bubbles up values, see the tests for
16319 // examples of what that means.
16320 data.milliseconds = milliseconds % 1000;
16321
16322 seconds = absFloor(milliseconds / 1000);
16323 data.seconds = seconds % 60;
16324
16325 minutes = absFloor(seconds / 60);
16326 data.minutes = minutes % 60;
16327
16328 hours = absFloor(minutes / 60);
16329 data.hours = hours % 24;
16330
16331 days += absFloor(hours / 24);
16332
16333 // convert days to months
16334 monthsFromDays = absFloor(daysToMonths(days));
16335 months += monthsFromDays;
16336 days -= absCeil(monthsToDays(monthsFromDays));
16337
16338 // 12 months -> 1 year
16339 years = absFloor(months / 12);
16340 months %= 12;
16341
16342 data.days = days;
16343 data.months = months;
16344 data.years = years;
16345
16346 return this;
16347 }
16348
16349 function daysToMonths (days) {
16350 // 400 years have 146097 days (taking into account leap year rules)
16351 // 400 years have 12 months === 4800
16352 return days * 4800 / 146097;
16353 }
16354
16355 function monthsToDays (months) {
16356 // the reverse of daysToMonths
16357 return months * 146097 / 4800;
16358 }
16359
16360 function as (units) {
16361 if (!this.isValid()) {
16362 return NaN;
16363 }
16364 var days;
16365 var months;
16366 var milliseconds = this._milliseconds;
16367
16368 units = normalizeUnits(units);
16369
16370 if (units === 'month' || units === 'year') {
16371 days = this._days + milliseconds / 864e5;
16372 months = this._months + daysToMonths(days);
16373 return units === 'month' ? months : months / 12;
16374 } else {
16375 // handle milliseconds separately because of floating point math errors (issue #1867)
16376 days = this._days + Math.round(monthsToDays(this._months));
16377 switch (units) {
16378 case 'week' : return days / 7 + milliseconds / 6048e5;
16379 case 'day' : return days + milliseconds / 864e5;
16380 case 'hour' : return days * 24 + milliseconds / 36e5;
16381 case 'minute' : return days * 1440 + milliseconds / 6e4;
16382 case 'second' : return days * 86400 + milliseconds / 1000;
16383 // Math.floor prevents floating point math errors here
16384 case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
16385 default: throw new Error('Unknown unit ' + units);
16386 }
16387 }
16388 }
16389
16390 // TODO: Use this.as('ms')?
16391 function valueOf$1 () {
16392 if (!this.isValid()) {
16393 return NaN;
16394 }
16395 return (
16396 this._milliseconds +
16397 this._days * 864e5 +
16398 (this._months % 12) * 2592e6 +
16399 toInt(this._months / 12) * 31536e6
16400 );
16401 }
16402
16403 function makeAs (alias) {
16404 return function () {
16405 return this.as(alias);
16406 };
16407 }
16408
16409 var asMilliseconds = makeAs('ms');
16410 var asSeconds = makeAs('s');
16411 var asMinutes = makeAs('m');
16412 var asHours = makeAs('h');
16413 var asDays = makeAs('d');
16414 var asWeeks = makeAs('w');
16415 var asMonths = makeAs('M');
16416 var asYears = makeAs('y');
16417
16418 function clone$1 () {
16419 return createDuration(this);
16420 }
16421
16422 function get$2 (units) {
16423 units = normalizeUnits(units);
16424 return this.isValid() ? this[units + 's']() : NaN;
16425 }
16426
16427 function makeGetter(name) {
16428 return function () {
16429 return this.isValid() ? this._data[name] : NaN;
16430 };
16431 }
16432
16433 var milliseconds = makeGetter('milliseconds');
16434 var seconds = makeGetter('seconds');
16435 var minutes = makeGetter('minutes');
16436 var hours = makeGetter('hours');
16437 var days = makeGetter('days');
16438 var months = makeGetter('months');
16439 var years = makeGetter('years');
16440
16441 function weeks () {
16442 return absFloor(this.days() / 7);
16443 }
16444
16445 var round = Math.round;
16446 var thresholds = {
16447 ss: 44, // a few seconds to seconds
16448 s : 45, // seconds to minute
16449 m : 45, // minutes to hour
16450 h : 22, // hours to day
16451 d : 26, // days to month
16452 M : 11 // months to year
16453 };
16454
16455 // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
16456 function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
16457 return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
16458 }
16459
16460 function relativeTime$1 (posNegDuration, withoutSuffix, locale) {
16461 var duration = createDuration(posNegDuration).abs();
16462 var seconds = round(duration.as('s'));
16463 var minutes = round(duration.as('m'));
16464 var hours = round(duration.as('h'));
16465 var days = round(duration.as('d'));
16466 var months = round(duration.as('M'));
16467 var years = round(duration.as('y'));
16468
16469 var a = seconds <= thresholds.ss && ['s', seconds] ||
16470 seconds < thresholds.s && ['ss', seconds] ||
16471 minutes <= 1 && ['m'] ||
16472 minutes < thresholds.m && ['mm', minutes] ||
16473 hours <= 1 && ['h'] ||
16474 hours < thresholds.h && ['hh', hours] ||
16475 days <= 1 && ['d'] ||
16476 days < thresholds.d && ['dd', days] ||
16477 months <= 1 && ['M'] ||
16478 months < thresholds.M && ['MM', months] ||
16479 years <= 1 && ['y'] || ['yy', years];
16480
16481 a[2] = withoutSuffix;
16482 a[3] = +posNegDuration > 0;
16483 a[4] = locale;
16484 return substituteTimeAgo.apply(null, a);
16485 }
16486
16487 // This function allows you to set the rounding function for relative time strings
16488 function getSetRelativeTimeRounding (roundingFunction) {
16489 if (roundingFunction === undefined) {
16490 return round;
16491 }
16492 if (typeof(roundingFunction) === 'function') {
16493 round = roundingFunction;
16494 return true;
16495 }
16496 return false;
16497 }
16498
16499 // This function allows you to set a threshold for relative time strings
16500 function getSetRelativeTimeThreshold (threshold, limit) {
16501 if (thresholds[threshold] === undefined) {
16502 return false;
16503 }
16504 if (limit === undefined) {
16505 return thresholds[threshold];
16506 }
16507 thresholds[threshold] = limit;
16508 if (threshold === 's') {
16509 thresholds.ss = limit - 1;
16510 }
16511 return true;
16512 }
16513
16514 function humanize (withSuffix) {
16515 if (!this.isValid()) {
16516 return this.localeData().invalidDate();
16517 }
16518
16519 var locale = this.localeData();
16520 var output = relativeTime$1(this, !withSuffix, locale);
16521
16522 if (withSuffix) {
16523 output = locale.pastFuture(+this, output);
16524 }
16525
16526 return locale.postformat(output);
16527 }
16528
16529 var abs$1 = Math.abs;
16530
16531 function sign(x) {
16532 return ((x > 0) - (x < 0)) || +x;
16533 }
16534
16535 function toISOString$1() {
16536 // for ISO strings we do not use the normal bubbling rules:
16537 // * milliseconds bubble up until they become hours
16538 // * days do not bubble at all
16539 // * months bubble up until they become years
16540 // This is because there is no context-free conversion between hours and days
16541 // (think of clock changes)
16542 // and also not between days and months (28-31 days per month)
16543 if (!this.isValid()) {
16544 return this.localeData().invalidDate();
16545 }
16546
16547 var seconds = abs$1(this._milliseconds) / 1000;
16548 var days = abs$1(this._days);
16549 var months = abs$1(this._months);
16550 var minutes, hours, years;
16551
16552 // 3600 seconds -> 60 minutes -> 1 hour
16553 minutes = absFloor(seconds / 60);
16554 hours = absFloor(minutes / 60);
16555 seconds %= 60;
16556 minutes %= 60;
16557
16558 // 12 months -> 1 year
16559 years = absFloor(months / 12);
16560 months %= 12;
16561
16562
16563 // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
16564 var Y = years;
16565 var M = months;
16566 var D = days;
16567 var h = hours;
16568 var m = minutes;
16569 var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
16570 var total = this.asSeconds();
16571
16572 if (!total) {
16573 // this is the same as C#'s (Noda) and python (isodate)...
16574 // but not other JS (goog.date)
16575 return 'P0D';
16576 }
16577
16578 var totalSign = total < 0 ? '-' : '';
16579 var ymSign = sign(this._months) !== sign(total) ? '-' : '';
16580 var daysSign = sign(this._days) !== sign(total) ? '-' : '';
16581 var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
16582
16583 return totalSign + 'P' +
16584 (Y ? ymSign + Y + 'Y' : '') +
16585 (M ? ymSign + M + 'M' : '') +
16586 (D ? daysSign + D + 'D' : '') +
16587 ((h || m || s) ? 'T' : '') +
16588 (h ? hmsSign + h + 'H' : '') +
16589 (m ? hmsSign + m + 'M' : '') +
16590 (s ? hmsSign + s + 'S' : '');
16591 }
16592
16593 var proto$2 = Duration.prototype;
16594
16595 proto$2.isValid = isValid$1;
16596 proto$2.abs = abs;
16597 proto$2.add = add$1;
16598 proto$2.subtract = subtract$1;
16599 proto$2.as = as;
16600 proto$2.asMilliseconds = asMilliseconds;
16601 proto$2.asSeconds = asSeconds;
16602 proto$2.asMinutes = asMinutes;
16603 proto$2.asHours = asHours;
16604 proto$2.asDays = asDays;
16605 proto$2.asWeeks = asWeeks;
16606 proto$2.asMonths = asMonths;
16607 proto$2.asYears = asYears;
16608 proto$2.valueOf = valueOf$1;
16609 proto$2._bubble = bubble;
16610 proto$2.clone = clone$1;
16611 proto$2.get = get$2;
16612 proto$2.milliseconds = milliseconds;
16613 proto$2.seconds = seconds;
16614 proto$2.minutes = minutes;
16615 proto$2.hours = hours;
16616 proto$2.days = days;
16617 proto$2.weeks = weeks;
16618 proto$2.months = months;
16619 proto$2.years = years;
16620 proto$2.humanize = humanize;
16621 proto$2.toISOString = toISOString$1;
16622 proto$2.toString = toISOString$1;
16623 proto$2.toJSON = toISOString$1;
16624 proto$2.locale = locale;
16625 proto$2.localeData = localeData;
16626
16627 proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1);
16628 proto$2.lang = lang;
16629
16630 // Side effect imports
16631
16632 // FORMATTING
16633
16634 addFormatToken('X', 0, 0, 'unix');
16635 addFormatToken('x', 0, 0, 'valueOf');
16636
16637 // PARSING
16638
16639 addRegexToken('x', matchSigned);
16640 addRegexToken('X', matchTimestamp);
16641 addParseToken('X', function (input, array, config) {
16642 config._d = new Date(parseFloat(input, 10) * 1000);
16643 });
16644 addParseToken('x', function (input, array, config) {
16645 config._d = new Date(toInt(input));
16646 });
16647
16648 // Side effect imports
16649
16650
16651 hooks.version = '2.23.0';
16652
16653 setHookCallback(createLocal);
16654
16655 hooks.fn = proto;
16656 hooks.min = min;
16657 hooks.max = max;
16658 hooks.now = now;
16659 hooks.utc = createUTC;
16660 hooks.unix = createUnix;
16661 hooks.months = listMonths;
16662 hooks.isDate = isDate;
16663 hooks.locale = getSetGlobalLocale;
16664 hooks.invalid = createInvalid;
16665 hooks.duration = createDuration;
16666 hooks.isMoment = isMoment;
16667 hooks.weekdays = listWeekdays;
16668 hooks.parseZone = createInZone;
16669 hooks.localeData = getLocale;
16670 hooks.isDuration = isDuration;
16671 hooks.monthsShort = listMonthsShort;
16672 hooks.weekdaysMin = listWeekdaysMin;
16673 hooks.defineLocale = defineLocale;
16674 hooks.updateLocale = updateLocale;
16675 hooks.locales = listLocales;
16676 hooks.weekdaysShort = listWeekdaysShort;
16677 hooks.normalizeUnits = normalizeUnits;
16678 hooks.relativeTimeRounding = getSetRelativeTimeRounding;
16679 hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
16680 hooks.calendarFormat = getCalendarFormat;
16681 hooks.prototype = proto;
16682
16683 // currently HTML5 input type only supports 24-hour formats
16684 hooks.HTML5_FMT = {
16685 DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
16686 DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
16687 DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
16688 DATE: 'YYYY-MM-DD', // <input type="date" />
16689 TIME: 'HH:mm', // <input type="time" />
16690 TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
16691 TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
16692 WEEK: 'GGGG-[W]WW', // <input type="week" />
16693 MONTH: 'YYYY-MM' // <input type="month" />
16694 };
16695
16696 return hooks;
16697
16698})));
16699});
16700
16701var valueOrDefault$a = helpers$1.valueOrDefault;
16702
16703// Integer constants are from the ES6 spec.
16704var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
16705var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
16706
16707var INTERVALS = {
16708 millisecond: {
16709 common: true,
16710 size: 1,
16711 steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
16712 },
16713 second: {
16714 common: true,
16715 size: 1000,
16716 steps: [1, 2, 5, 10, 15, 30]
16717 },
16718 minute: {
16719 common: true,
16720 size: 60000,
16721 steps: [1, 2, 5, 10, 15, 30]
16722 },
16723 hour: {
16724 common: true,
16725 size: 3600000,
16726 steps: [1, 2, 3, 6, 12]
16727 },
16728 day: {
16729 common: true,
16730 size: 86400000,
16731 steps: [1, 2, 5]
16732 },
16733 week: {
16734 common: false,
16735 size: 604800000,
16736 steps: [1, 2, 3, 4]
16737 },
16738 month: {
16739 common: true,
16740 size: 2.628e9,
16741 steps: [1, 2, 3]
16742 },
16743 quarter: {
16744 common: false,
16745 size: 7.884e9,
16746 steps: [1, 2, 3, 4]
16747 },
16748 year: {
16749 common: true,
16750 size: 3.154e10
16751 }
16752};
16753
16754var UNITS = Object.keys(INTERVALS);
16755
16756function sorter(a, b) {
16757 return a - b;
16758}
16759
16760function arrayUnique(items) {
16761 var hash = {};
16762 var out = [];
16763 var i, ilen, item;
16764
16765 for (i = 0, ilen = items.length; i < ilen; ++i) {
16766 item = items[i];
16767 if (!hash[item]) {
16768 hash[item] = true;
16769 out.push(item);
16770 }
16771 }
16772
16773 return out;
16774}
16775
16776/**
16777 * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
16778 * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
16779 * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
16780 * extremity (left + width or top + height). Note that it would be more optimized to directly
16781 * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
16782 * to create the lookup table. The table ALWAYS contains at least two items: min and max.
16783 *
16784 * @param {Number[]} timestamps - timestamps sorted from lowest to highest.
16785 * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min
16786 * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
16787 * If 'series', timestamps will be positioned at the same distance from each other. In this
16788 * case, only timestamps that break the time linearity are registered, meaning that in the
16789 * best case, all timestamps are linear, the table contains only min and max.
16790 */
16791function buildLookupTable(timestamps, min, max, distribution) {
16792 if (distribution === 'linear' || !timestamps.length) {
16793 return [
16794 {time: min, pos: 0},
16795 {time: max, pos: 1}
16796 ];
16797 }
16798
16799 var table = [];
16800 var items = [min];
16801 var i, ilen, prev, curr, next;
16802
16803 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
16804 curr = timestamps[i];
16805 if (curr > min && curr < max) {
16806 items.push(curr);
16807 }
16808 }
16809
16810 items.push(max);
16811
16812 for (i = 0, ilen = items.length; i < ilen; ++i) {
16813 next = items[i + 1];
16814 prev = items[i - 1];
16815 curr = items[i];
16816
16817 // only add points that breaks the scale linearity
16818 if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
16819 table.push({time: curr, pos: i / (ilen - 1)});
16820 }
16821 }
16822
16823 return table;
16824}
16825
16826// @see adapted from https://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
16827function lookup(table, key, value) {
16828 var lo = 0;
16829 var hi = table.length - 1;
16830 var mid, i0, i1;
16831
16832 while (lo >= 0 && lo <= hi) {
16833 mid = (lo + hi) >> 1;
16834 i0 = table[mid - 1] || null;
16835 i1 = table[mid];
16836
16837 if (!i0) {
16838 // given value is outside table (before first item)
16839 return {lo: null, hi: i1};
16840 } else if (i1[key] < value) {
16841 lo = mid + 1;
16842 } else if (i0[key] > value) {
16843 hi = mid - 1;
16844 } else {
16845 return {lo: i0, hi: i1};
16846 }
16847 }
16848
16849 // given value is outside table (after last item)
16850 return {lo: i1, hi: null};
16851}
16852
16853/**
16854 * Linearly interpolates the given source `value` using the table items `skey` values and
16855 * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
16856 * returns the position for a timestamp equal to 42. If value is out of bounds, values at
16857 * index [0, 1] or [n - 1, n] are used for the interpolation.
16858 */
16859function interpolate$1(table, skey, sval, tkey) {
16860 var range = lookup(table, skey, sval);
16861
16862 // Note: the lookup table ALWAYS contains at least 2 items (min and max)
16863 var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
16864 var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
16865
16866 var span = next[skey] - prev[skey];
16867 var ratio = span ? (sval - prev[skey]) / span : 0;
16868 var offset = (next[tkey] - prev[tkey]) * ratio;
16869
16870 return prev[tkey] + offset;
16871}
16872
16873/**
16874 * Convert the given value to a moment object using the given time options.
16875 * @see https://momentjs.com/docs/#/parsing/
16876 */
16877function momentify(value, options) {
16878 var parser = options.parser;
16879 var format = options.parser || options.format;
16880
16881 if (typeof parser === 'function') {
16882 return parser(value);
16883 }
16884
16885 if (typeof value === 'string' && typeof format === 'string') {
16886 return moment(value, format);
16887 }
16888
16889 if (!(value instanceof moment)) {
16890 value = moment(value);
16891 }
16892
16893 if (value.isValid()) {
16894 return value;
16895 }
16896
16897 // Labels are in an incompatible moment format and no `parser` has been provided.
16898 // The user might still use the deprecated `format` option to convert his inputs.
16899 if (typeof format === 'function') {
16900 return format(value);
16901 }
16902
16903 return value;
16904}
16905
16906function parse(input, scale) {
16907 if (helpers$1.isNullOrUndef(input)) {
16908 return null;
16909 }
16910
16911 var options = scale.options.time;
16912 var value = momentify(scale.getRightValue(input), options);
16913 if (!value.isValid()) {
16914 return null;
16915 }
16916
16917 if (options.round) {
16918 value.startOf(options.round);
16919 }
16920
16921 return value.valueOf();
16922}
16923
16924/**
16925 * Returns the number of unit to skip to be able to display up to `capacity` number of ticks
16926 * in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
16927 */
16928function determineStepSize(min, max, unit, capacity) {
16929 var range = max - min;
16930 var interval = INTERVALS[unit];
16931 var milliseconds = interval.size;
16932 var steps = interval.steps;
16933 var i, ilen, factor;
16934
16935 if (!steps) {
16936 return Math.ceil(range / (capacity * milliseconds));
16937 }
16938
16939 for (i = 0, ilen = steps.length; i < ilen; ++i) {
16940 factor = steps[i];
16941 if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
16942 break;
16943 }
16944 }
16945
16946 return factor;
16947}
16948
16949/**
16950 * Figures out what unit results in an appropriate number of auto-generated ticks
16951 */
16952function determineUnitForAutoTicks(minUnit, min, max, capacity) {
16953 var ilen = UNITS.length;
16954 var i, interval, factor;
16955
16956 for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
16957 interval = INTERVALS[UNITS[i]];
16958 factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER;
16959
16960 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
16961 return UNITS[i];
16962 }
16963 }
16964
16965 return UNITS[ilen - 1];
16966}
16967
16968/**
16969 * Figures out what unit to format a set of ticks with
16970 */
16971function determineUnitForFormatting(ticks, minUnit, min, max) {
16972 var duration = moment.duration(moment(max).diff(moment(min)));
16973 var ilen = UNITS.length;
16974 var i, unit;
16975
16976 for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) {
16977 unit = UNITS[i];
16978 if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) {
16979 return unit;
16980 }
16981 }
16982
16983 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
16984}
16985
16986function determineMajorUnit(unit) {
16987 for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
16988 if (INTERVALS[UNITS[i]].common) {
16989 return UNITS[i];
16990 }
16991 }
16992}
16993
16994/**
16995 * Generates a maximum of `capacity` timestamps between min and max, rounded to the
16996 * `minor` unit, aligned on the `major` unit and using the given scale time `options`.
16997 * Important: this method can return ticks outside the min and max range, it's the
16998 * responsibility of the calling code to clamp values if needed.
16999 */
17000function generate(min, max, capacity, options) {
17001 var timeOpts = options.time;
17002 var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
17003 var major = determineMajorUnit(minor);
17004 var stepSize = valueOrDefault$a(timeOpts.stepSize, timeOpts.unitStepSize);
17005 var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
17006 var majorTicksEnabled = options.ticks.major.enabled;
17007 var interval = INTERVALS[minor];
17008 var first = moment(min);
17009 var last = moment(max);
17010 var ticks = [];
17011 var time;
17012
17013 if (!stepSize) {
17014 stepSize = determineStepSize(min, max, minor, capacity);
17015 }
17016
17017 // For 'week' unit, handle the first day of week option
17018 if (weekday) {
17019 first = first.isoWeekday(weekday);
17020 last = last.isoWeekday(weekday);
17021 }
17022
17023 // Align first/last ticks on unit
17024 first = first.startOf(weekday ? 'day' : minor);
17025 last = last.startOf(weekday ? 'day' : minor);
17026
17027 // Make sure that the last tick include max
17028 if (last < max) {
17029 last.add(1, minor);
17030 }
17031
17032 time = moment(first);
17033
17034 if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
17035 // Align the first tick on the previous `minor` unit aligned on the `major` unit:
17036 // we first aligned time on the previous `major` unit then add the number of full
17037 // stepSize there is between first and the previous major time.
17038 time.startOf(major);
17039 time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
17040 }
17041
17042 for (; time < last; time.add(stepSize, minor)) {
17043 ticks.push(+time);
17044 }
17045
17046 ticks.push(+time);
17047
17048 return ticks;
17049}
17050
17051/**
17052 * Returns the start and end offsets from edges in the form of {start, end}
17053 * where each value is a relative width to the scale and ranges between 0 and 1.
17054 * They add extra margins on the both sides by scaling down the original scale.
17055 * Offsets are added when the `offset` option is true.
17056 */
17057function computeOffsets(table, ticks, min, max, options) {
17058 var start = 0;
17059 var end = 0;
17060 var first, last;
17061
17062 if (options.offset && ticks.length) {
17063 if (!options.time.min) {
17064 first = interpolate$1(table, 'time', ticks[0], 'pos');
17065 if (ticks.length === 1) {
17066 start = 1 - first;
17067 } else {
17068 start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2;
17069 }
17070 }
17071 if (!options.time.max) {
17072 last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos');
17073 if (ticks.length === 1) {
17074 end = last;
17075 } else {
17076 end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2;
17077 }
17078 }
17079 }
17080
17081 return {start: start, end: end};
17082}
17083
17084function ticksFromTimestamps(values, majorUnit) {
17085 var ticks = [];
17086 var i, ilen, value, major;
17087
17088 for (i = 0, ilen = values.length; i < ilen; ++i) {
17089 value = values[i];
17090 major = majorUnit ? value === +moment(value).startOf(majorUnit) : false;
17091
17092 ticks.push({
17093 value: value,
17094 major: major
17095 });
17096 }
17097
17098 return ticks;
17099}
17100
17101function determineLabelFormat(data, timeOpts) {
17102 var i, momentDate, hasTime;
17103 var ilen = data.length;
17104
17105 // find the label with the most parts (milliseconds, minutes, etc.)
17106 // format all labels with the same level of detail as the most specific label
17107 for (i = 0; i < ilen; i++) {
17108 momentDate = momentify(data[i], timeOpts);
17109 if (momentDate.millisecond() !== 0) {
17110 return 'MMM D, YYYY h:mm:ss.SSS a';
17111 }
17112 if (momentDate.second() !== 0 || momentDate.minute() !== 0 || momentDate.hour() !== 0) {
17113 hasTime = true;
17114 }
17115 }
17116 if (hasTime) {
17117 return 'MMM D, YYYY h:mm:ss a';
17118 }
17119 return 'MMM D, YYYY';
17120}
17121
17122var defaultConfig$4 = {
17123 position: 'bottom',
17124
17125 /**
17126 * Data distribution along the scale:
17127 * - 'linear': data are spread according to their time (distances can vary),
17128 * - 'series': data are spread at the same distance from each other.
17129 * @see https://github.com/chartjs/Chart.js/pull/4507
17130 * @since 2.7.0
17131 */
17132 distribution: 'linear',
17133
17134 /**
17135 * Scale boundary strategy (bypassed by min/max time options)
17136 * - `data`: make sure data are fully visible, ticks outside are removed
17137 * - `ticks`: make sure ticks are fully visible, data outside are truncated
17138 * @see https://github.com/chartjs/Chart.js/pull/4556
17139 * @since 2.7.0
17140 */
17141 bounds: 'data',
17142
17143 time: {
17144 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
17145 format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from https://momentjs.com/docs/#/parsing/string-format/
17146 unit: false, // false == automatic or override with week, month, year, etc.
17147 round: false, // none, or override with week, month, year, etc.
17148 displayFormat: false, // DEPRECATED
17149 isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/
17150 minUnit: 'millisecond',
17151
17152 // defaults to unit's corresponding unitFormat below or override using pattern string from https://momentjs.com/docs/#/displaying/format/
17153 displayFormats: {
17154 millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
17155 second: 'h:mm:ss a', // 11:20:01 AM
17156 minute: 'h:mm a', // 11:20 AM
17157 hour: 'hA', // 5PM
17158 day: 'MMM D', // Sep 4
17159 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
17160 month: 'MMM YYYY', // Sept 2015
17161 quarter: '[Q]Q - YYYY', // Q3
17162 year: 'YYYY' // 2015
17163 },
17164 },
17165 ticks: {
17166 autoSkip: false,
17167
17168 /**
17169 * Ticks generation input values:
17170 * - 'auto': generates "optimal" ticks based on scale size and time options.
17171 * - 'data': generates ticks from data (including labels from data {t|x|y} objects).
17172 * - 'labels': generates ticks from user given `data.labels` values ONLY.
17173 * @see https://github.com/chartjs/Chart.js/pull/4507
17174 * @since 2.7.0
17175 */
17176 source: 'auto',
17177
17178 major: {
17179 enabled: false
17180 }
17181 }
17182};
17183
17184var scale_time = core_scale.extend({
17185 initialize: function() {
17186 if (!moment) {
17187 throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
17188 }
17189
17190 this.mergeTicksOptions();
17191
17192 core_scale.prototype.initialize.call(this);
17193 },
17194
17195 update: function() {
17196 var me = this;
17197 var options = me.options;
17198
17199 // DEPRECATIONS: output a message only one time per update
17200 if (options.time && options.time.format) {
17201 console.warn('options.time.format is deprecated and replaced by options.time.parser.');
17202 }
17203
17204 return core_scale.prototype.update.apply(me, arguments);
17205 },
17206
17207 /**
17208 * Allows data to be referenced via 't' attribute
17209 */
17210 getRightValue: function(rawValue) {
17211 if (rawValue && rawValue.t !== undefined) {
17212 rawValue = rawValue.t;
17213 }
17214 return core_scale.prototype.getRightValue.call(this, rawValue);
17215 },
17216
17217 determineDataLimits: function() {
17218 var me = this;
17219 var chart = me.chart;
17220 var timeOpts = me.options.time;
17221 var unit = timeOpts.unit || 'day';
17222 var min = MAX_INTEGER;
17223 var max = MIN_INTEGER;
17224 var timestamps = [];
17225 var datasets = [];
17226 var labels = [];
17227 var i, j, ilen, jlen, data, timestamp;
17228 var dataLabels = chart.data.labels || [];
17229
17230 // Convert labels to timestamps
17231 for (i = 0, ilen = dataLabels.length; i < ilen; ++i) {
17232 labels.push(parse(dataLabels[i], me));
17233 }
17234
17235 // Convert data to timestamps
17236 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
17237 if (chart.isDatasetVisible(i)) {
17238 data = chart.data.datasets[i].data;
17239
17240 // Let's consider that all data have the same format.
17241 if (helpers$1.isObject(data[0])) {
17242 datasets[i] = [];
17243
17244 for (j = 0, jlen = data.length; j < jlen; ++j) {
17245 timestamp = parse(data[j], me);
17246 timestamps.push(timestamp);
17247 datasets[i][j] = timestamp;
17248 }
17249 } else {
17250 for (j = 0, jlen = labels.length; j < jlen; ++j) {
17251 timestamps.push(labels[j]);
17252 }
17253 datasets[i] = labels.slice(0);
17254 }
17255 } else {
17256 datasets[i] = [];
17257 }
17258 }
17259
17260 if (labels.length) {
17261 // Sort labels **after** data have been converted
17262 labels = arrayUnique(labels).sort(sorter);
17263 min = Math.min(min, labels[0]);
17264 max = Math.max(max, labels[labels.length - 1]);
17265 }
17266
17267 if (timestamps.length) {
17268 timestamps = arrayUnique(timestamps).sort(sorter);
17269 min = Math.min(min, timestamps[0]);
17270 max = Math.max(max, timestamps[timestamps.length - 1]);
17271 }
17272
17273 min = parse(timeOpts.min, me) || min;
17274 max = parse(timeOpts.max, me) || max;
17275
17276 // In case there is no valid min/max, set limits based on unit time option
17277 min = min === MAX_INTEGER ? +moment().startOf(unit) : min;
17278 max = max === MIN_INTEGER ? +moment().endOf(unit) + 1 : max;
17279
17280 // Make sure that max is strictly higher than min (required by the lookup table)
17281 me.min = Math.min(min, max);
17282 me.max = Math.max(min + 1, max);
17283
17284 // PRIVATE
17285 me._horizontal = me.isHorizontal();
17286 me._table = [];
17287 me._timestamps = {
17288 data: timestamps,
17289 datasets: datasets,
17290 labels: labels
17291 };
17292 },
17293
17294 buildTicks: function() {
17295 var me = this;
17296 var min = me.min;
17297 var max = me.max;
17298 var options = me.options;
17299 var timeOpts = options.time;
17300 var timestamps = [];
17301 var ticks = [];
17302 var i, ilen, timestamp;
17303
17304 switch (options.ticks.source) {
17305 case 'data':
17306 timestamps = me._timestamps.data;
17307 break;
17308 case 'labels':
17309 timestamps = me._timestamps.labels;
17310 break;
17311 case 'auto':
17312 default:
17313 timestamps = generate(min, max, me.getLabelCapacity(min), options);
17314 }
17315
17316 if (options.bounds === 'ticks' && timestamps.length) {
17317 min = timestamps[0];
17318 max = timestamps[timestamps.length - 1];
17319 }
17320
17321 // Enforce limits with user min/max options
17322 min = parse(timeOpts.min, me) || min;
17323 max = parse(timeOpts.max, me) || max;
17324
17325 // Remove ticks outside the min/max range
17326 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
17327 timestamp = timestamps[i];
17328 if (timestamp >= min && timestamp <= max) {
17329 ticks.push(timestamp);
17330 }
17331 }
17332
17333 me.min = min;
17334 me.max = max;
17335
17336 // PRIVATE
17337 me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max);
17338 me._majorUnit = determineMajorUnit(me._unit);
17339 me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
17340 me._offsets = computeOffsets(me._table, ticks, min, max, options);
17341 me._labelFormat = determineLabelFormat(me._timestamps.data, timeOpts);
17342
17343 if (options.ticks.reverse) {
17344 ticks.reverse();
17345 }
17346
17347 return ticksFromTimestamps(ticks, me._majorUnit);
17348 },
17349
17350 getLabelForIndex: function(index, datasetIndex) {
17351 var me = this;
17352 var data = me.chart.data;
17353 var timeOpts = me.options.time;
17354 var label = data.labels && index < data.labels.length ? data.labels[index] : '';
17355 var value = data.datasets[datasetIndex].data[index];
17356
17357 if (helpers$1.isObject(value)) {
17358 label = me.getRightValue(value);
17359 }
17360 if (timeOpts.tooltipFormat) {
17361 return momentify(label, timeOpts).format(timeOpts.tooltipFormat);
17362 }
17363 if (typeof label === 'string') {
17364 return label;
17365 }
17366
17367 return momentify(label, timeOpts).format(me._labelFormat);
17368 },
17369
17370 /**
17371 * Function to format an individual tick mark
17372 * @private
17373 */
17374 tickFormatFunction: function(tick, index, ticks, formatOverride) {
17375 var me = this;
17376 var options = me.options;
17377 var time = tick.valueOf();
17378 var formats = options.time.displayFormats;
17379 var minorFormat = formats[me._unit];
17380 var majorUnit = me._majorUnit;
17381 var majorFormat = formats[majorUnit];
17382 var majorTime = tick.clone().startOf(majorUnit).valueOf();
17383 var majorTickOpts = options.ticks.major;
17384 var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime;
17385 var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat);
17386 var tickOpts = major ? majorTickOpts : options.ticks.minor;
17387 var formatter = valueOrDefault$a(tickOpts.callback, tickOpts.userCallback);
17388
17389 return formatter ? formatter(label, index, ticks) : label;
17390 },
17391
17392 convertTicksToLabels: function(ticks) {
17393 var labels = [];
17394 var i, ilen;
17395
17396 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
17397 labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
17398 }
17399
17400 return labels;
17401 },
17402
17403 /**
17404 * @private
17405 */
17406 getPixelForOffset: function(time) {
17407 var me = this;
17408 var isReverse = me.options.ticks.reverse;
17409 var size = me._horizontal ? me.width : me.height;
17410 var start = me._horizontal ? isReverse ? me.right : me.left : isReverse ? me.bottom : me.top;
17411 var pos = interpolate$1(me._table, 'time', time, 'pos');
17412 var offset = size * (me._offsets.start + pos) / (me._offsets.start + 1 + me._offsets.end);
17413
17414 return isReverse ? start - offset : start + offset;
17415 },
17416
17417 getPixelForValue: function(value, index, datasetIndex) {
17418 var me = this;
17419 var time = null;
17420
17421 if (index !== undefined && datasetIndex !== undefined) {
17422 time = me._timestamps.datasets[datasetIndex][index];
17423 }
17424
17425 if (time === null) {
17426 time = parse(value, me);
17427 }
17428
17429 if (time !== null) {
17430 return me.getPixelForOffset(time);
17431 }
17432 },
17433
17434 getPixelForTick: function(index) {
17435 var ticks = this.getTicks();
17436 return index >= 0 && index < ticks.length ?
17437 this.getPixelForOffset(ticks[index].value) :
17438 null;
17439 },
17440
17441 getValueForPixel: function(pixel) {
17442 var me = this;
17443 var size = me._horizontal ? me.width : me.height;
17444 var start = me._horizontal ? me.left : me.top;
17445 var pos = (size ? (pixel - start) / size : 0) * (me._offsets.start + 1 + me._offsets.start) - me._offsets.end;
17446 var time = interpolate$1(me._table, 'pos', pos, 'time');
17447
17448 return moment(time);
17449 },
17450
17451 /**
17452 * Crude approximation of what the label width might be
17453 * @private
17454 */
17455 getLabelWidth: function(label) {
17456 var me = this;
17457 var ticksOpts = me.options.ticks;
17458 var tickLabelWidth = me.ctx.measureText(label).width;
17459 var angle = helpers$1.toRadians(ticksOpts.maxRotation);
17460 var cosRotation = Math.cos(angle);
17461 var sinRotation = Math.sin(angle);
17462 var tickFontSize = valueOrDefault$a(ticksOpts.fontSize, core_defaults.global.defaultFontSize);
17463
17464 return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
17465 },
17466
17467 /**
17468 * @private
17469 */
17470 getLabelCapacity: function(exampleTime) {
17471 var me = this;
17472
17473 var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation
17474
17475 var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride);
17476 var tickLabelWidth = me.getLabelWidth(exampleLabel);
17477 var innerWidth = me.isHorizontal() ? me.width : me.height;
17478
17479 var capacity = Math.floor(innerWidth / tickLabelWidth);
17480 return capacity > 0 ? capacity : 1;
17481 }
17482});
17483
17484// INTERNAL: static default options, registered in src/chart.js
17485var _defaults$4 = defaultConfig$4;
17486scale_time._defaults = _defaults$4;
17487
17488var scales = {
17489 category: scale_category,
17490 linear: scale_linear,
17491 logarithmic: scale_logarithmic,
17492 radialLinear: scale_radialLinear,
17493 time: scale_time
17494};
17495
17496core_defaults._set('global', {
17497 plugins: {
17498 filler: {
17499 propagate: true
17500 }
17501 }
17502});
17503
17504var mappers = {
17505 dataset: function(source) {
17506 var index = source.fill;
17507 var chart = source.chart;
17508 var meta = chart.getDatasetMeta(index);
17509 var visible = meta && chart.isDatasetVisible(index);
17510 var points = (visible && meta.dataset._children) || [];
17511 var length = points.length || 0;
17512
17513 return !length ? null : function(point, i) {
17514 return (i < length && points[i]._view) || null;
17515 };
17516 },
17517
17518 boundary: function(source) {
17519 var boundary = source.boundary;
17520 var x = boundary ? boundary.x : null;
17521 var y = boundary ? boundary.y : null;
17522
17523 return function(point) {
17524 return {
17525 x: x === null ? point.x : x,
17526 y: y === null ? point.y : y,
17527 };
17528 };
17529 }
17530};
17531
17532// @todo if (fill[0] === '#')
17533function decodeFill(el, index, count) {
17534 var model = el._model || {};
17535 var fill = model.fill;
17536 var target;
17537
17538 if (fill === undefined) {
17539 fill = !!model.backgroundColor;
17540 }
17541
17542 if (fill === false || fill === null) {
17543 return false;
17544 }
17545
17546 if (fill === true) {
17547 return 'origin';
17548 }
17549
17550 target = parseFloat(fill, 10);
17551 if (isFinite(target) && Math.floor(target) === target) {
17552 if (fill[0] === '-' || fill[0] === '+') {
17553 target = index + target;
17554 }
17555
17556 if (target === index || target < 0 || target >= count) {
17557 return false;
17558 }
17559
17560 return target;
17561 }
17562
17563 switch (fill) {
17564 // compatibility
17565 case 'bottom':
17566 return 'start';
17567 case 'top':
17568 return 'end';
17569 case 'zero':
17570 return 'origin';
17571 // supported boundaries
17572 case 'origin':
17573 case 'start':
17574 case 'end':
17575 return fill;
17576 // invalid fill values
17577 default:
17578 return false;
17579 }
17580}
17581
17582function computeBoundary(source) {
17583 var model = source.el._model || {};
17584 var scale = source.el._scale || {};
17585 var fill = source.fill;
17586 var target = null;
17587 var horizontal;
17588
17589 if (isFinite(fill)) {
17590 return null;
17591 }
17592
17593 // Backward compatibility: until v3, we still need to support boundary values set on
17594 // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
17595 // controllers might still use it (e.g. the Smith chart).
17596
17597 if (fill === 'start') {
17598 target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
17599 } else if (fill === 'end') {
17600 target = model.scaleTop === undefined ? scale.top : model.scaleTop;
17601 } else if (model.scaleZero !== undefined) {
17602 target = model.scaleZero;
17603 } else if (scale.getBasePosition) {
17604 target = scale.getBasePosition();
17605 } else if (scale.getBasePixel) {
17606 target = scale.getBasePixel();
17607 }
17608
17609 if (target !== undefined && target !== null) {
17610 if (target.x !== undefined && target.y !== undefined) {
17611 return target;
17612 }
17613
17614 if (helpers$1.isFinite(target)) {
17615 horizontal = scale.isHorizontal();
17616 return {
17617 x: horizontal ? target : null,
17618 y: horizontal ? null : target
17619 };
17620 }
17621 }
17622
17623 return null;
17624}
17625
17626function resolveTarget(sources, index, propagate) {
17627 var source = sources[index];
17628 var fill = source.fill;
17629 var visited = [index];
17630 var target;
17631
17632 if (!propagate) {
17633 return fill;
17634 }
17635
17636 while (fill !== false && visited.indexOf(fill) === -1) {
17637 if (!isFinite(fill)) {
17638 return fill;
17639 }
17640
17641 target = sources[fill];
17642 if (!target) {
17643 return false;
17644 }
17645
17646 if (target.visible) {
17647 return fill;
17648 }
17649
17650 visited.push(fill);
17651 fill = target.fill;
17652 }
17653
17654 return false;
17655}
17656
17657function createMapper(source) {
17658 var fill = source.fill;
17659 var type = 'dataset';
17660
17661 if (fill === false) {
17662 return null;
17663 }
17664
17665 if (!isFinite(fill)) {
17666 type = 'boundary';
17667 }
17668
17669 return mappers[type](source);
17670}
17671
17672function isDrawable(point) {
17673 return point && !point.skip;
17674}
17675
17676function drawArea(ctx, curve0, curve1, len0, len1) {
17677 var i;
17678
17679 if (!len0 || !len1) {
17680 return;
17681 }
17682
17683 // building first area curve (normal)
17684 ctx.moveTo(curve0[0].x, curve0[0].y);
17685 for (i = 1; i < len0; ++i) {
17686 helpers$1.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
17687 }
17688
17689 // joining the two area curves
17690 ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
17691
17692 // building opposite area curve (reverse)
17693 for (i = len1 - 1; i > 0; --i) {
17694 helpers$1.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
17695 }
17696}
17697
17698function doFill(ctx, points, mapper, view, color, loop) {
17699 var count = points.length;
17700 var span = view.spanGaps;
17701 var curve0 = [];
17702 var curve1 = [];
17703 var len0 = 0;
17704 var len1 = 0;
17705 var i, ilen, index, p0, p1, d0, d1;
17706
17707 ctx.beginPath();
17708
17709 for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
17710 index = i % count;
17711 p0 = points[index]._view;
17712 p1 = mapper(p0, index, view);
17713 d0 = isDrawable(p0);
17714 d1 = isDrawable(p1);
17715
17716 if (d0 && d1) {
17717 len0 = curve0.push(p0);
17718 len1 = curve1.push(p1);
17719 } else if (len0 && len1) {
17720 if (!span) {
17721 drawArea(ctx, curve0, curve1, len0, len1);
17722 len0 = len1 = 0;
17723 curve0 = [];
17724 curve1 = [];
17725 } else {
17726 if (d0) {
17727 curve0.push(p0);
17728 }
17729 if (d1) {
17730 curve1.push(p1);
17731 }
17732 }
17733 }
17734 }
17735
17736 drawArea(ctx, curve0, curve1, len0, len1);
17737
17738 ctx.closePath();
17739 ctx.fillStyle = color;
17740 ctx.fill();
17741}
17742
17743var plugin_filler = {
17744 id: 'filler',
17745
17746 afterDatasetsUpdate: function(chart, options) {
17747 var count = (chart.data.datasets || []).length;
17748 var propagate = options.propagate;
17749 var sources = [];
17750 var meta, i, el, source;
17751
17752 for (i = 0; i < count; ++i) {
17753 meta = chart.getDatasetMeta(i);
17754 el = meta.dataset;
17755 source = null;
17756
17757 if (el && el._model && el instanceof elements.Line) {
17758 source = {
17759 visible: chart.isDatasetVisible(i),
17760 fill: decodeFill(el, i, count),
17761 chart: chart,
17762 el: el
17763 };
17764 }
17765
17766 meta.$filler = source;
17767 sources.push(source);
17768 }
17769
17770 for (i = 0; i < count; ++i) {
17771 source = sources[i];
17772 if (!source) {
17773 continue;
17774 }
17775
17776 source.fill = resolveTarget(sources, i, propagate);
17777 source.boundary = computeBoundary(source);
17778 source.mapper = createMapper(source);
17779 }
17780 },
17781
17782 beforeDatasetDraw: function(chart, args) {
17783 var meta = args.meta.$filler;
17784 if (!meta) {
17785 return;
17786 }
17787
17788 var ctx = chart.ctx;
17789 var el = meta.el;
17790 var view = el._view;
17791 var points = el._children || [];
17792 var mapper = meta.mapper;
17793 var color = view.backgroundColor || core_defaults.global.defaultColor;
17794
17795 if (mapper && color && points.length) {
17796 helpers$1.canvas.clipArea(ctx, chart.chartArea);
17797 doFill(ctx, points, mapper, view, color, el._loop);
17798 helpers$1.canvas.unclipArea(ctx);
17799 }
17800 }
17801};
17802
17803var noop$1 = helpers$1.noop;
17804var valueOrDefault$b = helpers$1.valueOrDefault;
17805
17806core_defaults._set('global', {
17807 legend: {
17808 display: true,
17809 position: 'top',
17810 fullWidth: true,
17811 reverse: false,
17812 weight: 1000,
17813
17814 // a callback that will handle
17815 onClick: function(e, legendItem) {
17816 var index = legendItem.datasetIndex;
17817 var ci = this.chart;
17818 var meta = ci.getDatasetMeta(index);
17819
17820 // See controller.isDatasetVisible comment
17821 meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
17822
17823 // We hid a dataset ... rerender the chart
17824 ci.update();
17825 },
17826
17827 onHover: null,
17828
17829 labels: {
17830 boxWidth: 40,
17831 padding: 10,
17832 // Generates labels shown in the legend
17833 // Valid properties to return:
17834 // text : text to display
17835 // fillStyle : fill of coloured box
17836 // strokeStyle: stroke of coloured box
17837 // hidden : if this legend item refers to a hidden item
17838 // lineCap : cap style for line
17839 // lineDash
17840 // lineDashOffset :
17841 // lineJoin :
17842 // lineWidth :
17843 generateLabels: function(chart) {
17844 var data = chart.data;
17845 return helpers$1.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
17846 return {
17847 text: dataset.label,
17848 fillStyle: (!helpers$1.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
17849 hidden: !chart.isDatasetVisible(i),
17850 lineCap: dataset.borderCapStyle,
17851 lineDash: dataset.borderDash,
17852 lineDashOffset: dataset.borderDashOffset,
17853 lineJoin: dataset.borderJoinStyle,
17854 lineWidth: dataset.borderWidth,
17855 strokeStyle: dataset.borderColor,
17856 pointStyle: dataset.pointStyle,
17857
17858 // Below is extra data used for toggling the datasets
17859 datasetIndex: i
17860 };
17861 }, this) : [];
17862 }
17863 }
17864 },
17865
17866 legendCallback: function(chart) {
17867 var text = [];
17868 text.push('<ul class="' + chart.id + '-legend">');
17869 for (var i = 0; i < chart.data.datasets.length; i++) {
17870 text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
17871 if (chart.data.datasets[i].label) {
17872 text.push(chart.data.datasets[i].label);
17873 }
17874 text.push('</li>');
17875 }
17876 text.push('</ul>');
17877 return text.join('');
17878 }
17879});
17880
17881/**
17882 * Helper function to get the box width based on the usePointStyle option
17883 * @param labelopts {Object} the label options on the legend
17884 * @param fontSize {Number} the label font size
17885 * @return {Number} width of the color box area
17886 */
17887function getBoxWidth(labelOpts, fontSize) {
17888 return labelOpts.usePointStyle ?
17889 fontSize * Math.SQRT2 :
17890 labelOpts.boxWidth;
17891}
17892
17893/**
17894 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
17895 */
17896var Legend = core_element.extend({
17897
17898 initialize: function(config) {
17899 helpers$1.extend(this, config);
17900
17901 // Contains hit boxes for each dataset (in dataset order)
17902 this.legendHitBoxes = [];
17903
17904 // Are we in doughnut mode which has a different data type
17905 this.doughnutMode = false;
17906 },
17907
17908 // These methods are ordered by lifecycle. Utilities then follow.
17909 // Any function defined here is inherited by all legend types.
17910 // Any function can be extended by the legend type
17911
17912 beforeUpdate: noop$1,
17913 update: function(maxWidth, maxHeight, margins) {
17914 var me = this;
17915
17916 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
17917 me.beforeUpdate();
17918
17919 // Absorb the master measurements
17920 me.maxWidth = maxWidth;
17921 me.maxHeight = maxHeight;
17922 me.margins = margins;
17923
17924 // Dimensions
17925 me.beforeSetDimensions();
17926 me.setDimensions();
17927 me.afterSetDimensions();
17928 // Labels
17929 me.beforeBuildLabels();
17930 me.buildLabels();
17931 me.afterBuildLabels();
17932
17933 // Fit
17934 me.beforeFit();
17935 me.fit();
17936 me.afterFit();
17937 //
17938 me.afterUpdate();
17939
17940 return me.minSize;
17941 },
17942 afterUpdate: noop$1,
17943
17944 //
17945
17946 beforeSetDimensions: noop$1,
17947 setDimensions: function() {
17948 var me = this;
17949 // Set the unconstrained dimension before label rotation
17950 if (me.isHorizontal()) {
17951 // Reset position before calculating rotation
17952 me.width = me.maxWidth;
17953 me.left = 0;
17954 me.right = me.width;
17955 } else {
17956 me.height = me.maxHeight;
17957
17958 // Reset position before calculating rotation
17959 me.top = 0;
17960 me.bottom = me.height;
17961 }
17962
17963 // Reset padding
17964 me.paddingLeft = 0;
17965 me.paddingTop = 0;
17966 me.paddingRight = 0;
17967 me.paddingBottom = 0;
17968
17969 // Reset minSize
17970 me.minSize = {
17971 width: 0,
17972 height: 0
17973 };
17974 },
17975 afterSetDimensions: noop$1,
17976
17977 //
17978
17979 beforeBuildLabels: noop$1,
17980 buildLabels: function() {
17981 var me = this;
17982 var labelOpts = me.options.labels || {};
17983 var legendItems = helpers$1.callback(labelOpts.generateLabels, [me.chart], me) || [];
17984
17985 if (labelOpts.filter) {
17986 legendItems = legendItems.filter(function(item) {
17987 return labelOpts.filter(item, me.chart.data);
17988 });
17989 }
17990
17991 if (me.options.reverse) {
17992 legendItems.reverse();
17993 }
17994
17995 me.legendItems = legendItems;
17996 },
17997 afterBuildLabels: noop$1,
17998
17999 //
18000
18001 beforeFit: noop$1,
18002 fit: function() {
18003 var me = this;
18004 var opts = me.options;
18005 var labelOpts = opts.labels;
18006 var display = opts.display;
18007
18008 var ctx = me.ctx;
18009
18010 var labelFont = helpers$1.options._parseFont(labelOpts);
18011 var fontSize = labelFont.size;
18012
18013 // Reset hit boxes
18014 var hitboxes = me.legendHitBoxes = [];
18015
18016 var minSize = me.minSize;
18017 var isHorizontal = me.isHorizontal();
18018
18019 if (isHorizontal) {
18020 minSize.width = me.maxWidth; // fill all the width
18021 minSize.height = display ? 10 : 0;
18022 } else {
18023 minSize.width = display ? 10 : 0;
18024 minSize.height = me.maxHeight; // fill all the height
18025 }
18026
18027 // Increase sizes here
18028 if (display) {
18029 ctx.font = labelFont.string;
18030
18031 if (isHorizontal) {
18032 // Labels
18033
18034 // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
18035 var lineWidths = me.lineWidths = [0];
18036 var totalHeight = 0;
18037
18038 ctx.textAlign = 'left';
18039 ctx.textBaseline = 'top';
18040
18041 helpers$1.each(me.legendItems, function(legendItem, i) {
18042 var boxWidth = getBoxWidth(labelOpts, fontSize);
18043 var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
18044
18045 if (i === 0 || lineWidths[lineWidths.length - 1] + width + labelOpts.padding > minSize.width) {
18046 totalHeight += fontSize + labelOpts.padding;
18047 lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = labelOpts.padding;
18048 }
18049
18050 // Store the hitbox width and height here. Final position will be updated in `draw`
18051 hitboxes[i] = {
18052 left: 0,
18053 top: 0,
18054 width: width,
18055 height: fontSize
18056 };
18057
18058 lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
18059 });
18060
18061 minSize.height += totalHeight;
18062
18063 } else {
18064 var vPadding = labelOpts.padding;
18065 var columnWidths = me.columnWidths = [];
18066 var totalWidth = labelOpts.padding;
18067 var currentColWidth = 0;
18068 var currentColHeight = 0;
18069 var itemHeight = fontSize + vPadding;
18070
18071 helpers$1.each(me.legendItems, function(legendItem, i) {
18072 var boxWidth = getBoxWidth(labelOpts, fontSize);
18073 var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
18074
18075 // If too tall, go to new column
18076 if (i > 0 && currentColHeight + itemHeight > minSize.height - vPadding) {
18077 totalWidth += currentColWidth + labelOpts.padding;
18078 columnWidths.push(currentColWidth); // previous column width
18079
18080 currentColWidth = 0;
18081 currentColHeight = 0;
18082 }
18083
18084 // Get max width
18085 currentColWidth = Math.max(currentColWidth, itemWidth);
18086 currentColHeight += itemHeight;
18087
18088 // Store the hitbox width and height here. Final position will be updated in `draw`
18089 hitboxes[i] = {
18090 left: 0,
18091 top: 0,
18092 width: itemWidth,
18093 height: fontSize
18094 };
18095 });
18096
18097 totalWidth += currentColWidth;
18098 columnWidths.push(currentColWidth);
18099 minSize.width += totalWidth;
18100 }
18101 }
18102
18103 me.width = minSize.width;
18104 me.height = minSize.height;
18105 },
18106 afterFit: noop$1,
18107
18108 // Shared Methods
18109 isHorizontal: function() {
18110 return this.options.position === 'top' || this.options.position === 'bottom';
18111 },
18112
18113 // Actually draw the legend on the canvas
18114 draw: function() {
18115 var me = this;
18116 var opts = me.options;
18117 var labelOpts = opts.labels;
18118 var globalDefaults = core_defaults.global;
18119 var defaultColor = globalDefaults.defaultColor;
18120 var lineDefault = globalDefaults.elements.line;
18121 var legendWidth = me.width;
18122 var lineWidths = me.lineWidths;
18123
18124 if (opts.display) {
18125 var ctx = me.ctx;
18126 var fontColor = valueOrDefault$b(labelOpts.fontColor, globalDefaults.defaultFontColor);
18127 var labelFont = helpers$1.options._parseFont(labelOpts);
18128 var fontSize = labelFont.size;
18129 var cursor;
18130
18131 // Canvas setup
18132 ctx.textAlign = 'left';
18133 ctx.textBaseline = 'middle';
18134 ctx.lineWidth = 0.5;
18135 ctx.strokeStyle = fontColor; // for strikethrough effect
18136 ctx.fillStyle = fontColor; // render in correct colour
18137 ctx.font = labelFont.string;
18138
18139 var boxWidth = getBoxWidth(labelOpts, fontSize);
18140 var hitboxes = me.legendHitBoxes;
18141
18142 // current position
18143 var drawLegendBox = function(x, y, legendItem) {
18144 if (isNaN(boxWidth) || boxWidth <= 0) {
18145 return;
18146 }
18147
18148 // Set the ctx for the box
18149 ctx.save();
18150
18151 var lineWidth = valueOrDefault$b(legendItem.lineWidth, lineDefault.borderWidth);
18152 ctx.fillStyle = valueOrDefault$b(legendItem.fillStyle, defaultColor);
18153 ctx.lineCap = valueOrDefault$b(legendItem.lineCap, lineDefault.borderCapStyle);
18154 ctx.lineDashOffset = valueOrDefault$b(legendItem.lineDashOffset, lineDefault.borderDashOffset);
18155 ctx.lineJoin = valueOrDefault$b(legendItem.lineJoin, lineDefault.borderJoinStyle);
18156 ctx.lineWidth = lineWidth;
18157 ctx.strokeStyle = valueOrDefault$b(legendItem.strokeStyle, defaultColor);
18158
18159 if (ctx.setLineDash) {
18160 // IE 9 and 10 do not support line dash
18161 ctx.setLineDash(valueOrDefault$b(legendItem.lineDash, lineDefault.borderDash));
18162 }
18163
18164 if (opts.labels && opts.labels.usePointStyle) {
18165 // Recalculate x and y for drawPoint() because its expecting
18166 // x and y to be center of figure (instead of top left)
18167 var radius = fontSize * Math.SQRT2 / 2;
18168 var offSet = radius / Math.SQRT2;
18169 var centerX = x + offSet;
18170 var centerY = y + offSet;
18171
18172 // Draw pointStyle as legend symbol
18173 helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
18174 } else {
18175 // Draw box as legend symbol
18176 if (lineWidth !== 0) {
18177 ctx.strokeRect(x, y, boxWidth, fontSize);
18178 }
18179 ctx.fillRect(x, y, boxWidth, fontSize);
18180 }
18181
18182 ctx.restore();
18183 };
18184 var fillText = function(x, y, legendItem, textWidth) {
18185 var halfFontSize = fontSize / 2;
18186 var xLeft = boxWidth + halfFontSize + x;
18187 var yMiddle = y + halfFontSize;
18188
18189 ctx.fillText(legendItem.text, xLeft, yMiddle);
18190
18191 if (legendItem.hidden) {
18192 // Strikethrough the text if hidden
18193 ctx.beginPath();
18194 ctx.lineWidth = 2;
18195 ctx.moveTo(xLeft, yMiddle);
18196 ctx.lineTo(xLeft + textWidth, yMiddle);
18197 ctx.stroke();
18198 }
18199 };
18200
18201 // Horizontal
18202 var isHorizontal = me.isHorizontal();
18203 if (isHorizontal) {
18204 cursor = {
18205 x: me.left + ((legendWidth - lineWidths[0]) / 2) + labelOpts.padding,
18206 y: me.top + labelOpts.padding,
18207 line: 0
18208 };
18209 } else {
18210 cursor = {
18211 x: me.left + labelOpts.padding,
18212 y: me.top + labelOpts.padding,
18213 line: 0
18214 };
18215 }
18216
18217 var itemHeight = fontSize + labelOpts.padding;
18218 helpers$1.each(me.legendItems, function(legendItem, i) {
18219 var textWidth = ctx.measureText(legendItem.text).width;
18220 var width = boxWidth + (fontSize / 2) + textWidth;
18221 var x = cursor.x;
18222 var y = cursor.y;
18223
18224 // Use (me.left + me.minSize.width) and (me.top + me.minSize.height)
18225 // instead of me.right and me.bottom because me.width and me.height
18226 // may have been changed since me.minSize was calculated
18227 if (isHorizontal) {
18228 if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) {
18229 y = cursor.y += itemHeight;
18230 cursor.line++;
18231 x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2) + labelOpts.padding;
18232 }
18233 } else if (i > 0 && y + itemHeight > me.top + me.minSize.height) {
18234 x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
18235 y = cursor.y = me.top + labelOpts.padding;
18236 cursor.line++;
18237 }
18238
18239 drawLegendBox(x, y, legendItem);
18240
18241 hitboxes[i].left = x;
18242 hitboxes[i].top = y;
18243
18244 // Fill the actual label
18245 fillText(x, y, legendItem, textWidth);
18246
18247 if (isHorizontal) {
18248 cursor.x += width + labelOpts.padding;
18249 } else {
18250 cursor.y += itemHeight;
18251 }
18252
18253 });
18254 }
18255 },
18256
18257 /**
18258 * Handle an event
18259 * @private
18260 * @param {IEvent} event - The event to handle
18261 * @return {Boolean} true if a change occured
18262 */
18263 handleEvent: function(e) {
18264 var me = this;
18265 var opts = me.options;
18266 var type = e.type === 'mouseup' ? 'click' : e.type;
18267 var changed = false;
18268
18269 if (type === 'mousemove') {
18270 if (!opts.onHover) {
18271 return;
18272 }
18273 } else if (type === 'click') {
18274 if (!opts.onClick) {
18275 return;
18276 }
18277 } else {
18278 return;
18279 }
18280
18281 // Chart event already has relative position in it
18282 var x = e.x;
18283 var y = e.y;
18284
18285 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
18286 // See if we are touching one of the dataset boxes
18287 var lh = me.legendHitBoxes;
18288 for (var i = 0; i < lh.length; ++i) {
18289 var hitBox = lh[i];
18290
18291 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
18292 // Touching an element
18293 if (type === 'click') {
18294 // use e.native for backwards compatibility
18295 opts.onClick.call(me, e.native, me.legendItems[i]);
18296 changed = true;
18297 break;
18298 } else if (type === 'mousemove') {
18299 // use e.native for backwards compatibility
18300 opts.onHover.call(me, e.native, me.legendItems[i]);
18301 changed = true;
18302 break;
18303 }
18304 }
18305 }
18306 }
18307
18308 return changed;
18309 }
18310});
18311
18312function createNewLegendAndAttach(chart, legendOpts) {
18313 var legend = new Legend({
18314 ctx: chart.ctx,
18315 options: legendOpts,
18316 chart: chart
18317 });
18318
18319 core_layouts.configure(chart, legend, legendOpts);
18320 core_layouts.addBox(chart, legend);
18321 chart.legend = legend;
18322}
18323
18324var plugin_legend = {
18325 id: 'legend',
18326
18327 /**
18328 * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making
18329 * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of
18330 * the plugin, which one will be re-exposed in the chart.js file.
18331 * https://github.com/chartjs/Chart.js/pull/2640
18332 * @private
18333 */
18334 _element: Legend,
18335
18336 beforeInit: function(chart) {
18337 var legendOpts = chart.options.legend;
18338
18339 if (legendOpts) {
18340 createNewLegendAndAttach(chart, legendOpts);
18341 }
18342 },
18343
18344 beforeUpdate: function(chart) {
18345 var legendOpts = chart.options.legend;
18346 var legend = chart.legend;
18347
18348 if (legendOpts) {
18349 helpers$1.mergeIf(legendOpts, core_defaults.global.legend);
18350
18351 if (legend) {
18352 core_layouts.configure(chart, legend, legendOpts);
18353 legend.options = legendOpts;
18354 } else {
18355 createNewLegendAndAttach(chart, legendOpts);
18356 }
18357 } else if (legend) {
18358 core_layouts.removeBox(chart, legend);
18359 delete chart.legend;
18360 }
18361 },
18362
18363 afterEvent: function(chart, e) {
18364 var legend = chart.legend;
18365 if (legend) {
18366 legend.handleEvent(e);
18367 }
18368 }
18369};
18370
18371var noop$2 = helpers$1.noop;
18372
18373core_defaults._set('global', {
18374 title: {
18375 display: false,
18376 fontStyle: 'bold',
18377 fullWidth: true,
18378 padding: 10,
18379 position: 'top',
18380 text: '',
18381 weight: 2000 // by default greater than legend (1000) to be above
18382 }
18383});
18384
18385/**
18386 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
18387 */
18388var Title = core_element.extend({
18389 initialize: function(config) {
18390 var me = this;
18391 helpers$1.extend(me, config);
18392
18393 // Contains hit boxes for each dataset (in dataset order)
18394 me.legendHitBoxes = [];
18395 },
18396
18397 // These methods are ordered by lifecycle. Utilities then follow.
18398
18399 beforeUpdate: noop$2,
18400 update: function(maxWidth, maxHeight, margins) {
18401 var me = this;
18402
18403 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
18404 me.beforeUpdate();
18405
18406 // Absorb the master measurements
18407 me.maxWidth = maxWidth;
18408 me.maxHeight = maxHeight;
18409 me.margins = margins;
18410
18411 // Dimensions
18412 me.beforeSetDimensions();
18413 me.setDimensions();
18414 me.afterSetDimensions();
18415 // Labels
18416 me.beforeBuildLabels();
18417 me.buildLabels();
18418 me.afterBuildLabels();
18419
18420 // Fit
18421 me.beforeFit();
18422 me.fit();
18423 me.afterFit();
18424 //
18425 me.afterUpdate();
18426
18427 return me.minSize;
18428
18429 },
18430 afterUpdate: noop$2,
18431
18432 //
18433
18434 beforeSetDimensions: noop$2,
18435 setDimensions: function() {
18436 var me = this;
18437 // Set the unconstrained dimension before label rotation
18438 if (me.isHorizontal()) {
18439 // Reset position before calculating rotation
18440 me.width = me.maxWidth;
18441 me.left = 0;
18442 me.right = me.width;
18443 } else {
18444 me.height = me.maxHeight;
18445
18446 // Reset position before calculating rotation
18447 me.top = 0;
18448 me.bottom = me.height;
18449 }
18450
18451 // Reset padding
18452 me.paddingLeft = 0;
18453 me.paddingTop = 0;
18454 me.paddingRight = 0;
18455 me.paddingBottom = 0;
18456
18457 // Reset minSize
18458 me.minSize = {
18459 width: 0,
18460 height: 0
18461 };
18462 },
18463 afterSetDimensions: noop$2,
18464
18465 //
18466
18467 beforeBuildLabels: noop$2,
18468 buildLabels: noop$2,
18469 afterBuildLabels: noop$2,
18470
18471 //
18472
18473 beforeFit: noop$2,
18474 fit: function() {
18475 var me = this;
18476 var opts = me.options;
18477 var display = opts.display;
18478 var minSize = me.minSize;
18479 var lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1;
18480 var fontOpts = helpers$1.options._parseFont(opts);
18481 var textSize = display ? (lineCount * fontOpts.lineHeight) + (opts.padding * 2) : 0;
18482
18483 if (me.isHorizontal()) {
18484 minSize.width = me.maxWidth; // fill all the width
18485 minSize.height = textSize;
18486 } else {
18487 minSize.width = textSize;
18488 minSize.height = me.maxHeight; // fill all the height
18489 }
18490
18491 me.width = minSize.width;
18492 me.height = minSize.height;
18493
18494 },
18495 afterFit: noop$2,
18496
18497 // Shared Methods
18498 isHorizontal: function() {
18499 var pos = this.options.position;
18500 return pos === 'top' || pos === 'bottom';
18501 },
18502
18503 // Actually draw the title block on the canvas
18504 draw: function() {
18505 var me = this;
18506 var ctx = me.ctx;
18507 var opts = me.options;
18508
18509 if (opts.display) {
18510 var fontOpts = helpers$1.options._parseFont(opts);
18511 var lineHeight = fontOpts.lineHeight;
18512 var offset = lineHeight / 2 + opts.padding;
18513 var rotation = 0;
18514 var top = me.top;
18515 var left = me.left;
18516 var bottom = me.bottom;
18517 var right = me.right;
18518 var maxWidth, titleX, titleY;
18519
18520 ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour
18521 ctx.font = fontOpts.string;
18522
18523 // Horizontal
18524 if (me.isHorizontal()) {
18525 titleX = left + ((right - left) / 2); // midpoint of the width
18526 titleY = top + offset;
18527 maxWidth = right - left;
18528 } else {
18529 titleX = opts.position === 'left' ? left + offset : right - offset;
18530 titleY = top + ((bottom - top) / 2);
18531 maxWidth = bottom - top;
18532 rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
18533 }
18534
18535 ctx.save();
18536 ctx.translate(titleX, titleY);
18537 ctx.rotate(rotation);
18538 ctx.textAlign = 'center';
18539 ctx.textBaseline = 'middle';
18540
18541 var text = opts.text;
18542 if (helpers$1.isArray(text)) {
18543 var y = 0;
18544 for (var i = 0; i < text.length; ++i) {
18545 ctx.fillText(text[i], 0, y, maxWidth);
18546 y += lineHeight;
18547 }
18548 } else {
18549 ctx.fillText(text, 0, 0, maxWidth);
18550 }
18551
18552 ctx.restore();
18553 }
18554 }
18555});
18556
18557function createNewTitleBlockAndAttach(chart, titleOpts) {
18558 var title = new Title({
18559 ctx: chart.ctx,
18560 options: titleOpts,
18561 chart: chart
18562 });
18563
18564 core_layouts.configure(chart, title, titleOpts);
18565 core_layouts.addBox(chart, title);
18566 chart.titleBlock = title;
18567}
18568
18569var plugin_title = {
18570 id: 'title',
18571
18572 /**
18573 * Backward compatibility: since 2.1.5, the title is registered as a plugin, making
18574 * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of
18575 * the plugin, which one will be re-exposed in the chart.js file.
18576 * https://github.com/chartjs/Chart.js/pull/2640
18577 * @private
18578 */
18579 _element: Title,
18580
18581 beforeInit: function(chart) {
18582 var titleOpts = chart.options.title;
18583
18584 if (titleOpts) {
18585 createNewTitleBlockAndAttach(chart, titleOpts);
18586 }
18587 },
18588
18589 beforeUpdate: function(chart) {
18590 var titleOpts = chart.options.title;
18591 var titleBlock = chart.titleBlock;
18592
18593 if (titleOpts) {
18594 helpers$1.mergeIf(titleOpts, core_defaults.global.title);
18595
18596 if (titleBlock) {
18597 core_layouts.configure(chart, titleBlock, titleOpts);
18598 titleBlock.options = titleOpts;
18599 } else {
18600 createNewTitleBlockAndAttach(chart, titleOpts);
18601 }
18602 } else if (titleBlock) {
18603 core_layouts.removeBox(chart, titleBlock);
18604 delete chart.titleBlock;
18605 }
18606 }
18607};
18608
18609var plugins = {};
18610var filler = plugin_filler;
18611var legend = plugin_legend;
18612var title = plugin_title;
18613plugins.filler = filler;
18614plugins.legend = legend;
18615plugins.title = title;
18616
18617/**
18618 * @namespace Chart
18619 */
18620
18621
18622core_controller.helpers = helpers$1;
18623
18624// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
18625core_helpers(core_controller);
18626
18627core_controller.Animation = core_animation;
18628core_controller.animationService = core_animations;
18629core_controller.controllers = controllers;
18630core_controller.DatasetController = core_datasetController;
18631core_controller.defaults = core_defaults;
18632core_controller.Element = core_element;
18633core_controller.elements = elements;
18634core_controller.Interaction = core_interaction;
18635core_controller.layouts = core_layouts;
18636core_controller.platform = platform;
18637core_controller.plugins = core_plugins;
18638core_controller.Scale = core_scale;
18639core_controller.scaleService = core_scaleService;
18640core_controller.Ticks = core_ticks;
18641core_controller.Tooltip = core_tooltip;
18642
18643// Register built-in scales
18644
18645core_controller.helpers.each(scales, function(scale, type) {
18646 core_controller.scaleService.registerScaleType(type, scale, scale._defaults);
18647});
18648
18649// Loading built-in plugins
18650
18651for (var k in plugins) {
18652 if (plugins.hasOwnProperty(k)) {
18653 core_controller.plugins.register(plugins[k]);
18654 }
18655}
18656
18657core_controller.platform.initialize();
18658
18659var chart = core_controller;
18660if (typeof window !== 'undefined') {
18661 window.Chart = core_controller;
18662}
18663
18664// DEPRECATIONS
18665
18666/**
18667 * Provided for backward compatibility, not available anymore
18668 * @namespace Chart.Legend
18669 * @deprecated since version 2.1.5
18670 * @todo remove at version 3
18671 * @private
18672 */
18673core_controller.Legend = plugins.legend._element;
18674
18675/**
18676 * Provided for backward compatibility, not available anymore
18677 * @namespace Chart.Title
18678 * @deprecated since version 2.1.5
18679 * @todo remove at version 3
18680 * @private
18681 */
18682core_controller.Title = plugins.title._element;
18683
18684/**
18685 * Provided for backward compatibility, use Chart.plugins instead
18686 * @namespace Chart.pluginService
18687 * @deprecated since version 2.1.5
18688 * @todo remove at version 3
18689 * @private
18690 */
18691core_controller.pluginService = core_controller.plugins;
18692
18693/**
18694 * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
18695 * effect, instead simply create/register plugins via plain JavaScript objects.
18696 * @interface Chart.PluginBase
18697 * @deprecated since version 2.5.0
18698 * @todo remove at version 3
18699 * @private
18700 */
18701core_controller.PluginBase = core_controller.Element.extend({});
18702
18703/**
18704 * Provided for backward compatibility, use Chart.helpers.canvas instead.
18705 * @namespace Chart.canvasHelpers
18706 * @deprecated since version 2.6.0
18707 * @todo remove at version 3
18708 * @private
18709 */
18710core_controller.canvasHelpers = core_controller.helpers.canvas;
18711
18712/**
18713 * Provided for backward compatibility, use Chart.layouts instead.
18714 * @namespace Chart.layoutService
18715 * @deprecated since version 2.7.3
18716 * @todo remove at version 3
18717 * @private
18718 */
18719core_controller.layoutService = core_controller.layouts;
18720
18721/**
18722 * Provided for backward compatibility, not available anymore.
18723 * @namespace Chart.LinearScaleBase
18724 * @deprecated since version 2.8
18725 * @todo remove at version 3
18726 * @private
18727 */
18728core_controller.LinearScaleBase = scale_linearbase;
18729
18730/**
18731 * Provided for backward compatibility, instead we should create a new Chart
18732 * by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`).
18733 * @deprecated since version 2.8.0
18734 * @todo remove at version 3
18735 */
18736core_controller.helpers.each(
18737 [
18738 'Bar',
18739 'Bubble',
18740 'Doughnut',
18741 'Line',
18742 'PolarArea',
18743 'Radar',
18744 'Scatter'
18745 ],
18746 function(klass) {
18747 core_controller[klass] = function(ctx, cfg) {
18748 return new core_controller(ctx, core_controller.helpers.merge(cfg || {}, {
18749 type: klass.charAt(0).toLowerCase() + klass.slice(1)
18750 }));
18751 };
18752 }
18753);
18754
18755return chart;
18756
18757})));