· 5 years ago · May 05, 2020, 07:28 PM
1/*!
2 * Chart.js v2.9.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(function() { try { return require('moment'); } catch(e) { } }()) :
9typeof define === 'function' && define.amd ? define(['require'], function(require) { return factory(function() { try { return require('moment'); } catch(e) { } }()); }) :
10(global = global || self, global.Chart = factory(global.moment));
11}(this, (function (moment) { 'use strict';
12
13moment = moment && moment.hasOwnProperty('default') ? moment['default'] : moment;
14
15function createCommonjsModule(fn, module) {
16 return module = { exports: {} }, fn(module, module.exports), module.exports;
17}
18
19function getCjsExportFromNamespace (n) {
20 return n && n['default'] || n;
21}
22
23var colorName = {
24 "aliceblue": [240, 248, 255],
25 "antiquewhite": [250, 235, 215],
26 "aqua": [0, 255, 255],
27 "aquamarine": [127, 255, 212],
28 "azure": [240, 255, 255],
29 "beige": [245, 245, 220],
30 "bisque": [255, 228, 196],
31 "black": [0, 0, 0],
32 "blanchedalmond": [255, 235, 205],
33 "blue": [0, 0, 255],
34 "blueviolet": [138, 43, 226],
35 "brown": [165, 42, 42],
36 "burlywood": [222, 184, 135],
37 "cadetblue": [95, 158, 160],
38 "chartreuse": [127, 255, 0],
39 "chocolate": [210, 105, 30],
40 "coral": [255, 127, 80],
41 "cornflowerblue": [100, 149, 237],
42 "cornsilk": [255, 248, 220],
43 "crimson": [220, 20, 60],
44 "cyan": [0, 255, 255],
45 "darkblue": [0, 0, 139],
46 "darkcyan": [0, 139, 139],
47 "darkgoldenrod": [184, 134, 11],
48 "darkgray": [169, 169, 169],
49 "darkgreen": [0, 100, 0],
50 "darkgrey": [169, 169, 169],
51 "darkkhaki": [189, 183, 107],
52 "darkmagenta": [139, 0, 139],
53 "darkolivegreen": [85, 107, 47],
54 "darkorange": [255, 140, 0],
55 "darkorchid": [153, 50, 204],
56 "darkred": [139, 0, 0],
57 "darksalmon": [233, 150, 122],
58 "darkseagreen": [143, 188, 143],
59 "darkslateblue": [72, 61, 139],
60 "darkslategray": [47, 79, 79],
61 "darkslategrey": [47, 79, 79],
62 "darkturquoise": [0, 206, 209],
63 "darkviolet": [148, 0, 211],
64 "deeppink": [255, 20, 147],
65 "deepskyblue": [0, 191, 255],
66 "dimgray": [105, 105, 105],
67 "dimgrey": [105, 105, 105],
68 "dodgerblue": [30, 144, 255],
69 "firebrick": [178, 34, 34],
70 "floralwhite": [255, 250, 240],
71 "forestgreen": [34, 139, 34],
72 "fuchsia": [255, 0, 255],
73 "gainsboro": [220, 220, 220],
74 "ghostwhite": [248, 248, 255],
75 "gold": [255, 215, 0],
76 "goldenrod": [218, 165, 32],
77 "gray": [128, 128, 128],
78 "green": [0, 128, 0],
79 "greenyellow": [173, 255, 47],
80 "grey": [128, 128, 128],
81 "honeydew": [240, 255, 240],
82 "hotpink": [255, 105, 180],
83 "indianred": [205, 92, 92],
84 "indigo": [75, 0, 130],
85 "ivory": [255, 255, 240],
86 "khaki": [240, 230, 140],
87 "lavender": [230, 230, 250],
88 "lavenderblush": [255, 240, 245],
89 "lawngreen": [124, 252, 0],
90 "lemonchiffon": [255, 250, 205],
91 "lightblue": [173, 216, 230],
92 "lightcoral": [240, 128, 128],
93 "lightcyan": [224, 255, 255],
94 "lightgoldenrodyellow": [250, 250, 210],
95 "lightgray": [211, 211, 211],
96 "lightgreen": [144, 238, 144],
97 "lightgrey": [211, 211, 211],
98 "lightpink": [255, 182, 193],
99 "lightsalmon": [255, 160, 122],
100 "lightseagreen": [32, 178, 170],
101 "lightskyblue": [135, 206, 250],
102 "lightslategray": [119, 136, 153],
103 "lightslategrey": [119, 136, 153],
104 "lightsteelblue": [176, 196, 222],
105 "lightyellow": [255, 255, 224],
106 "lime": [0, 255, 0],
107 "limegreen": [50, 205, 50],
108 "linen": [250, 240, 230],
109 "magenta": [255, 0, 255],
110 "maroon": [128, 0, 0],
111 "mediumaquamarine": [102, 205, 170],
112 "mediumblue": [0, 0, 205],
113 "mediumorchid": [186, 85, 211],
114 "mediumpurple": [147, 112, 219],
115 "mediumseagreen": [60, 179, 113],
116 "mediumslateblue": [123, 104, 238],
117 "mediumspringgreen": [0, 250, 154],
118 "mediumturquoise": [72, 209, 204],
119 "mediumvioletred": [199, 21, 133],
120 "midnightblue": [25, 25, 112],
121 "mintcream": [245, 255, 250],
122 "mistyrose": [255, 228, 225],
123 "moccasin": [255, 228, 181],
124 "navajowhite": [255, 222, 173],
125 "navy": [0, 0, 128],
126 "oldlace": [253, 245, 230],
127 "olive": [128, 128, 0],
128 "olivedrab": [107, 142, 35],
129 "orange": [255, 165, 0],
130 "orangered": [255, 69, 0],
131 "orchid": [218, 112, 214],
132 "palegoldenrod": [238, 232, 170],
133 "palegreen": [152, 251, 152],
134 "paleturquoise": [175, 238, 238],
135 "palevioletred": [219, 112, 147],
136 "papayawhip": [255, 239, 213],
137 "peachpuff": [255, 218, 185],
138 "peru": [205, 133, 63],
139 "pink": [255, 192, 203],
140 "plum": [221, 160, 221],
141 "powderblue": [176, 224, 230],
142 "purple": [128, 0, 128],
143 "rebeccapurple": [102, 51, 153],
144 "red": [255, 0, 0],
145 "rosybrown": [188, 143, 143],
146 "royalblue": [65, 105, 225],
147 "saddlebrown": [139, 69, 19],
148 "salmon": [250, 128, 114],
149 "sandybrown": [244, 164, 96],
150 "seagreen": [46, 139, 87],
151 "seashell": [255, 245, 238],
152 "sienna": [160, 82, 45],
153 "silver": [192, 192, 192],
154 "skyblue": [135, 206, 235],
155 "slateblue": [106, 90, 205],
156 "slategray": [112, 128, 144],
157 "slategrey": [112, 128, 144],
158 "snow": [255, 250, 250],
159 "springgreen": [0, 255, 127],
160 "steelblue": [70, 130, 180],
161 "tan": [210, 180, 140],
162 "teal": [0, 128, 128],
163 "thistle": [216, 191, 216],
164 "tomato": [255, 99, 71],
165 "turquoise": [64, 224, 208],
166 "violet": [238, 130, 238],
167 "wheat": [245, 222, 179],
168 "white": [255, 255, 255],
169 "whitesmoke": [245, 245, 245],
170 "yellow": [255, 255, 0],
171 "yellowgreen": [154, 205, 50]
172};
173
174var conversions = createCommonjsModule(function (module) {
175/* MIT license */
176
177
178// NOTE: conversions should only return primitive values (i.e. arrays, or
179// values that give correct `typeof` results).
180// do not use box values types (i.e. Number(), String(), etc.)
181
182var reverseKeywords = {};
183for (var key in colorName) {
184 if (colorName.hasOwnProperty(key)) {
185 reverseKeywords[colorName[key]] = key;
186 }
187}
188
189var convert = module.exports = {
190 rgb: {channels: 3, labels: 'rgb'},
191 hsl: {channels: 3, labels: 'hsl'},
192 hsv: {channels: 3, labels: 'hsv'},
193 hwb: {channels: 3, labels: 'hwb'},
194 cmyk: {channels: 4, labels: 'cmyk'},
195 xyz: {channels: 3, labels: 'xyz'},
196 lab: {channels: 3, labels: 'lab'},
197 lch: {channels: 3, labels: 'lch'},
198 hex: {channels: 1, labels: ['hex']},
199 keyword: {channels: 1, labels: ['keyword']},
200 ansi16: {channels: 1, labels: ['ansi16']},
201 ansi256: {channels: 1, labels: ['ansi256']},
202 hcg: {channels: 3, labels: ['h', 'c', 'g']},
203 apple: {channels: 3, labels: ['r16', 'g16', 'b16']},
204 gray: {channels: 1, labels: ['gray']}
205};
206
207// hide .channels and .labels properties
208for (var model in convert) {
209 if (convert.hasOwnProperty(model)) {
210 if (!('channels' in convert[model])) {
211 throw new Error('missing channels property: ' + model);
212 }
213
214 if (!('labels' in convert[model])) {
215 throw new Error('missing channel labels property: ' + model);
216 }
217
218 if (convert[model].labels.length !== convert[model].channels) {
219 throw new Error('channel and label counts mismatch: ' + model);
220 }
221
222 var channels = convert[model].channels;
223 var labels = convert[model].labels;
224 delete convert[model].channels;
225 delete convert[model].labels;
226 Object.defineProperty(convert[model], 'channels', {value: channels});
227 Object.defineProperty(convert[model], 'labels', {value: labels});
228 }
229}
230
231convert.rgb.hsl = function (rgb) {
232 var r = rgb[0] / 255;
233 var g = rgb[1] / 255;
234 var b = rgb[2] / 255;
235 var min = Math.min(r, g, b);
236 var max = Math.max(r, g, b);
237 var delta = max - min;
238 var h;
239 var s;
240 var l;
241
242 if (max === min) {
243 h = 0;
244 } else if (r === max) {
245 h = (g - b) / delta;
246 } else if (g === max) {
247 h = 2 + (b - r) / delta;
248 } else if (b === max) {
249 h = 4 + (r - g) / delta;
250 }
251
252 h = Math.min(h * 60, 360);
253
254 if (h < 0) {
255 h += 360;
256 }
257
258 l = (min + max) / 2;
259
260 if (max === min) {
261 s = 0;
262 } else if (l <= 0.5) {
263 s = delta / (max + min);
264 } else {
265 s = delta / (2 - max - min);
266 }
267
268 return [h, s * 100, l * 100];
269};
270
271convert.rgb.hsv = function (rgb) {
272 var rdif;
273 var gdif;
274 var bdif;
275 var h;
276 var s;
277
278 var r = rgb[0] / 255;
279 var g = rgb[1] / 255;
280 var b = rgb[2] / 255;
281 var v = Math.max(r, g, b);
282 var diff = v - Math.min(r, g, b);
283 var diffc = function (c) {
284 return (v - c) / 6 / diff + 1 / 2;
285 };
286
287 if (diff === 0) {
288 h = s = 0;
289 } else {
290 s = diff / v;
291 rdif = diffc(r);
292 gdif = diffc(g);
293 bdif = diffc(b);
294
295 if (r === v) {
296 h = bdif - gdif;
297 } else if (g === v) {
298 h = (1 / 3) + rdif - bdif;
299 } else if (b === v) {
300 h = (2 / 3) + gdif - rdif;
301 }
302 if (h < 0) {
303 h += 1;
304 } else if (h > 1) {
305 h -= 1;
306 }
307 }
308
309 return [
310 h * 360,
311 s * 100,
312 v * 100
313 ];
314};
315
316convert.rgb.hwb = function (rgb) {
317 var r = rgb[0];
318 var g = rgb[1];
319 var b = rgb[2];
320 var h = convert.rgb.hsl(rgb)[0];
321 var w = 1 / 255 * Math.min(r, Math.min(g, b));
322
323 b = 1 - 1 / 255 * Math.max(r, Math.max(g, b));
324
325 return [h, w * 100, b * 100];
326};
327
328convert.rgb.cmyk = function (rgb) {
329 var r = rgb[0] / 255;
330 var g = rgb[1] / 255;
331 var b = rgb[2] / 255;
332 var c;
333 var m;
334 var y;
335 var k;
336
337 k = Math.min(1 - r, 1 - g, 1 - b);
338 c = (1 - r - k) / (1 - k) || 0;
339 m = (1 - g - k) / (1 - k) || 0;
340 y = (1 - b - k) / (1 - k) || 0;
341
342 return [c * 100, m * 100, y * 100, k * 100];
343};
344
345/**
346 * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance
347 * */
348function comparativeDistance(x, y) {
349 return (
350 Math.pow(x[0] - y[0], 2) +
351 Math.pow(x[1] - y[1], 2) +
352 Math.pow(x[2] - y[2], 2)
353 );
354}
355
356convert.rgb.keyword = function (rgb) {
357 var reversed = reverseKeywords[rgb];
358 if (reversed) {
359 return reversed;
360 }
361
362 var currentClosestDistance = Infinity;
363 var currentClosestKeyword;
364
365 for (var keyword in colorName) {
366 if (colorName.hasOwnProperty(keyword)) {
367 var value = colorName[keyword];
368
369 // Compute comparative distance
370 var distance = comparativeDistance(rgb, value);
371
372 // Check if its less, if so set as closest
373 if (distance < currentClosestDistance) {
374 currentClosestDistance = distance;
375 currentClosestKeyword = keyword;
376 }
377 }
378 }
379
380 return currentClosestKeyword;
381};
382
383convert.keyword.rgb = function (keyword) {
384 return colorName[keyword];
385};
386
387convert.rgb.xyz = function (rgb) {
388 var r = rgb[0] / 255;
389 var g = rgb[1] / 255;
390 var b = rgb[2] / 255;
391
392 // assume sRGB
393 r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
394 g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
395 b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
396
397 var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
398 var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
399 var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
400
401 return [x * 100, y * 100, z * 100];
402};
403
404convert.rgb.lab = function (rgb) {
405 var xyz = convert.rgb.xyz(rgb);
406 var x = xyz[0];
407 var y = xyz[1];
408 var z = xyz[2];
409 var l;
410 var a;
411 var b;
412
413 x /= 95.047;
414 y /= 100;
415 z /= 108.883;
416
417 x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);
418 y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);
419 z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);
420
421 l = (116 * y) - 16;
422 a = 500 * (x - y);
423 b = 200 * (y - z);
424
425 return [l, a, b];
426};
427
428convert.hsl.rgb = function (hsl) {
429 var h = hsl[0] / 360;
430 var s = hsl[1] / 100;
431 var l = hsl[2] / 100;
432 var t1;
433 var t2;
434 var t3;
435 var rgb;
436 var val;
437
438 if (s === 0) {
439 val = l * 255;
440 return [val, val, val];
441 }
442
443 if (l < 0.5) {
444 t2 = l * (1 + s);
445 } else {
446 t2 = l + s - l * s;
447 }
448
449 t1 = 2 * l - t2;
450
451 rgb = [0, 0, 0];
452 for (var i = 0; i < 3; i++) {
453 t3 = h + 1 / 3 * -(i - 1);
454 if (t3 < 0) {
455 t3++;
456 }
457 if (t3 > 1) {
458 t3--;
459 }
460
461 if (6 * t3 < 1) {
462 val = t1 + (t2 - t1) * 6 * t3;
463 } else if (2 * t3 < 1) {
464 val = t2;
465 } else if (3 * t3 < 2) {
466 val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
467 } else {
468 val = t1;
469 }
470
471 rgb[i] = val * 255;
472 }
473
474 return rgb;
475};
476
477convert.hsl.hsv = function (hsl) {
478 var h = hsl[0];
479 var s = hsl[1] / 100;
480 var l = hsl[2] / 100;
481 var smin = s;
482 var lmin = Math.max(l, 0.01);
483 var sv;
484 var v;
485
486 l *= 2;
487 s *= (l <= 1) ? l : 2 - l;
488 smin *= lmin <= 1 ? lmin : 2 - lmin;
489 v = (l + s) / 2;
490 sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s);
491
492 return [h, sv * 100, v * 100];
493};
494
495convert.hsv.rgb = function (hsv) {
496 var h = hsv[0] / 60;
497 var s = hsv[1] / 100;
498 var v = hsv[2] / 100;
499 var hi = Math.floor(h) % 6;
500
501 var f = h - Math.floor(h);
502 var p = 255 * v * (1 - s);
503 var q = 255 * v * (1 - (s * f));
504 var t = 255 * v * (1 - (s * (1 - f)));
505 v *= 255;
506
507 switch (hi) {
508 case 0:
509 return [v, t, p];
510 case 1:
511 return [q, v, p];
512 case 2:
513 return [p, v, t];
514 case 3:
515 return [p, q, v];
516 case 4:
517 return [t, p, v];
518 case 5:
519 return [v, p, q];
520 }
521};
522
523convert.hsv.hsl = function (hsv) {
524 var h = hsv[0];
525 var s = hsv[1] / 100;
526 var v = hsv[2] / 100;
527 var vmin = Math.max(v, 0.01);
528 var lmin;
529 var sl;
530 var l;
531
532 l = (2 - s) * v;
533 lmin = (2 - s) * vmin;
534 sl = s * vmin;
535 sl /= (lmin <= 1) ? lmin : 2 - lmin;
536 sl = sl || 0;
537 l /= 2;
538
539 return [h, sl * 100, l * 100];
540};
541
542// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
543convert.hwb.rgb = function (hwb) {
544 var h = hwb[0] / 360;
545 var wh = hwb[1] / 100;
546 var bl = hwb[2] / 100;
547 var ratio = wh + bl;
548 var i;
549 var v;
550 var f;
551 var n;
552
553 // wh + bl cant be > 1
554 if (ratio > 1) {
555 wh /= ratio;
556 bl /= ratio;
557 }
558
559 i = Math.floor(6 * h);
560 v = 1 - bl;
561 f = 6 * h - i;
562
563 if ((i & 0x01) !== 0) {
564 f = 1 - f;
565 }
566
567 n = wh + f * (v - wh); // linear interpolation
568
569 var r;
570 var g;
571 var b;
572 switch (i) {
573 default:
574 case 6:
575 case 0: r = v; g = n; b = wh; break;
576 case 1: r = n; g = v; b = wh; break;
577 case 2: r = wh; g = v; b = n; break;
578 case 3: r = wh; g = n; b = v; break;
579 case 4: r = n; g = wh; b = v; break;
580 case 5: r = v; g = wh; b = n; break;
581 }
582
583 return [r * 255, g * 255, b * 255];
584};
585
586convert.cmyk.rgb = function (cmyk) {
587 var c = cmyk[0] / 100;
588 var m = cmyk[1] / 100;
589 var y = cmyk[2] / 100;
590 var k = cmyk[3] / 100;
591 var r;
592 var g;
593 var b;
594
595 r = 1 - Math.min(1, c * (1 - k) + k);
596 g = 1 - Math.min(1, m * (1 - k) + k);
597 b = 1 - Math.min(1, y * (1 - k) + k);
598
599 return [r * 255, g * 255, b * 255];
600};
601
602convert.xyz.rgb = function (xyz) {
603 var x = xyz[0] / 100;
604 var y = xyz[1] / 100;
605 var z = xyz[2] / 100;
606 var r;
607 var g;
608 var b;
609
610 r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
611 g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
612 b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
613
614 // assume sRGB
615 r = r > 0.0031308
616 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
617 : r * 12.92;
618
619 g = g > 0.0031308
620 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
621 : g * 12.92;
622
623 b = b > 0.0031308
624 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
625 : b * 12.92;
626
627 r = Math.min(Math.max(0, r), 1);
628 g = Math.min(Math.max(0, g), 1);
629 b = Math.min(Math.max(0, b), 1);
630
631 return [r * 255, g * 255, b * 255];
632};
633
634convert.xyz.lab = function (xyz) {
635 var x = xyz[0];
636 var y = xyz[1];
637 var z = xyz[2];
638 var l;
639 var a;
640 var b;
641
642 x /= 95.047;
643 y /= 100;
644 z /= 108.883;
645
646 x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);
647 y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);
648 z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);
649
650 l = (116 * y) - 16;
651 a = 500 * (x - y);
652 b = 200 * (y - z);
653
654 return [l, a, b];
655};
656
657convert.lab.xyz = function (lab) {
658 var l = lab[0];
659 var a = lab[1];
660 var b = lab[2];
661 var x;
662 var y;
663 var z;
664
665 y = (l + 16) / 116;
666 x = a / 500 + y;
667 z = y - b / 200;
668
669 var y2 = Math.pow(y, 3);
670 var x2 = Math.pow(x, 3);
671 var z2 = Math.pow(z, 3);
672 y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787;
673 x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787;
674 z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787;
675
676 x *= 95.047;
677 y *= 100;
678 z *= 108.883;
679
680 return [x, y, z];
681};
682
683convert.lab.lch = function (lab) {
684 var l = lab[0];
685 var a = lab[1];
686 var b = lab[2];
687 var hr;
688 var h;
689 var c;
690
691 hr = Math.atan2(b, a);
692 h = hr * 360 / 2 / Math.PI;
693
694 if (h < 0) {
695 h += 360;
696 }
697
698 c = Math.sqrt(a * a + b * b);
699
700 return [l, c, h];
701};
702
703convert.lch.lab = function (lch) {
704 var l = lch[0];
705 var c = lch[1];
706 var h = lch[2];
707 var a;
708 var b;
709 var hr;
710
711 hr = h / 360 * 2 * Math.PI;
712 a = c * Math.cos(hr);
713 b = c * Math.sin(hr);
714
715 return [l, a, b];
716};
717
718convert.rgb.ansi16 = function (args) {
719 var r = args[0];
720 var g = args[1];
721 var b = args[2];
722 var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization
723
724 value = Math.round(value / 50);
725
726 if (value === 0) {
727 return 30;
728 }
729
730 var ansi = 30
731 + ((Math.round(b / 255) << 2)
732 | (Math.round(g / 255) << 1)
733 | Math.round(r / 255));
734
735 if (value === 2) {
736 ansi += 60;
737 }
738
739 return ansi;
740};
741
742convert.hsv.ansi16 = function (args) {
743 // optimization here; we already know the value and don't need to get
744 // it converted for us.
745 return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]);
746};
747
748convert.rgb.ansi256 = function (args) {
749 var r = args[0];
750 var g = args[1];
751 var b = args[2];
752
753 // we use the extended greyscale palette here, with the exception of
754 // black and white. normal palette only has 4 greyscale shades.
755 if (r === g && g === b) {
756 if (r < 8) {
757 return 16;
758 }
759
760 if (r > 248) {
761 return 231;
762 }
763
764 return Math.round(((r - 8) / 247) * 24) + 232;
765 }
766
767 var ansi = 16
768 + (36 * Math.round(r / 255 * 5))
769 + (6 * Math.round(g / 255 * 5))
770 + Math.round(b / 255 * 5);
771
772 return ansi;
773};
774
775convert.ansi16.rgb = function (args) {
776 var color = args % 10;
777
778 // handle greyscale
779 if (color === 0 || color === 7) {
780 if (args > 50) {
781 color += 3.5;
782 }
783
784 color = color / 10.5 * 255;
785
786 return [color, color, color];
787 }
788
789 var mult = (~~(args > 50) + 1) * 0.5;
790 var r = ((color & 1) * mult) * 255;
791 var g = (((color >> 1) & 1) * mult) * 255;
792 var b = (((color >> 2) & 1) * mult) * 255;
793
794 return [r, g, b];
795};
796
797convert.ansi256.rgb = function (args) {
798 // handle greyscale
799 if (args >= 232) {
800 var c = (args - 232) * 10 + 8;
801 return [c, c, c];
802 }
803
804 args -= 16;
805
806 var rem;
807 var r = Math.floor(args / 36) / 5 * 255;
808 var g = Math.floor((rem = args % 36) / 6) / 5 * 255;
809 var b = (rem % 6) / 5 * 255;
810
811 return [r, g, b];
812};
813
814convert.rgb.hex = function (args) {
815 var integer = ((Math.round(args[0]) & 0xFF) << 16)
816 + ((Math.round(args[1]) & 0xFF) << 8)
817 + (Math.round(args[2]) & 0xFF);
818
819 var string = integer.toString(16).toUpperCase();
820 return '000000'.substring(string.length) + string;
821};
822
823convert.hex.rgb = function (args) {
824 var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);
825 if (!match) {
826 return [0, 0, 0];
827 }
828
829 var colorString = match[0];
830
831 if (match[0].length === 3) {
832 colorString = colorString.split('').map(function (char) {
833 return char + char;
834 }).join('');
835 }
836
837 var integer = parseInt(colorString, 16);
838 var r = (integer >> 16) & 0xFF;
839 var g = (integer >> 8) & 0xFF;
840 var b = integer & 0xFF;
841
842 return [r, g, b];
843};
844
845convert.rgb.hcg = function (rgb) {
846 var r = rgb[0] / 255;
847 var g = rgb[1] / 255;
848 var b = rgb[2] / 255;
849 var max = Math.max(Math.max(r, g), b);
850 var min = Math.min(Math.min(r, g), b);
851 var chroma = (max - min);
852 var grayscale;
853 var hue;
854
855 if (chroma < 1) {
856 grayscale = min / (1 - chroma);
857 } else {
858 grayscale = 0;
859 }
860
861 if (chroma <= 0) {
862 hue = 0;
863 } else
864 if (max === r) {
865 hue = ((g - b) / chroma) % 6;
866 } else
867 if (max === g) {
868 hue = 2 + (b - r) / chroma;
869 } else {
870 hue = 4 + (r - g) / chroma + 4;
871 }
872
873 hue /= 6;
874 hue %= 1;
875
876 return [hue * 360, chroma * 100, grayscale * 100];
877};
878
879convert.hsl.hcg = function (hsl) {
880 var s = hsl[1] / 100;
881 var l = hsl[2] / 100;
882 var c = 1;
883 var f = 0;
884
885 if (l < 0.5) {
886 c = 2.0 * s * l;
887 } else {
888 c = 2.0 * s * (1.0 - l);
889 }
890
891 if (c < 1.0) {
892 f = (l - 0.5 * c) / (1.0 - c);
893 }
894
895 return [hsl[0], c * 100, f * 100];
896};
897
898convert.hsv.hcg = function (hsv) {
899 var s = hsv[1] / 100;
900 var v = hsv[2] / 100;
901
902 var c = s * v;
903 var f = 0;
904
905 if (c < 1.0) {
906 f = (v - c) / (1 - c);
907 }
908
909 return [hsv[0], c * 100, f * 100];
910};
911
912convert.hcg.rgb = function (hcg) {
913 var h = hcg[0] / 360;
914 var c = hcg[1] / 100;
915 var g = hcg[2] / 100;
916
917 if (c === 0.0) {
918 return [g * 255, g * 255, g * 255];
919 }
920
921 var pure = [0, 0, 0];
922 var hi = (h % 1) * 6;
923 var v = hi % 1;
924 var w = 1 - v;
925 var mg = 0;
926
927 switch (Math.floor(hi)) {
928 case 0:
929 pure[0] = 1; pure[1] = v; pure[2] = 0; break;
930 case 1:
931 pure[0] = w; pure[1] = 1; pure[2] = 0; break;
932 case 2:
933 pure[0] = 0; pure[1] = 1; pure[2] = v; break;
934 case 3:
935 pure[0] = 0; pure[1] = w; pure[2] = 1; break;
936 case 4:
937 pure[0] = v; pure[1] = 0; pure[2] = 1; break;
938 default:
939 pure[0] = 1; pure[1] = 0; pure[2] = w;
940 }
941
942 mg = (1.0 - c) * g;
943
944 return [
945 (c * pure[0] + mg) * 255,
946 (c * pure[1] + mg) * 255,
947 (c * pure[2] + mg) * 255
948 ];
949};
950
951convert.hcg.hsv = function (hcg) {
952 var c = hcg[1] / 100;
953 var g = hcg[2] / 100;
954
955 var v = c + g * (1.0 - c);
956 var f = 0;
957
958 if (v > 0.0) {
959 f = c / v;
960 }
961
962 return [hcg[0], f * 100, v * 100];
963};
964
965convert.hcg.hsl = function (hcg) {
966 var c = hcg[1] / 100;
967 var g = hcg[2] / 100;
968
969 var l = g * (1.0 - c) + 0.5 * c;
970 var s = 0;
971
972 if (l > 0.0 && l < 0.5) {
973 s = c / (2 * l);
974 } else
975 if (l >= 0.5 && l < 1.0) {
976 s = c / (2 * (1 - l));
977 }
978
979 return [hcg[0], s * 100, l * 100];
980};
981
982convert.hcg.hwb = function (hcg) {
983 var c = hcg[1] / 100;
984 var g = hcg[2] / 100;
985 var v = c + g * (1.0 - c);
986 return [hcg[0], (v - c) * 100, (1 - v) * 100];
987};
988
989convert.hwb.hcg = function (hwb) {
990 var w = hwb[1] / 100;
991 var b = hwb[2] / 100;
992 var v = 1 - b;
993 var c = v - w;
994 var g = 0;
995
996 if (c < 1) {
997 g = (v - c) / (1 - c);
998 }
999
1000 return [hwb[0], c * 100, g * 100];
1001};
1002
1003convert.apple.rgb = function (apple) {
1004 return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255];
1005};
1006
1007convert.rgb.apple = function (rgb) {
1008 return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535];
1009};
1010
1011convert.gray.rgb = function (args) {
1012 return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255];
1013};
1014
1015convert.gray.hsl = convert.gray.hsv = function (args) {
1016 return [0, 0, args[0]];
1017};
1018
1019convert.gray.hwb = function (gray) {
1020 return [0, 100, gray[0]];
1021};
1022
1023convert.gray.cmyk = function (gray) {
1024 return [0, 0, 0, gray[0]];
1025};
1026
1027convert.gray.lab = function (gray) {
1028 return [gray[0], 0, 0];
1029};
1030
1031convert.gray.hex = function (gray) {
1032 var val = Math.round(gray[0] / 100 * 255) & 0xFF;
1033 var integer = (val << 16) + (val << 8) + val;
1034
1035 var string = integer.toString(16).toUpperCase();
1036 return '000000'.substring(string.length) + string;
1037};
1038
1039convert.rgb.gray = function (rgb) {
1040 var val = (rgb[0] + rgb[1] + rgb[2]) / 3;
1041 return [val / 255 * 100];
1042};
1043});
1044var conversions_1 = conversions.rgb;
1045var conversions_2 = conversions.hsl;
1046var conversions_3 = conversions.hsv;
1047var conversions_4 = conversions.hwb;
1048var conversions_5 = conversions.cmyk;
1049var conversions_6 = conversions.xyz;
1050var conversions_7 = conversions.lab;
1051var conversions_8 = conversions.lch;
1052var conversions_9 = conversions.hex;
1053var conversions_10 = conversions.keyword;
1054var conversions_11 = conversions.ansi16;
1055var conversions_12 = conversions.ansi256;
1056var conversions_13 = conversions.hcg;
1057var conversions_14 = conversions.apple;
1058var conversions_15 = conversions.gray;
1059
1060/*
1061 this function routes a model to all other models.
1062
1063 all functions that are routed have a property `.conversion` attached
1064 to the returned synthetic function. This property is an array
1065 of strings, each with the steps in between the 'from' and 'to'
1066 color models (inclusive).
1067
1068 conversions that are not possible simply are not included.
1069*/
1070
1071function buildGraph() {
1072 var graph = {};
1073 // https://jsperf.com/object-keys-vs-for-in-with-closure/3
1074 var models = Object.keys(conversions);
1075
1076 for (var len = models.length, i = 0; i < len; i++) {
1077 graph[models[i]] = {
1078 // http://jsperf.com/1-vs-infinity
1079 // micro-opt, but this is simple.
1080 distance: -1,
1081 parent: null
1082 };
1083 }
1084
1085 return graph;
1086}
1087
1088// https://en.wikipedia.org/wiki/Breadth-first_search
1089function deriveBFS(fromModel) {
1090 var graph = buildGraph();
1091 var queue = [fromModel]; // unshift -> queue -> pop
1092
1093 graph[fromModel].distance = 0;
1094
1095 while (queue.length) {
1096 var current = queue.pop();
1097 var adjacents = Object.keys(conversions[current]);
1098
1099 for (var len = adjacents.length, i = 0; i < len; i++) {
1100 var adjacent = adjacents[i];
1101 var node = graph[adjacent];
1102
1103 if (node.distance === -1) {
1104 node.distance = graph[current].distance + 1;
1105 node.parent = current;
1106 queue.unshift(adjacent);
1107 }
1108 }
1109 }
1110
1111 return graph;
1112}
1113
1114function link(from, to) {
1115 return function (args) {
1116 return to(from(args));
1117 };
1118}
1119
1120function wrapConversion(toModel, graph) {
1121 var path = [graph[toModel].parent, toModel];
1122 var fn = conversions[graph[toModel].parent][toModel];
1123
1124 var cur = graph[toModel].parent;
1125 while (graph[cur].parent) {
1126 path.unshift(graph[cur].parent);
1127 fn = link(conversions[graph[cur].parent][cur], fn);
1128 cur = graph[cur].parent;
1129 }
1130
1131 fn.conversion = path;
1132 return fn;
1133}
1134
1135var route = function (fromModel) {
1136 var graph = deriveBFS(fromModel);
1137 var conversion = {};
1138
1139 var models = Object.keys(graph);
1140 for (var len = models.length, i = 0; i < len; i++) {
1141 var toModel = models[i];
1142 var node = graph[toModel];
1143
1144 if (node.parent === null) {
1145 // no possible conversion, or this node is the source model.
1146 continue;
1147 }
1148
1149 conversion[toModel] = wrapConversion(toModel, graph);
1150 }
1151
1152 return conversion;
1153};
1154
1155var convert = {};
1156
1157var models = Object.keys(conversions);
1158
1159function wrapRaw(fn) {
1160 var wrappedFn = function (args) {
1161 if (args === undefined || args === null) {
1162 return args;
1163 }
1164
1165 if (arguments.length > 1) {
1166 args = Array.prototype.slice.call(arguments);
1167 }
1168
1169 return fn(args);
1170 };
1171
1172 // preserve .conversion property if there is one
1173 if ('conversion' in fn) {
1174 wrappedFn.conversion = fn.conversion;
1175 }
1176
1177 return wrappedFn;
1178}
1179
1180function wrapRounded(fn) {
1181 var wrappedFn = function (args) {
1182 if (args === undefined || args === null) {
1183 return args;
1184 }
1185
1186 if (arguments.length > 1) {
1187 args = Array.prototype.slice.call(arguments);
1188 }
1189
1190 var result = fn(args);
1191
1192 // we're assuming the result is an array here.
1193 // see notice in conversions.js; don't use box types
1194 // in conversion functions.
1195 if (typeof result === 'object') {
1196 for (var len = result.length, i = 0; i < len; i++) {
1197 result[i] = Math.round(result[i]);
1198 }
1199 }
1200
1201 return result;
1202 };
1203
1204 // preserve .conversion property if there is one
1205 if ('conversion' in fn) {
1206 wrappedFn.conversion = fn.conversion;
1207 }
1208
1209 return wrappedFn;
1210}
1211
1212models.forEach(function (fromModel) {
1213 convert[fromModel] = {};
1214
1215 Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels});
1216 Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels});
1217
1218 var routes = route(fromModel);
1219 var routeModels = Object.keys(routes);
1220
1221 routeModels.forEach(function (toModel) {
1222 var fn = routes[toModel];
1223
1224 convert[fromModel][toModel] = wrapRounded(fn);
1225 convert[fromModel][toModel].raw = wrapRaw(fn);
1226 });
1227});
1228
1229var colorConvert = convert;
1230
1231var colorName$1 = {
1232 "aliceblue": [240, 248, 255],
1233 "antiquewhite": [250, 235, 215],
1234 "aqua": [0, 255, 255],
1235 "aquamarine": [127, 255, 212],
1236 "azure": [240, 255, 255],
1237 "beige": [245, 245, 220],
1238 "bisque": [255, 228, 196],
1239 "black": [0, 0, 0],
1240 "blanchedalmond": [255, 235, 205],
1241 "blue": [0, 0, 255],
1242 "blueviolet": [138, 43, 226],
1243 "brown": [165, 42, 42],
1244 "burlywood": [222, 184, 135],
1245 "cadetblue": [95, 158, 160],
1246 "chartreuse": [127, 255, 0],
1247 "chocolate": [210, 105, 30],
1248 "coral": [255, 127, 80],
1249 "cornflowerblue": [100, 149, 237],
1250 "cornsilk": [255, 248, 220],
1251 "crimson": [220, 20, 60],
1252 "cyan": [0, 255, 255],
1253 "darkblue": [0, 0, 139],
1254 "darkcyan": [0, 139, 139],
1255 "darkgoldenrod": [184, 134, 11],
1256 "darkgray": [169, 169, 169],
1257 "darkgreen": [0, 100, 0],
1258 "darkgrey": [169, 169, 169],
1259 "darkkhaki": [189, 183, 107],
1260 "darkmagenta": [139, 0, 139],
1261 "darkolivegreen": [85, 107, 47],
1262 "darkorange": [255, 140, 0],
1263 "darkorchid": [153, 50, 204],
1264 "darkred": [139, 0, 0],
1265 "darksalmon": [233, 150, 122],
1266 "darkseagreen": [143, 188, 143],
1267 "darkslateblue": [72, 61, 139],
1268 "darkslategray": [47, 79, 79],
1269 "darkslategrey": [47, 79, 79],
1270 "darkturquoise": [0, 206, 209],
1271 "darkviolet": [148, 0, 211],
1272 "deeppink": [255, 20, 147],
1273 "deepskyblue": [0, 191, 255],
1274 "dimgray": [105, 105, 105],
1275 "dimgrey": [105, 105, 105],
1276 "dodgerblue": [30, 144, 255],
1277 "firebrick": [178, 34, 34],
1278 "floralwhite": [255, 250, 240],
1279 "forestgreen": [34, 139, 34],
1280 "fuchsia": [255, 0, 255],
1281 "gainsboro": [220, 220, 220],
1282 "ghostwhite": [248, 248, 255],
1283 "gold": [255, 215, 0],
1284 "goldenrod": [218, 165, 32],
1285 "gray": [128, 128, 128],
1286 "green": [0, 128, 0],
1287 "greenyellow": [173, 255, 47],
1288 "grey": [128, 128, 128],
1289 "honeydew": [240, 255, 240],
1290 "hotpink": [255, 105, 180],
1291 "indianred": [205, 92, 92],
1292 "indigo": [75, 0, 130],
1293 "ivory": [255, 255, 240],
1294 "khaki": [240, 230, 140],
1295 "lavender": [230, 230, 250],
1296 "lavenderblush": [255, 240, 245],
1297 "lawngreen": [124, 252, 0],
1298 "lemonchiffon": [255, 250, 205],
1299 "lightblue": [173, 216, 230],
1300 "lightcoral": [240, 128, 128],
1301 "lightcyan": [224, 255, 255],
1302 "lightgoldenrodyellow": [250, 250, 210],
1303 "lightgray": [211, 211, 211],
1304 "lightgreen": [144, 238, 144],
1305 "lightgrey": [211, 211, 211],
1306 "lightpink": [255, 182, 193],
1307 "lightsalmon": [255, 160, 122],
1308 "lightseagreen": [32, 178, 170],
1309 "lightskyblue": [135, 206, 250],
1310 "lightslategray": [119, 136, 153],
1311 "lightslategrey": [119, 136, 153],
1312 "lightsteelblue": [176, 196, 222],
1313 "lightyellow": [255, 255, 224],
1314 "lime": [0, 255, 0],
1315 "limegreen": [50, 205, 50],
1316 "linen": [250, 240, 230],
1317 "magenta": [255, 0, 255],
1318 "maroon": [128, 0, 0],
1319 "mediumaquamarine": [102, 205, 170],
1320 "mediumblue": [0, 0, 205],
1321 "mediumorchid": [186, 85, 211],
1322 "mediumpurple": [147, 112, 219],
1323 "mediumseagreen": [60, 179, 113],
1324 "mediumslateblue": [123, 104, 238],
1325 "mediumspringgreen": [0, 250, 154],
1326 "mediumturquoise": [72, 209, 204],
1327 "mediumvioletred": [199, 21, 133],
1328 "midnightblue": [25, 25, 112],
1329 "mintcream": [245, 255, 250],
1330 "mistyrose": [255, 228, 225],
1331 "moccasin": [255, 228, 181],
1332 "navajowhite": [255, 222, 173],
1333 "navy": [0, 0, 128],
1334 "oldlace": [253, 245, 230],
1335 "olive": [128, 128, 0],
1336 "olivedrab": [107, 142, 35],
1337 "orange": [255, 165, 0],
1338 "orangered": [255, 69, 0],
1339 "orchid": [218, 112, 214],
1340 "palegoldenrod": [238, 232, 170],
1341 "palegreen": [152, 251, 152],
1342 "paleturquoise": [175, 238, 238],
1343 "palevioletred": [219, 112, 147],
1344 "papayawhip": [255, 239, 213],
1345 "peachpuff": [255, 218, 185],
1346 "peru": [205, 133, 63],
1347 "pink": [255, 192, 203],
1348 "plum": [221, 160, 221],
1349 "powderblue": [176, 224, 230],
1350 "purple": [128, 0, 128],
1351 "rebeccapurple": [102, 51, 153],
1352 "red": [255, 0, 0],
1353 "rosybrown": [188, 143, 143],
1354 "royalblue": [65, 105, 225],
1355 "saddlebrown": [139, 69, 19],
1356 "salmon": [250, 128, 114],
1357 "sandybrown": [244, 164, 96],
1358 "seagreen": [46, 139, 87],
1359 "seashell": [255, 245, 238],
1360 "sienna": [160, 82, 45],
1361 "silver": [192, 192, 192],
1362 "skyblue": [135, 206, 235],
1363 "slateblue": [106, 90, 205],
1364 "slategray": [112, 128, 144],
1365 "slategrey": [112, 128, 144],
1366 "snow": [255, 250, 250],
1367 "springgreen": [0, 255, 127],
1368 "steelblue": [70, 130, 180],
1369 "tan": [210, 180, 140],
1370 "teal": [0, 128, 128],
1371 "thistle": [216, 191, 216],
1372 "tomato": [255, 99, 71],
1373 "turquoise": [64, 224, 208],
1374 "violet": [238, 130, 238],
1375 "wheat": [245, 222, 179],
1376 "white": [255, 255, 255],
1377 "whitesmoke": [245, 245, 245],
1378 "yellow": [255, 255, 0],
1379 "yellowgreen": [154, 205, 50]
1380};
1381
1382/* MIT license */
1383
1384
1385var colorString = {
1386 getRgba: getRgba,
1387 getHsla: getHsla,
1388 getRgb: getRgb,
1389 getHsl: getHsl,
1390 getHwb: getHwb,
1391 getAlpha: getAlpha,
1392
1393 hexString: hexString,
1394 rgbString: rgbString,
1395 rgbaString: rgbaString,
1396 percentString: percentString,
1397 percentaString: percentaString,
1398 hslString: hslString,
1399 hslaString: hslaString,
1400 hwbString: hwbString,
1401 keyword: keyword
1402};
1403
1404function getRgba(string) {
1405 if (!string) {
1406 return;
1407 }
1408 var abbr = /^#([a-fA-F0-9]{3,4})$/i,
1409 hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i,
1410 rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
1411 per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
1412 keyword = /(\w+)/;
1413
1414 var rgb = [0, 0, 0],
1415 a = 1,
1416 match = string.match(abbr),
1417 hexAlpha = "";
1418 if (match) {
1419 match = match[1];
1420 hexAlpha = match[3];
1421 for (var i = 0; i < rgb.length; i++) {
1422 rgb[i] = parseInt(match[i] + match[i], 16);
1423 }
1424 if (hexAlpha) {
1425 a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100;
1426 }
1427 }
1428 else if (match = string.match(hex)) {
1429 hexAlpha = match[2];
1430 match = match[1];
1431 for (var i = 0; i < rgb.length; i++) {
1432 rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
1433 }
1434 if (hexAlpha) {
1435 a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100;
1436 }
1437 }
1438 else if (match = string.match(rgba)) {
1439 for (var i = 0; i < rgb.length; i++) {
1440 rgb[i] = parseInt(match[i + 1]);
1441 }
1442 a = parseFloat(match[4]);
1443 }
1444 else if (match = string.match(per)) {
1445 for (var i = 0; i < rgb.length; i++) {
1446 rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
1447 }
1448 a = parseFloat(match[4]);
1449 }
1450 else if (match = string.match(keyword)) {
1451 if (match[1] == "transparent") {
1452 return [0, 0, 0, 0];
1453 }
1454 rgb = colorName$1[match[1]];
1455 if (!rgb) {
1456 return;
1457 }
1458 }
1459
1460 for (var i = 0; i < rgb.length; i++) {
1461 rgb[i] = scale(rgb[i], 0, 255);
1462 }
1463 if (!a && a != 0) {
1464 a = 1;
1465 }
1466 else {
1467 a = scale(a, 0, 1);
1468 }
1469 rgb[3] = a;
1470 return rgb;
1471}
1472
1473function getHsla(string) {
1474 if (!string) {
1475 return;
1476 }
1477 var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
1478 var match = string.match(hsl);
1479 if (match) {
1480 var alpha = parseFloat(match[4]);
1481 var h = scale(parseInt(match[1]), 0, 360),
1482 s = scale(parseFloat(match[2]), 0, 100),
1483 l = scale(parseFloat(match[3]), 0, 100),
1484 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
1485 return [h, s, l, a];
1486 }
1487}
1488
1489function getHwb(string) {
1490 if (!string) {
1491 return;
1492 }
1493 var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
1494 var match = string.match(hwb);
1495 if (match) {
1496 var alpha = parseFloat(match[4]);
1497 var h = scale(parseInt(match[1]), 0, 360),
1498 w = scale(parseFloat(match[2]), 0, 100),
1499 b = scale(parseFloat(match[3]), 0, 100),
1500 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
1501 return [h, w, b, a];
1502 }
1503}
1504
1505function getRgb(string) {
1506 var rgba = getRgba(string);
1507 return rgba && rgba.slice(0, 3);
1508}
1509
1510function getHsl(string) {
1511 var hsla = getHsla(string);
1512 return hsla && hsla.slice(0, 3);
1513}
1514
1515function getAlpha(string) {
1516 var vals = getRgba(string);
1517 if (vals) {
1518 return vals[3];
1519 }
1520 else if (vals = getHsla(string)) {
1521 return vals[3];
1522 }
1523 else if (vals = getHwb(string)) {
1524 return vals[3];
1525 }
1526}
1527
1528// generators
1529function hexString(rgba, a) {
1530 var a = (a !== undefined && rgba.length === 3) ? a : rgba[3];
1531 return "#" + hexDouble(rgba[0])
1532 + hexDouble(rgba[1])
1533 + hexDouble(rgba[2])
1534 + (
1535 (a >= 0 && a < 1)
1536 ? hexDouble(Math.round(a * 255))
1537 : ""
1538 );
1539}
1540
1541function rgbString(rgba, alpha) {
1542 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
1543 return rgbaString(rgba, alpha);
1544 }
1545 return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
1546}
1547
1548function rgbaString(rgba, alpha) {
1549 if (alpha === undefined) {
1550 alpha = (rgba[3] !== undefined ? rgba[3] : 1);
1551 }
1552 return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
1553 + ", " + alpha + ")";
1554}
1555
1556function percentString(rgba, alpha) {
1557 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
1558 return percentaString(rgba, alpha);
1559 }
1560 var r = Math.round(rgba[0]/255 * 100),
1561 g = Math.round(rgba[1]/255 * 100),
1562 b = Math.round(rgba[2]/255 * 100);
1563
1564 return "rgb(" + r + "%, " + g + "%, " + b + "%)";
1565}
1566
1567function percentaString(rgba, alpha) {
1568 var r = Math.round(rgba[0]/255 * 100),
1569 g = Math.round(rgba[1]/255 * 100),
1570 b = Math.round(rgba[2]/255 * 100);
1571 return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
1572}
1573
1574function hslString(hsla, alpha) {
1575 if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
1576 return hslaString(hsla, alpha);
1577 }
1578 return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
1579}
1580
1581function hslaString(hsla, alpha) {
1582 if (alpha === undefined) {
1583 alpha = (hsla[3] !== undefined ? hsla[3] : 1);
1584 }
1585 return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
1586 + alpha + ")";
1587}
1588
1589// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
1590// (hwb have alpha optional & 1 is default value)
1591function hwbString(hwb, alpha) {
1592 if (alpha === undefined) {
1593 alpha = (hwb[3] !== undefined ? hwb[3] : 1);
1594 }
1595 return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
1596 + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
1597}
1598
1599function keyword(rgb) {
1600 return reverseNames[rgb.slice(0, 3)];
1601}
1602
1603// helpers
1604function scale(num, min, max) {
1605 return Math.min(Math.max(min, num), max);
1606}
1607
1608function hexDouble(num) {
1609 var str = num.toString(16).toUpperCase();
1610 return (str.length < 2) ? "0" + str : str;
1611}
1612
1613
1614//create a list of reverse color names
1615var reverseNames = {};
1616for (var name in colorName$1) {
1617 reverseNames[colorName$1[name]] = name;
1618}
1619
1620/* MIT license */
1621
1622
1623
1624var Color = function (obj) {
1625 if (obj instanceof Color) {
1626 return obj;
1627 }
1628 if (!(this instanceof Color)) {
1629 return new Color(obj);
1630 }
1631
1632 this.valid = false;
1633 this.values = {
1634 rgb: [0, 0, 0],
1635 hsl: [0, 0, 0],
1636 hsv: [0, 0, 0],
1637 hwb: [0, 0, 0],
1638 cmyk: [0, 0, 0, 0],
1639 alpha: 1
1640 };
1641
1642 // parse Color() argument
1643 var vals;
1644 if (typeof obj === 'string') {
1645 vals = colorString.getRgba(obj);
1646 if (vals) {
1647 this.setValues('rgb', vals);
1648 } else if (vals = colorString.getHsla(obj)) {
1649 this.setValues('hsl', vals);
1650 } else if (vals = colorString.getHwb(obj)) {
1651 this.setValues('hwb', vals);
1652 }
1653 } else if (typeof obj === 'object') {
1654 vals = obj;
1655 if (vals.r !== undefined || vals.red !== undefined) {
1656 this.setValues('rgb', vals);
1657 } else if (vals.l !== undefined || vals.lightness !== undefined) {
1658 this.setValues('hsl', vals);
1659 } else if (vals.v !== undefined || vals.value !== undefined) {
1660 this.setValues('hsv', vals);
1661 } else if (vals.w !== undefined || vals.whiteness !== undefined) {
1662 this.setValues('hwb', vals);
1663 } else if (vals.c !== undefined || vals.cyan !== undefined) {
1664 this.setValues('cmyk', vals);
1665 }
1666 }
1667};
1668
1669Color.prototype = {
1670 isValid: function () {
1671 return this.valid;
1672 },
1673 rgb: function () {
1674 return this.setSpace('rgb', arguments);
1675 },
1676 hsl: function () {
1677 return this.setSpace('hsl', arguments);
1678 },
1679 hsv: function () {
1680 return this.setSpace('hsv', arguments);
1681 },
1682 hwb: function () {
1683 return this.setSpace('hwb', arguments);
1684 },
1685 cmyk: function () {
1686 return this.setSpace('cmyk', arguments);
1687 },
1688
1689 rgbArray: function () {
1690 return this.values.rgb;
1691 },
1692 hslArray: function () {
1693 return this.values.hsl;
1694 },
1695 hsvArray: function () {
1696 return this.values.hsv;
1697 },
1698 hwbArray: function () {
1699 var values = this.values;
1700 if (values.alpha !== 1) {
1701 return values.hwb.concat([values.alpha]);
1702 }
1703 return values.hwb;
1704 },
1705 cmykArray: function () {
1706 return this.values.cmyk;
1707 },
1708 rgbaArray: function () {
1709 var values = this.values;
1710 return values.rgb.concat([values.alpha]);
1711 },
1712 hslaArray: function () {
1713 var values = this.values;
1714 return values.hsl.concat([values.alpha]);
1715 },
1716 alpha: function (val) {
1717 if (val === undefined) {
1718 return this.values.alpha;
1719 }
1720 this.setValues('alpha', val);
1721 return this;
1722 },
1723
1724 red: function (val) {
1725 return this.setChannel('rgb', 0, val);
1726 },
1727 green: function (val) {
1728 return this.setChannel('rgb', 1, val);
1729 },
1730 blue: function (val) {
1731 return this.setChannel('rgb', 2, val);
1732 },
1733 hue: function (val) {
1734 if (val) {
1735 val %= 360;
1736 val = val < 0 ? 360 + val : val;
1737 }
1738 return this.setChannel('hsl', 0, val);
1739 },
1740 saturation: function (val) {
1741 return this.setChannel('hsl', 1, val);
1742 },
1743 lightness: function (val) {
1744 return this.setChannel('hsl', 2, val);
1745 },
1746 saturationv: function (val) {
1747 return this.setChannel('hsv', 1, val);
1748 },
1749 whiteness: function (val) {
1750 return this.setChannel('hwb', 1, val);
1751 },
1752 blackness: function (val) {
1753 return this.setChannel('hwb', 2, val);
1754 },
1755 value: function (val) {
1756 return this.setChannel('hsv', 2, val);
1757 },
1758 cyan: function (val) {
1759 return this.setChannel('cmyk', 0, val);
1760 },
1761 magenta: function (val) {
1762 return this.setChannel('cmyk', 1, val);
1763 },
1764 yellow: function (val) {
1765 return this.setChannel('cmyk', 2, val);
1766 },
1767 black: function (val) {
1768 return this.setChannel('cmyk', 3, val);
1769 },
1770
1771 hexString: function () {
1772 return colorString.hexString(this.values.rgb);
1773 },
1774 rgbString: function () {
1775 return colorString.rgbString(this.values.rgb, this.values.alpha);
1776 },
1777 rgbaString: function () {
1778 return colorString.rgbaString(this.values.rgb, this.values.alpha);
1779 },
1780 percentString: function () {
1781 return colorString.percentString(this.values.rgb, this.values.alpha);
1782 },
1783 hslString: function () {
1784 return colorString.hslString(this.values.hsl, this.values.alpha);
1785 },
1786 hslaString: function () {
1787 return colorString.hslaString(this.values.hsl, this.values.alpha);
1788 },
1789 hwbString: function () {
1790 return colorString.hwbString(this.values.hwb, this.values.alpha);
1791 },
1792 keyword: function () {
1793 return colorString.keyword(this.values.rgb, this.values.alpha);
1794 },
1795
1796 rgbNumber: function () {
1797 var rgb = this.values.rgb;
1798 return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
1799 },
1800
1801 luminosity: function () {
1802 // http://www.w3.org/TR/WCAG20/#relativeluminancedef
1803 var rgb = this.values.rgb;
1804 var lum = [];
1805 for (var i = 0; i < rgb.length; i++) {
1806 var chan = rgb[i] / 255;
1807 lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
1808 }
1809 return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
1810 },
1811
1812 contrast: function (color2) {
1813 // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
1814 var lum1 = this.luminosity();
1815 var lum2 = color2.luminosity();
1816 if (lum1 > lum2) {
1817 return (lum1 + 0.05) / (lum2 + 0.05);
1818 }
1819 return (lum2 + 0.05) / (lum1 + 0.05);
1820 },
1821
1822 level: function (color2) {
1823 var contrastRatio = this.contrast(color2);
1824 if (contrastRatio >= 7.1) {
1825 return 'AAA';
1826 }
1827
1828 return (contrastRatio >= 4.5) ? 'AA' : '';
1829 },
1830
1831 dark: function () {
1832 // YIQ equation from http://24ways.org/2010/calculating-color-contrast
1833 var rgb = this.values.rgb;
1834 var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
1835 return yiq < 128;
1836 },
1837
1838 light: function () {
1839 return !this.dark();
1840 },
1841
1842 negate: function () {
1843 var rgb = [];
1844 for (var i = 0; i < 3; i++) {
1845 rgb[i] = 255 - this.values.rgb[i];
1846 }
1847 this.setValues('rgb', rgb);
1848 return this;
1849 },
1850
1851 lighten: function (ratio) {
1852 var hsl = this.values.hsl;
1853 hsl[2] += hsl[2] * ratio;
1854 this.setValues('hsl', hsl);
1855 return this;
1856 },
1857
1858 darken: function (ratio) {
1859 var hsl = this.values.hsl;
1860 hsl[2] -= hsl[2] * ratio;
1861 this.setValues('hsl', hsl);
1862 return this;
1863 },
1864
1865 saturate: function (ratio) {
1866 var hsl = this.values.hsl;
1867 hsl[1] += hsl[1] * ratio;
1868 this.setValues('hsl', hsl);
1869 return this;
1870 },
1871
1872 desaturate: function (ratio) {
1873 var hsl = this.values.hsl;
1874 hsl[1] -= hsl[1] * ratio;
1875 this.setValues('hsl', hsl);
1876 return this;
1877 },
1878
1879 whiten: function (ratio) {
1880 var hwb = this.values.hwb;
1881 hwb[1] += hwb[1] * ratio;
1882 this.setValues('hwb', hwb);
1883 return this;
1884 },
1885
1886 blacken: function (ratio) {
1887 var hwb = this.values.hwb;
1888 hwb[2] += hwb[2] * ratio;
1889 this.setValues('hwb', hwb);
1890 return this;
1891 },
1892
1893 greyscale: function () {
1894 var rgb = this.values.rgb;
1895 // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
1896 var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
1897 this.setValues('rgb', [val, val, val]);
1898 return this;
1899 },
1900
1901 clearer: function (ratio) {
1902 var alpha = this.values.alpha;
1903 this.setValues('alpha', alpha - (alpha * ratio));
1904 return this;
1905 },
1906
1907 opaquer: function (ratio) {
1908 var alpha = this.values.alpha;
1909 this.setValues('alpha', alpha + (alpha * ratio));
1910 return this;
1911 },
1912
1913 rotate: function (degrees) {
1914 var hsl = this.values.hsl;
1915 var hue = (hsl[0] + degrees) % 360;
1916 hsl[0] = hue < 0 ? 360 + hue : hue;
1917 this.setValues('hsl', hsl);
1918 return this;
1919 },
1920
1921 /**
1922 * Ported from sass implementation in C
1923 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
1924 */
1925 mix: function (mixinColor, weight) {
1926 var color1 = this;
1927 var color2 = mixinColor;
1928 var p = weight === undefined ? 0.5 : weight;
1929
1930 var w = 2 * p - 1;
1931 var a = color1.alpha() - color2.alpha();
1932
1933 var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
1934 var w2 = 1 - w1;
1935
1936 return this
1937 .rgb(
1938 w1 * color1.red() + w2 * color2.red(),
1939 w1 * color1.green() + w2 * color2.green(),
1940 w1 * color1.blue() + w2 * color2.blue()
1941 )
1942 .alpha(color1.alpha() * p + color2.alpha() * (1 - p));
1943 },
1944
1945 toJSON: function () {
1946 return this.rgb();
1947 },
1948
1949 clone: function () {
1950 // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
1951 // making the final build way to big to embed in Chart.js. So let's do it manually,
1952 // assuming that values to clone are 1 dimension arrays containing only numbers,
1953 // except 'alpha' which is a number.
1954 var result = new Color();
1955 var source = this.values;
1956 var target = result.values;
1957 var value, type;
1958
1959 for (var prop in source) {
1960 if (source.hasOwnProperty(prop)) {
1961 value = source[prop];
1962 type = ({}).toString.call(value);
1963 if (type === '[object Array]') {
1964 target[prop] = value.slice(0);
1965 } else if (type === '[object Number]') {
1966 target[prop] = value;
1967 } else {
1968 console.error('unexpected color value:', value);
1969 }
1970 }
1971 }
1972
1973 return result;
1974 }
1975};
1976
1977Color.prototype.spaces = {
1978 rgb: ['red', 'green', 'blue'],
1979 hsl: ['hue', 'saturation', 'lightness'],
1980 hsv: ['hue', 'saturation', 'value'],
1981 hwb: ['hue', 'whiteness', 'blackness'],
1982 cmyk: ['cyan', 'magenta', 'yellow', 'black']
1983};
1984
1985Color.prototype.maxes = {
1986 rgb: [255, 255, 255],
1987 hsl: [360, 100, 100],
1988 hsv: [360, 100, 100],
1989 hwb: [360, 100, 100],
1990 cmyk: [100, 100, 100, 100]
1991};
1992
1993Color.prototype.getValues = function (space) {
1994 var values = this.values;
1995 var vals = {};
1996
1997 for (var i = 0; i < space.length; i++) {
1998 vals[space.charAt(i)] = values[space][i];
1999 }
2000
2001 if (values.alpha !== 1) {
2002 vals.a = values.alpha;
2003 }
2004
2005 // {r: 255, g: 255, b: 255, a: 0.4}
2006 return vals;
2007};
2008
2009Color.prototype.setValues = function (space, vals) {
2010 var values = this.values;
2011 var spaces = this.spaces;
2012 var maxes = this.maxes;
2013 var alpha = 1;
2014 var i;
2015
2016 this.valid = true;
2017
2018 if (space === 'alpha') {
2019 alpha = vals;
2020 } else if (vals.length) {
2021 // [10, 10, 10]
2022 values[space] = vals.slice(0, space.length);
2023 alpha = vals[space.length];
2024 } else if (vals[space.charAt(0)] !== undefined) {
2025 // {r: 10, g: 10, b: 10}
2026 for (i = 0; i < space.length; i++) {
2027 values[space][i] = vals[space.charAt(i)];
2028 }
2029
2030 alpha = vals.a;
2031 } else if (vals[spaces[space][0]] !== undefined) {
2032 // {red: 10, green: 10, blue: 10}
2033 var chans = spaces[space];
2034
2035 for (i = 0; i < space.length; i++) {
2036 values[space][i] = vals[chans[i]];
2037 }
2038
2039 alpha = vals.alpha;
2040 }
2041
2042 values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
2043
2044 if (space === 'alpha') {
2045 return false;
2046 }
2047
2048 var capped;
2049
2050 // cap values of the space prior converting all values
2051 for (i = 0; i < space.length; i++) {
2052 capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
2053 values[space][i] = Math.round(capped);
2054 }
2055
2056 // convert to all the other color spaces
2057 for (var sname in spaces) {
2058 if (sname !== space) {
2059 values[sname] = colorConvert[space][sname](values[space]);
2060 }
2061 }
2062
2063 return true;
2064};
2065
2066Color.prototype.setSpace = function (space, args) {
2067 var vals = args[0];
2068
2069 if (vals === undefined) {
2070 // color.rgb()
2071 return this.getValues(space);
2072 }
2073
2074 // color.rgb(10, 10, 10)
2075 if (typeof vals === 'number') {
2076 vals = Array.prototype.slice.call(args);
2077 }
2078
2079 this.setValues(space, vals);
2080 return this;
2081};
2082
2083Color.prototype.setChannel = function (space, index, val) {
2084 var svalues = this.values[space];
2085 if (val === undefined) {
2086 // color.red()
2087 return svalues[index];
2088 } else if (val === svalues[index]) {
2089 // color.red(color.red())
2090 return this;
2091 }
2092
2093 // color.red(100)
2094 svalues[index] = val;
2095 this.setValues(space, svalues);
2096
2097 return this;
2098};
2099
2100if (typeof window !== 'undefined') {
2101 window.Color = Color;
2102}
2103
2104var chartjsColor = Color;
2105
2106/**
2107 * @namespace Chart.helpers
2108 */
2109var helpers = {
2110 /**
2111 * An empty function that can be used, for example, for optional callback.
2112 */
2113 noop: function() {},
2114
2115 /**
2116 * Returns a unique id, sequentially generated from a global variable.
2117 * @returns {number}
2118 * @function
2119 */
2120 uid: (function() {
2121 var id = 0;
2122 return function() {
2123 return id++;
2124 };
2125 }()),
2126
2127 /**
2128 * Returns true if `value` is neither null nor undefined, else returns false.
2129 * @param {*} value - The value to test.
2130 * @returns {boolean}
2131 * @since 2.7.0
2132 */
2133 isNullOrUndef: function(value) {
2134 return value === null || typeof value === 'undefined';
2135 },
2136
2137 /**
2138 * Returns true if `value` is an array (including typed arrays), else returns false.
2139 * @param {*} value - The value to test.
2140 * @returns {boolean}
2141 * @function
2142 */
2143 isArray: function(value) {
2144 if (Array.isArray && Array.isArray(value)) {
2145 return true;
2146 }
2147 var type = Object.prototype.toString.call(value);
2148 if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') {
2149 return true;
2150 }
2151 return false;
2152 },
2153
2154 /**
2155 * Returns true if `value` is an object (excluding null), else returns false.
2156 * @param {*} value - The value to test.
2157 * @returns {boolean}
2158 * @since 2.7.0
2159 */
2160 isObject: function(value) {
2161 return value !== null && Object.prototype.toString.call(value) === '[object Object]';
2162 },
2163
2164 /**
2165 * Returns true if `value` is a finite number, else returns false
2166 * @param {*} value - The value to test.
2167 * @returns {boolean}
2168 */
2169 isFinite: function(value) {
2170 return (typeof value === 'number' || value instanceof Number) && isFinite(value);
2171 },
2172
2173 /**
2174 * Returns `value` if defined, else returns `defaultValue`.
2175 * @param {*} value - The value to return if defined.
2176 * @param {*} defaultValue - The value to return if `value` is undefined.
2177 * @returns {*}
2178 */
2179 valueOrDefault: function(value, defaultValue) {
2180 return typeof value === 'undefined' ? defaultValue : value;
2181 },
2182
2183 /**
2184 * Returns value at the given `index` in array if defined, else returns `defaultValue`.
2185 * @param {Array} value - The array to lookup for value at `index`.
2186 * @param {number} index - The index in `value` to lookup for value.
2187 * @param {*} defaultValue - The value to return if `value[index]` is undefined.
2188 * @returns {*}
2189 */
2190 valueAtIndexOrDefault: function(value, index, defaultValue) {
2191 return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
2192 },
2193
2194 /**
2195 * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
2196 * value returned by `fn`. If `fn` is not a function, this method returns undefined.
2197 * @param {function} fn - The function to call.
2198 * @param {Array|undefined|null} args - The arguments with which `fn` should be called.
2199 * @param {object} [thisArg] - The value of `this` provided for the call to `fn`.
2200 * @returns {*}
2201 */
2202 callback: function(fn, args, thisArg) {
2203 if (fn && typeof fn.call === 'function') {
2204 return fn.apply(thisArg, args);
2205 }
2206 },
2207
2208 /**
2209 * Note(SB) for performance sake, this method should only be used when loopable type
2210 * is unknown or in none intensive code (not called often and small loopable). Else
2211 * it's preferable to use a regular for() loop and save extra function calls.
2212 * @param {object|Array} loopable - The object or array to be iterated.
2213 * @param {function} fn - The function to call for each item.
2214 * @param {object} [thisArg] - The value of `this` provided for the call to `fn`.
2215 * @param {boolean} [reverse] - If true, iterates backward on the loopable.
2216 */
2217 each: function(loopable, fn, thisArg, reverse) {
2218 var i, len, keys;
2219 if (helpers.isArray(loopable)) {
2220 len = loopable.length;
2221 if (reverse) {
2222 for (i = len - 1; i >= 0; i--) {
2223 fn.call(thisArg, loopable[i], i);
2224 }
2225 } else {
2226 for (i = 0; i < len; i++) {
2227 fn.call(thisArg, loopable[i], i);
2228 }
2229 }
2230 } else if (helpers.isObject(loopable)) {
2231 keys = Object.keys(loopable);
2232 len = keys.length;
2233 for (i = 0; i < len; i++) {
2234 fn.call(thisArg, loopable[keys[i]], keys[i]);
2235 }
2236 }
2237 },
2238
2239 /**
2240 * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
2241 * @see https://stackoverflow.com/a/14853974
2242 * @param {Array} a0 - The array to compare
2243 * @param {Array} a1 - The array to compare
2244 * @returns {boolean}
2245 */
2246 arrayEquals: function(a0, a1) {
2247 var i, ilen, v0, v1;
2248
2249 if (!a0 || !a1 || a0.length !== a1.length) {
2250 return false;
2251 }
2252
2253 for (i = 0, ilen = a0.length; i < ilen; ++i) {
2254 v0 = a0[i];
2255 v1 = a1[i];
2256
2257 if (v0 instanceof Array && v1 instanceof Array) {
2258 if (!helpers.arrayEquals(v0, v1)) {
2259 return false;
2260 }
2261 } else if (v0 !== v1) {
2262 // NOTE: two different object instances will never be equal: {x:20} != {x:20}
2263 return false;
2264 }
2265 }
2266
2267 return true;
2268 },
2269
2270 /**
2271 * Returns a deep copy of `source` without keeping references on objects and arrays.
2272 * @param {*} source - The value to clone.
2273 * @returns {*}
2274 */
2275 clone: function(source) {
2276 if (helpers.isArray(source)) {
2277 return source.map(helpers.clone);
2278 }
2279
2280 if (helpers.isObject(source)) {
2281 var target = {};
2282 var keys = Object.keys(source);
2283 var klen = keys.length;
2284 var k = 0;
2285
2286 for (; k < klen; ++k) {
2287 target[keys[k]] = helpers.clone(source[keys[k]]);
2288 }
2289
2290 return target;
2291 }
2292
2293 return source;
2294 },
2295
2296 /**
2297 * The default merger when Chart.helpers.merge is called without merger option.
2298 * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback.
2299 * @private
2300 */
2301 _merger: function(key, target, source, options) {
2302 var tval = target[key];
2303 var sval = source[key];
2304
2305 if (helpers.isObject(tval) && helpers.isObject(sval)) {
2306 helpers.merge(tval, sval, options);
2307 } else {
2308 target[key] = helpers.clone(sval);
2309 }
2310 },
2311
2312 /**
2313 * Merges source[key] in target[key] only if target[key] is undefined.
2314 * @private
2315 */
2316 _mergerIf: function(key, target, source) {
2317 var tval = target[key];
2318 var sval = source[key];
2319
2320 if (helpers.isObject(tval) && helpers.isObject(sval)) {
2321 helpers.mergeIf(tval, sval);
2322 } else if (!target.hasOwnProperty(key)) {
2323 target[key] = helpers.clone(sval);
2324 }
2325 },
2326
2327 /**
2328 * Recursively deep copies `source` properties into `target` with the given `options`.
2329 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
2330 * @param {object} target - The target object in which all sources are merged into.
2331 * @param {object|object[]} source - Object(s) to merge into `target`.
2332 * @param {object} [options] - Merging options:
2333 * @param {function} [options.merger] - The merge method (key, target, source, options)
2334 * @returns {object} The `target` object.
2335 */
2336 merge: function(target, source, options) {
2337 var sources = helpers.isArray(source) ? source : [source];
2338 var ilen = sources.length;
2339 var merge, i, keys, klen, k;
2340
2341 if (!helpers.isObject(target)) {
2342 return target;
2343 }
2344
2345 options = options || {};
2346 merge = options.merger || helpers._merger;
2347
2348 for (i = 0; i < ilen; ++i) {
2349 source = sources[i];
2350 if (!helpers.isObject(source)) {
2351 continue;
2352 }
2353
2354 keys = Object.keys(source);
2355 for (k = 0, klen = keys.length; k < klen; ++k) {
2356 merge(keys[k], target, source, options);
2357 }
2358 }
2359
2360 return target;
2361 },
2362
2363 /**
2364 * Recursively deep copies `source` properties into `target` *only* if not defined in target.
2365 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
2366 * @param {object} target - The target object in which all sources are merged into.
2367 * @param {object|object[]} source - Object(s) to merge into `target`.
2368 * @returns {object} The `target` object.
2369 */
2370 mergeIf: function(target, source) {
2371 return helpers.merge(target, source, {merger: helpers._mergerIf});
2372 },
2373
2374 /**
2375 * Applies the contents of two or more objects together into the first object.
2376 * @param {object} target - The target object in which all objects are merged into.
2377 * @param {object} arg1 - Object containing additional properties to merge in target.
2378 * @param {object} argN - Additional objects containing properties to merge in target.
2379 * @returns {object} The `target` object.
2380 */
2381 extend: Object.assign || function(target) {
2382 return helpers.merge(target, [].slice.call(arguments, 1), {
2383 merger: function(key, dst, src) {
2384 dst[key] = src[key];
2385 }
2386 });
2387 },
2388
2389 /**
2390 * Basic javascript inheritance based on the model created in Backbone.js
2391 */
2392 inherits: function(extensions) {
2393 var me = this;
2394 var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
2395 return me.apply(this, arguments);
2396 };
2397
2398 var Surrogate = function() {
2399 this.constructor = ChartElement;
2400 };
2401
2402 Surrogate.prototype = me.prototype;
2403 ChartElement.prototype = new Surrogate();
2404 ChartElement.extend = helpers.inherits;
2405
2406 if (extensions) {
2407 helpers.extend(ChartElement.prototype, extensions);
2408 }
2409
2410 ChartElement.__super__ = me.prototype;
2411 return ChartElement;
2412 },
2413
2414 _deprecated: function(scope, value, previous, current) {
2415 if (value !== undefined) {
2416 console.warn(scope + ': "' + previous +
2417 '" is deprecated. Please use "' + current + '" instead');
2418 }
2419 }
2420};
2421
2422var helpers_core = helpers;
2423
2424// DEPRECATIONS
2425
2426/**
2427 * Provided for backward compatibility, use Chart.helpers.callback instead.
2428 * @function Chart.helpers.callCallback
2429 * @deprecated since version 2.6.0
2430 * @todo remove at version 3
2431 * @private
2432 */
2433helpers.callCallback = helpers.callback;
2434
2435/**
2436 * Provided for backward compatibility, use Array.prototype.indexOf instead.
2437 * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
2438 * @function Chart.helpers.indexOf
2439 * @deprecated since version 2.7.0
2440 * @todo remove at version 3
2441 * @private
2442 */
2443helpers.indexOf = function(array, item, fromIndex) {
2444 return Array.prototype.indexOf.call(array, item, fromIndex);
2445};
2446
2447/**
2448 * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
2449 * @function Chart.helpers.getValueOrDefault
2450 * @deprecated since version 2.7.0
2451 * @todo remove at version 3
2452 * @private
2453 */
2454helpers.getValueOrDefault = helpers.valueOrDefault;
2455
2456/**
2457 * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
2458 * @function Chart.helpers.getValueAtIndexOrDefault
2459 * @deprecated since version 2.7.0
2460 * @todo remove at version 3
2461 * @private
2462 */
2463helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2464
2465/**
2466 * Easing functions adapted from Robert Penner's easing equations.
2467 * @namespace Chart.helpers.easingEffects
2468 * @see http://www.robertpenner.com/easing/
2469 */
2470var effects = {
2471 linear: function(t) {
2472 return t;
2473 },
2474
2475 easeInQuad: function(t) {
2476 return t * t;
2477 },
2478
2479 easeOutQuad: function(t) {
2480 return -t * (t - 2);
2481 },
2482
2483 easeInOutQuad: function(t) {
2484 if ((t /= 0.5) < 1) {
2485 return 0.5 * t * t;
2486 }
2487 return -0.5 * ((--t) * (t - 2) - 1);
2488 },
2489
2490 easeInCubic: function(t) {
2491 return t * t * t;
2492 },
2493
2494 easeOutCubic: function(t) {
2495 return (t = t - 1) * t * t + 1;
2496 },
2497
2498 easeInOutCubic: function(t) {
2499 if ((t /= 0.5) < 1) {
2500 return 0.5 * t * t * t;
2501 }
2502 return 0.5 * ((t -= 2) * t * t + 2);
2503 },
2504
2505 easeInQuart: function(t) {
2506 return t * t * t * t;
2507 },
2508
2509 easeOutQuart: function(t) {
2510 return -((t = t - 1) * t * t * t - 1);
2511 },
2512
2513 easeInOutQuart: function(t) {
2514 if ((t /= 0.5) < 1) {
2515 return 0.5 * t * t * t * t;
2516 }
2517 return -0.5 * ((t -= 2) * t * t * t - 2);
2518 },
2519
2520 easeInQuint: function(t) {
2521 return t * t * t * t * t;
2522 },
2523
2524 easeOutQuint: function(t) {
2525 return (t = t - 1) * t * t * t * t + 1;
2526 },
2527
2528 easeInOutQuint: function(t) {
2529 if ((t /= 0.5) < 1) {
2530 return 0.5 * t * t * t * t * t;
2531 }
2532 return 0.5 * ((t -= 2) * t * t * t * t + 2);
2533 },
2534
2535 easeInSine: function(t) {
2536 return -Math.cos(t * (Math.PI / 2)) + 1;
2537 },
2538
2539 easeOutSine: function(t) {
2540 return Math.sin(t * (Math.PI / 2));
2541 },
2542
2543 easeInOutSine: function(t) {
2544 return -0.5 * (Math.cos(Math.PI * t) - 1);
2545 },
2546
2547 easeInExpo: function(t) {
2548 return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
2549 },
2550
2551 easeOutExpo: function(t) {
2552 return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
2553 },
2554
2555 easeInOutExpo: function(t) {
2556 if (t === 0) {
2557 return 0;
2558 }
2559 if (t === 1) {
2560 return 1;
2561 }
2562 if ((t /= 0.5) < 1) {
2563 return 0.5 * Math.pow(2, 10 * (t - 1));
2564 }
2565 return 0.5 * (-Math.pow(2, -10 * --t) + 2);
2566 },
2567
2568 easeInCirc: function(t) {
2569 if (t >= 1) {
2570 return t;
2571 }
2572 return -(Math.sqrt(1 - t * t) - 1);
2573 },
2574
2575 easeOutCirc: function(t) {
2576 return Math.sqrt(1 - (t = t - 1) * t);
2577 },
2578
2579 easeInOutCirc: function(t) {
2580 if ((t /= 0.5) < 1) {
2581 return -0.5 * (Math.sqrt(1 - t * t) - 1);
2582 }
2583 return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
2584 },
2585
2586 easeInElastic: function(t) {
2587 var s = 1.70158;
2588 var p = 0;
2589 var a = 1;
2590 if (t === 0) {
2591 return 0;
2592 }
2593 if (t === 1) {
2594 return 1;
2595 }
2596 if (!p) {
2597 p = 0.3;
2598 }
2599 if (a < 1) {
2600 a = 1;
2601 s = p / 4;
2602 } else {
2603 s = p / (2 * Math.PI) * Math.asin(1 / a);
2604 }
2605 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
2606 },
2607
2608 easeOutElastic: function(t) {
2609 var s = 1.70158;
2610 var p = 0;
2611 var a = 1;
2612 if (t === 0) {
2613 return 0;
2614 }
2615 if (t === 1) {
2616 return 1;
2617 }
2618 if (!p) {
2619 p = 0.3;
2620 }
2621 if (a < 1) {
2622 a = 1;
2623 s = p / 4;
2624 } else {
2625 s = p / (2 * Math.PI) * Math.asin(1 / a);
2626 }
2627 return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
2628 },
2629
2630 easeInOutElastic: function(t) {
2631 var s = 1.70158;
2632 var p = 0;
2633 var a = 1;
2634 if (t === 0) {
2635 return 0;
2636 }
2637 if ((t /= 0.5) === 2) {
2638 return 1;
2639 }
2640 if (!p) {
2641 p = 0.45;
2642 }
2643 if (a < 1) {
2644 a = 1;
2645 s = p / 4;
2646 } else {
2647 s = p / (2 * Math.PI) * Math.asin(1 / a);
2648 }
2649 if (t < 1) {
2650 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
2651 }
2652 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
2653 },
2654 easeInBack: function(t) {
2655 var s = 1.70158;
2656 return t * t * ((s + 1) * t - s);
2657 },
2658
2659 easeOutBack: function(t) {
2660 var s = 1.70158;
2661 return (t = t - 1) * t * ((s + 1) * t + s) + 1;
2662 },
2663
2664 easeInOutBack: function(t) {
2665 var s = 1.70158;
2666 if ((t /= 0.5) < 1) {
2667 return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
2668 }
2669 return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
2670 },
2671
2672 easeInBounce: function(t) {
2673 return 1 - effects.easeOutBounce(1 - t);
2674 },
2675
2676 easeOutBounce: function(t) {
2677 if (t < (1 / 2.75)) {
2678 return 7.5625 * t * t;
2679 }
2680 if (t < (2 / 2.75)) {
2681 return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
2682 }
2683 if (t < (2.5 / 2.75)) {
2684 return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
2685 }
2686 return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
2687 },
2688
2689 easeInOutBounce: function(t) {
2690 if (t < 0.5) {
2691 return effects.easeInBounce(t * 2) * 0.5;
2692 }
2693 return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
2694 }
2695};
2696
2697var helpers_easing = {
2698 effects: effects
2699};
2700
2701// DEPRECATIONS
2702
2703/**
2704 * Provided for backward compatibility, use Chart.helpers.easing.effects instead.
2705 * @function Chart.helpers.easingEffects
2706 * @deprecated since version 2.7.0
2707 * @todo remove at version 3
2708 * @private
2709 */
2710helpers_core.easingEffects = effects;
2711
2712var PI = Math.PI;
2713var RAD_PER_DEG = PI / 180;
2714var DOUBLE_PI = PI * 2;
2715var HALF_PI = PI / 2;
2716var QUARTER_PI = PI / 4;
2717var TWO_THIRDS_PI = PI * 2 / 3;
2718
2719/**
2720 * @namespace Chart.helpers.canvas
2721 */
2722var exports$1 = {
2723 /**
2724 * Clears the entire canvas associated to the given `chart`.
2725 * @param {Chart} chart - The chart for which to clear the canvas.
2726 */
2727 clear: function(chart) {
2728 chart.ctx.clearRect(0, 0, chart.width, chart.height);
2729 },
2730
2731 /**
2732 * Creates a "path" for a rectangle with rounded corners at position (x, y) with a
2733 * given size (width, height) and the same `radius` for all corners.
2734 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
2735 * @param {number} x - The x axis of the coordinate for the rectangle starting point.
2736 * @param {number} y - The y axis of the coordinate for the rectangle starting point.
2737 * @param {number} width - The rectangle's width.
2738 * @param {number} height - The rectangle's height.
2739 * @param {number} radius - The rounded amount (in pixels) for the four corners.
2740 * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
2741 */
2742 roundedRect: function(ctx, x, y, width, height, radius) {
2743 if (radius) {
2744 var r = Math.min(radius, height / 2, width / 2);
2745 var left = x + r;
2746 var top = y + r;
2747 var right = x + width - r;
2748 var bottom = y + height - r;
2749
2750 ctx.moveTo(x, top);
2751 if (left < right && top < bottom) {
2752 ctx.arc(left, top, r, -PI, -HALF_PI);
2753 ctx.arc(right, top, r, -HALF_PI, 0);
2754 ctx.arc(right, bottom, r, 0, HALF_PI);
2755 ctx.arc(left, bottom, r, HALF_PI, PI);
2756 } else if (left < right) {
2757 ctx.moveTo(left, y);
2758 ctx.arc(right, top, r, -HALF_PI, HALF_PI);
2759 ctx.arc(left, top, r, HALF_PI, PI + HALF_PI);
2760 } else if (top < bottom) {
2761 ctx.arc(left, top, r, -PI, 0);
2762 ctx.arc(left, bottom, r, 0, PI);
2763 } else {
2764 ctx.arc(left, top, r, -PI, PI);
2765 }
2766 ctx.closePath();
2767 ctx.moveTo(x, y);
2768 } else {
2769 ctx.rect(x, y, width, height);
2770 }
2771 },
2772
2773 drawPoint: function(ctx, style, radius, x, y, rotation) {
2774 var type, xOffset, yOffset, size, cornerRadius;
2775 var rad = (rotation || 0) * RAD_PER_DEG;
2776
2777 if (style && typeof style === 'object') {
2778 type = style.toString();
2779 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
2780 ctx.save();
2781 ctx.translate(x, y);
2782 ctx.rotate(rad);
2783 ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);
2784 ctx.restore();
2785 return;
2786 }
2787 }
2788
2789 if (isNaN(radius) || radius <= 0) {
2790 return;
2791 }
2792
2793 ctx.beginPath();
2794
2795 switch (style) {
2796 // Default includes circle
2797 default:
2798 ctx.arc(x, y, radius, 0, DOUBLE_PI);
2799 ctx.closePath();
2800 break;
2801 case 'triangle':
2802 ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
2803 rad += TWO_THIRDS_PI;
2804 ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
2805 rad += TWO_THIRDS_PI;
2806 ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
2807 ctx.closePath();
2808 break;
2809 case 'rectRounded':
2810 // NOTE: the rounded rect implementation changed to use `arc` instead of
2811 // `quadraticCurveTo` since it generates better results when rect is
2812 // almost a circle. 0.516 (instead of 0.5) produces results with visually
2813 // closer proportion to the previous impl and it is inscribed in the
2814 // circle with `radius`. For more details, see the following PRs:
2815 // https://github.com/chartjs/Chart.js/issues/5597
2816 // https://github.com/chartjs/Chart.js/issues/5858
2817 cornerRadius = radius * 0.516;
2818 size = radius - cornerRadius;
2819 xOffset = Math.cos(rad + QUARTER_PI) * size;
2820 yOffset = Math.sin(rad + QUARTER_PI) * size;
2821 ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
2822 ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad);
2823 ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI);
2824 ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
2825 ctx.closePath();
2826 break;
2827 case 'rect':
2828 if (!rotation) {
2829 size = Math.SQRT1_2 * radius;
2830 ctx.rect(x - size, y - size, 2 * size, 2 * size);
2831 break;
2832 }
2833 rad += QUARTER_PI;
2834 /* falls through */
2835 case 'rectRot':
2836 xOffset = Math.cos(rad) * radius;
2837 yOffset = Math.sin(rad) * radius;
2838 ctx.moveTo(x - xOffset, y - yOffset);
2839 ctx.lineTo(x + yOffset, y - xOffset);
2840 ctx.lineTo(x + xOffset, y + yOffset);
2841 ctx.lineTo(x - yOffset, y + xOffset);
2842 ctx.closePath();
2843 break;
2844 case 'crossRot':
2845 rad += QUARTER_PI;
2846 /* falls through */
2847 case 'cross':
2848 xOffset = Math.cos(rad) * radius;
2849 yOffset = Math.sin(rad) * radius;
2850 ctx.moveTo(x - xOffset, y - yOffset);
2851 ctx.lineTo(x + xOffset, y + yOffset);
2852 ctx.moveTo(x + yOffset, y - xOffset);
2853 ctx.lineTo(x - yOffset, y + xOffset);
2854 break;
2855 case 'star':
2856 xOffset = Math.cos(rad) * radius;
2857 yOffset = Math.sin(rad) * radius;
2858 ctx.moveTo(x - xOffset, y - yOffset);
2859 ctx.lineTo(x + xOffset, y + yOffset);
2860 ctx.moveTo(x + yOffset, y - xOffset);
2861 ctx.lineTo(x - yOffset, y + xOffset);
2862 rad += QUARTER_PI;
2863 xOffset = Math.cos(rad) * radius;
2864 yOffset = Math.sin(rad) * radius;
2865 ctx.moveTo(x - xOffset, y - yOffset);
2866 ctx.lineTo(x + xOffset, y + yOffset);
2867 ctx.moveTo(x + yOffset, y - xOffset);
2868 ctx.lineTo(x - yOffset, y + xOffset);
2869 break;
2870 case 'line':
2871 xOffset = Math.cos(rad) * radius;
2872 yOffset = Math.sin(rad) * radius;
2873 ctx.moveTo(x - xOffset, y - yOffset);
2874 ctx.lineTo(x + xOffset, y + yOffset);
2875 break;
2876 case 'dash':
2877 ctx.moveTo(x, y);
2878 ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius);
2879 break;
2880 }
2881
2882 ctx.fill();
2883 ctx.stroke();
2884 },
2885
2886 /**
2887 * Returns true if the point is inside the rectangle
2888 * @param {object} point - The point to test
2889 * @param {object} area - The rectangle
2890 * @returns {boolean}
2891 * @private
2892 */
2893 _isPointInArea: function(point, area) {
2894 var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.
2895
2896 return point.x > area.left - epsilon && point.x < area.right + epsilon &&
2897 point.y > area.top - epsilon && point.y < area.bottom + epsilon;
2898 },
2899
2900 clipArea: function(ctx, area) {
2901 ctx.save();
2902 ctx.beginPath();
2903 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
2904 ctx.clip();
2905 },
2906
2907 unclipArea: function(ctx) {
2908 ctx.restore();
2909 },
2910
2911 lineTo: function(ctx, previous, target, flip) {
2912 var stepped = target.steppedLine;
2913 if (stepped) {
2914 if (stepped === 'middle') {
2915 var midpoint = (previous.x + target.x) / 2.0;
2916 ctx.lineTo(midpoint, flip ? target.y : previous.y);
2917 ctx.lineTo(midpoint, flip ? previous.y : target.y);
2918 } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) {
2919 ctx.lineTo(previous.x, target.y);
2920 } else {
2921 ctx.lineTo(target.x, previous.y);
2922 }
2923 ctx.lineTo(target.x, target.y);
2924 return;
2925 }
2926
2927 if (!target.tension) {
2928 ctx.lineTo(target.x, target.y);
2929 return;
2930 }
2931
2932 ctx.bezierCurveTo(
2933 flip ? previous.controlPointPreviousX : previous.controlPointNextX,
2934 flip ? previous.controlPointPreviousY : previous.controlPointNextY,
2935 flip ? target.controlPointNextX : target.controlPointPreviousX,
2936 flip ? target.controlPointNextY : target.controlPointPreviousY,
2937 target.x,
2938 target.y);
2939 }
2940};
2941
2942var helpers_canvas = exports$1;
2943
2944// DEPRECATIONS
2945
2946/**
2947 * Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
2948 * @namespace Chart.helpers.clear
2949 * @deprecated since version 2.7.0
2950 * @todo remove at version 3
2951 * @private
2952 */
2953helpers_core.clear = exports$1.clear;
2954
2955/**
2956 * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
2957 * @namespace Chart.helpers.drawRoundedRectangle
2958 * @deprecated since version 2.7.0
2959 * @todo remove at version 3
2960 * @private
2961 */
2962helpers_core.drawRoundedRectangle = function(ctx) {
2963 ctx.beginPath();
2964 exports$1.roundedRect.apply(exports$1, arguments);
2965};
2966
2967var defaults = {
2968 /**
2969 * @private
2970 */
2971 _set: function(scope, values) {
2972 return helpers_core.merge(this[scope] || (this[scope] = {}), values);
2973 }
2974};
2975
2976// TODO(v3): remove 'global' from namespace. all default are global and
2977// there's inconsistency around which options are under 'global'
2978defaults._set('global', {
2979 defaultColor: 'rgba(0,0,0,0.1)',
2980 defaultFontColor: '#666',
2981 defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
2982 defaultFontSize: 12,
2983 defaultFontStyle: 'normal',
2984 defaultLineHeight: 1.2,
2985 showLines: true
2986});
2987
2988var core_defaults = defaults;
2989
2990var valueOrDefault = helpers_core.valueOrDefault;
2991
2992/**
2993 * Converts the given font object into a CSS font string.
2994 * @param {object} font - A font object.
2995 * @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
2996 * @private
2997 */
2998function toFontString(font) {
2999 if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) {
3000 return null;
3001 }
3002
3003 return (font.style ? font.style + ' ' : '')
3004 + (font.weight ? font.weight + ' ' : '')
3005 + font.size + 'px '
3006 + font.family;
3007}
3008
3009/**
3010 * @alias Chart.helpers.options
3011 * @namespace
3012 */
3013var helpers_options = {
3014 /**
3015 * Converts the given line height `value` in pixels for a specific font `size`.
3016 * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
3017 * @param {number} size - The font size (in pixels) used to resolve relative `value`.
3018 * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid).
3019 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
3020 * @since 2.7.0
3021 */
3022 toLineHeight: function(value, size) {
3023 var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
3024 if (!matches || matches[1] === 'normal') {
3025 return size * 1.2;
3026 }
3027
3028 value = +matches[2];
3029
3030 switch (matches[3]) {
3031 case 'px':
3032 return value;
3033 case '%':
3034 value /= 100;
3035 break;
3036 }
3037
3038 return size * value;
3039 },
3040
3041 /**
3042 * Converts the given value into a padding object with pre-computed width/height.
3043 * @param {number|object} value - If a number, set the value to all TRBL component,
3044 * else, if and object, use defined properties and sets undefined ones to 0.
3045 * @returns {object} The padding values (top, right, bottom, left, width, height)
3046 * @since 2.7.0
3047 */
3048 toPadding: function(value) {
3049 var t, r, b, l;
3050
3051 if (helpers_core.isObject(value)) {
3052 t = +value.top || 0;
3053 r = +value.right || 0;
3054 b = +value.bottom || 0;
3055 l = +value.left || 0;
3056 } else {
3057 t = r = b = l = +value || 0;
3058 }
3059
3060 return {
3061 top: t,
3062 right: r,
3063 bottom: b,
3064 left: l,
3065 height: t + b,
3066 width: l + r
3067 };
3068 },
3069
3070 /**
3071 * Parses font options and returns the font object.
3072 * @param {object} options - A object that contains font options to be parsed.
3073 * @return {object} The font object.
3074 * @todo Support font.* options and renamed to toFont().
3075 * @private
3076 */
3077 _parseFont: function(options) {
3078 var globalDefaults = core_defaults.global;
3079 var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
3080 var font = {
3081 family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily),
3082 lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size),
3083 size: size,
3084 style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle),
3085 weight: null,
3086 string: ''
3087 };
3088
3089 font.string = toFontString(font);
3090 return font;
3091 },
3092
3093 /**
3094 * Evaluates the given `inputs` sequentially and returns the first defined value.
3095 * @param {Array} inputs - An array of values, falling back to the last value.
3096 * @param {object} [context] - If defined and the current value is a function, the value
3097 * is called with `context` as first argument and the result becomes the new input.
3098 * @param {number} [index] - If defined and the current value is an array, the value
3099 * at `index` become the new input.
3100 * @param {object} [info] - object to return information about resolution in
3101 * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable.
3102 * @since 2.7.0
3103 */
3104 resolve: function(inputs, context, index, info) {
3105 var cacheable = true;
3106 var i, ilen, value;
3107
3108 for (i = 0, ilen = inputs.length; i < ilen; ++i) {
3109 value = inputs[i];
3110 if (value === undefined) {
3111 continue;
3112 }
3113 if (context !== undefined && typeof value === 'function') {
3114 value = value(context);
3115 cacheable = false;
3116 }
3117 if (index !== undefined && helpers_core.isArray(value)) {
3118 value = value[index];
3119 cacheable = false;
3120 }
3121 if (value !== undefined) {
3122 if (info && !cacheable) {
3123 info.cacheable = false;
3124 }
3125 return value;
3126 }
3127 }
3128 }
3129};
3130
3131/**
3132 * @alias Chart.helpers.math
3133 * @namespace
3134 */
3135var exports$2 = {
3136 /**
3137 * Returns an array of factors sorted from 1 to sqrt(value)
3138 * @private
3139 */
3140 _factorize: function(value) {
3141 var result = [];
3142 var sqrt = Math.sqrt(value);
3143 var i;
3144
3145 for (i = 1; i < sqrt; i++) {
3146 if (value % i === 0) {
3147 result.push(i);
3148 result.push(value / i);
3149 }
3150 }
3151 if (sqrt === (sqrt | 0)) { // if value is a square number
3152 result.push(sqrt);
3153 }
3154
3155 result.sort(function(a, b) {
3156 return a - b;
3157 }).pop();
3158 return result;
3159 },
3160
3161 log10: Math.log10 || function(x) {
3162 var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10.
3163 // Check for whole powers of 10,
3164 // which due to floating point rounding error should be corrected.
3165 var powerOf10 = Math.round(exponent);
3166 var isPowerOf10 = x === Math.pow(10, powerOf10);
3167
3168 return isPowerOf10 ? powerOf10 : exponent;
3169 }
3170};
3171
3172var helpers_math = exports$2;
3173
3174// DEPRECATIONS
3175
3176/**
3177 * Provided for backward compatibility, use Chart.helpers.math.log10 instead.
3178 * @namespace Chart.helpers.log10
3179 * @deprecated since version 2.9.0
3180 * @todo remove at version 3
3181 * @private
3182 */
3183helpers_core.log10 = exports$2.log10;
3184
3185var getRtlAdapter = function(rectX, width) {
3186 return {
3187 x: function(x) {
3188 return rectX + rectX + width - x;
3189 },
3190 setWidth: function(w) {
3191 width = w;
3192 },
3193 textAlign: function(align) {
3194 if (align === 'center') {
3195 return align;
3196 }
3197 return align === 'right' ? 'left' : 'right';
3198 },
3199 xPlus: function(x, value) {
3200 return x - value;
3201 },
3202 leftForLtr: function(x, itemWidth) {
3203 return x - itemWidth;
3204 },
3205 };
3206};
3207
3208var getLtrAdapter = function() {
3209 return {
3210 x: function(x) {
3211 return x;
3212 },
3213 setWidth: function(w) { // eslint-disable-line no-unused-vars
3214 },
3215 textAlign: function(align) {
3216 return align;
3217 },
3218 xPlus: function(x, value) {
3219 return x + value;
3220 },
3221 leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars
3222 return x;
3223 },
3224 };
3225};
3226
3227var getAdapter = function(rtl, rectX, width) {
3228 return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter();
3229};
3230
3231var overrideTextDirection = function(ctx, direction) {
3232 var style, original;
3233 if (direction === 'ltr' || direction === 'rtl') {
3234 style = ctx.canvas.style;
3235 original = [
3236 style.getPropertyValue('direction'),
3237 style.getPropertyPriority('direction'),
3238 ];
3239
3240 style.setProperty('direction', direction, 'important');
3241 ctx.prevTextDirection = original;
3242 }
3243};
3244
3245var restoreTextDirection = function(ctx) {
3246 var original = ctx.prevTextDirection;
3247 if (original !== undefined) {
3248 delete ctx.prevTextDirection;
3249 ctx.canvas.style.setProperty('direction', original[0], original[1]);
3250 }
3251};
3252
3253var helpers_rtl = {
3254 getRtlAdapter: getAdapter,
3255 overrideTextDirection: overrideTextDirection,
3256 restoreTextDirection: restoreTextDirection,
3257};
3258
3259var helpers$1 = helpers_core;
3260var easing = helpers_easing;
3261var canvas = helpers_canvas;
3262var options = helpers_options;
3263var math = helpers_math;
3264var rtl = helpers_rtl;
3265helpers$1.easing = easing;
3266helpers$1.canvas = canvas;
3267helpers$1.options = options;
3268helpers$1.math = math;
3269helpers$1.rtl = rtl;
3270
3271function interpolate(start, view, model, ease) {
3272 var keys = Object.keys(model);
3273 var i, ilen, key, actual, origin, target, type, c0, c1;
3274
3275 for (i = 0, ilen = keys.length; i < ilen; ++i) {
3276 key = keys[i];
3277
3278 target = model[key];
3279
3280 // if a value is added to the model after pivot() has been called, the view
3281 // doesn't contain it, so let's initialize the view to the target value.
3282 if (!view.hasOwnProperty(key)) {
3283 view[key] = target;
3284 }
3285
3286 actual = view[key];
3287
3288 if (actual === target || key[0] === '_') {
3289 continue;
3290 }
3291
3292 if (!start.hasOwnProperty(key)) {
3293 start[key] = actual;
3294 }
3295
3296 origin = start[key];
3297
3298 type = typeof target;
3299
3300 if (type === typeof origin) {
3301 if (type === 'string') {
3302 c0 = chartjsColor(origin);
3303 if (c0.valid) {
3304 c1 = chartjsColor(target);
3305 if (c1.valid) {
3306 view[key] = c1.mix(c0, ease).rgbString();
3307 continue;
3308 }
3309 }
3310 } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) {
3311 view[key] = origin + (target - origin) * ease;
3312 continue;
3313 }
3314 }
3315
3316 view[key] = target;
3317 }
3318}
3319
3320var Element = function(configuration) {
3321 helpers$1.extend(this, configuration);
3322 this.initialize.apply(this, arguments);
3323};
3324
3325helpers$1.extend(Element.prototype, {
3326 _type: undefined,
3327
3328 initialize: function() {
3329 this.hidden = false;
3330 },
3331
3332 pivot: function() {
3333 var me = this;
3334 if (!me._view) {
3335 me._view = helpers$1.extend({}, me._model);
3336 }
3337 me._start = {};
3338 return me;
3339 },
3340
3341 transition: function(ease) {
3342 var me = this;
3343 var model = me._model;
3344 var start = me._start;
3345 var view = me._view;
3346
3347 // No animation -> No Transition
3348 if (!model || ease === 1) {
3349 me._view = helpers$1.extend({}, model);
3350 me._start = null;
3351 return me;
3352 }
3353
3354 if (!view) {
3355 view = me._view = {};
3356 }
3357
3358 if (!start) {
3359 start = me._start = {};
3360 }
3361
3362 interpolate(start, view, model, ease);
3363
3364 return me;
3365 },
3366
3367 tooltipPosition: function() {
3368 return {
3369 x: this._model.x,
3370 y: this._model.y
3371 };
3372 },
3373
3374 hasValue: function() {
3375 return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y);
3376 }
3377});
3378
3379Element.extend = helpers$1.inherits;
3380
3381var core_element = Element;
3382
3383var exports$3 = core_element.extend({
3384 chart: null, // the animation associated chart instance
3385 currentStep: 0, // the current animation step
3386 numSteps: 60, // default number of steps
3387 easing: '', // the easing to use for this animation
3388 render: null, // render function used by the animation service
3389
3390 onAnimationProgress: null, // user specified callback to fire on each step of the animation
3391 onAnimationComplete: null, // user specified callback to fire when the animation finishes
3392});
3393
3394var core_animation = exports$3;
3395
3396// DEPRECATIONS
3397
3398/**
3399 * Provided for backward compatibility, use Chart.Animation instead
3400 * @prop Chart.Animation#animationObject
3401 * @deprecated since version 2.6.0
3402 * @todo remove at version 3
3403 */
3404Object.defineProperty(exports$3.prototype, 'animationObject', {
3405 get: function() {
3406 return this;
3407 }
3408});
3409
3410/**
3411 * Provided for backward compatibility, use Chart.Animation#chart instead
3412 * @prop Chart.Animation#chartInstance
3413 * @deprecated since version 2.6.0
3414 * @todo remove at version 3
3415 */
3416Object.defineProperty(exports$3.prototype, 'chartInstance', {
3417 get: function() {
3418 return this.chart;
3419 },
3420 set: function(value) {
3421 this.chart = value;
3422 }
3423});
3424
3425core_defaults._set('global', {
3426 animation: {
3427 duration: 1000,
3428 easing: 'easeOutQuart',
3429 onProgress: helpers$1.noop,
3430 onComplete: helpers$1.noop
3431 }
3432});
3433
3434var core_animations = {
3435 animations: [],
3436 request: null,
3437
3438 /**
3439 * @param {Chart} chart - The chart to animate.
3440 * @param {Chart.Animation} animation - The animation that we will animate.
3441 * @param {number} duration - The animation duration in ms.
3442 * @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
3443 */
3444 addAnimation: function(chart, animation, duration, lazy) {
3445 var animations = this.animations;
3446 var i, ilen;
3447
3448 animation.chart = chart;
3449 animation.startTime = Date.now();
3450 animation.duration = duration;
3451
3452 if (!lazy) {
3453 chart.animating = true;
3454 }
3455
3456 for (i = 0, ilen = animations.length; i < ilen; ++i) {
3457 if (animations[i].chart === chart) {
3458 animations[i] = animation;
3459 return;
3460 }
3461 }
3462
3463 animations.push(animation);
3464
3465 // If there are no animations queued, manually kickstart a digest, for lack of a better word
3466 if (animations.length === 1) {
3467 this.requestAnimationFrame();
3468 }
3469 },
3470
3471 cancelAnimation: function(chart) {
3472 var index = helpers$1.findIndex(this.animations, function(animation) {
3473 return animation.chart === chart;
3474 });
3475
3476 if (index !== -1) {
3477 this.animations.splice(index, 1);
3478 chart.animating = false;
3479 }
3480 },
3481
3482 requestAnimationFrame: function() {
3483 var me = this;
3484 if (me.request === null) {
3485 // Skip animation frame requests until the active one is executed.
3486 // This can happen when processing mouse events, e.g. 'mousemove'
3487 // and 'mouseout' events will trigger multiple renders.
3488 me.request = helpers$1.requestAnimFrame.call(window, function() {
3489 me.request = null;
3490 me.startDigest();
3491 });
3492 }
3493 },
3494
3495 /**
3496 * @private
3497 */
3498 startDigest: function() {
3499 var me = this;
3500
3501 me.advance();
3502
3503 // Do we have more stuff to animate?
3504 if (me.animations.length > 0) {
3505 me.requestAnimationFrame();
3506 }
3507 },
3508
3509 /**
3510 * @private
3511 */
3512 advance: function() {
3513 var animations = this.animations;
3514 var animation, chart, numSteps, nextStep;
3515 var i = 0;
3516
3517 // 1 animation per chart, so we are looping charts here
3518 while (i < animations.length) {
3519 animation = animations[i];
3520 chart = animation.chart;
3521 numSteps = animation.numSteps;
3522
3523 // Make sure that currentStep starts at 1
3524 // https://github.com/chartjs/Chart.js/issues/6104
3525 nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1;
3526 animation.currentStep = Math.min(nextStep, numSteps);
3527
3528 helpers$1.callback(animation.render, [chart, animation], chart);
3529 helpers$1.callback(animation.onAnimationProgress, [animation], chart);
3530
3531 if (animation.currentStep >= numSteps) {
3532 helpers$1.callback(animation.onAnimationComplete, [animation], chart);
3533 chart.animating = false;
3534 animations.splice(i, 1);
3535 } else {
3536 ++i;
3537 }
3538 }
3539 }
3540};
3541
3542var resolve = helpers$1.options.resolve;
3543
3544var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
3545
3546/**
3547 * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
3548 * 'unshift') and notify the listener AFTER the array has been altered. Listeners are
3549 * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
3550 */
3551function listenArrayEvents(array, listener) {
3552 if (array._chartjs) {
3553 array._chartjs.listeners.push(listener);
3554 return;
3555 }
3556
3557 Object.defineProperty(array, '_chartjs', {
3558 configurable: true,
3559 enumerable: false,
3560 value: {
3561 listeners: [listener]
3562 }
3563 });
3564
3565 arrayEvents.forEach(function(key) {
3566 var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
3567 var base = array[key];
3568
3569 Object.defineProperty(array, key, {
3570 configurable: true,
3571 enumerable: false,
3572 value: function() {
3573 var args = Array.prototype.slice.call(arguments);
3574 var res = base.apply(this, args);
3575
3576 helpers$1.each(array._chartjs.listeners, function(object) {
3577 if (typeof object[method] === 'function') {
3578 object[method].apply(object, args);
3579 }
3580 });
3581
3582 return res;
3583 }
3584 });
3585 });
3586}
3587
3588/**
3589 * Removes the given array event listener and cleanup extra attached properties (such as
3590 * the _chartjs stub and overridden methods) if array doesn't have any more listeners.
3591 */
3592function unlistenArrayEvents(array, listener) {
3593 var stub = array._chartjs;
3594 if (!stub) {
3595 return;
3596 }
3597
3598 var listeners = stub.listeners;
3599 var index = listeners.indexOf(listener);
3600 if (index !== -1) {
3601 listeners.splice(index, 1);
3602 }
3603
3604 if (listeners.length > 0) {
3605 return;
3606 }
3607
3608 arrayEvents.forEach(function(key) {
3609 delete array[key];
3610 });
3611
3612 delete array._chartjs;
3613}
3614
3615// Base class for all dataset controllers (line, bar, etc)
3616var DatasetController = function(chart, datasetIndex) {
3617 this.initialize(chart, datasetIndex);
3618};
3619
3620helpers$1.extend(DatasetController.prototype, {
3621
3622 /**
3623 * Element type used to generate a meta dataset (e.g. Chart.element.Line).
3624 * @type {Chart.core.element}
3625 */
3626 datasetElementType: null,
3627
3628 /**
3629 * Element type used to generate a meta data (e.g. Chart.element.Point).
3630 * @type {Chart.core.element}
3631 */
3632 dataElementType: null,
3633
3634 /**
3635 * Dataset element option keys to be resolved in _resolveDatasetElementOptions.
3636 * A derived controller may override this to resolve controller-specific options.
3637 * The keys defined here are for backward compatibility for legend styles.
3638 * @private
3639 */
3640 _datasetElementOptions: [
3641 'backgroundColor',
3642 'borderCapStyle',
3643 'borderColor',
3644 'borderDash',
3645 'borderDashOffset',
3646 'borderJoinStyle',
3647 'borderWidth'
3648 ],
3649
3650 /**
3651 * Data element option keys to be resolved in _resolveDataElementOptions.
3652 * A derived controller may override this to resolve controller-specific options.
3653 * The keys defined here are for backward compatibility for legend styles.
3654 * @private
3655 */
3656 _dataElementOptions: [
3657 'backgroundColor',
3658 'borderColor',
3659 'borderWidth',
3660 'pointStyle'
3661 ],
3662
3663 initialize: function(chart, datasetIndex) {
3664 var me = this;
3665 me.chart = chart;
3666 me.index = datasetIndex;
3667 me.linkScales();
3668 me.addElements();
3669 me._type = me.getMeta().type;
3670 },
3671
3672 updateIndex: function(datasetIndex) {
3673 this.index = datasetIndex;
3674 },
3675
3676 linkScales: function() {
3677 var me = this;
3678 var meta = me.getMeta();
3679 var chart = me.chart;
3680 var scales = chart.scales;
3681 var dataset = me.getDataset();
3682 var scalesOpts = chart.options.scales;
3683
3684 if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) {
3685 meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id;
3686 }
3687 if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) {
3688 meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id;
3689 }
3690 },
3691
3692 getDataset: function() {
3693 return this.chart.data.datasets[this.index];
3694 },
3695
3696 getMeta: function() {
3697 return this.chart.getDatasetMeta(this.index);
3698 },
3699
3700 getScaleForId: function(scaleID) {
3701 return this.chart.scales[scaleID];
3702 },
3703
3704 /**
3705 * @private
3706 */
3707 _getValueScaleId: function() {
3708 return this.getMeta().yAxisID;
3709 },
3710
3711 /**
3712 * @private
3713 */
3714 _getIndexScaleId: function() {
3715 return this.getMeta().xAxisID;
3716 },
3717
3718 /**
3719 * @private
3720 */
3721 _getValueScale: function() {
3722 return this.getScaleForId(this._getValueScaleId());
3723 },
3724
3725 /**
3726 * @private
3727 */
3728 _getIndexScale: function() {
3729 return this.getScaleForId(this._getIndexScaleId());
3730 },
3731
3732 reset: function() {
3733 this._update(true);
3734 },
3735
3736 /**
3737 * @private
3738 */
3739 destroy: function() {
3740 if (this._data) {
3741 unlistenArrayEvents(this._data, this);
3742 }
3743 },
3744
3745 createMetaDataset: function() {
3746 var me = this;
3747 var type = me.datasetElementType;
3748 return type && new type({
3749 _chart: me.chart,
3750 _datasetIndex: me.index
3751 });
3752 },
3753
3754 createMetaData: function(index) {
3755 var me = this;
3756 var type = me.dataElementType;
3757 return type && new type({
3758 _chart: me.chart,
3759 _datasetIndex: me.index,
3760 _index: index
3761 });
3762 },
3763
3764 addElements: function() {
3765 var me = this;
3766 var meta = me.getMeta();
3767 var data = me.getDataset().data || [];
3768 var metaData = meta.data;
3769 var i, ilen;
3770
3771 for (i = 0, ilen = data.length; i < ilen; ++i) {
3772 metaData[i] = metaData[i] || me.createMetaData(i);
3773 }
3774
3775 meta.dataset = meta.dataset || me.createMetaDataset();
3776 },
3777
3778 addElementAndReset: function(index) {
3779 var element = this.createMetaData(index);
3780 this.getMeta().data.splice(index, 0, element);
3781 this.updateElement(element, index, true);
3782 },
3783
3784 buildOrUpdateElements: function() {
3785 var me = this;
3786 var dataset = me.getDataset();
3787 var data = dataset.data || (dataset.data = []);
3788
3789 // In order to correctly handle data addition/deletion animation (an thus simulate
3790 // real-time charts), we need to monitor these data modifications and synchronize
3791 // the internal meta data accordingly.
3792 if (me._data !== data) {
3793 if (me._data) {
3794 // This case happens when the user replaced the data array instance.
3795 unlistenArrayEvents(me._data, me);
3796 }
3797
3798 if (data && Object.isExtensible(data)) {
3799 listenArrayEvents(data, me);
3800 }
3801 me._data = data;
3802 }
3803
3804 // Re-sync meta data in case the user replaced the data array or if we missed
3805 // any updates and so make sure that we handle number of datapoints changing.
3806 me.resyncElements();
3807 },
3808
3809 /**
3810 * Returns the merged user-supplied and default dataset-level options
3811 * @private
3812 */
3813 _configure: function() {
3814 var me = this;
3815 me._config = helpers$1.merge({}, [
3816 me.chart.options.datasets[me._type],
3817 me.getDataset(),
3818 ], {
3819 merger: function(key, target, source) {
3820 if (key !== '_meta' && key !== 'data') {
3821 helpers$1._merger(key, target, source);
3822 }
3823 }
3824 });
3825 },
3826
3827 _update: function(reset) {
3828 var me = this;
3829 me._configure();
3830 me._cachedDataOpts = null;
3831 me.update(reset);
3832 },
3833
3834 update: helpers$1.noop,
3835
3836 transition: function(easingValue) {
3837 var meta = this.getMeta();
3838 var elements = meta.data || [];
3839 var ilen = elements.length;
3840 var i = 0;
3841
3842 for (; i < ilen; ++i) {
3843 elements[i].transition(easingValue);
3844 }
3845
3846 if (meta.dataset) {
3847 meta.dataset.transition(easingValue);
3848 }
3849 },
3850
3851 draw: function() {
3852 var meta = this.getMeta();
3853 var elements = meta.data || [];
3854 var ilen = elements.length;
3855 var i = 0;
3856
3857 if (meta.dataset) {
3858 meta.dataset.draw();
3859 }
3860
3861 for (; i < ilen; ++i) {
3862 elements[i].draw();
3863 }
3864 },
3865
3866 /**
3867 * Returns a set of predefined style properties that should be used to represent the dataset
3868 * or the data if the index is specified
3869 * @param {number} index - data index
3870 * @return {IStyleInterface} style object
3871 */
3872 getStyle: function(index) {
3873 var me = this;
3874 var meta = me.getMeta();
3875 var dataset = meta.dataset;
3876 var style;
3877
3878 me._configure();
3879 if (dataset && index === undefined) {
3880 style = me._resolveDatasetElementOptions(dataset || {});
3881 } else {
3882 index = index || 0;
3883 style = me._resolveDataElementOptions(meta.data[index] || {}, index);
3884 }
3885
3886 if (style.fill === false || style.fill === null) {
3887 style.backgroundColor = style.borderColor;
3888 }
3889
3890 return style;
3891 },
3892
3893 /**
3894 * @private
3895 */
3896 _resolveDatasetElementOptions: function(element, hover) {
3897 var me = this;
3898 var chart = me.chart;
3899 var datasetOpts = me._config;
3900 var custom = element.custom || {};
3901 var options = chart.options.elements[me.datasetElementType.prototype._type] || {};
3902 var elementOptions = me._datasetElementOptions;
3903 var values = {};
3904 var i, ilen, key, readKey;
3905
3906 // Scriptable options
3907 var context = {
3908 chart: chart,
3909 dataset: me.getDataset(),
3910 datasetIndex: me.index,
3911 hover: hover
3912 };
3913
3914 for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
3915 key = elementOptions[i];
3916 readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key;
3917 values[key] = resolve([
3918 custom[readKey],
3919 datasetOpts[readKey],
3920 options[readKey]
3921 ], context);
3922 }
3923
3924 return values;
3925 },
3926
3927 /**
3928 * @private
3929 */
3930 _resolveDataElementOptions: function(element, index) {
3931 var me = this;
3932 var custom = element && element.custom;
3933 var cached = me._cachedDataOpts;
3934 if (cached && !custom) {
3935 return cached;
3936 }
3937 var chart = me.chart;
3938 var datasetOpts = me._config;
3939 var options = chart.options.elements[me.dataElementType.prototype._type] || {};
3940 var elementOptions = me._dataElementOptions;
3941 var values = {};
3942
3943 // Scriptable options
3944 var context = {
3945 chart: chart,
3946 dataIndex: index,
3947 dataset: me.getDataset(),
3948 datasetIndex: me.index
3949 };
3950
3951 // `resolve` sets cacheable to `false` if any option is indexed or scripted
3952 var info = {cacheable: !custom};
3953
3954 var keys, i, ilen, key;
3955
3956 custom = custom || {};
3957
3958 if (helpers$1.isArray(elementOptions)) {
3959 for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
3960 key = elementOptions[i];
3961 values[key] = resolve([
3962 custom[key],
3963 datasetOpts[key],
3964 options[key]
3965 ], context, index, info);
3966 }
3967 } else {
3968 keys = Object.keys(elementOptions);
3969 for (i = 0, ilen = keys.length; i < ilen; ++i) {
3970 key = keys[i];
3971 values[key] = resolve([
3972 custom[key],
3973 datasetOpts[elementOptions[key]],
3974 datasetOpts[key],
3975 options[key]
3976 ], context, index, info);
3977 }
3978 }
3979
3980 if (info.cacheable) {
3981 me._cachedDataOpts = Object.freeze(values);
3982 }
3983
3984 return values;
3985 },
3986
3987 removeHoverStyle: function(element) {
3988 helpers$1.merge(element._model, element.$previousStyle || {});
3989 delete element.$previousStyle;
3990 },
3991
3992 setHoverStyle: function(element) {
3993 var dataset = this.chart.data.datasets[element._datasetIndex];
3994 var index = element._index;
3995 var custom = element.custom || {};
3996 var model = element._model;
3997 var getHoverColor = helpers$1.getHoverColor;
3998
3999 element.$previousStyle = {
4000 backgroundColor: model.backgroundColor,
4001 borderColor: model.borderColor,
4002 borderWidth: model.borderWidth
4003 };
4004
4005 model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index);
4006 model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index);
4007 model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index);
4008 },
4009
4010 /**
4011 * @private
4012 */
4013 _removeDatasetHoverStyle: function() {
4014 var element = this.getMeta().dataset;
4015
4016 if (element) {
4017 this.removeHoverStyle(element);
4018 }
4019 },
4020
4021 /**
4022 * @private
4023 */
4024 _setDatasetHoverStyle: function() {
4025 var element = this.getMeta().dataset;
4026 var prev = {};
4027 var i, ilen, key, keys, hoverOptions, model;
4028
4029 if (!element) {
4030 return;
4031 }
4032
4033 model = element._model;
4034 hoverOptions = this._resolveDatasetElementOptions(element, true);
4035
4036 keys = Object.keys(hoverOptions);
4037 for (i = 0, ilen = keys.length; i < ilen; ++i) {
4038 key = keys[i];
4039 prev[key] = model[key];
4040 model[key] = hoverOptions[key];
4041 }
4042
4043 element.$previousStyle = prev;
4044 },
4045
4046 /**
4047 * @private
4048 */
4049 resyncElements: function() {
4050 var me = this;
4051 var meta = me.getMeta();
4052 var data = me.getDataset().data;
4053 var numMeta = meta.data.length;
4054 var numData = data.length;
4055
4056 if (numData < numMeta) {
4057 meta.data.splice(numData, numMeta - numData);
4058 } else if (numData > numMeta) {
4059 me.insertElements(numMeta, numData - numMeta);
4060 }
4061 },
4062
4063 /**
4064 * @private
4065 */
4066 insertElements: function(start, count) {
4067 for (var i = 0; i < count; ++i) {
4068 this.addElementAndReset(start + i);
4069 }
4070 },
4071
4072 /**
4073 * @private
4074 */
4075 onDataPush: function() {
4076 var count = arguments.length;
4077 this.insertElements(this.getDataset().data.length - count, count);
4078 },
4079
4080 /**
4081 * @private
4082 */
4083 onDataPop: function() {
4084 this.getMeta().data.pop();
4085 },
4086
4087 /**
4088 * @private
4089 */
4090 onDataShift: function() {
4091 this.getMeta().data.shift();
4092 },
4093
4094 /**
4095 * @private
4096 */
4097 onDataSplice: function(start, count) {
4098 this.getMeta().data.splice(start, count);
4099 this.insertElements(start, arguments.length - 2);
4100 },
4101
4102 /**
4103 * @private
4104 */
4105 onDataUnshift: function() {
4106 this.insertElements(0, arguments.length);
4107 }
4108});
4109
4110DatasetController.extend = helpers$1.inherits;
4111
4112var core_datasetController = DatasetController;
4113
4114var TAU = Math.PI * 2;
4115
4116core_defaults._set('global', {
4117 elements: {
4118 arc: {
4119 backgroundColor: core_defaults.global.defaultColor,
4120 borderColor: '#fff',
4121 borderWidth: 2,
4122 borderAlign: 'center'
4123 }
4124 }
4125});
4126
4127function clipArc(ctx, arc) {
4128 var startAngle = arc.startAngle;
4129 var endAngle = arc.endAngle;
4130 var pixelMargin = arc.pixelMargin;
4131 var angleMargin = pixelMargin / arc.outerRadius;
4132 var x = arc.x;
4133 var y = arc.y;
4134
4135 // Draw an inner border by cliping the arc and drawing a double-width border
4136 // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
4137 ctx.beginPath();
4138 ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin);
4139 if (arc.innerRadius > pixelMargin) {
4140 angleMargin = pixelMargin / arc.innerRadius;
4141 ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true);
4142 } else {
4143 ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2);
4144 }
4145 ctx.closePath();
4146 ctx.clip();
4147}
4148
4149function drawFullCircleBorders(ctx, vm, arc, inner) {
4150 var endAngle = arc.endAngle;
4151 var i;
4152
4153 if (inner) {
4154 arc.endAngle = arc.startAngle + TAU;
4155 clipArc(ctx, arc);
4156 arc.endAngle = endAngle;
4157 if (arc.endAngle === arc.startAngle && arc.fullCircles) {
4158 arc.endAngle += TAU;
4159 arc.fullCircles--;
4160 }
4161 }
4162
4163 ctx.beginPath();
4164 ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true);
4165 for (i = 0; i < arc.fullCircles; ++i) {
4166 ctx.stroke();
4167 }
4168
4169 ctx.beginPath();
4170 ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU);
4171 for (i = 0; i < arc.fullCircles; ++i) {
4172 ctx.stroke();
4173 }
4174}
4175
4176function drawBorder(ctx, vm, arc) {
4177 var inner = vm.borderAlign === 'inner';
4178
4179 if (inner) {
4180 ctx.lineWidth = vm.borderWidth * 2;
4181 ctx.lineJoin = 'round';
4182 } else {
4183 ctx.lineWidth = vm.borderWidth;
4184 ctx.lineJoin = 'bevel';
4185 }
4186
4187 if (arc.fullCircles) {
4188 drawFullCircleBorders(ctx, vm, arc, inner);
4189 }
4190
4191 if (inner) {
4192 clipArc(ctx, arc);
4193 }
4194
4195 ctx.beginPath();
4196 ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle);
4197 ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
4198 ctx.closePath();
4199 ctx.stroke();
4200}
4201
4202var element_arc = core_element.extend({
4203 _type: 'arc',
4204
4205 inLabelRange: function(mouseX) {
4206 var vm = this._view;
4207
4208 if (vm) {
4209 return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
4210 }
4211 return false;
4212 },
4213
4214 inRange: function(chartX, chartY) {
4215 var vm = this._view;
4216
4217 if (vm) {
4218 var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY});
4219 var angle = pointRelativePosition.angle;
4220 var distance = pointRelativePosition.distance;
4221
4222 // Sanitise angle range
4223 var startAngle = vm.startAngle;
4224 var endAngle = vm.endAngle;
4225 while (endAngle < startAngle) {
4226 endAngle += TAU;
4227 }
4228 while (angle > endAngle) {
4229 angle -= TAU;
4230 }
4231 while (angle < startAngle) {
4232 angle += TAU;
4233 }
4234
4235 // Check if within the range of the open/close angle
4236 var betweenAngles = (angle >= startAngle && angle <= endAngle);
4237 var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
4238
4239 return (betweenAngles && withinRadius);
4240 }
4241 return false;
4242 },
4243
4244 getCenterPoint: function() {
4245 var vm = this._view;
4246 var halfAngle = (vm.startAngle + vm.endAngle) / 2;
4247 var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
4248 return {
4249 x: vm.x + Math.cos(halfAngle) * halfRadius,
4250 y: vm.y + Math.sin(halfAngle) * halfRadius
4251 };
4252 },
4253
4254 getArea: function() {
4255 var vm = this._view;
4256 return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
4257 },
4258
4259 tooltipPosition: function() {
4260 var vm = this._view;
4261 var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
4262 var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
4263
4264 return {
4265 x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
4266 y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
4267 };
4268 },
4269
4270 draw: function() {
4271 var ctx = this._chart.ctx;
4272 var vm = this._view;
4273 var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0;
4274 var arc = {
4275 x: vm.x,
4276 y: vm.y,
4277 innerRadius: vm.innerRadius,
4278 outerRadius: Math.max(vm.outerRadius - pixelMargin, 0),
4279 pixelMargin: pixelMargin,
4280 startAngle: vm.startAngle,
4281 endAngle: vm.endAngle,
4282 fullCircles: Math.floor(vm.circumference / TAU)
4283 };
4284 var i;
4285
4286 ctx.save();
4287
4288 ctx.fillStyle = vm.backgroundColor;
4289 ctx.strokeStyle = vm.borderColor;
4290
4291 if (arc.fullCircles) {
4292 arc.endAngle = arc.startAngle + TAU;
4293 ctx.beginPath();
4294 ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle);
4295 ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
4296 ctx.closePath();
4297 for (i = 0; i < arc.fullCircles; ++i) {
4298 ctx.fill();
4299 }
4300 arc.endAngle = arc.startAngle + vm.circumference % TAU;
4301 }
4302
4303 ctx.beginPath();
4304 ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle);
4305 ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
4306 ctx.closePath();
4307 ctx.fill();
4308
4309 if (vm.borderWidth) {
4310 drawBorder(ctx, vm, arc);
4311 }
4312
4313 ctx.restore();
4314 }
4315});
4316
4317var valueOrDefault$1 = helpers$1.valueOrDefault;
4318
4319var defaultColor = core_defaults.global.defaultColor;
4320
4321core_defaults._set('global', {
4322 elements: {
4323 line: {
4324 tension: 0.4,
4325 backgroundColor: defaultColor,
4326 borderWidth: 3,
4327 borderColor: defaultColor,
4328 borderCapStyle: 'butt',
4329 borderDash: [],
4330 borderDashOffset: 0.0,
4331 borderJoinStyle: 'miter',
4332 capBezierPoints: true,
4333 fill: true, // do we fill in the area between the line and its base axis
4334 }
4335 }
4336});
4337
4338var element_line = core_element.extend({
4339 _type: 'line',
4340
4341 draw: function() {
4342 var me = this;
4343 var vm = me._view;
4344 var ctx = me._chart.ctx;
4345 var spanGaps = vm.spanGaps;
4346 var points = me._children.slice(); // clone array
4347 var globalDefaults = core_defaults.global;
4348 var globalOptionLineElements = globalDefaults.elements.line;
4349 var lastDrawnIndex = -1;
4350 var closePath = me._loop;
4351 var index, previous, currentVM;
4352
4353 if (!points.length) {
4354 return;
4355 }
4356
4357 if (me._loop) {
4358 for (index = 0; index < points.length; ++index) {
4359 previous = helpers$1.previousItem(points, index);
4360 // If the line has an open path, shift the point array
4361 if (!points[index]._view.skip && previous._view.skip) {
4362 points = points.slice(index).concat(points.slice(0, index));
4363 closePath = spanGaps;
4364 break;
4365 }
4366 }
4367 // If the line has a close path, add the first point again
4368 if (closePath) {
4369 points.push(points[0]);
4370 }
4371 }
4372
4373 ctx.save();
4374
4375 // Stroke Line Options
4376 ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
4377
4378 // IE 9 and 10 do not support line dash
4379 if (ctx.setLineDash) {
4380 ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
4381 }
4382
4383 ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset);
4384 ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
4385 ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth);
4386 ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
4387
4388 // Stroke Line
4389 ctx.beginPath();
4390
4391 // First point moves to it's starting position no matter what
4392 currentVM = points[0]._view;
4393 if (!currentVM.skip) {
4394 ctx.moveTo(currentVM.x, currentVM.y);
4395 lastDrawnIndex = 0;
4396 }
4397
4398 for (index = 1; index < points.length; ++index) {
4399 currentVM = points[index]._view;
4400 previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex];
4401
4402 if (!currentVM.skip) {
4403 if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
4404 // There was a gap and this is the first point after the gap
4405 ctx.moveTo(currentVM.x, currentVM.y);
4406 } else {
4407 // Line to next point
4408 helpers$1.canvas.lineTo(ctx, previous._view, currentVM);
4409 }
4410 lastDrawnIndex = index;
4411 }
4412 }
4413
4414 if (closePath) {
4415 ctx.closePath();
4416 }
4417
4418 ctx.stroke();
4419 ctx.restore();
4420 }
4421});
4422
4423var valueOrDefault$2 = helpers$1.valueOrDefault;
4424
4425var defaultColor$1 = core_defaults.global.defaultColor;
4426
4427core_defaults._set('global', {
4428 elements: {
4429 point: {
4430 radius: 3,
4431 pointStyle: 'circle',
4432 backgroundColor: defaultColor$1,
4433 borderColor: defaultColor$1,
4434 borderWidth: 1,
4435 // Hover
4436 hitRadius: 1,
4437 hoverRadius: 4,
4438 hoverBorderWidth: 1
4439 }
4440 }
4441});
4442
4443function xRange(mouseX) {
4444 var vm = this._view;
4445 return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false;
4446}
4447
4448function yRange(mouseY) {
4449 var vm = this._view;
4450 return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false;
4451}
4452
4453var element_point = core_element.extend({
4454 _type: 'point',
4455
4456 inRange: function(mouseX, mouseY) {
4457 var vm = this._view;
4458 return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
4459 },
4460
4461 inLabelRange: xRange,
4462 inXRange: xRange,
4463 inYRange: yRange,
4464
4465 getCenterPoint: function() {
4466 var vm = this._view;
4467 return {
4468 x: vm.x,
4469 y: vm.y
4470 };
4471 },
4472
4473 getArea: function() {
4474 return Math.PI * Math.pow(this._view.radius, 2);
4475 },
4476
4477 tooltipPosition: function() {
4478 var vm = this._view;
4479 return {
4480 x: vm.x,
4481 y: vm.y,
4482 padding: vm.radius + vm.borderWidth
4483 };
4484 },
4485
4486 draw: function(chartArea) {
4487 var vm = this._view;
4488 var ctx = this._chart.ctx;
4489 var pointStyle = vm.pointStyle;
4490 var rotation = vm.rotation;
4491 var radius = vm.radius;
4492 var x = vm.x;
4493 var y = vm.y;
4494 var globalDefaults = core_defaults.global;
4495 var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow
4496
4497 if (vm.skip) {
4498 return;
4499 }
4500
4501 // Clipping for Points.
4502 if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) {
4503 ctx.strokeStyle = vm.borderColor || defaultColor;
4504 ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth);
4505 ctx.fillStyle = vm.backgroundColor || defaultColor;
4506 helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation);
4507 }
4508 }
4509});
4510
4511var defaultColor$2 = core_defaults.global.defaultColor;
4512
4513core_defaults._set('global', {
4514 elements: {
4515 rectangle: {
4516 backgroundColor: defaultColor$2,
4517 borderColor: defaultColor$2,
4518 borderSkipped: 'bottom',
4519 borderWidth: 0
4520 }
4521 }
4522});
4523
4524function isVertical(vm) {
4525 return vm && vm.width !== undefined;
4526}
4527
4528/**
4529 * Helper function to get the bounds of the bar regardless of the orientation
4530 * @param bar {Chart.Element.Rectangle} the bar
4531 * @return {Bounds} bounds of the bar
4532 * @private
4533 */
4534function getBarBounds(vm) {
4535 var x1, x2, y1, y2, half;
4536
4537 if (isVertical(vm)) {
4538 half = vm.width / 2;
4539 x1 = vm.x - half;
4540 x2 = vm.x + half;
4541 y1 = Math.min(vm.y, vm.base);
4542 y2 = Math.max(vm.y, vm.base);
4543 } else {
4544 half = vm.height / 2;
4545 x1 = Math.min(vm.x, vm.base);
4546 x2 = Math.max(vm.x, vm.base);
4547 y1 = vm.y - half;
4548 y2 = vm.y + half;
4549 }
4550
4551 return {
4552 left: x1,
4553 top: y1,
4554 right: x2,
4555 bottom: y2
4556 };
4557}
4558
4559function swap(orig, v1, v2) {
4560 return orig === v1 ? v2 : orig === v2 ? v1 : orig;
4561}
4562
4563function parseBorderSkipped(vm) {
4564 var edge = vm.borderSkipped;
4565 var res = {};
4566
4567 if (!edge) {
4568 return res;
4569 }
4570
4571 if (vm.horizontal) {
4572 if (vm.base > vm.x) {
4573 edge = swap(edge, 'left', 'right');
4574 }
4575 } else if (vm.base < vm.y) {
4576 edge = swap(edge, 'bottom', 'top');
4577 }
4578
4579 res[edge] = true;
4580 return res;
4581}
4582
4583function parseBorderWidth(vm, maxW, maxH) {
4584 var value = vm.borderWidth;
4585 var skip = parseBorderSkipped(vm);
4586 var t, r, b, l;
4587
4588 if (helpers$1.isObject(value)) {
4589 t = +value.top || 0;
4590 r = +value.right || 0;
4591 b = +value.bottom || 0;
4592 l = +value.left || 0;
4593 } else {
4594 t = r = b = l = +value || 0;
4595 }
4596
4597 return {
4598 t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t,
4599 r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r,
4600 b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b,
4601 l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l
4602 };
4603}
4604
4605function boundingRects(vm) {
4606 var bounds = getBarBounds(vm);
4607 var width = bounds.right - bounds.left;
4608 var height = bounds.bottom - bounds.top;
4609 var border = parseBorderWidth(vm, width / 2, height / 2);
4610
4611 return {
4612 outer: {
4613 x: bounds.left,
4614 y: bounds.top,
4615 w: width,
4616 h: height
4617 },
4618 inner: {
4619 x: bounds.left + border.l,
4620 y: bounds.top + border.t,
4621 w: width - border.l - border.r,
4622 h: height - border.t - border.b
4623 }
4624 };
4625}
4626
4627function inRange(vm, x, y) {
4628 var skipX = x === null;
4629 var skipY = y === null;
4630 var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm);
4631
4632 return bounds
4633 && (skipX || x >= bounds.left && x <= bounds.right)
4634 && (skipY || y >= bounds.top && y <= bounds.bottom);
4635}
4636
4637var element_rectangle = core_element.extend({
4638 _type: 'rectangle',
4639
4640 draw: function() {
4641 var ctx = this._chart.ctx;
4642 var vm = this._view;
4643 var rects = boundingRects(vm);
4644 var outer = rects.outer;
4645 var inner = rects.inner;
4646
4647 ctx.fillStyle = vm.backgroundColor;
4648 ctx.fillRect(outer.x, outer.y, outer.w, outer.h);
4649
4650 if (outer.w === inner.w && outer.h === inner.h) {
4651 return;
4652 }
4653
4654 ctx.save();
4655 ctx.beginPath();
4656 ctx.rect(outer.x, outer.y, outer.w, outer.h);
4657 ctx.clip();
4658 ctx.fillStyle = vm.borderColor;
4659 ctx.rect(inner.x, inner.y, inner.w, inner.h);
4660 ctx.fill('evenodd');
4661 ctx.restore();
4662 },
4663
4664 height: function() {
4665 var vm = this._view;
4666 return vm.base - vm.y;
4667 },
4668
4669 inRange: function(mouseX, mouseY) {
4670 return inRange(this._view, mouseX, mouseY);
4671 },
4672
4673 inLabelRange: function(mouseX, mouseY) {
4674 var vm = this._view;
4675 return isVertical(vm)
4676 ? inRange(vm, mouseX, null)
4677 : inRange(vm, null, mouseY);
4678 },
4679
4680 inXRange: function(mouseX) {
4681 return inRange(this._view, mouseX, null);
4682 },
4683
4684 inYRange: function(mouseY) {
4685 return inRange(this._view, null, mouseY);
4686 },
4687
4688 getCenterPoint: function() {
4689 var vm = this._view;
4690 var x, y;
4691 if (isVertical(vm)) {
4692 x = vm.x;
4693 y = (vm.y + vm.base) / 2;
4694 } else {
4695 x = (vm.x + vm.base) / 2;
4696 y = vm.y;
4697 }
4698
4699 return {x: x, y: y};
4700 },
4701
4702 getArea: function() {
4703 var vm = this._view;
4704
4705 return isVertical(vm)
4706 ? vm.width * Math.abs(vm.y - vm.base)
4707 : vm.height * Math.abs(vm.x - vm.base);
4708 },
4709
4710 tooltipPosition: function() {
4711 var vm = this._view;
4712 return {
4713 x: vm.x,
4714 y: vm.y
4715 };
4716 }
4717});
4718
4719var elements = {};
4720var Arc = element_arc;
4721var Line = element_line;
4722var Point = element_point;
4723var Rectangle = element_rectangle;
4724elements.Arc = Arc;
4725elements.Line = Line;
4726elements.Point = Point;
4727elements.Rectangle = Rectangle;
4728
4729var deprecated = helpers$1._deprecated;
4730var valueOrDefault$3 = helpers$1.valueOrDefault;
4731
4732core_defaults._set('bar', {
4733 hover: {
4734 mode: 'label'
4735 },
4736
4737 scales: {
4738 xAxes: [{
4739 type: 'category',
4740 offset: true,
4741 gridLines: {
4742 offsetGridLines: true
4743 }
4744 }],
4745
4746 yAxes: [{
4747 type: 'linear'
4748 }]
4749 }
4750});
4751
4752core_defaults._set('global', {
4753 datasets: {
4754 bar: {
4755 categoryPercentage: 0.8,
4756 barPercentage: 0.9
4757 }
4758 }
4759});
4760
4761/**
4762 * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap.
4763 * @private
4764 */
4765function computeMinSampleSize(scale, pixels) {
4766 var min = scale._length;
4767 var prev, curr, i, ilen;
4768
4769 for (i = 1, ilen = pixels.length; i < ilen; ++i) {
4770 min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1]));
4771 }
4772
4773 for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) {
4774 curr = scale.getPixelForTick(i);
4775 min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min;
4776 prev = curr;
4777 }
4778
4779 return min;
4780}
4781
4782/**
4783 * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null,
4784 * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This
4785 * mode currently always generates bars equally sized (until we introduce scriptable options?).
4786 * @private
4787 */
4788function computeFitCategoryTraits(index, ruler, options) {
4789 var thickness = options.barThickness;
4790 var count = ruler.stackCount;
4791 var curr = ruler.pixels[index];
4792 var min = helpers$1.isNullOrUndef(thickness)
4793 ? computeMinSampleSize(ruler.scale, ruler.pixels)
4794 : -1;
4795 var size, ratio;
4796
4797 if (helpers$1.isNullOrUndef(thickness)) {
4798 size = min * options.categoryPercentage;
4799 ratio = options.barPercentage;
4800 } else {
4801 // When bar thickness is enforced, category and bar percentages are ignored.
4802 // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%')
4803 // and deprecate barPercentage since this value is ignored when thickness is absolute.
4804 size = thickness * count;
4805 ratio = 1;
4806 }
4807
4808 return {
4809 chunk: size / count,
4810 ratio: ratio,
4811 start: curr - (size / 2)
4812 };
4813}
4814
4815/**
4816 * Computes an "optimal" category that globally arranges bars side by side (no gap when
4817 * percentage options are 1), based on the previous and following categories. This mode
4818 * generates bars with different widths when data are not evenly spaced.
4819 * @private
4820 */
4821function computeFlexCategoryTraits(index, ruler, options) {
4822 var pixels = ruler.pixels;
4823 var curr = pixels[index];
4824 var prev = index > 0 ? pixels[index - 1] : null;
4825 var next = index < pixels.length - 1 ? pixels[index + 1] : null;
4826 var percent = options.categoryPercentage;
4827 var start, size;
4828
4829 if (prev === null) {
4830 // first data: its size is double based on the next point or,
4831 // if it's also the last data, we use the scale size.
4832 prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
4833 }
4834
4835 if (next === null) {
4836 // last data: its size is also double based on the previous point.
4837 next = curr + curr - prev;
4838 }
4839
4840 start = curr - (curr - Math.min(prev, next)) / 2 * percent;
4841 size = Math.abs(next - prev) / 2 * percent;
4842
4843 return {
4844 chunk: size / ruler.stackCount,
4845 ratio: options.barPercentage,
4846 start: start
4847 };
4848}
4849
4850var controller_bar = core_datasetController.extend({
4851
4852 dataElementType: elements.Rectangle,
4853
4854 /**
4855 * @private
4856 */
4857 _dataElementOptions: [
4858 'backgroundColor',
4859 'borderColor',
4860 'borderSkipped',
4861 'borderWidth',
4862 'barPercentage',
4863 'barThickness',
4864 'categoryPercentage',
4865 'maxBarThickness',
4866 'minBarLength'
4867 ],
4868
4869 initialize: function() {
4870 var me = this;
4871 var meta, scaleOpts;
4872
4873 core_datasetController.prototype.initialize.apply(me, arguments);
4874
4875 meta = me.getMeta();
4876 meta.stack = me.getDataset().stack;
4877 meta.bar = true;
4878
4879 scaleOpts = me._getIndexScale().options;
4880 deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage');
4881 deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness');
4882 deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage');
4883 deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength');
4884 deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness');
4885 },
4886
4887 update: function(reset) {
4888 var me = this;
4889 var rects = me.getMeta().data;
4890 var i, ilen;
4891
4892 me._ruler = me.getRuler();
4893
4894 for (i = 0, ilen = rects.length; i < ilen; ++i) {
4895 me.updateElement(rects[i], i, reset);
4896 }
4897 },
4898
4899 updateElement: function(rectangle, index, reset) {
4900 var me = this;
4901 var meta = me.getMeta();
4902 var dataset = me.getDataset();
4903 var options = me._resolveDataElementOptions(rectangle, index);
4904
4905 rectangle._xScale = me.getScaleForId(meta.xAxisID);
4906 rectangle._yScale = me.getScaleForId(meta.yAxisID);
4907 rectangle._datasetIndex = me.index;
4908 rectangle._index = index;
4909 rectangle._model = {
4910 backgroundColor: options.backgroundColor,
4911 borderColor: options.borderColor,
4912 borderSkipped: options.borderSkipped,
4913 borderWidth: options.borderWidth,
4914 datasetLabel: dataset.label,
4915 label: me.chart.data.labels[index]
4916 };
4917
4918 if (helpers$1.isArray(dataset.data[index])) {
4919 rectangle._model.borderSkipped = null;
4920 }
4921
4922 me._updateElementGeometry(rectangle, index, reset, options);
4923
4924 rectangle.pivot();
4925 },
4926
4927 /**
4928 * @private
4929 */
4930 _updateElementGeometry: function(rectangle, index, reset, options) {
4931 var me = this;
4932 var model = rectangle._model;
4933 var vscale = me._getValueScale();
4934 var base = vscale.getBasePixel();
4935 var horizontal = vscale.isHorizontal();
4936 var ruler = me._ruler || me.getRuler();
4937 var vpixels = me.calculateBarValuePixels(me.index, index, options);
4938 var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options);
4939
4940 model.horizontal = horizontal;
4941 model.base = reset ? base : vpixels.base;
4942 model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
4943 model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
4944 model.height = horizontal ? ipixels.size : undefined;
4945 model.width = horizontal ? undefined : ipixels.size;
4946 },
4947
4948 /**
4949 * Returns the stacks based on groups and bar visibility.
4950 * @param {number} [last] - The dataset index
4951 * @returns {string[]} The list of stack IDs
4952 * @private
4953 */
4954 _getStacks: function(last) {
4955 var me = this;
4956 var scale = me._getIndexScale();
4957 var metasets = scale._getMatchingVisibleMetas(me._type);
4958 var stacked = scale.options.stacked;
4959 var ilen = metasets.length;
4960 var stacks = [];
4961 var i, meta;
4962
4963 for (i = 0; i < ilen; ++i) {
4964 meta = metasets[i];
4965 // stacked | meta.stack
4966 // | found | not found | undefined
4967 // false | x | x | x
4968 // true | | x |
4969 // undefined | | x | x
4970 if (stacked === false || stacks.indexOf(meta.stack) === -1 ||
4971 (stacked === undefined && meta.stack === undefined)) {
4972 stacks.push(meta.stack);
4973 }
4974 if (meta.index === last) {
4975 break;
4976 }
4977 }
4978
4979 return stacks;
4980 },
4981
4982 /**
4983 * Returns the effective number of stacks based on groups and bar visibility.
4984 * @private
4985 */
4986 getStackCount: function() {
4987 return this._getStacks().length;
4988 },
4989
4990 /**
4991 * Returns the stack index for the given dataset based on groups and bar visibility.
4992 * @param {number} [datasetIndex] - The dataset index
4993 * @param {string} [name] - The stack name to find
4994 * @returns {number} The stack index
4995 * @private
4996 */
4997 getStackIndex: function(datasetIndex, name) {
4998 var stacks = this._getStacks(datasetIndex);
4999 var index = (name !== undefined)
5000 ? stacks.indexOf(name)
5001 : -1; // indexOf returns -1 if element is not present
5002
5003 return (index === -1)
5004 ? stacks.length - 1
5005 : index;
5006 },
5007
5008 /**
5009 * @private
5010 */
5011 getRuler: function() {
5012 var me = this;
5013 var scale = me._getIndexScale();
5014 var pixels = [];
5015 var i, ilen;
5016
5017 for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
5018 pixels.push(scale.getPixelForValue(null, i, me.index));
5019 }
5020
5021 return {
5022 pixels: pixels,
5023 start: scale._startPixel,
5024 end: scale._endPixel,
5025 stackCount: me.getStackCount(),
5026 scale: scale
5027 };
5028 },
5029
5030 /**
5031 * Note: pixel values are not clamped to the scale area.
5032 * @private
5033 */
5034 calculateBarValuePixels: function(datasetIndex, index, options) {
5035 var me = this;
5036 var chart = me.chart;
5037 var scale = me._getValueScale();
5038 var isHorizontal = scale.isHorizontal();
5039 var datasets = chart.data.datasets;
5040 var metasets = scale._getMatchingVisibleMetas(me._type);
5041 var value = scale._parseValue(datasets[datasetIndex].data[index]);
5042 var minBarLength = options.minBarLength;
5043 var stacked = scale.options.stacked;
5044 var stack = me.getMeta().stack;
5045 var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max;
5046 var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max;
5047 var ilen = metasets.length;
5048 var i, imeta, ivalue, base, head, size, stackLength;
5049
5050 if (stacked || (stacked === undefined && stack !== undefined)) {
5051 for (i = 0; i < ilen; ++i) {
5052 imeta = metasets[i];
5053
5054 if (imeta.index === datasetIndex) {
5055 break;
5056 }
5057
5058 if (imeta.stack === stack) {
5059 stackLength = scale._parseValue(datasets[imeta.index].data[index]);
5060 ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min;
5061
5062 if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) {
5063 start += ivalue;
5064 }
5065 }
5066 }
5067 }
5068
5069 base = scale.getPixelForValue(start);
5070 head = scale.getPixelForValue(start + length);
5071 size = head - base;
5072
5073 if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
5074 size = minBarLength;
5075 if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) {
5076 head = base - minBarLength;
5077 } else {
5078 head = base + minBarLength;
5079 }
5080 }
5081
5082 return {
5083 size: size,
5084 base: base,
5085 head: head,
5086 center: head + size / 2
5087 };
5088 },
5089
5090 /**
5091 * @private
5092 */
5093 calculateBarIndexPixels: function(datasetIndex, index, ruler, options) {
5094 var me = this;
5095 var range = options.barThickness === 'flex'
5096 ? computeFlexCategoryTraits(index, ruler, options)
5097 : computeFitCategoryTraits(index, ruler, options);
5098
5099 var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);
5100 var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
5101 var size = Math.min(
5102 valueOrDefault$3(options.maxBarThickness, Infinity),
5103 range.chunk * range.ratio);
5104
5105 return {
5106 base: center - size / 2,
5107 head: center + size / 2,
5108 center: center,
5109 size: size
5110 };
5111 },
5112
5113 draw: function() {
5114 var me = this;
5115 var chart = me.chart;
5116 var scale = me._getValueScale();
5117 var rects = me.getMeta().data;
5118 var dataset = me.getDataset();
5119 var ilen = rects.length;
5120 var i = 0;
5121
5122 helpers$1.canvas.clipArea(chart.ctx, chart.chartArea);
5123
5124 for (; i < ilen; ++i) {
5125 var val = scale._parseValue(dataset.data[i]);
5126 if (!isNaN(val.min) && !isNaN(val.max)) {
5127 rects[i].draw();
5128 }
5129 }
5130
5131 helpers$1.canvas.unclipArea(chart.ctx);
5132 },
5133
5134 /**
5135 * @private
5136 */
5137 _resolveDataElementOptions: function() {
5138 var me = this;
5139 var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments));
5140 var indexOpts = me._getIndexScale().options;
5141 var valueOpts = me._getValueScale().options;
5142
5143 values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage);
5144 values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness);
5145 values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage);
5146 values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness);
5147 values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength);
5148
5149 return values;
5150 }
5151
5152});
5153
5154var valueOrDefault$4 = helpers$1.valueOrDefault;
5155var resolve$1 = helpers$1.options.resolve;
5156
5157core_defaults._set('bubble', {
5158 hover: {
5159 mode: 'single'
5160 },
5161
5162 scales: {
5163 xAxes: [{
5164 type: 'linear', // bubble should probably use a linear scale by default
5165 position: 'bottom',
5166 id: 'x-axis-0' // need an ID so datasets can reference the scale
5167 }],
5168 yAxes: [{
5169 type: 'linear',
5170 position: 'left',
5171 id: 'y-axis-0'
5172 }]
5173 },
5174
5175 tooltips: {
5176 callbacks: {
5177 title: function() {
5178 // Title doesn't make sense for scatter since we format the data as a point
5179 return '';
5180 },
5181 label: function(item, data) {
5182 var datasetLabel = data.datasets[item.datasetIndex].label || '';
5183 var dataPoint = data.datasets[item.datasetIndex].data[item.index];
5184 return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
5185 }
5186 }
5187 }
5188});
5189
5190var controller_bubble = core_datasetController.extend({
5191 /**
5192 * @protected
5193 */
5194 dataElementType: elements.Point,
5195
5196 /**
5197 * @private
5198 */
5199 _dataElementOptions: [
5200 'backgroundColor',
5201 'borderColor',
5202 'borderWidth',
5203 'hoverBackgroundColor',
5204 'hoverBorderColor',
5205 'hoverBorderWidth',
5206 'hoverRadius',
5207 'hitRadius',
5208 'pointStyle',
5209 'rotation'
5210 ],
5211
5212 /**
5213 * @protected
5214 */
5215 update: function(reset) {
5216 var me = this;
5217 var meta = me.getMeta();
5218 var points = meta.data;
5219
5220 // Update Points
5221 helpers$1.each(points, function(point, index) {
5222 me.updateElement(point, index, reset);
5223 });
5224 },
5225
5226 /**
5227 * @protected
5228 */
5229 updateElement: function(point, index, reset) {
5230 var me = this;
5231 var meta = me.getMeta();
5232 var custom = point.custom || {};
5233 var xScale = me.getScaleForId(meta.xAxisID);
5234 var yScale = me.getScaleForId(meta.yAxisID);
5235 var options = me._resolveDataElementOptions(point, index);
5236 var data = me.getDataset().data[index];
5237 var dsIndex = me.index;
5238
5239 var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
5240 var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
5241
5242 point._xScale = xScale;
5243 point._yScale = yScale;
5244 point._options = options;
5245 point._datasetIndex = dsIndex;
5246 point._index = index;
5247 point._model = {
5248 backgroundColor: options.backgroundColor,
5249 borderColor: options.borderColor,
5250 borderWidth: options.borderWidth,
5251 hitRadius: options.hitRadius,
5252 pointStyle: options.pointStyle,
5253 rotation: options.rotation,
5254 radius: reset ? 0 : options.radius,
5255 skip: custom.skip || isNaN(x) || isNaN(y),
5256 x: x,
5257 y: y,
5258 };
5259
5260 point.pivot();
5261 },
5262
5263 /**
5264 * @protected
5265 */
5266 setHoverStyle: function(point) {
5267 var model = point._model;
5268 var options = point._options;
5269 var getHoverColor = helpers$1.getHoverColor;
5270
5271 point.$previousStyle = {
5272 backgroundColor: model.backgroundColor,
5273 borderColor: model.borderColor,
5274 borderWidth: model.borderWidth,
5275 radius: model.radius
5276 };
5277
5278 model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
5279 model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor));
5280 model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth);
5281 model.radius = options.radius + options.hoverRadius;
5282 },
5283
5284 /**
5285 * @private
5286 */
5287 _resolveDataElementOptions: function(point, index) {
5288 var me = this;
5289 var chart = me.chart;
5290 var dataset = me.getDataset();
5291 var custom = point.custom || {};
5292 var data = dataset.data[index] || {};
5293 var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments);
5294
5295 // Scriptable options
5296 var context = {
5297 chart: chart,
5298 dataIndex: index,
5299 dataset: dataset,
5300 datasetIndex: me.index
5301 };
5302
5303 // In case values were cached (and thus frozen), we need to clone the values
5304 if (me._cachedDataOpts === values) {
5305 values = helpers$1.extend({}, values);
5306 }
5307
5308 // Custom radius resolution
5309 values.radius = resolve$1([
5310 custom.radius,
5311 data.r,
5312 me._config.radius,
5313 chart.options.elements.point.radius
5314 ], context, index);
5315
5316 return values;
5317 }
5318});
5319
5320var valueOrDefault$5 = helpers$1.valueOrDefault;
5321
5322var PI$1 = Math.PI;
5323var DOUBLE_PI$1 = PI$1 * 2;
5324var HALF_PI$1 = PI$1 / 2;
5325
5326core_defaults._set('doughnut', {
5327 animation: {
5328 // Boolean - Whether we animate the rotation of the Doughnut
5329 animateRotate: true,
5330 // Boolean - Whether we animate scaling the Doughnut from the centre
5331 animateScale: false
5332 },
5333 hover: {
5334 mode: 'single'
5335 },
5336 legendCallback: function(chart) {
5337 var list = document.createElement('ul');
5338 var data = chart.data;
5339 var datasets = data.datasets;
5340 var labels = data.labels;
5341 var i, ilen, listItem, listItemSpan;
5342
5343 list.setAttribute('class', chart.id + '-legend');
5344 if (datasets.length) {
5345 for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) {
5346 listItem = list.appendChild(document.createElement('li'));
5347 listItemSpan = listItem.appendChild(document.createElement('span'));
5348 listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i];
5349 if (labels[i]) {
5350 listItem.appendChild(document.createTextNode(labels[i]));
5351 }
5352 }
5353 }
5354
5355 return list.outerHTML;
5356 },
5357 legend: {
5358 labels: {
5359 generateLabels: function(chart) {
5360 var data = chart.data;
5361 if (data.labels.length && data.datasets.length) {
5362 return data.labels.map(function(label, i) {
5363 var meta = chart.getDatasetMeta(0);
5364 var style = meta.controller.getStyle(i);
5365
5366 return {
5367 text: label,
5368 fillStyle: style.backgroundColor,
5369 strokeStyle: style.borderColor,
5370 lineWidth: style.borderWidth,
5371 hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden,
5372
5373 // Extra data used for toggling the correct item
5374 index: i
5375 };
5376 });
5377 }
5378 return [];
5379 }
5380 },
5381
5382 onClick: function(e, legendItem) {
5383 var index = legendItem.index;
5384 var chart = this.chart;
5385 var i, ilen, meta;
5386
5387 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
5388 meta = chart.getDatasetMeta(i);
5389 // toggle visibility of index if exists
5390 if (meta.data[index]) {
5391 meta.data[index].hidden = !meta.data[index].hidden;
5392 }
5393 }
5394
5395 chart.update();
5396 }
5397 },
5398
5399 // The percentage of the chart that we cut out of the middle.
5400 cutoutPercentage: 50,
5401
5402 // The rotation of the chart, where the first data arc begins.
5403 rotation: -HALF_PI$1,
5404
5405 // The total circumference of the chart.
5406 circumference: DOUBLE_PI$1,
5407
5408 // Need to override these to give a nice default
5409 tooltips: {
5410 callbacks: {
5411 title: function() {
5412 return '';
5413 },
5414 label: function(tooltipItem, data) {
5415 var dataLabel = data.labels[tooltipItem.index];
5416 var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
5417
5418 if (helpers$1.isArray(dataLabel)) {
5419 // show value on first line of multiline label
5420 // need to clone because we are changing the value
5421 dataLabel = dataLabel.slice();
5422 dataLabel[0] += value;
5423 } else {
5424 dataLabel += value;
5425 }
5426
5427 return dataLabel;
5428 }
5429 }
5430 }
5431});
5432
5433var controller_doughnut = core_datasetController.extend({
5434
5435 dataElementType: elements.Arc,
5436
5437 linkScales: helpers$1.noop,
5438
5439 /**
5440 * @private
5441 */
5442 _dataElementOptions: [
5443 'backgroundColor',
5444 'borderColor',
5445 'borderWidth',
5446 'borderAlign',
5447 'hoverBackgroundColor',
5448 'hoverBorderColor',
5449 'hoverBorderWidth',
5450 ],
5451
5452 // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
5453 getRingIndex: function(datasetIndex) {
5454 var ringIndex = 0;
5455
5456 for (var j = 0; j < datasetIndex; ++j) {
5457 if (this.chart.isDatasetVisible(j)) {
5458 ++ringIndex;
5459 }
5460 }
5461
5462 return ringIndex;
5463 },
5464
5465 update: function(reset) {
5466 var me = this;
5467 var chart = me.chart;
5468 var chartArea = chart.chartArea;
5469 var opts = chart.options;
5470 var ratioX = 1;
5471 var ratioY = 1;
5472 var offsetX = 0;
5473 var offsetY = 0;
5474 var meta = me.getMeta();
5475 var arcs = meta.data;
5476 var cutout = opts.cutoutPercentage / 100 || 0;
5477 var circumference = opts.circumference;
5478 var chartWeight = me._getRingWeight(me.index);
5479 var maxWidth, maxHeight, i, ilen;
5480
5481 // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc
5482 if (circumference < DOUBLE_PI$1) {
5483 var startAngle = opts.rotation % DOUBLE_PI$1;
5484 startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0;
5485 var endAngle = startAngle + circumference;
5486 var startX = Math.cos(startAngle);
5487 var startY = Math.sin(startAngle);
5488 var endX = Math.cos(endAngle);
5489 var endY = Math.sin(endAngle);
5490 var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI$1;
5491 var contains90 = (startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1) || endAngle >= DOUBLE_PI$1 + HALF_PI$1;
5492 var contains180 = startAngle === -PI$1 || endAngle >= PI$1;
5493 var contains270 = (startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1) || endAngle >= PI$1 + HALF_PI$1;
5494 var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout);
5495 var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout);
5496 var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout);
5497 var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout);
5498 ratioX = (maxX - minX) / 2;
5499 ratioY = (maxY - minY) / 2;
5500 offsetX = -(maxX + minX) / 2;
5501 offsetY = -(maxY + minY) / 2;
5502 }
5503
5504 for (i = 0, ilen = arcs.length; i < ilen; ++i) {
5505 arcs[i]._options = me._resolveDataElementOptions(arcs[i], i);
5506 }
5507
5508 chart.borderWidth = me.getMaxBorderWidth();
5509 maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX;
5510 maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY;
5511 chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
5512 chart.innerRadius = Math.max(chart.outerRadius * cutout, 0);
5513 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1);
5514 chart.offsetX = offsetX * chart.outerRadius;
5515 chart.offsetY = offsetY * chart.outerRadius;
5516
5517 meta.total = me.calculateTotal();
5518
5519 me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index);
5520 me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0);
5521
5522 for (i = 0, ilen = arcs.length; i < ilen; ++i) {
5523 me.updateElement(arcs[i], i, reset);
5524 }
5525 },
5526
5527 updateElement: function(arc, index, reset) {
5528 var me = this;
5529 var chart = me.chart;
5530 var chartArea = chart.chartArea;
5531 var opts = chart.options;
5532 var animationOpts = opts.animation;
5533 var centerX = (chartArea.left + chartArea.right) / 2;
5534 var centerY = (chartArea.top + chartArea.bottom) / 2;
5535 var startAngle = opts.rotation; // non reset case handled later
5536 var endAngle = opts.rotation; // non reset case handled later
5537 var dataset = me.getDataset();
5538 var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1);
5539 var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
5540 var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
5541 var options = arc._options || {};
5542
5543 helpers$1.extend(arc, {
5544 // Utility
5545 _datasetIndex: me.index,
5546 _index: index,
5547
5548 // Desired view properties
5549 _model: {
5550 backgroundColor: options.backgroundColor,
5551 borderColor: options.borderColor,
5552 borderWidth: options.borderWidth,
5553 borderAlign: options.borderAlign,
5554 x: centerX + chart.offsetX,
5555 y: centerY + chart.offsetY,
5556 startAngle: startAngle,
5557 endAngle: endAngle,
5558 circumference: circumference,
5559 outerRadius: outerRadius,
5560 innerRadius: innerRadius,
5561 label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
5562 }
5563 });
5564
5565 var model = arc._model;
5566
5567 // Set correct angles if not resetting
5568 if (!reset || !animationOpts.animateRotate) {
5569 if (index === 0) {
5570 model.startAngle = opts.rotation;
5571 } else {
5572 model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
5573 }
5574
5575 model.endAngle = model.startAngle + model.circumference;
5576 }
5577
5578 arc.pivot();
5579 },
5580
5581 calculateTotal: function() {
5582 var dataset = this.getDataset();
5583 var meta = this.getMeta();
5584 var total = 0;
5585 var value;
5586
5587 helpers$1.each(meta.data, function(element, index) {
5588 value = dataset.data[index];
5589 if (!isNaN(value) && !element.hidden) {
5590 total += Math.abs(value);
5591 }
5592 });
5593
5594 /* if (total === 0) {
5595 total = NaN;
5596 }*/
5597
5598 return total;
5599 },
5600
5601 calculateCircumference: function(value) {
5602 var total = this.getMeta().total;
5603 if (total > 0 && !isNaN(value)) {
5604 return DOUBLE_PI$1 * (Math.abs(value) / total);
5605 }
5606 return 0;
5607 },
5608
5609 // gets the max border or hover width to properly scale pie charts
5610 getMaxBorderWidth: function(arcs) {
5611 var me = this;
5612 var max = 0;
5613 var chart = me.chart;
5614 var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth;
5615
5616 if (!arcs) {
5617 // Find the outmost visible dataset
5618 for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
5619 if (chart.isDatasetVisible(i)) {
5620 meta = chart.getDatasetMeta(i);
5621 arcs = meta.data;
5622 if (i !== me.index) {
5623 controller = meta.controller;
5624 }
5625 break;
5626 }
5627 }
5628 }
5629
5630 if (!arcs) {
5631 return 0;
5632 }
5633
5634 for (i = 0, ilen = arcs.length; i < ilen; ++i) {
5635 arc = arcs[i];
5636 if (controller) {
5637 controller._configure();
5638 options = controller._resolveDataElementOptions(arc, i);
5639 } else {
5640 options = arc._options;
5641 }
5642 if (options.borderAlign !== 'inner') {
5643 borderWidth = options.borderWidth;
5644 hoverWidth = options.hoverBorderWidth;
5645
5646 max = borderWidth > max ? borderWidth : max;
5647 max = hoverWidth > max ? hoverWidth : max;
5648 }
5649 }
5650 return max;
5651 },
5652
5653 /**
5654 * @protected
5655 */
5656 setHoverStyle: function(arc) {
5657 var model = arc._model;
5658 var options = arc._options;
5659 var getHoverColor = helpers$1.getHoverColor;
5660
5661 arc.$previousStyle = {
5662 backgroundColor: model.backgroundColor,
5663 borderColor: model.borderColor,
5664 borderWidth: model.borderWidth,
5665 };
5666
5667 model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
5668 model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor));
5669 model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth);
5670 },
5671
5672 /**
5673 * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly
5674 * @private
5675 */
5676 _getRingWeightOffset: function(datasetIndex) {
5677 var ringWeightOffset = 0;
5678
5679 for (var i = 0; i < datasetIndex; ++i) {
5680 if (this.chart.isDatasetVisible(i)) {
5681 ringWeightOffset += this._getRingWeight(i);
5682 }
5683 }
5684
5685 return ringWeightOffset;
5686 },
5687
5688 /**
5689 * @private
5690 */
5691 _getRingWeight: function(dataSetIndex) {
5692 return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0);
5693 },
5694
5695 /**
5696 * Returns the sum of all visibile data set weights. This value can be 0.
5697 * @private
5698 */
5699 _getVisibleDatasetWeightTotal: function() {
5700 return this._getRingWeightOffset(this.chart.data.datasets.length);
5701 }
5702});
5703
5704core_defaults._set('horizontalBar', {
5705 hover: {
5706 mode: 'index',
5707 axis: 'y'
5708 },
5709
5710 scales: {
5711 xAxes: [{
5712 type: 'linear',
5713 position: 'bottom'
5714 }],
5715
5716 yAxes: [{
5717 type: 'category',
5718 position: 'left',
5719 offset: true,
5720 gridLines: {
5721 offsetGridLines: true
5722 }
5723 }]
5724 },
5725
5726 elements: {
5727 rectangle: {
5728 borderSkipped: 'left'
5729 }
5730 },
5731
5732 tooltips: {
5733 mode: 'index',
5734 axis: 'y'
5735 }
5736});
5737
5738core_defaults._set('global', {
5739 datasets: {
5740 horizontalBar: {
5741 categoryPercentage: 0.8,
5742 barPercentage: 0.9
5743 }
5744 }
5745});
5746
5747var controller_horizontalBar = controller_bar.extend({
5748 /**
5749 * @private
5750 */
5751 _getValueScaleId: function() {
5752 return this.getMeta().xAxisID;
5753 },
5754
5755 /**
5756 * @private
5757 */
5758 _getIndexScaleId: function() {
5759 return this.getMeta().yAxisID;
5760 }
5761});
5762
5763var valueOrDefault$6 = helpers$1.valueOrDefault;
5764var resolve$2 = helpers$1.options.resolve;
5765var isPointInArea = helpers$1.canvas._isPointInArea;
5766
5767core_defaults._set('line', {
5768 showLines: true,
5769 spanGaps: false,
5770
5771 hover: {
5772 mode: 'label'
5773 },
5774
5775 scales: {
5776 xAxes: [{
5777 type: 'category',
5778 id: 'x-axis-0'
5779 }],
5780 yAxes: [{
5781 type: 'linear',
5782 id: 'y-axis-0'
5783 }]
5784 }
5785});
5786
5787function scaleClip(scale, halfBorderWidth) {
5788 var tickOpts = scale && scale.options.ticks || {};
5789 var reverse = tickOpts.reverse;
5790 var min = tickOpts.min === undefined ? halfBorderWidth : 0;
5791 var max = tickOpts.max === undefined ? halfBorderWidth : 0;
5792 return {
5793 start: reverse ? max : min,
5794 end: reverse ? min : max
5795 };
5796}
5797
5798function defaultClip(xScale, yScale, borderWidth) {
5799 var halfBorderWidth = borderWidth / 2;
5800 var x = scaleClip(xScale, halfBorderWidth);
5801 var y = scaleClip(yScale, halfBorderWidth);
5802
5803 return {
5804 top: y.end,
5805 right: x.end,
5806 bottom: y.start,
5807 left: x.start
5808 };
5809}
5810
5811function toClip(value) {
5812 var t, r, b, l;
5813
5814 if (helpers$1.isObject(value)) {
5815 t = value.top;
5816 r = value.right;
5817 b = value.bottom;
5818 l = value.left;
5819 } else {
5820 t = r = b = l = value;
5821 }
5822
5823 return {
5824 top: t,
5825 right: r,
5826 bottom: b,
5827 left: l
5828 };
5829}
5830
5831
5832var controller_line = core_datasetController.extend({
5833
5834 datasetElementType: elements.Line,
5835
5836 dataElementType: elements.Point,
5837
5838 /**
5839 * @private
5840 */
5841 _datasetElementOptions: [
5842 'backgroundColor',
5843 'borderCapStyle',
5844 'borderColor',
5845 'borderDash',
5846 'borderDashOffset',
5847 'borderJoinStyle',
5848 'borderWidth',
5849 'cubicInterpolationMode',
5850 'fill'
5851 ],
5852
5853 /**
5854 * @private
5855 */
5856 _dataElementOptions: {
5857 backgroundColor: 'pointBackgroundColor',
5858 borderColor: 'pointBorderColor',
5859 borderWidth: 'pointBorderWidth',
5860 hitRadius: 'pointHitRadius',
5861 hoverBackgroundColor: 'pointHoverBackgroundColor',
5862 hoverBorderColor: 'pointHoverBorderColor',
5863 hoverBorderWidth: 'pointHoverBorderWidth',
5864 hoverRadius: 'pointHoverRadius',
5865 pointStyle: 'pointStyle',
5866 radius: 'pointRadius',
5867 rotation: 'pointRotation'
5868 },
5869
5870 update: function(reset) {
5871 var me = this;
5872 var meta = me.getMeta();
5873 var line = meta.dataset;
5874 var points = meta.data || [];
5875 var options = me.chart.options;
5876 var config = me._config;
5877 var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines);
5878 var i, ilen;
5879
5880 me._xScale = me.getScaleForId(meta.xAxisID);
5881 me._yScale = me.getScaleForId(meta.yAxisID);
5882
5883 // Update Line
5884 if (showLine) {
5885 // Compatibility: If the properties are defined with only the old name, use those values
5886 if (config.tension !== undefined && config.lineTension === undefined) {
5887 config.lineTension = config.tension;
5888 }
5889
5890 // Utility
5891 line._scale = me._yScale;
5892 line._datasetIndex = me.index;
5893 // Data
5894 line._children = points;
5895 // Model
5896 line._model = me._resolveDatasetElementOptions(line);
5897
5898 line.pivot();
5899 }
5900
5901 // Update Points
5902 for (i = 0, ilen = points.length; i < ilen; ++i) {
5903 me.updateElement(points[i], i, reset);
5904 }
5905
5906 if (showLine && line._model.tension !== 0) {
5907 me.updateBezierControlPoints();
5908 }
5909
5910 // Now pivot the point for animation
5911 for (i = 0, ilen = points.length; i < ilen; ++i) {
5912 points[i].pivot();
5913 }
5914 },
5915
5916 updateElement: function(point, index, reset) {
5917 var me = this;
5918 var meta = me.getMeta();
5919 var custom = point.custom || {};
5920 var dataset = me.getDataset();
5921 var datasetIndex = me.index;
5922 var value = dataset.data[index];
5923 var xScale = me._xScale;
5924 var yScale = me._yScale;
5925 var lineModel = meta.dataset._model;
5926 var x, y;
5927
5928 var options = me._resolveDataElementOptions(point, index);
5929
5930 x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
5931 y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
5932
5933 // Utility
5934 point._xScale = xScale;
5935 point._yScale = yScale;
5936 point._options = options;
5937 point._datasetIndex = datasetIndex;
5938 point._index = index;
5939
5940 // Desired view properties
5941 point._model = {
5942 x: x,
5943 y: y,
5944 skip: custom.skip || isNaN(x) || isNaN(y),
5945 // Appearance
5946 radius: options.radius,
5947 pointStyle: options.pointStyle,
5948 rotation: options.rotation,
5949 backgroundColor: options.backgroundColor,
5950 borderColor: options.borderColor,
5951 borderWidth: options.borderWidth,
5952 tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0),
5953 steppedLine: lineModel ? lineModel.steppedLine : false,
5954 // Tooltip
5955 hitRadius: options.hitRadius
5956 };
5957 },
5958
5959 /**
5960 * @private
5961 */
5962 _resolveDatasetElementOptions: function(element) {
5963 var me = this;
5964 var config = me._config;
5965 var custom = element.custom || {};
5966 var options = me.chart.options;
5967 var lineOptions = options.elements.line;
5968 var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments);
5969
5970 // The default behavior of lines is to break at null values, according
5971 // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
5972 // This option gives lines the ability to span gaps
5973 values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps);
5974 values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension);
5975 values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]);
5976 values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth)));
5977
5978 return values;
5979 },
5980
5981 calculatePointY: function(value, index, datasetIndex) {
5982 var me = this;
5983 var chart = me.chart;
5984 var yScale = me._yScale;
5985 var sumPos = 0;
5986 var sumNeg = 0;
5987 var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen;
5988
5989 if (yScale.options.stacked) {
5990 rightValue = +yScale.getRightValue(value);
5991 metasets = chart._getSortedVisibleDatasetMetas();
5992 ilen = metasets.length;
5993
5994 for (i = 0; i < ilen; ++i) {
5995 dsMeta = metasets[i];
5996 if (dsMeta.index === datasetIndex) {
5997 break;
5998 }
5999
6000 ds = chart.data.datasets[dsMeta.index];
6001 if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) {
6002 stackedRightValue = +yScale.getRightValue(ds.data[index]);
6003 if (stackedRightValue < 0) {
6004 sumNeg += stackedRightValue || 0;
6005 } else {
6006 sumPos += stackedRightValue || 0;
6007 }
6008 }
6009 }
6010
6011 if (rightValue < 0) {
6012 return yScale.getPixelForValue(sumNeg + rightValue);
6013 }
6014 return yScale.getPixelForValue(sumPos + rightValue);
6015 }
6016 return yScale.getPixelForValue(value);
6017 },
6018
6019 updateBezierControlPoints: function() {
6020 var me = this;
6021 var chart = me.chart;
6022 var meta = me.getMeta();
6023 var lineModel = meta.dataset._model;
6024 var area = chart.chartArea;
6025 var points = meta.data || [];
6026 var i, ilen, model, controlPoints;
6027
6028 // Only consider points that are drawn in case the spanGaps option is used
6029 if (lineModel.spanGaps) {
6030 points = points.filter(function(pt) {
6031 return !pt._model.skip;
6032 });
6033 }
6034
6035 function capControlPoint(pt, min, max) {
6036 return Math.max(Math.min(pt, max), min);
6037 }
6038
6039 if (lineModel.cubicInterpolationMode === 'monotone') {
6040 helpers$1.splineCurveMonotone(points);
6041 } else {
6042 for (i = 0, ilen = points.length; i < ilen; ++i) {
6043 model = points[i]._model;
6044 controlPoints = helpers$1.splineCurve(
6045 helpers$1.previousItem(points, i)._model,
6046 model,
6047 helpers$1.nextItem(points, i)._model,
6048 lineModel.tension
6049 );
6050 model.controlPointPreviousX = controlPoints.previous.x;
6051 model.controlPointPreviousY = controlPoints.previous.y;
6052 model.controlPointNextX = controlPoints.next.x;
6053 model.controlPointNextY = controlPoints.next.y;
6054 }
6055 }
6056
6057 if (chart.options.elements.line.capBezierPoints) {
6058 for (i = 0, ilen = points.length; i < ilen; ++i) {
6059 model = points[i]._model;
6060 if (isPointInArea(model, area)) {
6061 if (i > 0 && isPointInArea(points[i - 1]._model, area)) {
6062 model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
6063 model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
6064 }
6065 if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) {
6066 model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
6067 model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
6068 }
6069 }
6070 }
6071 }
6072 },
6073
6074 draw: function() {
6075 var me = this;
6076 var chart = me.chart;
6077 var meta = me.getMeta();
6078 var points = meta.data || [];
6079 var area = chart.chartArea;
6080 var canvas = chart.canvas;
6081 var i = 0;
6082 var ilen = points.length;
6083 var clip;
6084
6085 if (me._showLine) {
6086 clip = meta.dataset._model.clip;
6087
6088 helpers$1.canvas.clipArea(chart.ctx, {
6089 left: clip.left === false ? 0 : area.left - clip.left,
6090 right: clip.right === false ? canvas.width : area.right + clip.right,
6091 top: clip.top === false ? 0 : area.top - clip.top,
6092 bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom
6093 });
6094
6095 meta.dataset.draw();
6096
6097 helpers$1.canvas.unclipArea(chart.ctx);
6098 }
6099
6100 // Draw the points
6101 for (; i < ilen; ++i) {
6102 points[i].draw(area);
6103 }
6104 },
6105
6106 /**
6107 * @protected
6108 */
6109 setHoverStyle: function(point) {
6110 var model = point._model;
6111 var options = point._options;
6112 var getHoverColor = helpers$1.getHoverColor;
6113
6114 point.$previousStyle = {
6115 backgroundColor: model.backgroundColor,
6116 borderColor: model.borderColor,
6117 borderWidth: model.borderWidth,
6118 radius: model.radius
6119 };
6120
6121 model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
6122 model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor));
6123 model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth);
6124 model.radius = valueOrDefault$6(options.hoverRadius, options.radius);
6125 },
6126});
6127
6128var resolve$3 = helpers$1.options.resolve;
6129
6130core_defaults._set('polarArea', {
6131 scale: {
6132 type: 'radialLinear',
6133 angleLines: {
6134 display: false
6135 },
6136 gridLines: {
6137 circular: true
6138 },
6139 pointLabels: {
6140 display: false
6141 },
6142 ticks: {
6143 beginAtZero: true
6144 }
6145 },
6146
6147 // Boolean - Whether to animate the rotation of the chart
6148 animation: {
6149 animateRotate: true,
6150 animateScale: true
6151 },
6152
6153 startAngle: -0.5 * Math.PI,
6154 legendCallback: function(chart) {
6155 var list = document.createElement('ul');
6156 var data = chart.data;
6157 var datasets = data.datasets;
6158 var labels = data.labels;
6159 var i, ilen, listItem, listItemSpan;
6160
6161 list.setAttribute('class', chart.id + '-legend');
6162 if (datasets.length) {
6163 for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) {
6164 listItem = list.appendChild(document.createElement('li'));
6165 listItemSpan = listItem.appendChild(document.createElement('span'));
6166 listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i];
6167 if (labels[i]) {
6168 listItem.appendChild(document.createTextNode(labels[i]));
6169 }
6170 }
6171 }
6172
6173 return list.outerHTML;
6174 },
6175 legend: {
6176 labels: {
6177 generateLabels: function(chart) {
6178 var data = chart.data;
6179 if (data.labels.length && data.datasets.length) {
6180 return data.labels.map(function(label, i) {
6181 var meta = chart.getDatasetMeta(0);
6182 var style = meta.controller.getStyle(i);
6183
6184 return {
6185 text: label,
6186 fillStyle: style.backgroundColor,
6187 strokeStyle: style.borderColor,
6188 lineWidth: style.borderWidth,
6189 hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden,
6190
6191 // Extra data used for toggling the correct item
6192 index: i
6193 };
6194 });
6195 }
6196 return [];
6197 }
6198 },
6199
6200 onClick: function(e, legendItem) {
6201 var index = legendItem.index;
6202 var chart = this.chart;
6203 var i, ilen, meta;
6204
6205 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
6206 meta = chart.getDatasetMeta(i);
6207 meta.data[index].hidden = !meta.data[index].hidden;
6208 }
6209
6210 chart.update();
6211 }
6212 },
6213
6214 // Need to override these to give a nice default
6215 tooltips: {
6216 callbacks: {
6217 title: function() {
6218 return '';
6219 },
6220 label: function(item, data) {
6221 return data.labels[item.index] + ': ' + item.yLabel;
6222 }
6223 }
6224 }
6225});
6226
6227var controller_polarArea = core_datasetController.extend({
6228
6229 dataElementType: elements.Arc,
6230
6231 linkScales: helpers$1.noop,
6232
6233 /**
6234 * @private
6235 */
6236 _dataElementOptions: [
6237 'backgroundColor',
6238 'borderColor',
6239 'borderWidth',
6240 'borderAlign',
6241 'hoverBackgroundColor',
6242 'hoverBorderColor',
6243 'hoverBorderWidth',
6244 ],
6245
6246 /**
6247 * @private
6248 */
6249 _getIndexScaleId: function() {
6250 return this.chart.scale.id;
6251 },
6252
6253 /**
6254 * @private
6255 */
6256 _getValueScaleId: function() {
6257 return this.chart.scale.id;
6258 },
6259
6260 update: function(reset) {
6261 var me = this;
6262 var dataset = me.getDataset();
6263 var meta = me.getMeta();
6264 var start = me.chart.options.startAngle || 0;
6265 var starts = me._starts = [];
6266 var angles = me._angles = [];
6267 var arcs = meta.data;
6268 var i, ilen, angle;
6269
6270 me._updateRadius();
6271
6272 meta.count = me.countVisibleElements();
6273
6274 for (i = 0, ilen = dataset.data.length; i < ilen; i++) {
6275 starts[i] = start;
6276 angle = me._computeAngle(i);
6277 angles[i] = angle;
6278 start += angle;
6279 }
6280
6281 for (i = 0, ilen = arcs.length; i < ilen; ++i) {
6282 arcs[i]._options = me._resolveDataElementOptions(arcs[i], i);
6283 me.updateElement(arcs[i], i, reset);
6284 }
6285 },
6286
6287 /**
6288 * @private
6289 */
6290 _updateRadius: function() {
6291 var me = this;
6292 var chart = me.chart;
6293 var chartArea = chart.chartArea;
6294 var opts = chart.options;
6295 var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
6296
6297 chart.outerRadius = Math.max(minSize / 2, 0);
6298 chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
6299 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
6300
6301 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
6302 me.innerRadius = me.outerRadius - chart.radiusLength;
6303 },
6304
6305 updateElement: function(arc, index, reset) {
6306 var me = this;
6307 var chart = me.chart;
6308 var dataset = me.getDataset();
6309 var opts = chart.options;
6310 var animationOpts = opts.animation;
6311 var scale = chart.scale;
6312 var labels = chart.data.labels;
6313
6314 var centerX = scale.xCenter;
6315 var centerY = scale.yCenter;
6316
6317 // var negHalfPI = -0.5 * Math.PI;
6318 var datasetStartAngle = opts.startAngle;
6319 var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
6320 var startAngle = me._starts[index];
6321 var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]);
6322
6323 var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
6324 var options = arc._options || {};
6325
6326 helpers$1.extend(arc, {
6327 // Utility
6328 _datasetIndex: me.index,
6329 _index: index,
6330 _scale: scale,
6331
6332 // Desired view properties
6333 _model: {
6334 backgroundColor: options.backgroundColor,
6335 borderColor: options.borderColor,
6336 borderWidth: options.borderWidth,
6337 borderAlign: options.borderAlign,
6338 x: centerX,
6339 y: centerY,
6340 innerRadius: 0,
6341 outerRadius: reset ? resetRadius : distance,
6342 startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
6343 endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
6344 label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index])
6345 }
6346 });
6347
6348 arc.pivot();
6349 },
6350
6351 countVisibleElements: function() {
6352 var dataset = this.getDataset();
6353 var meta = this.getMeta();
6354 var count = 0;
6355
6356 helpers$1.each(meta.data, function(element, index) {
6357 if (!isNaN(dataset.data[index]) && !element.hidden) {
6358 count++;
6359 }
6360 });
6361
6362 return count;
6363 },
6364
6365 /**
6366 * @protected
6367 */
6368 setHoverStyle: function(arc) {
6369 var model = arc._model;
6370 var options = arc._options;
6371 var getHoverColor = helpers$1.getHoverColor;
6372 var valueOrDefault = helpers$1.valueOrDefault;
6373
6374 arc.$previousStyle = {
6375 backgroundColor: model.backgroundColor,
6376 borderColor: model.borderColor,
6377 borderWidth: model.borderWidth,
6378 };
6379
6380 model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
6381 model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor));
6382 model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth);
6383 },
6384
6385 /**
6386 * @private
6387 */
6388 _computeAngle: function(index) {
6389 var me = this;
6390 var count = this.getMeta().count;
6391 var dataset = me.getDataset();
6392 var meta = me.getMeta();
6393
6394 if (isNaN(dataset.data[index]) || meta.data[index].hidden) {
6395 return 0;
6396 }
6397
6398 // Scriptable options
6399 var context = {
6400 chart: me.chart,
6401 dataIndex: index,
6402 dataset: dataset,
6403 datasetIndex: me.index
6404 };
6405
6406 return resolve$3([
6407 me.chart.options.elements.arc.angle,
6408 (2 * Math.PI) / count
6409 ], context, index);
6410 }
6411});
6412
6413core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut));
6414core_defaults._set('pie', {
6415 cutoutPercentage: 0
6416});
6417
6418// Pie charts are Doughnut chart with different defaults
6419var controller_pie = controller_doughnut;
6420
6421var valueOrDefault$7 = helpers$1.valueOrDefault;
6422
6423core_defaults._set('radar', {
6424 spanGaps: false,
6425 scale: {
6426 type: 'radialLinear'
6427 },
6428 elements: {
6429 line: {
6430 fill: 'start',
6431 tension: 0 // no bezier in radar
6432 }
6433 }
6434});
6435
6436var controller_radar = core_datasetController.extend({
6437 datasetElementType: elements.Line,
6438
6439 dataElementType: elements.Point,
6440
6441 linkScales: helpers$1.noop,
6442
6443 /**
6444 * @private
6445 */
6446 _datasetElementOptions: [
6447 'backgroundColor',
6448 'borderWidth',
6449 'borderColor',
6450 'borderCapStyle',
6451 'borderDash',
6452 'borderDashOffset',
6453 'borderJoinStyle',
6454 'fill'
6455 ],
6456
6457 /**
6458 * @private
6459 */
6460 _dataElementOptions: {
6461 backgroundColor: 'pointBackgroundColor',
6462 borderColor: 'pointBorderColor',
6463 borderWidth: 'pointBorderWidth',
6464 hitRadius: 'pointHitRadius',
6465 hoverBackgroundColor: 'pointHoverBackgroundColor',
6466 hoverBorderColor: 'pointHoverBorderColor',
6467 hoverBorderWidth: 'pointHoverBorderWidth',
6468 hoverRadius: 'pointHoverRadius',
6469 pointStyle: 'pointStyle',
6470 radius: 'pointRadius',
6471 rotation: 'pointRotation'
6472 },
6473
6474 /**
6475 * @private
6476 */
6477 _getIndexScaleId: function() {
6478 return this.chart.scale.id;
6479 },
6480
6481 /**
6482 * @private
6483 */
6484 _getValueScaleId: function() {
6485 return this.chart.scale.id;
6486 },
6487
6488 update: function(reset) {
6489 var me = this;
6490 var meta = me.getMeta();
6491 var line = meta.dataset;
6492 var points = meta.data || [];
6493 var scale = me.chart.scale;
6494 var config = me._config;
6495 var i, ilen;
6496
6497 // Compatibility: If the properties are defined with only the old name, use those values
6498 if (config.tension !== undefined && config.lineTension === undefined) {
6499 config.lineTension = config.tension;
6500 }
6501
6502 // Utility
6503 line._scale = scale;
6504 line._datasetIndex = me.index;
6505 // Data
6506 line._children = points;
6507 line._loop = true;
6508 // Model
6509 line._model = me._resolveDatasetElementOptions(line);
6510
6511 line.pivot();
6512
6513 // Update Points
6514 for (i = 0, ilen = points.length; i < ilen; ++i) {
6515 me.updateElement(points[i], i, reset);
6516 }
6517
6518 // Update bezier control points
6519 me.updateBezierControlPoints();
6520
6521 // Now pivot the point for animation
6522 for (i = 0, ilen = points.length; i < ilen; ++i) {
6523 points[i].pivot();
6524 }
6525 },
6526
6527 updateElement: function(point, index, reset) {
6528 var me = this;
6529 var custom = point.custom || {};
6530 var dataset = me.getDataset();
6531 var scale = me.chart.scale;
6532 var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
6533 var options = me._resolveDataElementOptions(point, index);
6534 var lineModel = me.getMeta().dataset._model;
6535 var x = reset ? scale.xCenter : pointPosition.x;
6536 var y = reset ? scale.yCenter : pointPosition.y;
6537
6538 // Utility
6539 point._scale = scale;
6540 point._options = options;
6541 point._datasetIndex = me.index;
6542 point._index = index;
6543
6544 // Desired view properties
6545 point._model = {
6546 x: x, // value not used in dataset scale, but we want a consistent API between scales
6547 y: y,
6548 skip: custom.skip || isNaN(x) || isNaN(y),
6549 // Appearance
6550 radius: options.radius,
6551 pointStyle: options.pointStyle,
6552 rotation: options.rotation,
6553 backgroundColor: options.backgroundColor,
6554 borderColor: options.borderColor,
6555 borderWidth: options.borderWidth,
6556 tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0),
6557
6558 // Tooltip
6559 hitRadius: options.hitRadius
6560 };
6561 },
6562
6563 /**
6564 * @private
6565 */
6566 _resolveDatasetElementOptions: function() {
6567 var me = this;
6568 var config = me._config;
6569 var options = me.chart.options;
6570 var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments);
6571
6572 values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps);
6573 values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension);
6574
6575 return values;
6576 },
6577
6578 updateBezierControlPoints: function() {
6579 var me = this;
6580 var meta = me.getMeta();
6581 var area = me.chart.chartArea;
6582 var points = meta.data || [];
6583 var i, ilen, model, controlPoints;
6584
6585 // Only consider points that are drawn in case the spanGaps option is used
6586 if (meta.dataset._model.spanGaps) {
6587 points = points.filter(function(pt) {
6588 return !pt._model.skip;
6589 });
6590 }
6591
6592 function capControlPoint(pt, min, max) {
6593 return Math.max(Math.min(pt, max), min);
6594 }
6595
6596 for (i = 0, ilen = points.length; i < ilen; ++i) {
6597 model = points[i]._model;
6598 controlPoints = helpers$1.splineCurve(
6599 helpers$1.previousItem(points, i, true)._model,
6600 model,
6601 helpers$1.nextItem(points, i, true)._model,
6602 model.tension
6603 );
6604
6605 // Prevent the bezier going outside of the bounds of the graph
6606 model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right);
6607 model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom);
6608 model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right);
6609 model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom);
6610 }
6611 },
6612
6613 setHoverStyle: function(point) {
6614 var model = point._model;
6615 var options = point._options;
6616 var getHoverColor = helpers$1.getHoverColor;
6617
6618 point.$previousStyle = {
6619 backgroundColor: model.backgroundColor,
6620 borderColor: model.borderColor,
6621 borderWidth: model.borderWidth,
6622 radius: model.radius
6623 };
6624
6625 model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
6626 model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor));
6627 model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth);
6628 model.radius = valueOrDefault$7(options.hoverRadius, options.radius);
6629 }
6630});
6631
6632core_defaults._set('scatter', {
6633 hover: {
6634 mode: 'single'
6635 },
6636
6637 scales: {
6638 xAxes: [{
6639 id: 'x-axis-1', // need an ID so datasets can reference the scale
6640 type: 'linear', // scatter should not use a category axis
6641 position: 'bottom'
6642 }],
6643 yAxes: [{
6644 id: 'y-axis-1',
6645 type: 'linear',
6646 position: 'left'
6647 }]
6648 },
6649
6650 tooltips: {
6651 callbacks: {
6652 title: function() {
6653 return ''; // doesn't make sense for scatter since data are formatted as a point
6654 },
6655 label: function(item) {
6656 return '(' + item.xLabel + ', ' + item.yLabel + ')';
6657 }
6658 }
6659 }
6660});
6661
6662core_defaults._set('global', {
6663 datasets: {
6664 scatter: {
6665 showLine: false
6666 }
6667 }
6668});
6669
6670// Scatter charts use line controllers
6671var controller_scatter = controller_line;
6672
6673// NOTE export a map in which the key represents the controller type, not
6674// the class, and so must be CamelCase in order to be correctly retrieved
6675// by the controller in core.controller.js (`controllers[meta.type]`).
6676
6677var controllers = {
6678 bar: controller_bar,
6679 bubble: controller_bubble,
6680 doughnut: controller_doughnut,
6681 horizontalBar: controller_horizontalBar,
6682 line: controller_line,
6683 polarArea: controller_polarArea,
6684 pie: controller_pie,
6685 radar: controller_radar,
6686 scatter: controller_scatter
6687};
6688
6689/**
6690 * Helper function to get relative position for an event
6691 * @param {Event|IEvent} event - The event to get the position for
6692 * @param {Chart} chart - The chart
6693 * @returns {object} the event position
6694 */
6695function getRelativePosition(e, chart) {
6696 if (e.native) {
6697 return {
6698 x: e.x,
6699 y: e.y
6700 };
6701 }
6702
6703 return helpers$1.getRelativePosition(e, chart);
6704}
6705
6706/**
6707 * Helper function to traverse all of the visible elements in the chart
6708 * @param {Chart} chart - the chart
6709 * @param {function} handler - the callback to execute for each visible item
6710 */
6711function parseVisibleItems(chart, handler) {
6712 var metasets = chart._getSortedVisibleDatasetMetas();
6713 var metadata, i, j, ilen, jlen, element;
6714
6715 for (i = 0, ilen = metasets.length; i < ilen; ++i) {
6716 metadata = metasets[i].data;
6717 for (j = 0, jlen = metadata.length; j < jlen; ++j) {
6718 element = metadata[j];
6719 if (!element._view.skip) {
6720 handler(element);
6721 }
6722 }
6723 }
6724}
6725
6726/**
6727 * Helper function to get the items that intersect the event position
6728 * @param {ChartElement[]} items - elements to filter
6729 * @param {object} position - the point to be nearest to
6730 * @return {ChartElement[]} the nearest items
6731 */
6732function getIntersectItems(chart, position) {
6733 var elements = [];
6734
6735 parseVisibleItems(chart, function(element) {
6736 if (element.inRange(position.x, position.y)) {
6737 elements.push(element);
6738 }
6739 });
6740
6741 return elements;
6742}
6743
6744/**
6745 * Helper function to get the items nearest to the event position considering all visible items in teh chart
6746 * @param {Chart} chart - the chart to look at elements from
6747 * @param {object} position - the point to be nearest to
6748 * @param {boolean} intersect - if true, only consider items that intersect the position
6749 * @param {function} distanceMetric - function to provide the distance between points
6750 * @return {ChartElement[]} the nearest items
6751 */
6752function getNearestItems(chart, position, intersect, distanceMetric) {
6753 var minDistance = Number.POSITIVE_INFINITY;
6754 var nearestItems = [];
6755
6756 parseVisibleItems(chart, function(element) {
6757 if (intersect && !element.inRange(position.x, position.y)) {
6758 return;
6759 }
6760
6761 var center = element.getCenterPoint();
6762 var distance = distanceMetric(position, center);
6763 if (distance < minDistance) {
6764 nearestItems = [element];
6765 minDistance = distance;
6766 } else if (distance === minDistance) {
6767 // Can have multiple items at the same distance in which case we sort by size
6768 nearestItems.push(element);
6769 }
6770 });
6771
6772 return nearestItems;
6773}
6774
6775/**
6776 * Get a distance metric function for two points based on the
6777 * axis mode setting
6778 * @param {string} axis - the axis mode. x|y|xy
6779 */
6780function getDistanceMetricForAxis(axis) {
6781 var useX = axis.indexOf('x') !== -1;
6782 var useY = axis.indexOf('y') !== -1;
6783
6784 return function(pt1, pt2) {
6785 var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
6786 var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
6787 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
6788 };
6789}
6790
6791function indexMode(chart, e, options) {
6792 var position = getRelativePosition(e, chart);
6793 // Default axis for index mode is 'x' to match old behaviour
6794 options.axis = options.axis || 'x';
6795 var distanceMetric = getDistanceMetricForAxis(options.axis);
6796 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
6797 var elements = [];
6798
6799 if (!items.length) {
6800 return [];
6801 }
6802
6803 chart._getSortedVisibleDatasetMetas().forEach(function(meta) {
6804 var element = meta.data[items[0]._index];
6805
6806 // don't count items that are skipped (null data)
6807 if (element && !element._view.skip) {
6808 elements.push(element);
6809 }
6810 });
6811
6812 return elements;
6813}
6814
6815/**
6816 * @interface IInteractionOptions
6817 */
6818/**
6819 * If true, only consider items that intersect the point
6820 * @name IInterfaceOptions#boolean
6821 * @type Boolean
6822 */
6823
6824/**
6825 * Contains interaction related functions
6826 * @namespace Chart.Interaction
6827 */
6828var core_interaction = {
6829 // Helper function for different modes
6830 modes: {
6831 single: function(chart, e) {
6832 var position = getRelativePosition(e, chart);
6833 var elements = [];
6834
6835 parseVisibleItems(chart, function(element) {
6836 if (element.inRange(position.x, position.y)) {
6837 elements.push(element);
6838 return elements;
6839 }
6840 });
6841
6842 return elements.slice(0, 1);
6843 },
6844
6845 /**
6846 * @function Chart.Interaction.modes.label
6847 * @deprecated since version 2.4.0
6848 * @todo remove at version 3
6849 * @private
6850 */
6851 label: indexMode,
6852
6853 /**
6854 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
6855 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
6856 * @function Chart.Interaction.modes.index
6857 * @since v2.4.0
6858 * @param {Chart} chart - the chart we are returning items from
6859 * @param {Event} e - the event we are find things at
6860 * @param {IInteractionOptions} options - options to use during interaction
6861 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6862 */
6863 index: indexMode,
6864
6865 /**
6866 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
6867 * If the options.intersect is false, we find the nearest item and return the items in that dataset
6868 * @function Chart.Interaction.modes.dataset
6869 * @param {Chart} chart - the chart we are returning items from
6870 * @param {Event} e - the event we are find things at
6871 * @param {IInteractionOptions} options - options to use during interaction
6872 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6873 */
6874 dataset: function(chart, e, options) {
6875 var position = getRelativePosition(e, chart);
6876 options.axis = options.axis || 'xy';
6877 var distanceMetric = getDistanceMetricForAxis(options.axis);
6878 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
6879
6880 if (items.length > 0) {
6881 items = chart.getDatasetMeta(items[0]._datasetIndex).data;
6882 }
6883
6884 return items;
6885 },
6886
6887 /**
6888 * @function Chart.Interaction.modes.x-axis
6889 * @deprecated since version 2.4.0. Use index mode and intersect == true
6890 * @todo remove at version 3
6891 * @private
6892 */
6893 'x-axis': function(chart, e) {
6894 return indexMode(chart, e, {intersect: false});
6895 },
6896
6897 /**
6898 * Point mode returns all elements that hit test based on the event position
6899 * of the event
6900 * @function Chart.Interaction.modes.intersect
6901 * @param {Chart} chart - the chart we are returning items from
6902 * @param {Event} e - the event we are find things at
6903 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6904 */
6905 point: function(chart, e) {
6906 var position = getRelativePosition(e, chart);
6907 return getIntersectItems(chart, position);
6908 },
6909
6910 /**
6911 * nearest mode returns the element closest to the point
6912 * @function Chart.Interaction.modes.intersect
6913 * @param {Chart} chart - the chart we are returning items from
6914 * @param {Event} e - the event we are find things at
6915 * @param {IInteractionOptions} options - options to use
6916 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6917 */
6918 nearest: function(chart, e, options) {
6919 var position = getRelativePosition(e, chart);
6920 options.axis = options.axis || 'xy';
6921 var distanceMetric = getDistanceMetricForAxis(options.axis);
6922 return getNearestItems(chart, position, options.intersect, distanceMetric);
6923 },
6924
6925 /**
6926 * x mode returns the elements that hit-test at the current x coordinate
6927 * @function Chart.Interaction.modes.x
6928 * @param {Chart} chart - the chart we are returning items from
6929 * @param {Event} e - the event we are find things at
6930 * @param {IInteractionOptions} options - options to use
6931 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6932 */
6933 x: function(chart, e, options) {
6934 var position = getRelativePosition(e, chart);
6935 var items = [];
6936 var intersectsItem = false;
6937
6938 parseVisibleItems(chart, function(element) {
6939 if (element.inXRange(position.x)) {
6940 items.push(element);
6941 }
6942
6943 if (element.inRange(position.x, position.y)) {
6944 intersectsItem = true;
6945 }
6946 });
6947
6948 // If we want to trigger on an intersect and we don't have any items
6949 // that intersect the position, return nothing
6950 if (options.intersect && !intersectsItem) {
6951 items = [];
6952 }
6953 return items;
6954 },
6955
6956 /**
6957 * y mode returns the elements that hit-test at the current y coordinate
6958 * @function Chart.Interaction.modes.y
6959 * @param {Chart} chart - the chart we are returning items from
6960 * @param {Event} e - the event we are find things at
6961 * @param {IInteractionOptions} options - options to use
6962 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6963 */
6964 y: function(chart, e, options) {
6965 var position = getRelativePosition(e, chart);
6966 var items = [];
6967 var intersectsItem = false;
6968
6969 parseVisibleItems(chart, function(element) {
6970 if (element.inYRange(position.y)) {
6971 items.push(element);
6972 }
6973
6974 if (element.inRange(position.x, position.y)) {
6975 intersectsItem = true;
6976 }
6977 });
6978
6979 // If we want to trigger on an intersect and we don't have any items
6980 // that intersect the position, return nothing
6981 if (options.intersect && !intersectsItem) {
6982 items = [];
6983 }
6984 return items;
6985 }
6986 }
6987};
6988
6989var extend = helpers$1.extend;
6990
6991function filterByPosition(array, position) {
6992 return helpers$1.where(array, function(v) {
6993 return v.pos === position;
6994 });
6995}
6996
6997function sortByWeight(array, reverse) {
6998 return array.sort(function(a, b) {
6999 var v0 = reverse ? b : a;
7000 var v1 = reverse ? a : b;
7001 return v0.weight === v1.weight ?
7002 v0.index - v1.index :
7003 v0.weight - v1.weight;
7004 });
7005}
7006
7007function wrapBoxes(boxes) {
7008 var layoutBoxes = [];
7009 var i, ilen, box;
7010
7011 for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) {
7012 box = boxes[i];
7013 layoutBoxes.push({
7014 index: i,
7015 box: box,
7016 pos: box.position,
7017 horizontal: box.isHorizontal(),
7018 weight: box.weight
7019 });
7020 }
7021 return layoutBoxes;
7022}
7023
7024function setLayoutDims(layouts, params) {
7025 var i, ilen, layout;
7026 for (i = 0, ilen = layouts.length; i < ilen; ++i) {
7027 layout = layouts[i];
7028 // store width used instead of chartArea.w in fitBoxes
7029 layout.width = layout.horizontal
7030 ? layout.box.fullWidth && params.availableWidth
7031 : params.vBoxMaxWidth;
7032 // store height used instead of chartArea.h in fitBoxes
7033 layout.height = layout.horizontal && params.hBoxMaxHeight;
7034 }
7035}
7036
7037function buildLayoutBoxes(boxes) {
7038 var layoutBoxes = wrapBoxes(boxes);
7039 var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
7040 var right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
7041 var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
7042 var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
7043
7044 return {
7045 leftAndTop: left.concat(top),
7046 rightAndBottom: right.concat(bottom),
7047 chartArea: filterByPosition(layoutBoxes, 'chartArea'),
7048 vertical: left.concat(right),
7049 horizontal: top.concat(bottom)
7050 };
7051}
7052
7053function getCombinedMax(maxPadding, chartArea, a, b) {
7054 return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
7055}
7056
7057function updateDims(chartArea, params, layout) {
7058 var box = layout.box;
7059 var maxPadding = chartArea.maxPadding;
7060 var newWidth, newHeight;
7061
7062 if (layout.size) {
7063 // this layout was already counted for, lets first reduce old size
7064 chartArea[layout.pos] -= layout.size;
7065 }
7066 layout.size = layout.horizontal ? box.height : box.width;
7067 chartArea[layout.pos] += layout.size;
7068
7069 if (box.getPadding) {
7070 var boxPadding = box.getPadding();
7071 maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
7072 maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
7073 maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
7074 maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
7075 }
7076
7077 newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right');
7078 newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom');
7079
7080 if (newWidth !== chartArea.w || newHeight !== chartArea.h) {
7081 chartArea.w = newWidth;
7082 chartArea.h = newHeight;
7083
7084 // return true if chart area changed in layout's direction
7085 return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h;
7086 }
7087}
7088
7089function handleMaxPadding(chartArea) {
7090 var maxPadding = chartArea.maxPadding;
7091
7092 function updatePos(pos) {
7093 var change = Math.max(maxPadding[pos] - chartArea[pos], 0);
7094 chartArea[pos] += change;
7095 return change;
7096 }
7097 chartArea.y += updatePos('top');
7098 chartArea.x += updatePos('left');
7099 updatePos('right');
7100 updatePos('bottom');
7101}
7102
7103function getMargins(horizontal, chartArea) {
7104 var maxPadding = chartArea.maxPadding;
7105
7106 function marginForPositions(positions) {
7107 var margin = {left: 0, top: 0, right: 0, bottom: 0};
7108 positions.forEach(function(pos) {
7109 margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
7110 });
7111 return margin;
7112 }
7113
7114 return horizontal
7115 ? marginForPositions(['left', 'right'])
7116 : marginForPositions(['top', 'bottom']);
7117}
7118
7119function fitBoxes(boxes, chartArea, params) {
7120 var refitBoxes = [];
7121 var i, ilen, layout, box, refit, changed;
7122
7123 for (i = 0, ilen = boxes.length; i < ilen; ++i) {
7124 layout = boxes[i];
7125 box = layout.box;
7126
7127 box.update(
7128 layout.width || chartArea.w,
7129 layout.height || chartArea.h,
7130 getMargins(layout.horizontal, chartArea)
7131 );
7132 if (updateDims(chartArea, params, layout)) {
7133 changed = true;
7134 if (refitBoxes.length) {
7135 // Dimensions changed and there were non full width boxes before this
7136 // -> we have to refit those
7137 refit = true;
7138 }
7139 }
7140 if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case
7141 refitBoxes.push(layout);
7142 }
7143 }
7144
7145 return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed;
7146}
7147
7148function placeBoxes(boxes, chartArea, params) {
7149 var userPadding = params.padding;
7150 var x = chartArea.x;
7151 var y = chartArea.y;
7152 var i, ilen, layout, box;
7153
7154 for (i = 0, ilen = boxes.length; i < ilen; ++i) {
7155 layout = boxes[i];
7156 box = layout.box;
7157 if (layout.horizontal) {
7158 box.left = box.fullWidth ? userPadding.left : chartArea.left;
7159 box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w;
7160 box.top = y;
7161 box.bottom = y + box.height;
7162 box.width = box.right - box.left;
7163 y = box.bottom;
7164 } else {
7165 box.left = x;
7166 box.right = x + box.width;
7167 box.top = chartArea.top;
7168 box.bottom = chartArea.top + chartArea.h;
7169 box.height = box.bottom - box.top;
7170 x = box.right;
7171 }
7172 }
7173
7174 chartArea.x = x;
7175 chartArea.y = y;
7176}
7177
7178core_defaults._set('global', {
7179 layout: {
7180 padding: {
7181 top: 0,
7182 right: 0,
7183 bottom: 0,
7184 left: 0
7185 }
7186 }
7187});
7188
7189/**
7190 * @interface ILayoutItem
7191 * @prop {string} position - The position of the item in the chart layout. Possible values are
7192 * 'left', 'top', 'right', 'bottom', and 'chartArea'
7193 * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area
7194 * @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
7195 * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
7196 * @prop {function} update - Takes two parameters: width and height. Returns size of item
7197 * @prop {function} getPadding - Returns an object with padding on the edges
7198 * @prop {number} width - Width of item. Must be valid after update()
7199 * @prop {number} height - Height of item. Must be valid after update()
7200 * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update
7201 * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update
7202 * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update
7203 * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
7204 */
7205
7206// The layout service is very self explanatory. It's responsible for the layout within a chart.
7207// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
7208// It is this service's responsibility of carrying out that layout.
7209var core_layouts = {
7210 defaults: {},
7211
7212 /**
7213 * Register a box to a chart.
7214 * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
7215 * @param {Chart} chart - the chart to use
7216 * @param {ILayoutItem} item - the item to add to be layed out
7217 */
7218 addBox: function(chart, item) {
7219 if (!chart.boxes) {
7220 chart.boxes = [];
7221 }
7222
7223 // initialize item with default values
7224 item.fullWidth = item.fullWidth || false;
7225 item.position = item.position || 'top';
7226 item.weight = item.weight || 0;
7227 item._layers = item._layers || function() {
7228 return [{
7229 z: 0,
7230 draw: function() {
7231 item.draw.apply(item, arguments);
7232 }
7233 }];
7234 };
7235
7236 chart.boxes.push(item);
7237 },
7238
7239 /**
7240 * Remove a layoutItem from a chart
7241 * @param {Chart} chart - the chart to remove the box from
7242 * @param {ILayoutItem} layoutItem - the item to remove from the layout
7243 */
7244 removeBox: function(chart, layoutItem) {
7245 var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
7246 if (index !== -1) {
7247 chart.boxes.splice(index, 1);
7248 }
7249 },
7250
7251 /**
7252 * Sets (or updates) options on the given `item`.
7253 * @param {Chart} chart - the chart in which the item lives (or will be added to)
7254 * @param {ILayoutItem} item - the item to configure with the given options
7255 * @param {object} options - the new item options.
7256 */
7257 configure: function(chart, item, options) {
7258 var props = ['fullWidth', 'position', 'weight'];
7259 var ilen = props.length;
7260 var i = 0;
7261 var prop;
7262
7263 for (; i < ilen; ++i) {
7264 prop = props[i];
7265 if (options.hasOwnProperty(prop)) {
7266 item[prop] = options[prop];
7267 }
7268 }
7269 },
7270
7271 /**
7272 * Fits boxes of the given chart into the given size by having each box measure itself
7273 * then running a fitting algorithm
7274 * @param {Chart} chart - the chart
7275 * @param {number} width - the width to fit into
7276 * @param {number} height - the height to fit into
7277 */
7278 update: function(chart, width, height) {
7279 if (!chart) {
7280 return;
7281 }
7282
7283 var layoutOptions = chart.options.layout || {};
7284 var padding = helpers$1.options.toPadding(layoutOptions.padding);
7285
7286 var availableWidth = width - padding.width;
7287 var availableHeight = height - padding.height;
7288 var boxes = buildLayoutBoxes(chart.boxes);
7289 var verticalBoxes = boxes.vertical;
7290 var horizontalBoxes = boxes.horizontal;
7291
7292 // Essentially we now have any number of boxes on each of the 4 sides.
7293 // Our canvas looks like the following.
7294 // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
7295 // B1 is the bottom axis
7296 // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
7297 // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
7298 // an error will be thrown.
7299 //
7300 // |----------------------------------------------------|
7301 // | T1 (Full Width) |
7302 // |----------------------------------------------------|
7303 // | | | T2 | |
7304 // | |----|-------------------------------------|----|
7305 // | | | C1 | | C2 | |
7306 // | | |----| |----| |
7307 // | | | | |
7308 // | L1 | L2 | ChartArea (C0) | R1 |
7309 // | | | | |
7310 // | | |----| |----| |
7311 // | | | C3 | | C4 | |
7312 // | |----|-------------------------------------|----|
7313 // | | | B1 | |
7314 // |----------------------------------------------------|
7315 // | B2 (Full Width) |
7316 // |----------------------------------------------------|
7317 //
7318
7319 var params = Object.freeze({
7320 outerWidth: width,
7321 outerHeight: height,
7322 padding: padding,
7323 availableWidth: availableWidth,
7324 vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length,
7325 hBoxMaxHeight: availableHeight / 2
7326 });
7327 var chartArea = extend({
7328 maxPadding: extend({}, padding),
7329 w: availableWidth,
7330 h: availableHeight,
7331 x: padding.left,
7332 y: padding.top
7333 }, padding);
7334
7335 setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
7336
7337 // First fit vertical boxes
7338 fitBoxes(verticalBoxes, chartArea, params);
7339
7340 // Then fit horizontal boxes
7341 if (fitBoxes(horizontalBoxes, chartArea, params)) {
7342 // if the area changed, re-fit vertical boxes
7343 fitBoxes(verticalBoxes, chartArea, params);
7344 }
7345
7346 handleMaxPadding(chartArea);
7347
7348 // Finally place the boxes to correct coordinates
7349 placeBoxes(boxes.leftAndTop, chartArea, params);
7350
7351 // Move to opposite side of chart
7352 chartArea.x += chartArea.w;
7353 chartArea.y += chartArea.h;
7354
7355 placeBoxes(boxes.rightAndBottom, chartArea, params);
7356
7357 chart.chartArea = {
7358 left: chartArea.left,
7359 top: chartArea.top,
7360 right: chartArea.left + chartArea.w,
7361 bottom: chartArea.top + chartArea.h
7362 };
7363
7364 // Finally update boxes in chartArea (radial scale for example)
7365 helpers$1.each(boxes.chartArea, function(layout) {
7366 var box = layout.box;
7367 extend(box, chart.chartArea);
7368 box.update(chartArea.w, chartArea.h);
7369 });
7370 }
7371};
7372
7373/**
7374 * Platform fallback implementation (minimal).
7375 * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
7376 */
7377
7378var platform_basic = {
7379 acquireContext: function(item) {
7380 if (item && item.canvas) {
7381 // Support for any object associated to a canvas (including a context2d)
7382 item = item.canvas;
7383 }
7384
7385 return item && item.getContext('2d') || null;
7386 }
7387};
7388
7389var platform_dom = "/*\n * DOM element rendering detection\n * https://davidwalsh.name/detect-node-insertion\n */\n@keyframes chartjs-render-animation {\n\tfrom { opacity: 0.99; }\n\tto { opacity: 1; }\n}\n\n.chartjs-render-monitor {\n\tanimation: chartjs-render-animation 0.001s;\n}\n\n/*\n * DOM element resizing detection\n * https://github.com/marcj/css-element-queries\n */\n.chartjs-size-monitor,\n.chartjs-size-monitor-expand,\n.chartjs-size-monitor-shrink {\n\tposition: absolute;\n\tdirection: ltr;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\toverflow: hidden;\n\tpointer-events: none;\n\tvisibility: hidden;\n\tz-index: -1;\n}\n\n.chartjs-size-monitor-expand > div {\n\tposition: absolute;\n\twidth: 1000000px;\n\theight: 1000000px;\n\tleft: 0;\n\ttop: 0;\n}\n\n.chartjs-size-monitor-shrink > div {\n\tposition: absolute;\n\twidth: 200%;\n\theight: 200%;\n\tleft: 0;\n\ttop: 0;\n}\n";
7390
7391var platform_dom$1 = /*#__PURE__*/Object.freeze({
7392__proto__: null,
7393'default': platform_dom
7394});
7395
7396var stylesheet = getCjsExportFromNamespace(platform_dom$1);
7397
7398var EXPANDO_KEY = '$chartjs';
7399var CSS_PREFIX = 'chartjs-';
7400var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor';
7401var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
7402var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
7403var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
7404
7405/**
7406 * DOM event types -> Chart.js event types.
7407 * Note: only events with different types are mapped.
7408 * @see https://developer.mozilla.org/en-US/docs/Web/Events
7409 */
7410var EVENT_TYPES = {
7411 touchstart: 'mousedown',
7412 touchmove: 'mousemove',
7413 touchend: 'mouseup',
7414 pointerenter: 'mouseenter',
7415 pointerdown: 'mousedown',
7416 pointermove: 'mousemove',
7417 pointerup: 'mouseup',
7418 pointerleave: 'mouseout',
7419 pointerout: 'mouseout'
7420};
7421
7422/**
7423 * The "used" size is the final value of a dimension property after all calculations have
7424 * been performed. This method uses the computed style of `element` but returns undefined
7425 * if the computed style is not expressed in pixels. That can happen in some cases where
7426 * `element` has a size relative to its parent and this last one is not yet displayed,
7427 * for example because of `display: none` on a parent node.
7428 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
7429 * @returns {number} Size in pixels or undefined if unknown.
7430 */
7431function readUsedSize(element, property) {
7432 var value = helpers$1.getStyle(element, property);
7433 var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
7434 return matches ? Number(matches[1]) : undefined;
7435}
7436
7437/**
7438 * Initializes the canvas style and render size without modifying the canvas display size,
7439 * since responsiveness is handled by the controller.resize() method. The config is used
7440 * to determine the aspect ratio to apply in case no explicit height has been specified.
7441 */
7442function initCanvas(canvas, config) {
7443 var style = canvas.style;
7444
7445 // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
7446 // returns null or '' if no explicit value has been set to the canvas attribute.
7447 var renderHeight = canvas.getAttribute('height');
7448 var renderWidth = canvas.getAttribute('width');
7449
7450 // Chart.js modifies some canvas values that we want to restore on destroy
7451 canvas[EXPANDO_KEY] = {
7452 initial: {
7453 height: renderHeight,
7454 width: renderWidth,
7455 style: {
7456 display: style.display,
7457 height: style.height,
7458 width: style.width
7459 }
7460 }
7461 };
7462
7463 // Force canvas to display as block to avoid extra space caused by inline
7464 // elements, which would interfere with the responsive resize process.
7465 // https://github.com/chartjs/Chart.js/issues/2538
7466 style.display = style.display || 'block';
7467
7468 if (renderWidth === null || renderWidth === '') {
7469 var displayWidth = readUsedSize(canvas, 'width');
7470 if (displayWidth !== undefined) {
7471 canvas.width = displayWidth;
7472 }
7473 }
7474
7475 if (renderHeight === null || renderHeight === '') {
7476 if (canvas.style.height === '') {
7477 // If no explicit render height and style height, let's apply the aspect ratio,
7478 // which one can be specified by the user but also by charts as default option
7479 // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
7480 canvas.height = canvas.width / (config.options.aspectRatio || 2);
7481 } else {
7482 var displayHeight = readUsedSize(canvas, 'height');
7483 if (displayWidth !== undefined) {
7484 canvas.height = displayHeight;
7485 }
7486 }
7487 }
7488
7489 return canvas;
7490}
7491
7492/**
7493 * Detects support for options object argument in addEventListener.
7494 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
7495 * @private
7496 */
7497var supportsEventListenerOptions = (function() {
7498 var supports = false;
7499 try {
7500 var options = Object.defineProperty({}, 'passive', {
7501 // eslint-disable-next-line getter-return
7502 get: function() {
7503 supports = true;
7504 }
7505 });
7506 window.addEventListener('e', null, options);
7507 } catch (e) {
7508 // continue regardless of error
7509 }
7510 return supports;
7511}());
7512
7513// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
7514// https://github.com/chartjs/Chart.js/issues/4287
7515var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
7516
7517function addListener(node, type, listener) {
7518 node.addEventListener(type, listener, eventListenerOptions);
7519}
7520
7521function removeListener(node, type, listener) {
7522 node.removeEventListener(type, listener, eventListenerOptions);
7523}
7524
7525function createEvent(type, chart, x, y, nativeEvent) {
7526 return {
7527 type: type,
7528 chart: chart,
7529 native: nativeEvent || null,
7530 x: x !== undefined ? x : null,
7531 y: y !== undefined ? y : null,
7532 };
7533}
7534
7535function fromNativeEvent(event, chart) {
7536 var type = EVENT_TYPES[event.type] || event.type;
7537 var pos = helpers$1.getRelativePosition(event, chart);
7538 return createEvent(type, chart, pos.x, pos.y, event);
7539}
7540
7541function throttled(fn, thisArg) {
7542 var ticking = false;
7543 var args = [];
7544
7545 return function() {
7546 args = Array.prototype.slice.call(arguments);
7547 thisArg = thisArg || this;
7548
7549 if (!ticking) {
7550 ticking = true;
7551 helpers$1.requestAnimFrame.call(window, function() {
7552 ticking = false;
7553 fn.apply(thisArg, args);
7554 });
7555 }
7556 };
7557}
7558
7559function createDiv(cls) {
7560 var el = document.createElement('div');
7561 el.className = cls || '';
7562 return el;
7563}
7564
7565// Implementation based on https://github.com/marcj/css-element-queries
7566function createResizer(handler) {
7567 var maxSize = 1000000;
7568
7569 // NOTE(SB) Don't use innerHTML because it could be considered unsafe.
7570 // https://github.com/chartjs/Chart.js/issues/5902
7571 var resizer = createDiv(CSS_SIZE_MONITOR);
7572 var expand = createDiv(CSS_SIZE_MONITOR + '-expand');
7573 var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink');
7574
7575 expand.appendChild(createDiv());
7576 shrink.appendChild(createDiv());
7577
7578 resizer.appendChild(expand);
7579 resizer.appendChild(shrink);
7580 resizer._reset = function() {
7581 expand.scrollLeft = maxSize;
7582 expand.scrollTop = maxSize;
7583 shrink.scrollLeft = maxSize;
7584 shrink.scrollTop = maxSize;
7585 };
7586
7587 var onScroll = function() {
7588 resizer._reset();
7589 handler();
7590 };
7591
7592 addListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
7593 addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
7594
7595 return resizer;
7596}
7597
7598// https://davidwalsh.name/detect-node-insertion
7599function watchForRender(node, handler) {
7600 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
7601 var proxy = expando.renderProxy = function(e) {
7602 if (e.animationName === CSS_RENDER_ANIMATION) {
7603 handler();
7604 }
7605 };
7606
7607 helpers$1.each(ANIMATION_START_EVENTS, function(type) {
7608 addListener(node, type, proxy);
7609 });
7610
7611 // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
7612 // is removed then added back immediately (same animation frame?). Accessing the
7613 // `offsetParent` property will force a reflow and re-evaluate the CSS animation.
7614 // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
7615 // https://github.com/chartjs/Chart.js/issues/4737
7616 expando.reflow = !!node.offsetParent;
7617
7618 node.classList.add(CSS_RENDER_MONITOR);
7619}
7620
7621function unwatchForRender(node) {
7622 var expando = node[EXPANDO_KEY] || {};
7623 var proxy = expando.renderProxy;
7624
7625 if (proxy) {
7626 helpers$1.each(ANIMATION_START_EVENTS, function(type) {
7627 removeListener(node, type, proxy);
7628 });
7629
7630 delete expando.renderProxy;
7631 }
7632
7633 node.classList.remove(CSS_RENDER_MONITOR);
7634}
7635
7636function addResizeListener(node, listener, chart) {
7637 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
7638
7639 // Let's keep track of this added resizer and thus avoid DOM query when removing it.
7640 var resizer = expando.resizer = createResizer(throttled(function() {
7641 if (expando.resizer) {
7642 var container = chart.options.maintainAspectRatio && node.parentNode;
7643 var w = container ? container.clientWidth : 0;
7644 listener(createEvent('resize', chart));
7645 if (container && container.clientWidth < w && chart.canvas) {
7646 // If the container size shrank during chart resize, let's assume
7647 // scrollbar appeared. So we resize again with the scrollbar visible -
7648 // effectively making chart smaller and the scrollbar hidden again.
7649 // Because we are inside `throttled`, and currently `ticking`, scroll
7650 // events are ignored during this whole 2 resize process.
7651 // If we assumed wrong and something else happened, we are resizing
7652 // twice in a frame (potential performance issue)
7653 listener(createEvent('resize', chart));
7654 }
7655 }
7656 }));
7657
7658 // The resizer needs to be attached to the node parent, so we first need to be
7659 // sure that `node` is attached to the DOM before injecting the resizer element.
7660 watchForRender(node, function() {
7661 if (expando.resizer) {
7662 var container = node.parentNode;
7663 if (container && container !== resizer.parentNode) {
7664 container.insertBefore(resizer, container.firstChild);
7665 }
7666
7667 // The container size might have changed, let's reset the resizer state.
7668 resizer._reset();
7669 }
7670 });
7671}
7672
7673function removeResizeListener(node) {
7674 var expando = node[EXPANDO_KEY] || {};
7675 var resizer = expando.resizer;
7676
7677 delete expando.resizer;
7678 unwatchForRender(node);
7679
7680 if (resizer && resizer.parentNode) {
7681 resizer.parentNode.removeChild(resizer);
7682 }
7683}
7684
7685/**
7686 * Injects CSS styles inline if the styles are not already present.
7687 * @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the <style>.
7688 * @param {string} css - the CSS to be injected.
7689 */
7690function injectCSS(rootNode, css) {
7691 // https://stackoverflow.com/q/3922139
7692 var expando = rootNode[EXPANDO_KEY] || (rootNode[EXPANDO_KEY] = {});
7693 if (!expando.containsStyles) {
7694 expando.containsStyles = true;
7695 css = '/* Chart.js */\n' + css;
7696 var style = document.createElement('style');
7697 style.setAttribute('type', 'text/css');
7698 style.appendChild(document.createTextNode(css));
7699 rootNode.appendChild(style);
7700 }
7701}
7702
7703var platform_dom$2 = {
7704 /**
7705 * When `true`, prevents the automatic injection of the stylesheet required to
7706 * correctly detect when the chart is added to the DOM and then resized. This
7707 * switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`)
7708 * to be manually imported to make this library compatible with any CSP.
7709 * See https://github.com/chartjs/Chart.js/issues/5208
7710 */
7711 disableCSSInjection: false,
7712
7713 /**
7714 * This property holds whether this platform is enabled for the current environment.
7715 * Currently used by platform.js to select the proper implementation.
7716 * @private
7717 */
7718 _enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
7719
7720 /**
7721 * Initializes resources that depend on platform options.
7722 * @param {HTMLCanvasElement} canvas - The Canvas element.
7723 * @private
7724 */
7725 _ensureLoaded: function(canvas) {
7726 if (!this.disableCSSInjection) {
7727 // If the canvas is in a shadow DOM, then the styles must also be inserted
7728 // into the same shadow DOM.
7729 // https://github.com/chartjs/Chart.js/issues/5763
7730 var root = canvas.getRootNode ? canvas.getRootNode() : document;
7731 var targetNode = root.host ? root : document.head;
7732 injectCSS(targetNode, stylesheet);
7733 }
7734 },
7735
7736 acquireContext: function(item, config) {
7737 if (typeof item === 'string') {
7738 item = document.getElementById(item);
7739 } else if (item.length) {
7740 // Support for array based queries (such as jQuery)
7741 item = item[0];
7742 }
7743
7744 if (item && item.canvas) {
7745 // Support for any object associated to a canvas (including a context2d)
7746 item = item.canvas;
7747 }
7748
7749 // To prevent canvas fingerprinting, some add-ons undefine the getContext
7750 // method, for example: https://github.com/kkapsner/CanvasBlocker
7751 // https://github.com/chartjs/Chart.js/issues/2807
7752 var context = item && item.getContext && item.getContext('2d');
7753
7754 // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
7755 // inside an iframe or when running in a protected environment. We could guess the
7756 // types from their toString() value but let's keep things flexible and assume it's
7757 // a sufficient condition if the item has a context2D which has item as `canvas`.
7758 // https://github.com/chartjs/Chart.js/issues/3887
7759 // https://github.com/chartjs/Chart.js/issues/4102
7760 // https://github.com/chartjs/Chart.js/issues/4152
7761 if (context && context.canvas === item) {
7762 // Load platform resources on first chart creation, to make it possible to
7763 // import the library before setting platform options.
7764 this._ensureLoaded(item);
7765 initCanvas(item, config);
7766 return context;
7767 }
7768
7769 return null;
7770 },
7771
7772 releaseContext: function(context) {
7773 var canvas = context.canvas;
7774 if (!canvas[EXPANDO_KEY]) {
7775 return;
7776 }
7777
7778 var initial = canvas[EXPANDO_KEY].initial;
7779 ['height', 'width'].forEach(function(prop) {
7780 var value = initial[prop];
7781 if (helpers$1.isNullOrUndef(value)) {
7782 canvas.removeAttribute(prop);
7783 } else {
7784 canvas.setAttribute(prop, value);
7785 }
7786 });
7787
7788 helpers$1.each(initial.style || {}, function(value, key) {
7789 canvas.style[key] = value;
7790 });
7791
7792 // The canvas render size might have been changed (and thus the state stack discarded),
7793 // we can't use save() and restore() to restore the initial state. So make sure that at
7794 // least the canvas context is reset to the default state by setting the canvas width.
7795 // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
7796 // eslint-disable-next-line no-self-assign
7797 canvas.width = canvas.width;
7798
7799 delete canvas[EXPANDO_KEY];
7800 },
7801
7802 addEventListener: function(chart, type, listener) {
7803 var canvas = chart.canvas;
7804 if (type === 'resize') {
7805 // Note: the resize event is not supported on all browsers.
7806 addResizeListener(canvas, listener, chart);
7807 return;
7808 }
7809
7810 var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
7811 var proxies = expando.proxies || (expando.proxies = {});
7812 var proxy = proxies[chart.id + '_' + type] = function(event) {
7813 listener(fromNativeEvent(event, chart));
7814 };
7815
7816 addListener(canvas, type, proxy);
7817 },
7818
7819 removeEventListener: function(chart, type, listener) {
7820 var canvas = chart.canvas;
7821 if (type === 'resize') {
7822 // Note: the resize event is not supported on all browsers.
7823 removeResizeListener(canvas);
7824 return;
7825 }
7826
7827 var expando = listener[EXPANDO_KEY] || {};
7828 var proxies = expando.proxies || {};
7829 var proxy = proxies[chart.id + '_' + type];
7830 if (!proxy) {
7831 return;
7832 }
7833
7834 removeListener(canvas, type, proxy);
7835 }
7836};
7837
7838// DEPRECATIONS
7839
7840/**
7841 * Provided for backward compatibility, use EventTarget.addEventListener instead.
7842 * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
7843 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
7844 * @function Chart.helpers.addEvent
7845 * @deprecated since version 2.7.0
7846 * @todo remove at version 3
7847 * @private
7848 */
7849helpers$1.addEvent = addListener;
7850
7851/**
7852 * Provided for backward compatibility, use EventTarget.removeEventListener instead.
7853 * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
7854 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
7855 * @function Chart.helpers.removeEvent
7856 * @deprecated since version 2.7.0
7857 * @todo remove at version 3
7858 * @private
7859 */
7860helpers$1.removeEvent = removeListener;
7861
7862// @TODO Make possible to select another platform at build time.
7863var implementation = platform_dom$2._enabled ? platform_dom$2 : platform_basic;
7864
7865/**
7866 * @namespace Chart.platform
7867 * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
7868 * @since 2.4.0
7869 */
7870var platform = helpers$1.extend({
7871 /**
7872 * @since 2.7.0
7873 */
7874 initialize: function() {},
7875
7876 /**
7877 * Called at chart construction time, returns a context2d instance implementing
7878 * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
7879 * @param {*} item - The native item from which to acquire context (platform specific)
7880 * @param {object} options - The chart options
7881 * @returns {CanvasRenderingContext2D} context2d instance
7882 */
7883 acquireContext: function() {},
7884
7885 /**
7886 * Called at chart destruction time, releases any resources associated to the context
7887 * previously returned by the acquireContext() method.
7888 * @param {CanvasRenderingContext2D} context - The context2d instance
7889 * @returns {boolean} true if the method succeeded, else false
7890 */
7891 releaseContext: function() {},
7892
7893 /**
7894 * Registers the specified listener on the given chart.
7895 * @param {Chart} chart - Chart from which to listen for event
7896 * @param {string} type - The ({@link IEvent}) type to listen for
7897 * @param {function} listener - Receives a notification (an object that implements
7898 * the {@link IEvent} interface) when an event of the specified type occurs.
7899 */
7900 addEventListener: function() {},
7901
7902 /**
7903 * Removes the specified listener previously registered with addEventListener.
7904 * @param {Chart} chart - Chart from which to remove the listener
7905 * @param {string} type - The ({@link IEvent}) type to remove
7906 * @param {function} listener - The listener function to remove from the event target.
7907 */
7908 removeEventListener: function() {}
7909
7910}, implementation);
7911
7912core_defaults._set('global', {
7913 plugins: {}
7914});
7915
7916/**
7917 * The plugin service singleton
7918 * @namespace Chart.plugins
7919 * @since 2.1.0
7920 */
7921var core_plugins = {
7922 /**
7923 * Globally registered plugins.
7924 * @private
7925 */
7926 _plugins: [],
7927
7928 /**
7929 * This identifier is used to invalidate the descriptors cache attached to each chart
7930 * when a global plugin is registered or unregistered. In this case, the cache ID is
7931 * incremented and descriptors are regenerated during following API calls.
7932 * @private
7933 */
7934 _cacheId: 0,
7935
7936 /**
7937 * Registers the given plugin(s) if not already registered.
7938 * @param {IPlugin[]|IPlugin} plugins plugin instance(s).
7939 */
7940 register: function(plugins) {
7941 var p = this._plugins;
7942 ([]).concat(plugins).forEach(function(plugin) {
7943 if (p.indexOf(plugin) === -1) {
7944 p.push(plugin);
7945 }
7946 });
7947
7948 this._cacheId++;
7949 },
7950
7951 /**
7952 * Unregisters the given plugin(s) only if registered.
7953 * @param {IPlugin[]|IPlugin} plugins plugin instance(s).
7954 */
7955 unregister: function(plugins) {
7956 var p = this._plugins;
7957 ([]).concat(plugins).forEach(function(plugin) {
7958 var idx = p.indexOf(plugin);
7959 if (idx !== -1) {
7960 p.splice(idx, 1);
7961 }
7962 });
7963
7964 this._cacheId++;
7965 },
7966
7967 /**
7968 * Remove all registered plugins.
7969 * @since 2.1.5
7970 */
7971 clear: function() {
7972 this._plugins = [];
7973 this._cacheId++;
7974 },
7975
7976 /**
7977 * Returns the number of registered plugins?
7978 * @returns {number}
7979 * @since 2.1.5
7980 */
7981 count: function() {
7982 return this._plugins.length;
7983 },
7984
7985 /**
7986 * Returns all registered plugin instances.
7987 * @returns {IPlugin[]} array of plugin objects.
7988 * @since 2.1.5
7989 */
7990 getAll: function() {
7991 return this._plugins;
7992 },
7993
7994 /**
7995 * Calls enabled plugins for `chart` on the specified hook and with the given args.
7996 * This method immediately returns as soon as a plugin explicitly returns false. The
7997 * returned value can be used, for instance, to interrupt the current action.
7998 * @param {Chart} chart - The chart instance for which plugins should be called.
7999 * @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
8000 * @param {Array} [args] - Extra arguments to apply to the hook call.
8001 * @returns {boolean} false if any of the plugins return false, else returns true.
8002 */
8003 notify: function(chart, hook, args) {
8004 var descriptors = this.descriptors(chart);
8005 var ilen = descriptors.length;
8006 var i, descriptor, plugin, params, method;
8007
8008 for (i = 0; i < ilen; ++i) {
8009 descriptor = descriptors[i];
8010 plugin = descriptor.plugin;
8011 method = plugin[hook];
8012 if (typeof method === 'function') {
8013 params = [chart].concat(args || []);
8014 params.push(descriptor.options);
8015 if (method.apply(plugin, params) === false) {
8016 return false;
8017 }
8018 }
8019 }
8020
8021 return true;
8022 },
8023
8024 /**
8025 * Returns descriptors of enabled plugins for the given chart.
8026 * @returns {object[]} [{ plugin, options }]
8027 * @private
8028 */
8029 descriptors: function(chart) {
8030 var cache = chart.$plugins || (chart.$plugins = {});
8031 if (cache.id === this._cacheId) {
8032 return cache.descriptors;
8033 }
8034
8035 var plugins = [];
8036 var descriptors = [];
8037 var config = (chart && chart.config) || {};
8038 var options = (config.options && config.options.plugins) || {};
8039
8040 this._plugins.concat(config.plugins || []).forEach(function(plugin) {
8041 var idx = plugins.indexOf(plugin);
8042 if (idx !== -1) {
8043 return;
8044 }
8045
8046 var id = plugin.id;
8047 var opts = options[id];
8048 if (opts === false) {
8049 return;
8050 }
8051
8052 if (opts === true) {
8053 opts = helpers$1.clone(core_defaults.global.plugins[id]);
8054 }
8055
8056 plugins.push(plugin);
8057 descriptors.push({
8058 plugin: plugin,
8059 options: opts || {}
8060 });
8061 });
8062
8063 cache.descriptors = descriptors;
8064 cache.id = this._cacheId;
8065 return descriptors;
8066 },
8067
8068 /**
8069 * Invalidates cache for the given chart: descriptors hold a reference on plugin option,
8070 * but in some cases, this reference can be changed by the user when updating options.
8071 * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
8072 * @private
8073 */
8074 _invalidate: function(chart) {
8075 delete chart.$plugins;
8076 }
8077};
8078
8079var core_scaleService = {
8080 // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
8081 // use the new chart options to grab the correct scale
8082 constructors: {},
8083 // Use a registration function so that we can move to an ES6 map when we no longer need to support
8084 // old browsers
8085
8086 // Scale config defaults
8087 defaults: {},
8088 registerScaleType: function(type, scaleConstructor, scaleDefaults) {
8089 this.constructors[type] = scaleConstructor;
8090 this.defaults[type] = helpers$1.clone(scaleDefaults);
8091 },
8092 getScaleConstructor: function(type) {
8093 return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
8094 },
8095 getScaleDefaults: function(type) {
8096 // Return the scale defaults merged with the global settings so that we always use the latest ones
8097 return this.defaults.hasOwnProperty(type) ? helpers$1.merge({}, [core_defaults.scale, this.defaults[type]]) : {};
8098 },
8099 updateScaleDefaults: function(type, additions) {
8100 var me = this;
8101 if (me.defaults.hasOwnProperty(type)) {
8102 me.defaults[type] = helpers$1.extend(me.defaults[type], additions);
8103 }
8104 },
8105 addScalesToLayout: function(chart) {
8106 // Adds each scale to the chart.boxes array to be sized accordingly
8107 helpers$1.each(chart.scales, function(scale) {
8108 // Set ILayoutItem parameters for backwards compatibility
8109 scale.fullWidth = scale.options.fullWidth;
8110 scale.position = scale.options.position;
8111 scale.weight = scale.options.weight;
8112 core_layouts.addBox(chart, scale);
8113 });
8114 }
8115};
8116
8117var valueOrDefault$8 = helpers$1.valueOrDefault;
8118var getRtlHelper = helpers$1.rtl.getRtlAdapter;
8119
8120core_defaults._set('global', {
8121 tooltips: {
8122 enabled: true,
8123 custom: null,
8124 mode: 'nearest',
8125 position: 'average',
8126 intersect: true,
8127 backgroundColor: 'rgba(0,0,0,0.8)',
8128 titleFontStyle: 'bold',
8129 titleSpacing: 2,
8130 titleMarginBottom: 6,
8131 titleFontColor: '#fff',
8132 titleAlign: 'left',
8133 bodySpacing: 2,
8134 bodyFontColor: '#fff',
8135 bodyAlign: 'left',
8136 footerFontStyle: 'bold',
8137 footerSpacing: 2,
8138 footerMarginTop: 6,
8139 footerFontColor: '#fff',
8140 footerAlign: 'left',
8141 yPadding: 6,
8142 xPadding: 6,
8143 caretPadding: 2,
8144 caretSize: 5,
8145 cornerRadius: 6,
8146 multiKeyBackground: '#fff',
8147 displayColors: true,
8148 borderColor: 'rgba(0,0,0,0)',
8149 borderWidth: 0,
8150 callbacks: {
8151 // Args are: (tooltipItems, data)
8152 beforeTitle: helpers$1.noop,
8153 title: function(tooltipItems, data) {
8154 var title = '';
8155 var labels = data.labels;
8156 var labelCount = labels ? labels.length : 0;
8157
8158 if (tooltipItems.length > 0) {
8159 var item = tooltipItems[0];
8160 if (item.label) {
8161 title = item.label;
8162 } else if (item.xLabel) {
8163 title = item.xLabel;
8164 } else if (labelCount > 0 && item.index < labelCount) {
8165 title = labels[item.index];
8166 }
8167 }
8168
8169 return title;
8170 },
8171 afterTitle: helpers$1.noop,
8172
8173 // Args are: (tooltipItems, data)
8174 beforeBody: helpers$1.noop,
8175
8176 // Args are: (tooltipItem, data)
8177 beforeLabel: helpers$1.noop,
8178 label: function(tooltipItem, data) {
8179 var label = data.datasets[tooltipItem.datasetIndex].label || '';
8180
8181 if (label) {
8182 label += ': ';
8183 }
8184 if (!helpers$1.isNullOrUndef(tooltipItem.value)) {
8185 label += tooltipItem.value;
8186 } else {
8187 label += tooltipItem.yLabel;
8188 }
8189 return label;
8190 },
8191 labelColor: function(tooltipItem, chart) {
8192 var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
8193 var activeElement = meta.data[tooltipItem.index];
8194 var view = activeElement._view;
8195 return {
8196 borderColor: view.borderColor,
8197 backgroundColor: view.backgroundColor
8198 };
8199 },
8200 labelTextColor: function() {
8201 return this._options.bodyFontColor;
8202 },
8203 afterLabel: helpers$1.noop,
8204
8205 // Args are: (tooltipItems, data)
8206 afterBody: helpers$1.noop,
8207
8208 // Args are: (tooltipItems, data)
8209 beforeFooter: helpers$1.noop,
8210 footer: helpers$1.noop,
8211 afterFooter: helpers$1.noop
8212 }
8213 }
8214});
8215
8216var positioners = {
8217 /**
8218 * Average mode places the tooltip at the average position of the elements shown
8219 * @function Chart.Tooltip.positioners.average
8220 * @param elements {ChartElement[]} the elements being displayed in the tooltip
8221 * @returns {object} tooltip position
8222 */
8223 average: function(elements) {
8224 if (!elements.length) {
8225 return false;
8226 }
8227
8228 var i, len;
8229 var x = 0;
8230 var y = 0;
8231 var count = 0;
8232
8233 for (i = 0, len = elements.length; i < len; ++i) {
8234 var el = elements[i];
8235 if (el && el.hasValue()) {
8236 var pos = el.tooltipPosition();
8237 x += pos.x;
8238 y += pos.y;
8239 ++count;
8240 }
8241 }
8242
8243 return {
8244 x: x / count,
8245 y: y / count
8246 };
8247 },
8248
8249 /**
8250 * Gets the tooltip position nearest of the item nearest to the event position
8251 * @function Chart.Tooltip.positioners.nearest
8252 * @param elements {Chart.Element[]} the tooltip elements
8253 * @param eventPosition {object} the position of the event in canvas coordinates
8254 * @returns {object} the tooltip position
8255 */
8256 nearest: function(elements, eventPosition) {
8257 var x = eventPosition.x;
8258 var y = eventPosition.y;
8259 var minDistance = Number.POSITIVE_INFINITY;
8260 var i, len, nearestElement;
8261
8262 for (i = 0, len = elements.length; i < len; ++i) {
8263 var el = elements[i];
8264 if (el && el.hasValue()) {
8265 var center = el.getCenterPoint();
8266 var d = helpers$1.distanceBetweenPoints(eventPosition, center);
8267
8268 if (d < minDistance) {
8269 minDistance = d;
8270 nearestElement = el;
8271 }
8272 }
8273 }
8274
8275 if (nearestElement) {
8276 var tp = nearestElement.tooltipPosition();
8277 x = tp.x;
8278 y = tp.y;
8279 }
8280
8281 return {
8282 x: x,
8283 y: y
8284 };
8285 }
8286};
8287
8288// Helper to push or concat based on if the 2nd parameter is an array or not
8289function pushOrConcat(base, toPush) {
8290 if (toPush) {
8291 if (helpers$1.isArray(toPush)) {
8292 // base = base.concat(toPush);
8293 Array.prototype.push.apply(base, toPush);
8294 } else {
8295 base.push(toPush);
8296 }
8297 }
8298
8299 return base;
8300}
8301
8302/**
8303 * Returns array of strings split by newline
8304 * @param {string} value - The value to split by newline.
8305 * @returns {string[]} value if newline present - Returned from String split() method
8306 * @function
8307 */
8308function splitNewlines(str) {
8309 if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
8310 return str.split('\n');
8311 }
8312 return str;
8313}
8314
8315
8316/**
8317 * Private helper to create a tooltip item model
8318 * @param element - the chart element (point, arc, bar) to create the tooltip item for
8319 * @return new tooltip item
8320 */
8321function createTooltipItem(element) {
8322 var xScale = element._xScale;
8323 var yScale = element._yScale || element._scale; // handle radar || polarArea charts
8324 var index = element._index;
8325 var datasetIndex = element._datasetIndex;
8326 var controller = element._chart.getDatasetMeta(datasetIndex).controller;
8327 var indexScale = controller._getIndexScale();
8328 var valueScale = controller._getValueScale();
8329
8330 return {
8331 xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
8332 yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
8333 label: indexScale ? '' + indexScale.getLabelForIndex(index, datasetIndex) : '',
8334 value: valueScale ? '' + valueScale.getLabelForIndex(index, datasetIndex) : '',
8335 index: index,
8336 datasetIndex: datasetIndex,
8337 x: element._model.x,
8338 y: element._model.y
8339 };
8340}
8341
8342/**
8343 * Helper to get the reset model for the tooltip
8344 * @param tooltipOpts {object} the tooltip options
8345 */
8346function getBaseModel(tooltipOpts) {
8347 var globalDefaults = core_defaults.global;
8348
8349 return {
8350 // Positioning
8351 xPadding: tooltipOpts.xPadding,
8352 yPadding: tooltipOpts.yPadding,
8353 xAlign: tooltipOpts.xAlign,
8354 yAlign: tooltipOpts.yAlign,
8355
8356 // Drawing direction and text direction
8357 rtl: tooltipOpts.rtl,
8358 textDirection: tooltipOpts.textDirection,
8359
8360 // Body
8361 bodyFontColor: tooltipOpts.bodyFontColor,
8362 _bodyFontFamily: valueOrDefault$8(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
8363 _bodyFontStyle: valueOrDefault$8(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
8364 _bodyAlign: tooltipOpts.bodyAlign,
8365 bodyFontSize: valueOrDefault$8(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
8366 bodySpacing: tooltipOpts.bodySpacing,
8367
8368 // Title
8369 titleFontColor: tooltipOpts.titleFontColor,
8370 _titleFontFamily: valueOrDefault$8(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
8371 _titleFontStyle: valueOrDefault$8(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
8372 titleFontSize: valueOrDefault$8(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
8373 _titleAlign: tooltipOpts.titleAlign,
8374 titleSpacing: tooltipOpts.titleSpacing,
8375 titleMarginBottom: tooltipOpts.titleMarginBottom,
8376
8377 // Footer
8378 footerFontColor: tooltipOpts.footerFontColor,
8379 _footerFontFamily: valueOrDefault$8(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
8380 _footerFontStyle: valueOrDefault$8(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
8381 footerFontSize: valueOrDefault$8(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
8382 _footerAlign: tooltipOpts.footerAlign,
8383 footerSpacing: tooltipOpts.footerSpacing,
8384 footerMarginTop: tooltipOpts.footerMarginTop,
8385
8386 // Appearance
8387 caretSize: tooltipOpts.caretSize,
8388 cornerRadius: tooltipOpts.cornerRadius,
8389 backgroundColor: tooltipOpts.backgroundColor,
8390 opacity: 0,
8391 legendColorBackground: tooltipOpts.multiKeyBackground,
8392 displayColors: tooltipOpts.displayColors,
8393 borderColor: tooltipOpts.borderColor,
8394 borderWidth: tooltipOpts.borderWidth
8395 };
8396}
8397
8398/**
8399 * Get the size of the tooltip
8400 */
8401function getTooltipSize(tooltip, model) {
8402 var ctx = tooltip._chart.ctx;
8403
8404 var height = model.yPadding * 2; // Tooltip Padding
8405 var width = 0;
8406
8407 // Count of all lines in the body
8408 var body = model.body;
8409 var combinedBodyLength = body.reduce(function(count, bodyItem) {
8410 return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
8411 }, 0);
8412 combinedBodyLength += model.beforeBody.length + model.afterBody.length;
8413
8414 var titleLineCount = model.title.length;
8415 var footerLineCount = model.footer.length;
8416 var titleFontSize = model.titleFontSize;
8417 var bodyFontSize = model.bodyFontSize;
8418 var footerFontSize = model.footerFontSize;
8419
8420 height += titleLineCount * titleFontSize; // Title Lines
8421 height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
8422 height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
8423 height += combinedBodyLength * bodyFontSize; // Body Lines
8424 height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
8425 height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
8426 height += footerLineCount * (footerFontSize); // Footer Lines
8427 height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
8428
8429 // Title width
8430 var widthPadding = 0;
8431 var maxLineWidth = function(line) {
8432 width = Math.max(width, ctx.measureText(line).width + widthPadding);
8433 };
8434
8435 ctx.font = helpers$1.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
8436 helpers$1.each(model.title, maxLineWidth);
8437
8438 // Body width
8439 ctx.font = helpers$1.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
8440 helpers$1.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
8441
8442 // Body lines may include some extra width due to the color box
8443 widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
8444 helpers$1.each(body, function(bodyItem) {
8445 helpers$1.each(bodyItem.before, maxLineWidth);
8446 helpers$1.each(bodyItem.lines, maxLineWidth);
8447 helpers$1.each(bodyItem.after, maxLineWidth);
8448 });
8449
8450 // Reset back to 0
8451 widthPadding = 0;
8452
8453 // Footer width
8454 ctx.font = helpers$1.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
8455 helpers$1.each(model.footer, maxLineWidth);
8456
8457 // Add padding
8458 width += 2 * model.xPadding;
8459
8460 return {
8461 width: width,
8462 height: height
8463 };
8464}
8465
8466/**
8467 * Helper to get the alignment of a tooltip given the size
8468 */
8469function determineAlignment(tooltip, size) {
8470 var model = tooltip._model;
8471 var chart = tooltip._chart;
8472 var chartArea = tooltip._chart.chartArea;
8473 var xAlign = 'center';
8474 var yAlign = 'center';
8475
8476 if (model.y < size.height) {
8477 yAlign = 'top';
8478 } else if (model.y > (chart.height - size.height)) {
8479 yAlign = 'bottom';
8480 }
8481
8482 var lf, rf; // functions to determine left, right alignment
8483 var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
8484 var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
8485 var midX = (chartArea.left + chartArea.right) / 2;
8486 var midY = (chartArea.top + chartArea.bottom) / 2;
8487
8488 if (yAlign === 'center') {
8489 lf = function(x) {
8490 return x <= midX;
8491 };
8492 rf = function(x) {
8493 return x > midX;
8494 };
8495 } else {
8496 lf = function(x) {
8497 return x <= (size.width / 2);
8498 };
8499 rf = function(x) {
8500 return x >= (chart.width - (size.width / 2));
8501 };
8502 }
8503
8504 olf = function(x) {
8505 return x + size.width + model.caretSize + model.caretPadding > chart.width;
8506 };
8507 orf = function(x) {
8508 return x - size.width - model.caretSize - model.caretPadding < 0;
8509 };
8510 yf = function(y) {
8511 return y <= midY ? 'top' : 'bottom';
8512 };
8513
8514 if (lf(model.x)) {
8515 xAlign = 'left';
8516
8517 // Is tooltip too wide and goes over the right side of the chart.?
8518 if (olf(model.x)) {
8519 xAlign = 'center';
8520 yAlign = yf(model.y);
8521 }
8522 } else if (rf(model.x)) {
8523 xAlign = 'right';
8524
8525 // Is tooltip too wide and goes outside left edge of canvas?
8526 if (orf(model.x)) {
8527 xAlign = 'center';
8528 yAlign = yf(model.y);
8529 }
8530 }
8531
8532 var opts = tooltip._options;
8533 return {
8534 xAlign: opts.xAlign ? opts.xAlign : xAlign,
8535 yAlign: opts.yAlign ? opts.yAlign : yAlign
8536 };
8537}
8538
8539/**
8540 * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
8541 */
8542function getBackgroundPoint(vm, size, alignment, chart) {
8543 // Background Position
8544 var x = vm.x;
8545 var y = vm.y;
8546
8547 var caretSize = vm.caretSize;
8548 var caretPadding = vm.caretPadding;
8549 var cornerRadius = vm.cornerRadius;
8550 var xAlign = alignment.xAlign;
8551 var yAlign = alignment.yAlign;
8552 var paddingAndSize = caretSize + caretPadding;
8553 var radiusAndPadding = cornerRadius + caretPadding;
8554
8555 if (xAlign === 'right') {
8556 x -= size.width;
8557 } else if (xAlign === 'center') {
8558 x -= (size.width / 2);
8559 if (x + size.width > chart.width) {
8560 x = chart.width - size.width;
8561 }
8562 if (x < 0) {
8563 x = 0;
8564 }
8565 }
8566
8567 if (yAlign === 'top') {
8568 y += paddingAndSize;
8569 } else if (yAlign === 'bottom') {
8570 y -= size.height + paddingAndSize;
8571 } else {
8572 y -= (size.height / 2);
8573 }
8574
8575 if (yAlign === 'center') {
8576 if (xAlign === 'left') {
8577 x += paddingAndSize;
8578 } else if (xAlign === 'right') {
8579 x -= paddingAndSize;
8580 }
8581 } else if (xAlign === 'left') {
8582 x -= radiusAndPadding;
8583 } else if (xAlign === 'right') {
8584 x += radiusAndPadding;
8585 }
8586
8587 return {
8588 x: x,
8589 y: y
8590 };
8591}
8592
8593function getAlignedX(vm, align) {
8594 return align === 'center'
8595 ? vm.x + vm.width / 2
8596 : align === 'right'
8597 ? vm.x + vm.width - vm.xPadding
8598 : vm.x + vm.xPadding;
8599}
8600
8601/**
8602 * Helper to build before and after body lines
8603 */
8604function getBeforeAfterBodyLines(callback) {
8605 return pushOrConcat([], splitNewlines(callback));
8606}
8607
8608var exports$4 = core_element.extend({
8609 initialize: function() {
8610 this._model = getBaseModel(this._options);
8611 this._lastActive = [];
8612 },
8613
8614 // Get the title
8615 // Args are: (tooltipItem, data)
8616 getTitle: function() {
8617 var me = this;
8618 var opts = me._options;
8619 var callbacks = opts.callbacks;
8620
8621 var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
8622 var title = callbacks.title.apply(me, arguments);
8623 var afterTitle = callbacks.afterTitle.apply(me, arguments);
8624
8625 var lines = [];
8626 lines = pushOrConcat(lines, splitNewlines(beforeTitle));
8627 lines = pushOrConcat(lines, splitNewlines(title));
8628 lines = pushOrConcat(lines, splitNewlines(afterTitle));
8629
8630 return lines;
8631 },
8632
8633 // Args are: (tooltipItem, data)
8634 getBeforeBody: function() {
8635 return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments));
8636 },
8637
8638 // Args are: (tooltipItem, data)
8639 getBody: function(tooltipItems, data) {
8640 var me = this;
8641 var callbacks = me._options.callbacks;
8642 var bodyItems = [];
8643
8644 helpers$1.each(tooltipItems, function(tooltipItem) {
8645 var bodyItem = {
8646 before: [],
8647 lines: [],
8648 after: []
8649 };
8650 pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data)));
8651 pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
8652 pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data)));
8653
8654 bodyItems.push(bodyItem);
8655 });
8656
8657 return bodyItems;
8658 },
8659
8660 // Args are: (tooltipItem, data)
8661 getAfterBody: function() {
8662 return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments));
8663 },
8664
8665 // Get the footer and beforeFooter and afterFooter lines
8666 // Args are: (tooltipItem, data)
8667 getFooter: function() {
8668 var me = this;
8669 var callbacks = me._options.callbacks;
8670
8671 var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
8672 var footer = callbacks.footer.apply(me, arguments);
8673 var afterFooter = callbacks.afterFooter.apply(me, arguments);
8674
8675 var lines = [];
8676 lines = pushOrConcat(lines, splitNewlines(beforeFooter));
8677 lines = pushOrConcat(lines, splitNewlines(footer));
8678 lines = pushOrConcat(lines, splitNewlines(afterFooter));
8679
8680 return lines;
8681 },
8682
8683 update: function(changed) {
8684 var me = this;
8685 var opts = me._options;
8686
8687 // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
8688 // 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
8689 // which breaks any animations.
8690 var existingModel = me._model;
8691 var model = me._model = getBaseModel(opts);
8692 var active = me._active;
8693
8694 var data = me._data;
8695
8696 // In the case where active.length === 0 we need to keep these at existing values for good animations
8697 var alignment = {
8698 xAlign: existingModel.xAlign,
8699 yAlign: existingModel.yAlign
8700 };
8701 var backgroundPoint = {
8702 x: existingModel.x,
8703 y: existingModel.y
8704 };
8705 var tooltipSize = {
8706 width: existingModel.width,
8707 height: existingModel.height
8708 };
8709 var tooltipPosition = {
8710 x: existingModel.caretX,
8711 y: existingModel.caretY
8712 };
8713
8714 var i, len;
8715
8716 if (active.length) {
8717 model.opacity = 1;
8718
8719 var labelColors = [];
8720 var labelTextColors = [];
8721 tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition);
8722
8723 var tooltipItems = [];
8724 for (i = 0, len = active.length; i < len; ++i) {
8725 tooltipItems.push(createTooltipItem(active[i]));
8726 }
8727
8728 // If the user provided a filter function, use it to modify the tooltip items
8729 if (opts.filter) {
8730 tooltipItems = tooltipItems.filter(function(a) {
8731 return opts.filter(a, data);
8732 });
8733 }
8734
8735 // If the user provided a sorting function, use it to modify the tooltip items
8736 if (opts.itemSort) {
8737 tooltipItems = tooltipItems.sort(function(a, b) {
8738 return opts.itemSort(a, b, data);
8739 });
8740 }
8741
8742 // Determine colors for boxes
8743 helpers$1.each(tooltipItems, function(tooltipItem) {
8744 labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
8745 labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
8746 });
8747
8748
8749 // Build the Text Lines
8750 model.title = me.getTitle(tooltipItems, data);
8751 model.beforeBody = me.getBeforeBody(tooltipItems, data);
8752 model.body = me.getBody(tooltipItems, data);
8753 model.afterBody = me.getAfterBody(tooltipItems, data);
8754 model.footer = me.getFooter(tooltipItems, data);
8755
8756 // Initial positioning and colors
8757 model.x = tooltipPosition.x;
8758 model.y = tooltipPosition.y;
8759 model.caretPadding = opts.caretPadding;
8760 model.labelColors = labelColors;
8761 model.labelTextColors = labelTextColors;
8762
8763 // data points
8764 model.dataPoints = tooltipItems;
8765
8766 // We need to determine alignment of the tooltip
8767 tooltipSize = getTooltipSize(this, model);
8768 alignment = determineAlignment(this, tooltipSize);
8769 // Final Size and Position
8770 backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart);
8771 } else {
8772 model.opacity = 0;
8773 }
8774
8775 model.xAlign = alignment.xAlign;
8776 model.yAlign = alignment.yAlign;
8777 model.x = backgroundPoint.x;
8778 model.y = backgroundPoint.y;
8779 model.width = tooltipSize.width;
8780 model.height = tooltipSize.height;
8781
8782 // Point where the caret on the tooltip points to
8783 model.caretX = tooltipPosition.x;
8784 model.caretY = tooltipPosition.y;
8785
8786 me._model = model;
8787
8788 if (changed && opts.custom) {
8789 opts.custom.call(me, model);
8790 }
8791
8792 return me;
8793 },
8794
8795 drawCaret: function(tooltipPoint, size) {
8796 var ctx = this._chart.ctx;
8797 var vm = this._view;
8798 var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
8799
8800 ctx.lineTo(caretPosition.x1, caretPosition.y1);
8801 ctx.lineTo(caretPosition.x2, caretPosition.y2);
8802 ctx.lineTo(caretPosition.x3, caretPosition.y3);
8803 },
8804 getCaretPosition: function(tooltipPoint, size, vm) {
8805 var x1, x2, x3, y1, y2, y3;
8806 var caretSize = vm.caretSize;
8807 var cornerRadius = vm.cornerRadius;
8808 var xAlign = vm.xAlign;
8809 var yAlign = vm.yAlign;
8810 var ptX = tooltipPoint.x;
8811 var ptY = tooltipPoint.y;
8812 var width = size.width;
8813 var height = size.height;
8814
8815 if (yAlign === 'center') {
8816 y2 = ptY + (height / 2);
8817
8818 if (xAlign === 'left') {
8819 x1 = ptX;
8820 x2 = x1 - caretSize;
8821 x3 = x1;
8822
8823 y1 = y2 + caretSize;
8824 y3 = y2 - caretSize;
8825 } else {
8826 x1 = ptX + width;
8827 x2 = x1 + caretSize;
8828 x3 = x1;
8829
8830 y1 = y2 - caretSize;
8831 y3 = y2 + caretSize;
8832 }
8833 } else {
8834 if (xAlign === 'left') {
8835 x2 = ptX + cornerRadius + (caretSize);
8836 x1 = x2 - caretSize;
8837 x3 = x2 + caretSize;
8838 } else if (xAlign === 'right') {
8839 x2 = ptX + width - cornerRadius - caretSize;
8840 x1 = x2 - caretSize;
8841 x3 = x2 + caretSize;
8842 } else {
8843 x2 = vm.caretX;
8844 x1 = x2 - caretSize;
8845 x3 = x2 + caretSize;
8846 }
8847 if (yAlign === 'top') {
8848 y1 = ptY;
8849 y2 = y1 - caretSize;
8850 y3 = y1;
8851 } else {
8852 y1 = ptY + height;
8853 y2 = y1 + caretSize;
8854 y3 = y1;
8855 // invert drawing order
8856 var tmp = x3;
8857 x3 = x1;
8858 x1 = tmp;
8859 }
8860 }
8861 return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
8862 },
8863
8864 drawTitle: function(pt, vm, ctx) {
8865 var title = vm.title;
8866 var length = title.length;
8867 var titleFontSize, titleSpacing, i;
8868
8869 if (length) {
8870 var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
8871
8872 pt.x = getAlignedX(vm, vm._titleAlign);
8873
8874 ctx.textAlign = rtlHelper.textAlign(vm._titleAlign);
8875 ctx.textBaseline = 'middle';
8876
8877 titleFontSize = vm.titleFontSize;
8878 titleSpacing = vm.titleSpacing;
8879
8880 ctx.fillStyle = vm.titleFontColor;
8881 ctx.font = helpers$1.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
8882
8883 for (i = 0; i < length; ++i) {
8884 ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFontSize / 2);
8885 pt.y += titleFontSize + titleSpacing; // Line Height and spacing
8886
8887 if (i + 1 === length) {
8888 pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
8889 }
8890 }
8891 }
8892 },
8893
8894 drawBody: function(pt, vm, ctx) {
8895 var bodyFontSize = vm.bodyFontSize;
8896 var bodySpacing = vm.bodySpacing;
8897 var bodyAlign = vm._bodyAlign;
8898 var body = vm.body;
8899 var drawColorBoxes = vm.displayColors;
8900 var xLinePadding = 0;
8901 var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0;
8902
8903 var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
8904
8905 var fillLineOfText = function(line) {
8906 ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyFontSize / 2);
8907 pt.y += bodyFontSize + bodySpacing;
8908 };
8909
8910 var bodyItem, textColor, labelColors, lines, i, j, ilen, jlen;
8911 var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
8912
8913 ctx.textAlign = bodyAlign;
8914 ctx.textBaseline = 'middle';
8915 ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
8916
8917 pt.x = getAlignedX(vm, bodyAlignForCalculation);
8918
8919 // Before body lines
8920 ctx.fillStyle = vm.bodyFontColor;
8921 helpers$1.each(vm.beforeBody, fillLineOfText);
8922
8923 xLinePadding = drawColorBoxes && bodyAlignForCalculation !== 'right'
8924 ? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2)
8925 : 0;
8926
8927 // Draw body lines now
8928 for (i = 0, ilen = body.length; i < ilen; ++i) {
8929 bodyItem = body[i];
8930 textColor = vm.labelTextColors[i];
8931 labelColors = vm.labelColors[i];
8932
8933 ctx.fillStyle = textColor;
8934 helpers$1.each(bodyItem.before, fillLineOfText);
8935
8936 lines = bodyItem.lines;
8937 for (j = 0, jlen = lines.length; j < jlen; ++j) {
8938 // Draw Legend-like boxes if needed
8939 if (drawColorBoxes) {
8940 var rtlColorX = rtlHelper.x(colorX);
8941
8942 // Fill a white rect so that colours merge nicely if the opacity is < 1
8943 ctx.fillStyle = vm.legendColorBackground;
8944 ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
8945
8946 // Border
8947 ctx.lineWidth = 1;
8948 ctx.strokeStyle = labelColors.borderColor;
8949 ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
8950
8951 // Inner square
8952 ctx.fillStyle = labelColors.backgroundColor;
8953 ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
8954 ctx.fillStyle = textColor;
8955 }
8956
8957 fillLineOfText(lines[j]);
8958 }
8959
8960 helpers$1.each(bodyItem.after, fillLineOfText);
8961 }
8962
8963 // Reset back to 0 for after body
8964 xLinePadding = 0;
8965
8966 // After body lines
8967 helpers$1.each(vm.afterBody, fillLineOfText);
8968 pt.y -= bodySpacing; // Remove last body spacing
8969 },
8970
8971 drawFooter: function(pt, vm, ctx) {
8972 var footer = vm.footer;
8973 var length = footer.length;
8974 var footerFontSize, i;
8975
8976 if (length) {
8977 var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
8978
8979 pt.x = getAlignedX(vm, vm._footerAlign);
8980 pt.y += vm.footerMarginTop;
8981
8982 ctx.textAlign = rtlHelper.textAlign(vm._footerAlign);
8983 ctx.textBaseline = 'middle';
8984
8985 footerFontSize = vm.footerFontSize;
8986
8987 ctx.fillStyle = vm.footerFontColor;
8988 ctx.font = helpers$1.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
8989
8990 for (i = 0; i < length; ++i) {
8991 ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFontSize / 2);
8992 pt.y += footerFontSize + vm.footerSpacing;
8993 }
8994 }
8995 },
8996
8997 drawBackground: function(pt, vm, ctx, tooltipSize) {
8998 ctx.fillStyle = vm.backgroundColor;
8999 ctx.strokeStyle = vm.borderColor;
9000 ctx.lineWidth = vm.borderWidth;
9001 var xAlign = vm.xAlign;
9002 var yAlign = vm.yAlign;
9003 var x = pt.x;
9004 var y = pt.y;
9005 var width = tooltipSize.width;
9006 var height = tooltipSize.height;
9007 var radius = vm.cornerRadius;
9008
9009 ctx.beginPath();
9010 ctx.moveTo(x + radius, y);
9011 if (yAlign === 'top') {
9012 this.drawCaret(pt, tooltipSize);
9013 }
9014 ctx.lineTo(x + width - radius, y);
9015 ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
9016 if (yAlign === 'center' && xAlign === 'right') {
9017 this.drawCaret(pt, tooltipSize);
9018 }
9019 ctx.lineTo(x + width, y + height - radius);
9020 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
9021 if (yAlign === 'bottom') {
9022 this.drawCaret(pt, tooltipSize);
9023 }
9024 ctx.lineTo(x + radius, y + height);
9025 ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
9026 if (yAlign === 'center' && xAlign === 'left') {
9027 this.drawCaret(pt, tooltipSize);
9028 }
9029 ctx.lineTo(x, y + radius);
9030 ctx.quadraticCurveTo(x, y, x + radius, y);
9031 ctx.closePath();
9032
9033 ctx.fill();
9034
9035 if (vm.borderWidth > 0) {
9036 ctx.stroke();
9037 }
9038 },
9039
9040 draw: function() {
9041 var ctx = this._chart.ctx;
9042 var vm = this._view;
9043
9044 if (vm.opacity === 0) {
9045 return;
9046 }
9047
9048 var tooltipSize = {
9049 width: vm.width,
9050 height: vm.height
9051 };
9052 var pt = {
9053 x: vm.x,
9054 y: vm.y
9055 };
9056
9057 // IE11/Edge does not like very small opacities, so snap to 0
9058 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
9059
9060 // Truthy/falsey value for empty tooltip
9061 var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
9062
9063 if (this._options.enabled && hasTooltipContent) {
9064 ctx.save();
9065 ctx.globalAlpha = opacity;
9066
9067 // Draw Background
9068 this.drawBackground(pt, vm, ctx, tooltipSize);
9069
9070 // Draw Title, Body, and Footer
9071 pt.y += vm.yPadding;
9072
9073 helpers$1.rtl.overrideTextDirection(ctx, vm.textDirection);
9074
9075 // Titles
9076 this.drawTitle(pt, vm, ctx);
9077
9078 // Body
9079 this.drawBody(pt, vm, ctx);
9080
9081 // Footer
9082 this.drawFooter(pt, vm, ctx);
9083
9084 helpers$1.rtl.restoreTextDirection(ctx, vm.textDirection);
9085
9086 ctx.restore();
9087 }
9088 },
9089
9090 /**
9091 * Handle an event
9092 * @private
9093 * @param {IEvent} event - The event to handle
9094 * @returns {boolean} true if the tooltip changed
9095 */
9096 handleEvent: function(e) {
9097 var me = this;
9098 var options = me._options;
9099 var changed = false;
9100
9101 me._lastActive = me._lastActive || [];
9102
9103 // Find Active Elements for tooltips
9104 if (e.type === 'mouseout') {
9105 me._active = [];
9106 } else {
9107 me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
9108 if (options.reverse) {
9109 me._active.reverse();
9110 }
9111 }
9112
9113 // Remember Last Actives
9114 changed = !helpers$1.arrayEquals(me._active, me._lastActive);
9115
9116 // Only handle target event on tooltip change
9117 if (changed) {
9118 me._lastActive = me._active;
9119
9120 if (options.enabled || options.custom) {
9121 me._eventPosition = {
9122 x: e.x,
9123 y: e.y
9124 };
9125
9126 me.update(true);
9127 me.pivot();
9128 }
9129 }
9130
9131 return changed;
9132 }
9133});
9134
9135/**
9136 * @namespace Chart.Tooltip.positioners
9137 */
9138var positioners_1 = positioners;
9139
9140var core_tooltip = exports$4;
9141core_tooltip.positioners = positioners_1;
9142
9143var valueOrDefault$9 = helpers$1.valueOrDefault;
9144
9145core_defaults._set('global', {
9146 elements: {},
9147 events: [
9148 'mousemove',
9149 'mouseout',
9150 'click',
9151 'touchstart',
9152 'touchmove'
9153 ],
9154 hover: {
9155 onHover: null,
9156 mode: 'nearest',
9157 intersect: true,
9158 animationDuration: 400
9159 },
9160 onClick: null,
9161 maintainAspectRatio: true,
9162 responsive: true,
9163 responsiveAnimationDuration: 0
9164});
9165
9166/**
9167 * Recursively merge the given config objects representing the `scales` option
9168 * by incorporating scale defaults in `xAxes` and `yAxes` array items, then
9169 * returns a deep copy of the result, thus doesn't alter inputs.
9170 */
9171function mergeScaleConfig(/* config objects ... */) {
9172 return helpers$1.merge({}, [].slice.call(arguments), {
9173 merger: function(key, target, source, options) {
9174 if (key === 'xAxes' || key === 'yAxes') {
9175 var slen = source[key].length;
9176 var i, type, scale;
9177
9178 if (!target[key]) {
9179 target[key] = [];
9180 }
9181
9182 for (i = 0; i < slen; ++i) {
9183 scale = source[key][i];
9184 type = valueOrDefault$9(scale.type, key === 'xAxes' ? 'category' : 'linear');
9185
9186 if (i >= target[key].length) {
9187 target[key].push({});
9188 }
9189
9190 if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
9191 // new/untyped scale or type changed: let's apply the new defaults
9192 // then merge source scale to correctly overwrite the defaults.
9193 helpers$1.merge(target[key][i], [core_scaleService.getScaleDefaults(type), scale]);
9194 } else {
9195 // scales type are the same
9196 helpers$1.merge(target[key][i], scale);
9197 }
9198 }
9199 } else {
9200 helpers$1._merger(key, target, source, options);
9201 }
9202 }
9203 });
9204}
9205
9206/**
9207 * Recursively merge the given config objects as the root options by handling
9208 * default scale options for the `scales` and `scale` properties, then returns
9209 * a deep copy of the result, thus doesn't alter inputs.
9210 */
9211function mergeConfig(/* config objects ... */) {
9212 return helpers$1.merge({}, [].slice.call(arguments), {
9213 merger: function(key, target, source, options) {
9214 var tval = target[key] || {};
9215 var sval = source[key];
9216
9217 if (key === 'scales') {
9218 // scale config merging is complex. Add our own function here for that
9219 target[key] = mergeScaleConfig(tval, sval);
9220 } else if (key === 'scale') {
9221 // used in polar area & radar charts since there is only one scale
9222 target[key] = helpers$1.merge(tval, [core_scaleService.getScaleDefaults(sval.type), sval]);
9223 } else {
9224 helpers$1._merger(key, target, source, options);
9225 }
9226 }
9227 });
9228}
9229
9230function initConfig(config) {
9231 config = config || {};
9232
9233 // Do NOT use mergeConfig for the data object because this method merges arrays
9234 // and so would change references to labels and datasets, preventing data updates.
9235 var data = config.data = config.data || {};
9236 data.datasets = data.datasets || [];
9237 data.labels = data.labels || [];
9238
9239 config.options = mergeConfig(
9240 core_defaults.global,
9241 core_defaults[config.type],
9242 config.options || {});
9243
9244 return config;
9245}
9246
9247function updateConfig(chart) {
9248 var newOptions = chart.options;
9249
9250 helpers$1.each(chart.scales, function(scale) {
9251 core_layouts.removeBox(chart, scale);
9252 });
9253
9254 newOptions = mergeConfig(
9255 core_defaults.global,
9256 core_defaults[chart.config.type],
9257 newOptions);
9258
9259 chart.options = chart.config.options = newOptions;
9260 chart.ensureScalesHaveIDs();
9261 chart.buildOrUpdateScales();
9262
9263 // Tooltip
9264 chart.tooltip._options = newOptions.tooltips;
9265 chart.tooltip.initialize();
9266}
9267
9268function nextAvailableScaleId(axesOpts, prefix, index) {
9269 var id;
9270 var hasId = function(obj) {
9271 return obj.id === id;
9272 };
9273
9274 do {
9275 id = prefix + index++;
9276 } while (helpers$1.findIndex(axesOpts, hasId) >= 0);
9277
9278 return id;
9279}
9280
9281function positionIsHorizontal(position) {
9282 return position === 'top' || position === 'bottom';
9283}
9284
9285function compare2Level(l1, l2) {
9286 return function(a, b) {
9287 return a[l1] === b[l1]
9288 ? a[l2] - b[l2]
9289 : a[l1] - b[l1];
9290 };
9291}
9292
9293var Chart = function(item, config) {
9294 this.construct(item, config);
9295 return this;
9296};
9297
9298helpers$1.extend(Chart.prototype, /** @lends Chart */ {
9299 /**
9300 * @private
9301 */
9302 construct: function(item, config) {
9303 var me = this;
9304
9305 config = initConfig(config);
9306
9307 var context = platform.acquireContext(item, config);
9308 var canvas = context && context.canvas;
9309 var height = canvas && canvas.height;
9310 var width = canvas && canvas.width;
9311
9312 me.id = helpers$1.uid();
9313 me.ctx = context;
9314 me.canvas = canvas;
9315 me.config = config;
9316 me.width = width;
9317 me.height = height;
9318 me.aspectRatio = height ? width / height : null;
9319 me.options = config.options;
9320 me._bufferedRender = false;
9321 me._layers = [];
9322
9323 /**
9324 * Provided for backward compatibility, Chart and Chart.Controller have been merged,
9325 * the "instance" still need to be defined since it might be called from plugins.
9326 * @prop Chart#chart
9327 * @deprecated since version 2.6.0
9328 * @todo remove at version 3
9329 * @private
9330 */
9331 me.chart = me;
9332 me.controller = me; // chart.chart.controller #inception
9333
9334 // Add the chart instance to the global namespace
9335 Chart.instances[me.id] = me;
9336
9337 // Define alias to the config data: `chart.data === chart.config.data`
9338 Object.defineProperty(me, 'data', {
9339 get: function() {
9340 return me.config.data;
9341 },
9342 set: function(value) {
9343 me.config.data = value;
9344 }
9345 });
9346
9347 if (!context || !canvas) {
9348 // The given item is not a compatible context2d element, let's return before finalizing
9349 // the chart initialization but after setting basic chart / controller properties that
9350 // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
9351 // https://github.com/chartjs/Chart.js/issues/2807
9352 console.error("Failed to create chart: can't acquire context from the given item");
9353 return;
9354 }
9355
9356 me.initialize();
9357 me.update();
9358 },
9359
9360 /**
9361 * @private
9362 */
9363 initialize: function() {
9364 var me = this;
9365
9366 // Before init plugin notification
9367 core_plugins.notify(me, 'beforeInit');
9368
9369 helpers$1.retinaScale(me, me.options.devicePixelRatio);
9370
9371 me.bindEvents();
9372
9373 if (me.options.responsive) {
9374 // Initial resize before chart draws (must be silent to preserve initial animations).
9375 me.resize(true);
9376 }
9377
9378 me.initToolTip();
9379
9380 // After init plugin notification
9381 core_plugins.notify(me, 'afterInit');
9382
9383 return me;
9384 },
9385
9386 clear: function() {
9387 helpers$1.canvas.clear(this);
9388 return this;
9389 },
9390
9391 stop: function() {
9392 // Stops any current animation loop occurring
9393 core_animations.cancelAnimation(this);
9394 return this;
9395 },
9396
9397 resize: function(silent) {
9398 var me = this;
9399 var options = me.options;
9400 var canvas = me.canvas;
9401 var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
9402
9403 // the canvas render width and height will be casted to integers so make sure that
9404 // the canvas display style uses the same integer values to avoid blurring effect.
9405
9406 // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed
9407 var newWidth = Math.max(0, Math.floor(helpers$1.getMaximumWidth(canvas)));
9408 var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers$1.getMaximumHeight(canvas)));
9409
9410 if (me.width === newWidth && me.height === newHeight) {
9411 return;
9412 }
9413
9414 canvas.width = me.width = newWidth;
9415 canvas.height = me.height = newHeight;
9416 canvas.style.width = newWidth + 'px';
9417 canvas.style.height = newHeight + 'px';
9418
9419 helpers$1.retinaScale(me, options.devicePixelRatio);
9420
9421 if (!silent) {
9422 // Notify any plugins about the resize
9423 var newSize = {width: newWidth, height: newHeight};
9424 core_plugins.notify(me, 'resize', [newSize]);
9425
9426 // Notify of resize
9427 if (options.onResize) {
9428 options.onResize(me, newSize);
9429 }
9430
9431 me.stop();
9432 me.update({
9433 duration: options.responsiveAnimationDuration
9434 });
9435 }
9436 },
9437
9438 ensureScalesHaveIDs: function() {
9439 var options = this.options;
9440 var scalesOptions = options.scales || {};
9441 var scaleOptions = options.scale;
9442
9443 helpers$1.each(scalesOptions.xAxes, function(xAxisOptions, index) {
9444 if (!xAxisOptions.id) {
9445 xAxisOptions.id = nextAvailableScaleId(scalesOptions.xAxes, 'x-axis-', index);
9446 }
9447 });
9448
9449 helpers$1.each(scalesOptions.yAxes, function(yAxisOptions, index) {
9450 if (!yAxisOptions.id) {
9451 yAxisOptions.id = nextAvailableScaleId(scalesOptions.yAxes, 'y-axis-', index);
9452 }
9453 });
9454
9455 if (scaleOptions) {
9456 scaleOptions.id = scaleOptions.id || 'scale';
9457 }
9458 },
9459
9460 /**
9461 * Builds a map of scale ID to scale object for future lookup.
9462 */
9463 buildOrUpdateScales: function() {
9464 var me = this;
9465 var options = me.options;
9466 var scales = me.scales || {};
9467 var items = [];
9468 var updated = Object.keys(scales).reduce(function(obj, id) {
9469 obj[id] = false;
9470 return obj;
9471 }, {});
9472
9473 if (options.scales) {
9474 items = items.concat(
9475 (options.scales.xAxes || []).map(function(xAxisOptions) {
9476 return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
9477 }),
9478 (options.scales.yAxes || []).map(function(yAxisOptions) {
9479 return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
9480 })
9481 );
9482 }
9483
9484 if (options.scale) {
9485 items.push({
9486 options: options.scale,
9487 dtype: 'radialLinear',
9488 isDefault: true,
9489 dposition: 'chartArea'
9490 });
9491 }
9492
9493 helpers$1.each(items, function(item) {
9494 var scaleOptions = item.options;
9495 var id = scaleOptions.id;
9496 var scaleType = valueOrDefault$9(scaleOptions.type, item.dtype);
9497
9498 if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
9499 scaleOptions.position = item.dposition;
9500 }
9501
9502 updated[id] = true;
9503 var scale = null;
9504 if (id in scales && scales[id].type === scaleType) {
9505 scale = scales[id];
9506 scale.options = scaleOptions;
9507 scale.ctx = me.ctx;
9508 scale.chart = me;
9509 } else {
9510 var scaleClass = core_scaleService.getScaleConstructor(scaleType);
9511 if (!scaleClass) {
9512 return;
9513 }
9514 scale = new scaleClass({
9515 id: id,
9516 type: scaleType,
9517 options: scaleOptions,
9518 ctx: me.ctx,
9519 chart: me
9520 });
9521 scales[scale.id] = scale;
9522 }
9523
9524 scale.mergeTicksOptions();
9525
9526 // TODO(SB): I think we should be able to remove this custom case (options.scale)
9527 // and consider it as a regular scale part of the "scales"" map only! This would
9528 // make the logic easier and remove some useless? custom code.
9529 if (item.isDefault) {
9530 me.scale = scale;
9531 }
9532 });
9533 // clear up discarded scales
9534 helpers$1.each(updated, function(hasUpdated, id) {
9535 if (!hasUpdated) {
9536 delete scales[id];
9537 }
9538 });
9539
9540 me.scales = scales;
9541
9542 core_scaleService.addScalesToLayout(this);
9543 },
9544
9545 buildOrUpdateControllers: function() {
9546 var me = this;
9547 var newControllers = [];
9548 var datasets = me.data.datasets;
9549 var i, ilen;
9550
9551 for (i = 0, ilen = datasets.length; i < ilen; i++) {
9552 var dataset = datasets[i];
9553 var meta = me.getDatasetMeta(i);
9554 var type = dataset.type || me.config.type;
9555
9556 if (meta.type && meta.type !== type) {
9557 me.destroyDatasetMeta(i);
9558 meta = me.getDatasetMeta(i);
9559 }
9560 meta.type = type;
9561 meta.order = dataset.order || 0;
9562 meta.index = i;
9563
9564 if (meta.controller) {
9565 meta.controller.updateIndex(i);
9566 meta.controller.linkScales();
9567 } else {
9568 var ControllerClass = controllers[meta.type];
9569 if (ControllerClass === undefined) {
9570 throw new Error('"' + meta.type + '" is not a chart type.');
9571 }
9572
9573 meta.controller = new ControllerClass(me, i);
9574 newControllers.push(meta.controller);
9575 }
9576 }
9577
9578 return newControllers;
9579 },
9580
9581 /**
9582 * Reset the elements of all datasets
9583 * @private
9584 */
9585 resetElements: function() {
9586 var me = this;
9587 helpers$1.each(me.data.datasets, function(dataset, datasetIndex) {
9588 me.getDatasetMeta(datasetIndex).controller.reset();
9589 }, me);
9590 },
9591
9592 /**
9593 * Resets the chart back to it's state before the initial animation
9594 */
9595 reset: function() {
9596 this.resetElements();
9597 this.tooltip.initialize();
9598 },
9599
9600 update: function(config) {
9601 var me = this;
9602 var i, ilen;
9603
9604 if (!config || typeof config !== 'object') {
9605 // backwards compatibility
9606 config = {
9607 duration: config,
9608 lazy: arguments[1]
9609 };
9610 }
9611
9612 updateConfig(me);
9613
9614 // plugins options references might have change, let's invalidate the cache
9615 // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
9616 core_plugins._invalidate(me);
9617
9618 if (core_plugins.notify(me, 'beforeUpdate') === false) {
9619 return;
9620 }
9621
9622 // In case the entire data object changed
9623 me.tooltip._data = me.data;
9624
9625 // Make sure dataset controllers are updated and new controllers are reset
9626 var newControllers = me.buildOrUpdateControllers();
9627
9628 // Make sure all dataset controllers have correct meta data counts
9629 for (i = 0, ilen = me.data.datasets.length; i < ilen; i++) {
9630 me.getDatasetMeta(i).controller.buildOrUpdateElements();
9631 }
9632
9633 me.updateLayout();
9634
9635 // Can only reset the new controllers after the scales have been updated
9636 if (me.options.animation && me.options.animation.duration) {
9637 helpers$1.each(newControllers, function(controller) {
9638 controller.reset();
9639 });
9640 }
9641
9642 me.updateDatasets();
9643
9644 // Need to reset tooltip in case it is displayed with elements that are removed
9645 // after update.
9646 me.tooltip.initialize();
9647
9648 // Last active contains items that were previously in the tooltip.
9649 // When we reset the tooltip, we need to clear it
9650 me.lastActive = [];
9651
9652 // Do this before render so that any plugins that need final scale updates can use it
9653 core_plugins.notify(me, 'afterUpdate');
9654
9655 me._layers.sort(compare2Level('z', '_idx'));
9656
9657 if (me._bufferedRender) {
9658 me._bufferedRequest = {
9659 duration: config.duration,
9660 easing: config.easing,
9661 lazy: config.lazy
9662 };
9663 } else {
9664 me.render(config);
9665 }
9666 },
9667
9668 /**
9669 * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
9670 * hook, in which case, plugins will not be called on `afterLayout`.
9671 * @private
9672 */
9673 updateLayout: function() {
9674 var me = this;
9675
9676 if (core_plugins.notify(me, 'beforeLayout') === false) {
9677 return;
9678 }
9679
9680 core_layouts.update(this, this.width, this.height);
9681
9682 me._layers = [];
9683 helpers$1.each(me.boxes, function(box) {
9684 // _configure is called twice, once in core.scale.update and once here.
9685 // Here the boxes are fully updated and at their final positions.
9686 if (box._configure) {
9687 box._configure();
9688 }
9689 me._layers.push.apply(me._layers, box._layers());
9690 }, me);
9691
9692 me._layers.forEach(function(item, index) {
9693 item._idx = index;
9694 });
9695
9696 /**
9697 * Provided for backward compatibility, use `afterLayout` instead.
9698 * @method IPlugin#afterScaleUpdate
9699 * @deprecated since version 2.5.0
9700 * @todo remove at version 3
9701 * @private
9702 */
9703 core_plugins.notify(me, 'afterScaleUpdate');
9704 core_plugins.notify(me, 'afterLayout');
9705 },
9706
9707 /**
9708 * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
9709 * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
9710 * @private
9711 */
9712 updateDatasets: function() {
9713 var me = this;
9714
9715 if (core_plugins.notify(me, 'beforeDatasetsUpdate') === false) {
9716 return;
9717 }
9718
9719 for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
9720 me.updateDataset(i);
9721 }
9722
9723 core_plugins.notify(me, 'afterDatasetsUpdate');
9724 },
9725
9726 /**
9727 * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
9728 * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
9729 * @private
9730 */
9731 updateDataset: function(index) {
9732 var me = this;
9733 var meta = me.getDatasetMeta(index);
9734 var args = {
9735 meta: meta,
9736 index: index
9737 };
9738
9739 if (core_plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
9740 return;
9741 }
9742
9743 meta.controller._update();
9744
9745 core_plugins.notify(me, 'afterDatasetUpdate', [args]);
9746 },
9747
9748 render: function(config) {
9749 var me = this;
9750
9751 if (!config || typeof config !== 'object') {
9752 // backwards compatibility
9753 config = {
9754 duration: config,
9755 lazy: arguments[1]
9756 };
9757 }
9758
9759 var animationOptions = me.options.animation;
9760 var duration = valueOrDefault$9(config.duration, animationOptions && animationOptions.duration);
9761 var lazy = config.lazy;
9762
9763 if (core_plugins.notify(me, 'beforeRender') === false) {
9764 return;
9765 }
9766
9767 var onComplete = function(animation) {
9768 core_plugins.notify(me, 'afterRender');
9769 helpers$1.callback(animationOptions && animationOptions.onComplete, [animation], me);
9770 };
9771
9772 if (animationOptions && duration) {
9773 var animation = new core_animation({
9774 numSteps: duration / 16.66, // 60 fps
9775 easing: config.easing || animationOptions.easing,
9776
9777 render: function(chart, animationObject) {
9778 var easingFunction = helpers$1.easing.effects[animationObject.easing];
9779 var currentStep = animationObject.currentStep;
9780 var stepDecimal = currentStep / animationObject.numSteps;
9781
9782 chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
9783 },
9784
9785 onAnimationProgress: animationOptions.onProgress,
9786 onAnimationComplete: onComplete
9787 });
9788
9789 core_animations.addAnimation(me, animation, duration, lazy);
9790 } else {
9791 me.draw();
9792
9793 // See https://github.com/chartjs/Chart.js/issues/3781
9794 onComplete(new core_animation({numSteps: 0, chart: me}));
9795 }
9796
9797 return me;
9798 },
9799
9800 draw: function(easingValue) {
9801 var me = this;
9802 var i, layers;
9803
9804 me.clear();
9805
9806 if (helpers$1.isNullOrUndef(easingValue)) {
9807 easingValue = 1;
9808 }
9809
9810 me.transition(easingValue);
9811
9812 if (me.width <= 0 || me.height <= 0) {
9813 return;
9814 }
9815
9816 if (core_plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
9817 return;
9818 }
9819
9820 // Because of plugin hooks (before/afterDatasetsDraw), datasets can't
9821 // currently be part of layers. Instead, we draw
9822 // layers <= 0 before(default, backward compat), and the rest after
9823 layers = me._layers;
9824 for (i = 0; i < layers.length && layers[i].z <= 0; ++i) {
9825 layers[i].draw(me.chartArea);
9826 }
9827
9828 me.drawDatasets(easingValue);
9829
9830 // Rest of layers
9831 for (; i < layers.length; ++i) {
9832 layers[i].draw(me.chartArea);
9833 }
9834
9835 me._drawTooltip(easingValue);
9836
9837 core_plugins.notify(me, 'afterDraw', [easingValue]);
9838 },
9839
9840 /**
9841 * @private
9842 */
9843 transition: function(easingValue) {
9844 var me = this;
9845
9846 for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
9847 if (me.isDatasetVisible(i)) {
9848 me.getDatasetMeta(i).controller.transition(easingValue);
9849 }
9850 }
9851
9852 me.tooltip.transition(easingValue);
9853 },
9854
9855 /**
9856 * @private
9857 */
9858 _getSortedDatasetMetas: function(filterVisible) {
9859 var me = this;
9860 var datasets = me.data.datasets || [];
9861 var result = [];
9862 var i, ilen;
9863
9864 for (i = 0, ilen = datasets.length; i < ilen; ++i) {
9865 if (!filterVisible || me.isDatasetVisible(i)) {
9866 result.push(me.getDatasetMeta(i));
9867 }
9868 }
9869
9870 result.sort(compare2Level('order', 'index'));
9871
9872 return result;
9873 },
9874
9875 /**
9876 * @private
9877 */
9878 _getSortedVisibleDatasetMetas: function() {
9879 return this._getSortedDatasetMetas(true);
9880 },
9881
9882 /**
9883 * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
9884 * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
9885 * @private
9886 */
9887 drawDatasets: function(easingValue) {
9888 var me = this;
9889 var metasets, i;
9890
9891 if (core_plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
9892 return;
9893 }
9894
9895 metasets = me._getSortedVisibleDatasetMetas();
9896 for (i = metasets.length - 1; i >= 0; --i) {
9897 me.drawDataset(metasets[i], easingValue);
9898 }
9899
9900 core_plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
9901 },
9902
9903 /**
9904 * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
9905 * hook, in which case, plugins will not be called on `afterDatasetDraw`.
9906 * @private
9907 */
9908 drawDataset: function(meta, easingValue) {
9909 var me = this;
9910 var args = {
9911 meta: meta,
9912 index: meta.index,
9913 easingValue: easingValue
9914 };
9915
9916 if (core_plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
9917 return;
9918 }
9919
9920 meta.controller.draw(easingValue);
9921
9922 core_plugins.notify(me, 'afterDatasetDraw', [args]);
9923 },
9924
9925 /**
9926 * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
9927 * hook, in which case, plugins will not be called on `afterTooltipDraw`.
9928 * @private
9929 */
9930 _drawTooltip: function(easingValue) {
9931 var me = this;
9932 var tooltip = me.tooltip;
9933 var args = {
9934 tooltip: tooltip,
9935 easingValue: easingValue
9936 };
9937
9938 if (core_plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
9939 return;
9940 }
9941
9942 tooltip.draw();
9943
9944 core_plugins.notify(me, 'afterTooltipDraw', [args]);
9945 },
9946
9947 /**
9948 * Get the single element that was clicked on
9949 * @return An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
9950 */
9951 getElementAtEvent: function(e) {
9952 return core_interaction.modes.single(this, e);
9953 },
9954
9955 getElementsAtEvent: function(e) {
9956 return core_interaction.modes.label(this, e, {intersect: true});
9957 },
9958
9959 getElementsAtXAxis: function(e) {
9960 return core_interaction.modes['x-axis'](this, e, {intersect: true});
9961 },
9962
9963 getElementsAtEventForMode: function(e, mode, options) {
9964 var method = core_interaction.modes[mode];
9965 if (typeof method === 'function') {
9966 return method(this, e, options);
9967 }
9968
9969 return [];
9970 },
9971
9972 getDatasetAtEvent: function(e) {
9973 return core_interaction.modes.dataset(this, e, {intersect: true});
9974 },
9975
9976 getDatasetMeta: function(datasetIndex) {
9977 var me = this;
9978 var dataset = me.data.datasets[datasetIndex];
9979 if (!dataset._meta) {
9980 dataset._meta = {};
9981 }
9982
9983 var meta = dataset._meta[me.id];
9984 if (!meta) {
9985 meta = dataset._meta[me.id] = {
9986 type: null,
9987 data: [],
9988 dataset: null,
9989 controller: null,
9990 hidden: null, // See isDatasetVisible() comment
9991 xAxisID: null,
9992 yAxisID: null,
9993 order: dataset.order || 0,
9994 index: datasetIndex
9995 };
9996 }
9997
9998 return meta;
9999 },
10000
10001 getVisibleDatasetCount: function() {
10002 var count = 0;
10003 for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
10004 if (this.isDatasetVisible(i)) {
10005 count++;
10006 }
10007 }
10008 return count;
10009 },
10010
10011 isDatasetVisible: function(datasetIndex) {
10012 var meta = this.getDatasetMeta(datasetIndex);
10013
10014 // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
10015 // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
10016 return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
10017 },
10018
10019 generateLegend: function() {
10020 return this.options.legendCallback(this);
10021 },
10022
10023 /**
10024 * @private
10025 */
10026 destroyDatasetMeta: function(datasetIndex) {
10027 var id = this.id;
10028 var dataset = this.data.datasets[datasetIndex];
10029 var meta = dataset._meta && dataset._meta[id];
10030
10031 if (meta) {
10032 meta.controller.destroy();
10033 delete dataset._meta[id];
10034 }
10035 },
10036
10037 destroy: function() {
10038 var me = this;
10039 var canvas = me.canvas;
10040 var i, ilen;
10041
10042 me.stop();
10043
10044 // dataset controllers need to cleanup associated data
10045 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
10046 me.destroyDatasetMeta(i);
10047 }
10048
10049 if (canvas) {
10050 me.unbindEvents();
10051 helpers$1.canvas.clear(me);
10052 platform.releaseContext(me.ctx);
10053 me.canvas = null;
10054 me.ctx = null;
10055 }
10056
10057 core_plugins.notify(me, 'destroy');
10058
10059 delete Chart.instances[me.id];
10060 },
10061
10062 toBase64Image: function() {
10063 return this.canvas.toDataURL.apply(this.canvas, arguments);
10064 },
10065
10066 initToolTip: function() {
10067 var me = this;
10068 me.tooltip = new core_tooltip({
10069 _chart: me,
10070 _chartInstance: me, // deprecated, backward compatibility
10071 _data: me.data,
10072 _options: me.options.tooltips
10073 }, me);
10074 },
10075
10076 /**
10077 * @private
10078 */
10079 bindEvents: function() {
10080 var me = this;
10081 var listeners = me._listeners = {};
10082 var listener = function() {
10083 me.eventHandler.apply(me, arguments);
10084 };
10085
10086 helpers$1.each(me.options.events, function(type) {
10087 platform.addEventListener(me, type, listener);
10088 listeners[type] = listener;
10089 });
10090
10091 // Elements used to detect size change should not be injected for non responsive charts.
10092 // See https://github.com/chartjs/Chart.js/issues/2210
10093 if (me.options.responsive) {
10094 listener = function() {
10095 me.resize();
10096 };
10097
10098 platform.addEventListener(me, 'resize', listener);
10099 listeners.resize = listener;
10100 }
10101 },
10102
10103 /**
10104 * @private
10105 */
10106 unbindEvents: function() {
10107 var me = this;
10108 var listeners = me._listeners;
10109 if (!listeners) {
10110 return;
10111 }
10112
10113 delete me._listeners;
10114 helpers$1.each(listeners, function(listener, type) {
10115 platform.removeEventListener(me, type, listener);
10116 });
10117 },
10118
10119 updateHoverStyle: function(elements, mode, enabled) {
10120 var prefix = enabled ? 'set' : 'remove';
10121 var element, i, ilen;
10122
10123 for (i = 0, ilen = elements.length; i < ilen; ++i) {
10124 element = elements[i];
10125 if (element) {
10126 this.getDatasetMeta(element._datasetIndex).controller[prefix + 'HoverStyle'](element);
10127 }
10128 }
10129
10130 if (mode === 'dataset') {
10131 this.getDatasetMeta(elements[0]._datasetIndex).controller['_' + prefix + 'DatasetHoverStyle']();
10132 }
10133 },
10134
10135 /**
10136 * @private
10137 */
10138 eventHandler: function(e) {
10139 var me = this;
10140 var tooltip = me.tooltip;
10141
10142 if (core_plugins.notify(me, 'beforeEvent', [e]) === false) {
10143 return;
10144 }
10145
10146 // Buffer any update calls so that renders do not occur
10147 me._bufferedRender = true;
10148 me._bufferedRequest = null;
10149
10150 var changed = me.handleEvent(e);
10151 // for smooth tooltip animations issue #4989
10152 // the tooltip should be the source of change
10153 // Animation check workaround:
10154 // tooltip._start will be null when tooltip isn't animating
10155 if (tooltip) {
10156 changed = tooltip._start
10157 ? tooltip.handleEvent(e)
10158 : changed | tooltip.handleEvent(e);
10159 }
10160
10161 core_plugins.notify(me, 'afterEvent', [e]);
10162
10163 var bufferedRequest = me._bufferedRequest;
10164 if (bufferedRequest) {
10165 // If we have an update that was triggered, we need to do a normal render
10166 me.render(bufferedRequest);
10167 } else if (changed && !me.animating) {
10168 // If entering, leaving, or changing elements, animate the change via pivot
10169 me.stop();
10170
10171 // We only need to render at this point. Updating will cause scales to be
10172 // recomputed generating flicker & using more memory than necessary.
10173 me.render({
10174 duration: me.options.hover.animationDuration,
10175 lazy: true
10176 });
10177 }
10178
10179 me._bufferedRender = false;
10180 me._bufferedRequest = null;
10181
10182 return me;
10183 },
10184
10185 /**
10186 * Handle an event
10187 * @private
10188 * @param {IEvent} event the event to handle
10189 * @return {boolean} true if the chart needs to re-render
10190 */
10191 handleEvent: function(e) {
10192 var me = this;
10193 var options = me.options || {};
10194 var hoverOptions = options.hover;
10195 var changed = false;
10196
10197 me.lastActive = me.lastActive || [];
10198
10199 // Find Active Elements for hover and tooltips
10200 if (e.type === 'mouseout') {
10201 me.active = [];
10202 } else {
10203 me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
10204 }
10205
10206 // Invoke onHover hook
10207 // Need to call with native event here to not break backwards compatibility
10208 helpers$1.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
10209
10210 if (e.type === 'mouseup' || e.type === 'click') {
10211 if (options.onClick) {
10212 // Use e.native here for backwards compatibility
10213 options.onClick.call(me, e.native, me.active);
10214 }
10215 }
10216
10217 // Remove styling for last active (even if it may still be active)
10218 if (me.lastActive.length) {
10219 me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
10220 }
10221
10222 // Built in hover styling
10223 if (me.active.length && hoverOptions.mode) {
10224 me.updateHoverStyle(me.active, hoverOptions.mode, true);
10225 }
10226
10227 changed = !helpers$1.arrayEquals(me.active, me.lastActive);
10228
10229 // Remember Last Actives
10230 me.lastActive = me.active;
10231
10232 return changed;
10233 }
10234});
10235
10236/**
10237 * NOTE(SB) We actually don't use this container anymore but we need to keep it
10238 * for backward compatibility. Though, it can still be useful for plugins that
10239 * would need to work on multiple charts?!
10240 */
10241Chart.instances = {};
10242
10243var core_controller = Chart;
10244
10245// DEPRECATIONS
10246
10247/**
10248 * Provided for backward compatibility, use Chart instead.
10249 * @class Chart.Controller
10250 * @deprecated since version 2.6
10251 * @todo remove at version 3
10252 * @private
10253 */
10254Chart.Controller = Chart;
10255
10256/**
10257 * Provided for backward compatibility, not available anymore.
10258 * @namespace Chart
10259 * @deprecated since version 2.8
10260 * @todo remove at version 3
10261 * @private
10262 */
10263Chart.types = {};
10264
10265/**
10266 * Provided for backward compatibility, not available anymore.
10267 * @namespace Chart.helpers.configMerge
10268 * @deprecated since version 2.8.0
10269 * @todo remove at version 3
10270 * @private
10271 */
10272helpers$1.configMerge = mergeConfig;
10273
10274/**
10275 * Provided for backward compatibility, not available anymore.
10276 * @namespace Chart.helpers.scaleMerge
10277 * @deprecated since version 2.8.0
10278 * @todo remove at version 3
10279 * @private
10280 */
10281helpers$1.scaleMerge = mergeScaleConfig;
10282
10283var core_helpers = function() {
10284
10285 // -- Basic js utility methods
10286
10287 helpers$1.where = function(collection, filterCallback) {
10288 if (helpers$1.isArray(collection) && Array.prototype.filter) {
10289 return collection.filter(filterCallback);
10290 }
10291 var filtered = [];
10292
10293 helpers$1.each(collection, function(item) {
10294 if (filterCallback(item)) {
10295 filtered.push(item);
10296 }
10297 });
10298
10299 return filtered;
10300 };
10301 helpers$1.findIndex = Array.prototype.findIndex ?
10302 function(array, callback, scope) {
10303 return array.findIndex(callback, scope);
10304 } :
10305 function(array, callback, scope) {
10306 scope = scope === undefined ? array : scope;
10307 for (var i = 0, ilen = array.length; i < ilen; ++i) {
10308 if (callback.call(scope, array[i], i, array)) {
10309 return i;
10310 }
10311 }
10312 return -1;
10313 };
10314 helpers$1.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
10315 // Default to start of the array
10316 if (helpers$1.isNullOrUndef(startIndex)) {
10317 startIndex = -1;
10318 }
10319 for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
10320 var currentItem = arrayToSearch[i];
10321 if (filterCallback(currentItem)) {
10322 return currentItem;
10323 }
10324 }
10325 };
10326 helpers$1.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
10327 // Default to end of the array
10328 if (helpers$1.isNullOrUndef(startIndex)) {
10329 startIndex = arrayToSearch.length;
10330 }
10331 for (var i = startIndex - 1; i >= 0; i--) {
10332 var currentItem = arrayToSearch[i];
10333 if (filterCallback(currentItem)) {
10334 return currentItem;
10335 }
10336 }
10337 };
10338
10339 // -- Math methods
10340 helpers$1.isNumber = function(n) {
10341 return !isNaN(parseFloat(n)) && isFinite(n);
10342 };
10343 helpers$1.almostEquals = function(x, y, epsilon) {
10344 return Math.abs(x - y) < epsilon;
10345 };
10346 helpers$1.almostWhole = function(x, epsilon) {
10347 var rounded = Math.round(x);
10348 return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x);
10349 };
10350 helpers$1.max = function(array) {
10351 return array.reduce(function(max, value) {
10352 if (!isNaN(value)) {
10353 return Math.max(max, value);
10354 }
10355 return max;
10356 }, Number.NEGATIVE_INFINITY);
10357 };
10358 helpers$1.min = function(array) {
10359 return array.reduce(function(min, value) {
10360 if (!isNaN(value)) {
10361 return Math.min(min, value);
10362 }
10363 return min;
10364 }, Number.POSITIVE_INFINITY);
10365 };
10366 helpers$1.sign = Math.sign ?
10367 function(x) {
10368 return Math.sign(x);
10369 } :
10370 function(x) {
10371 x = +x; // convert to a number
10372 if (x === 0 || isNaN(x)) {
10373 return x;
10374 }
10375 return x > 0 ? 1 : -1;
10376 };
10377 helpers$1.toRadians = function(degrees) {
10378 return degrees * (Math.PI / 180);
10379 };
10380 helpers$1.toDegrees = function(radians) {
10381 return radians * (180 / Math.PI);
10382 };
10383
10384 /**
10385 * Returns the number of decimal places
10386 * i.e. the number of digits after the decimal point, of the value of this Number.
10387 * @param {number} x - A number.
10388 * @returns {number} The number of decimal places.
10389 * @private
10390 */
10391 helpers$1._decimalPlaces = function(x) {
10392 if (!helpers$1.isFinite(x)) {
10393 return;
10394 }
10395 var e = 1;
10396 var p = 0;
10397 while (Math.round(x * e) / e !== x) {
10398 e *= 10;
10399 p++;
10400 }
10401 return p;
10402 };
10403
10404 // Gets the angle from vertical upright to the point about a centre.
10405 helpers$1.getAngleFromPoint = function(centrePoint, anglePoint) {
10406 var distanceFromXCenter = anglePoint.x - centrePoint.x;
10407 var distanceFromYCenter = anglePoint.y - centrePoint.y;
10408 var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
10409
10410 var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
10411
10412 if (angle < (-0.5 * Math.PI)) {
10413 angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
10414 }
10415
10416 return {
10417 angle: angle,
10418 distance: radialDistanceFromCenter
10419 };
10420 };
10421 helpers$1.distanceBetweenPoints = function(pt1, pt2) {
10422 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
10423 };
10424
10425 /**
10426 * Provided for backward compatibility, not available anymore
10427 * @function Chart.helpers.aliasPixel
10428 * @deprecated since version 2.8.0
10429 * @todo remove at version 3
10430 */
10431 helpers$1.aliasPixel = function(pixelWidth) {
10432 return (pixelWidth % 2 === 0) ? 0 : 0.5;
10433 };
10434
10435 /**
10436 * Returns the aligned pixel value to avoid anti-aliasing blur
10437 * @param {Chart} chart - The chart instance.
10438 * @param {number} pixel - A pixel value.
10439 * @param {number} width - The width of the element.
10440 * @returns {number} The aligned pixel value.
10441 * @private
10442 */
10443 helpers$1._alignPixel = function(chart, pixel, width) {
10444 var devicePixelRatio = chart.currentDevicePixelRatio;
10445 var halfWidth = width / 2;
10446 return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
10447 };
10448
10449 helpers$1.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
10450 // Props to Rob Spencer at scaled innovation for his post on splining between points
10451 // http://scaledinnovation.com/analytics/splines/aboutSplines.html
10452
10453 // This function must also respect "skipped" points
10454
10455 var previous = firstPoint.skip ? middlePoint : firstPoint;
10456 var current = middlePoint;
10457 var next = afterPoint.skip ? middlePoint : afterPoint;
10458
10459 var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
10460 var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
10461
10462 var s01 = d01 / (d01 + d12);
10463 var s12 = d12 / (d01 + d12);
10464
10465 // If all points are the same, s01 & s02 will be inf
10466 s01 = isNaN(s01) ? 0 : s01;
10467 s12 = isNaN(s12) ? 0 : s12;
10468
10469 var fa = t * s01; // scaling factor for triangle Ta
10470 var fb = t * s12;
10471
10472 return {
10473 previous: {
10474 x: current.x - fa * (next.x - previous.x),
10475 y: current.y - fa * (next.y - previous.y)
10476 },
10477 next: {
10478 x: current.x + fb * (next.x - previous.x),
10479 y: current.y + fb * (next.y - previous.y)
10480 }
10481 };
10482 };
10483 helpers$1.EPSILON = Number.EPSILON || 1e-14;
10484 helpers$1.splineCurveMonotone = function(points) {
10485 // This function calculates Bézier control points in a similar way than |splineCurve|,
10486 // but preserves monotonicity of the provided data and ensures no local extremums are added
10487 // between the dataset discrete points due to the interpolation.
10488 // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
10489
10490 var pointsWithTangents = (points || []).map(function(point) {
10491 return {
10492 model: point._model,
10493 deltaK: 0,
10494 mK: 0
10495 };
10496 });
10497
10498 // Calculate slopes (deltaK) and initialize tangents (mK)
10499 var pointsLen = pointsWithTangents.length;
10500 var i, pointBefore, pointCurrent, pointAfter;
10501 for (i = 0; i < pointsLen; ++i) {
10502 pointCurrent = pointsWithTangents[i];
10503 if (pointCurrent.model.skip) {
10504 continue;
10505 }
10506
10507 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
10508 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
10509 if (pointAfter && !pointAfter.model.skip) {
10510 var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
10511
10512 // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
10513 pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
10514 }
10515
10516 if (!pointBefore || pointBefore.model.skip) {
10517 pointCurrent.mK = pointCurrent.deltaK;
10518 } else if (!pointAfter || pointAfter.model.skip) {
10519 pointCurrent.mK = pointBefore.deltaK;
10520 } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
10521 pointCurrent.mK = 0;
10522 } else {
10523 pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
10524 }
10525 }
10526
10527 // Adjust tangents to ensure monotonic properties
10528 var alphaK, betaK, tauK, squaredMagnitude;
10529 for (i = 0; i < pointsLen - 1; ++i) {
10530 pointCurrent = pointsWithTangents[i];
10531 pointAfter = pointsWithTangents[i + 1];
10532 if (pointCurrent.model.skip || pointAfter.model.skip) {
10533 continue;
10534 }
10535
10536 if (helpers$1.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
10537 pointCurrent.mK = pointAfter.mK = 0;
10538 continue;
10539 }
10540
10541 alphaK = pointCurrent.mK / pointCurrent.deltaK;
10542 betaK = pointAfter.mK / pointCurrent.deltaK;
10543 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
10544 if (squaredMagnitude <= 9) {
10545 continue;
10546 }
10547
10548 tauK = 3 / Math.sqrt(squaredMagnitude);
10549 pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
10550 pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
10551 }
10552
10553 // Compute control points
10554 var deltaX;
10555 for (i = 0; i < pointsLen; ++i) {
10556 pointCurrent = pointsWithTangents[i];
10557 if (pointCurrent.model.skip) {
10558 continue;
10559 }
10560
10561 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
10562 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
10563 if (pointBefore && !pointBefore.model.skip) {
10564 deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
10565 pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
10566 pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
10567 }
10568 if (pointAfter && !pointAfter.model.skip) {
10569 deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
10570 pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
10571 pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
10572 }
10573 }
10574 };
10575 helpers$1.nextItem = function(collection, index, loop) {
10576 if (loop) {
10577 return index >= collection.length - 1 ? collection[0] : collection[index + 1];
10578 }
10579 return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
10580 };
10581 helpers$1.previousItem = function(collection, index, loop) {
10582 if (loop) {
10583 return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
10584 }
10585 return index <= 0 ? collection[0] : collection[index - 1];
10586 };
10587 // Implementation of the nice number algorithm used in determining where axis labels will go
10588 helpers$1.niceNum = function(range, round) {
10589 var exponent = Math.floor(helpers$1.log10(range));
10590 var fraction = range / Math.pow(10, exponent);
10591 var niceFraction;
10592
10593 if (round) {
10594 if (fraction < 1.5) {
10595 niceFraction = 1;
10596 } else if (fraction < 3) {
10597 niceFraction = 2;
10598 } else if (fraction < 7) {
10599 niceFraction = 5;
10600 } else {
10601 niceFraction = 10;
10602 }
10603 } else if (fraction <= 1.0) {
10604 niceFraction = 1;
10605 } else if (fraction <= 2) {
10606 niceFraction = 2;
10607 } else if (fraction <= 5) {
10608 niceFraction = 5;
10609 } else {
10610 niceFraction = 10;
10611 }
10612
10613 return niceFraction * Math.pow(10, exponent);
10614 };
10615 // Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
10616 helpers$1.requestAnimFrame = (function() {
10617 if (typeof window === 'undefined') {
10618 return function(callback) {
10619 callback();
10620 };
10621 }
10622 return window.requestAnimationFrame ||
10623 window.webkitRequestAnimationFrame ||
10624 window.mozRequestAnimationFrame ||
10625 window.oRequestAnimationFrame ||
10626 window.msRequestAnimationFrame ||
10627 function(callback) {
10628 return window.setTimeout(callback, 1000 / 60);
10629 };
10630 }());
10631 // -- DOM methods
10632 helpers$1.getRelativePosition = function(evt, chart) {
10633 var mouseX, mouseY;
10634 var e = evt.originalEvent || evt;
10635 var canvas = evt.target || evt.srcElement;
10636 var boundingRect = canvas.getBoundingClientRect();
10637
10638 var touches = e.touches;
10639 if (touches && touches.length > 0) {
10640 mouseX = touches[0].clientX;
10641 mouseY = touches[0].clientY;
10642
10643 } else {
10644 mouseX = e.clientX;
10645 mouseY = e.clientY;
10646 }
10647
10648 // Scale mouse coordinates into canvas coordinates
10649 // by following the pattern laid out by 'jerryj' in the comments of
10650 // https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
10651 var paddingLeft = parseFloat(helpers$1.getStyle(canvas, 'padding-left'));
10652 var paddingTop = parseFloat(helpers$1.getStyle(canvas, 'padding-top'));
10653 var paddingRight = parseFloat(helpers$1.getStyle(canvas, 'padding-right'));
10654 var paddingBottom = parseFloat(helpers$1.getStyle(canvas, 'padding-bottom'));
10655 var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
10656 var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
10657
10658 // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
10659 // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
10660 mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
10661 mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
10662
10663 return {
10664 x: mouseX,
10665 y: mouseY
10666 };
10667
10668 };
10669
10670 // Private helper function to convert max-width/max-height values that may be percentages into a number
10671 function parseMaxStyle(styleValue, node, parentProperty) {
10672 var valueInPixels;
10673 if (typeof styleValue === 'string') {
10674 valueInPixels = parseInt(styleValue, 10);
10675
10676 if (styleValue.indexOf('%') !== -1) {
10677 // percentage * size in dimension
10678 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
10679 }
10680 } else {
10681 valueInPixels = styleValue;
10682 }
10683
10684 return valueInPixels;
10685 }
10686
10687 /**
10688 * Returns if the given value contains an effective constraint.
10689 * @private
10690 */
10691 function isConstrainedValue(value) {
10692 return value !== undefined && value !== null && value !== 'none';
10693 }
10694
10695 /**
10696 * Returns the max width or height of the given DOM node in a cross-browser compatible fashion
10697 * @param {HTMLElement} domNode - the node to check the constraint on
10698 * @param {string} maxStyle - the style that defines the maximum for the direction we are using ('max-width' / 'max-height')
10699 * @param {string} percentageProperty - property of parent to use when calculating width as a percentage
10700 * @see {@link https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser}
10701 */
10702 function getConstraintDimension(domNode, maxStyle, percentageProperty) {
10703 var view = document.defaultView;
10704 var parentNode = helpers$1._getParentNode(domNode);
10705 var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
10706 var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
10707 var hasCNode = isConstrainedValue(constrainedNode);
10708 var hasCContainer = isConstrainedValue(constrainedContainer);
10709 var infinity = Number.POSITIVE_INFINITY;
10710
10711 if (hasCNode || hasCContainer) {
10712 return Math.min(
10713 hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
10714 hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
10715 }
10716
10717 return 'none';
10718 }
10719 // returns Number or undefined if no constraint
10720 helpers$1.getConstraintWidth = function(domNode) {
10721 return getConstraintDimension(domNode, 'max-width', 'clientWidth');
10722 };
10723 // returns Number or undefined if no constraint
10724 helpers$1.getConstraintHeight = function(domNode) {
10725 return getConstraintDimension(domNode, 'max-height', 'clientHeight');
10726 };
10727 /**
10728 * @private
10729 */
10730 helpers$1._calculatePadding = function(container, padding, parentDimension) {
10731 padding = helpers$1.getStyle(container, padding);
10732
10733 return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10);
10734 };
10735 /**
10736 * @private
10737 */
10738 helpers$1._getParentNode = function(domNode) {
10739 var parent = domNode.parentNode;
10740 if (parent && parent.toString() === '[object ShadowRoot]') {
10741 parent = parent.host;
10742 }
10743 return parent;
10744 };
10745 helpers$1.getMaximumWidth = function(domNode) {
10746 var container = helpers$1._getParentNode(domNode);
10747 if (!container) {
10748 return domNode.clientWidth;
10749 }
10750
10751 var clientWidth = container.clientWidth;
10752 var paddingLeft = helpers$1._calculatePadding(container, 'padding-left', clientWidth);
10753 var paddingRight = helpers$1._calculatePadding(container, 'padding-right', clientWidth);
10754
10755 var w = clientWidth - paddingLeft - paddingRight;
10756 var cw = helpers$1.getConstraintWidth(domNode);
10757 return isNaN(cw) ? w : Math.min(w, cw);
10758 };
10759 helpers$1.getMaximumHeight = function(domNode) {
10760 var container = helpers$1._getParentNode(domNode);
10761 if (!container) {
10762 return domNode.clientHeight;
10763 }
10764
10765 var clientHeight = container.clientHeight;
10766 var paddingTop = helpers$1._calculatePadding(container, 'padding-top', clientHeight);
10767 var paddingBottom = helpers$1._calculatePadding(container, 'padding-bottom', clientHeight);
10768
10769 var h = clientHeight - paddingTop - paddingBottom;
10770 var ch = helpers$1.getConstraintHeight(domNode);
10771 return isNaN(ch) ? h : Math.min(h, ch);
10772 };
10773 helpers$1.getStyle = function(el, property) {
10774 return el.currentStyle ?
10775 el.currentStyle[property] :
10776 document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
10777 };
10778 helpers$1.retinaScale = function(chart, forceRatio) {
10779 var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1;
10780 if (pixelRatio === 1) {
10781 return;
10782 }
10783
10784 var canvas = chart.canvas;
10785 var height = chart.height;
10786 var width = chart.width;
10787
10788 canvas.height = height * pixelRatio;
10789 canvas.width = width * pixelRatio;
10790 chart.ctx.scale(pixelRatio, pixelRatio);
10791
10792 // If no style has been set on the canvas, the render size is used as display size,
10793 // making the chart visually bigger, so let's enforce it to the "correct" values.
10794 // See https://github.com/chartjs/Chart.js/issues/3575
10795 if (!canvas.style.height && !canvas.style.width) {
10796 canvas.style.height = height + 'px';
10797 canvas.style.width = width + 'px';
10798 }
10799 };
10800 // -- Canvas methods
10801 helpers$1.fontString = function(pixelSize, fontStyle, fontFamily) {
10802 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
10803 };
10804 helpers$1.longestText = function(ctx, font, arrayOfThings, cache) {
10805 cache = cache || {};
10806 var data = cache.data = cache.data || {};
10807 var gc = cache.garbageCollect = cache.garbageCollect || [];
10808
10809 if (cache.font !== font) {
10810 data = cache.data = {};
10811 gc = cache.garbageCollect = [];
10812 cache.font = font;
10813 }
10814
10815 ctx.font = font;
10816 var longest = 0;
10817 var ilen = arrayOfThings.length;
10818 var i, j, jlen, thing, nestedThing;
10819 for (i = 0; i < ilen; i++) {
10820 thing = arrayOfThings[i];
10821
10822 // Undefined strings and arrays should not be measured
10823 if (thing !== undefined && thing !== null && helpers$1.isArray(thing) !== true) {
10824 longest = helpers$1.measureText(ctx, data, gc, longest, thing);
10825 } else if (helpers$1.isArray(thing)) {
10826 // if it is an array lets measure each element
10827 // to do maybe simplify this function a bit so we can do this more recursively?
10828 for (j = 0, jlen = thing.length; j < jlen; j++) {
10829 nestedThing = thing[j];
10830 // Undefined strings and arrays should not be measured
10831 if (nestedThing !== undefined && nestedThing !== null && !helpers$1.isArray(nestedThing)) {
10832 longest = helpers$1.measureText(ctx, data, gc, longest, nestedThing);
10833 }
10834 }
10835 }
10836 }
10837
10838 var gcLen = gc.length / 2;
10839 if (gcLen > arrayOfThings.length) {
10840 for (i = 0; i < gcLen; i++) {
10841 delete data[gc[i]];
10842 }
10843 gc.splice(0, gcLen);
10844 }
10845 return longest;
10846 };
10847 helpers$1.measureText = function(ctx, data, gc, longest, string) {
10848 var textWidth = data[string];
10849 if (!textWidth) {
10850 textWidth = data[string] = ctx.measureText(string).width;
10851 gc.push(string);
10852 }
10853 if (textWidth > longest) {
10854 longest = textWidth;
10855 }
10856 return longest;
10857 };
10858
10859 /**
10860 * @deprecated
10861 */
10862 helpers$1.numberOfLabelLines = function(arrayOfThings) {
10863 var numberOfLines = 1;
10864 helpers$1.each(arrayOfThings, function(thing) {
10865 if (helpers$1.isArray(thing)) {
10866 if (thing.length > numberOfLines) {
10867 numberOfLines = thing.length;
10868 }
10869 }
10870 });
10871 return numberOfLines;
10872 };
10873
10874 helpers$1.color = !chartjsColor ?
10875 function(value) {
10876 console.error('Color.js not found!');
10877 return value;
10878 } :
10879 function(value) {
10880 /* global CanvasGradient */
10881 if (value instanceof CanvasGradient) {
10882 value = core_defaults.global.defaultColor;
10883 }
10884
10885 return chartjsColor(value);
10886 };
10887
10888 helpers$1.getHoverColor = function(colorValue) {
10889 /* global CanvasPattern */
10890 return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ?
10891 colorValue :
10892 helpers$1.color(colorValue).saturate(0.5).darken(0.1).rgbString();
10893 };
10894};
10895
10896function abstract() {
10897 throw new Error(
10898 'This method is not implemented: either no adapter can ' +
10899 'be found or an incomplete integration was provided.'
10900 );
10901}
10902
10903/**
10904 * Date adapter (current used by the time scale)
10905 * @namespace Chart._adapters._date
10906 * @memberof Chart._adapters
10907 * @private
10908 */
10909
10910/**
10911 * Currently supported unit string values.
10912 * @typedef {('millisecond'|'second'|'minute'|'hour'|'day'|'week'|'month'|'quarter'|'year')}
10913 * @memberof Chart._adapters._date
10914 * @name Unit
10915 */
10916
10917/**
10918 * @class
10919 */
10920function DateAdapter(options) {
10921 this.options = options || {};
10922}
10923
10924helpers$1.extend(DateAdapter.prototype, /** @lends DateAdapter */ {
10925 /**
10926 * Returns a map of time formats for the supported formatting units defined
10927 * in Unit as well as 'datetime' representing a detailed date/time string.
10928 * @returns {{string: string}}
10929 */
10930 formats: abstract,
10931
10932 /**
10933 * Parses the given `value` and return the associated timestamp.
10934 * @param {any} value - the value to parse (usually comes from the data)
10935 * @param {string} [format] - the expected data format
10936 * @returns {(number|null)}
10937 * @function
10938 */
10939 parse: abstract,
10940
10941 /**
10942 * Returns the formatted date in the specified `format` for a given `timestamp`.
10943 * @param {number} timestamp - the timestamp to format
10944 * @param {string} format - the date/time token
10945 * @return {string}
10946 * @function
10947 */
10948 format: abstract,
10949
10950 /**
10951 * Adds the specified `amount` of `unit` to the given `timestamp`.
10952 * @param {number} timestamp - the input timestamp
10953 * @param {number} amount - the amount to add
10954 * @param {Unit} unit - the unit as string
10955 * @return {number}
10956 * @function
10957 */
10958 add: abstract,
10959
10960 /**
10961 * Returns the number of `unit` between the given timestamps.
10962 * @param {number} max - the input timestamp (reference)
10963 * @param {number} min - the timestamp to substract
10964 * @param {Unit} unit - the unit as string
10965 * @return {number}
10966 * @function
10967 */
10968 diff: abstract,
10969
10970 /**
10971 * Returns start of `unit` for the given `timestamp`.
10972 * @param {number} timestamp - the input timestamp
10973 * @param {Unit} unit - the unit as string
10974 * @param {number} [weekday] - the ISO day of the week with 1 being Monday
10975 * and 7 being Sunday (only needed if param *unit* is `isoWeek`).
10976 * @function
10977 */
10978 startOf: abstract,
10979
10980 /**
10981 * Returns end of `unit` for the given `timestamp`.
10982 * @param {number} timestamp - the input timestamp
10983 * @param {Unit} unit - the unit as string
10984 * @function
10985 */
10986 endOf: abstract,
10987
10988 // DEPRECATIONS
10989
10990 /**
10991 * Provided for backward compatibility for scale.getValueForPixel(),
10992 * this method should be overridden only by the moment adapter.
10993 * @deprecated since version 2.8.0
10994 * @todo remove at version 3
10995 * @private
10996 */
10997 _create: function(value) {
10998 return value;
10999 }
11000});
11001
11002DateAdapter.override = function(members) {
11003 helpers$1.extend(DateAdapter.prototype, members);
11004};
11005
11006var _date = DateAdapter;
11007
11008var core_adapters = {
11009 _date: _date
11010};
11011
11012/**
11013 * Namespace to hold static tick generation functions
11014 * @namespace Chart.Ticks
11015 */
11016var core_ticks = {
11017 /**
11018 * Namespace to hold formatters for different types of ticks
11019 * @namespace Chart.Ticks.formatters
11020 */
11021 formatters: {
11022 /**
11023 * Formatter for value labels
11024 * @method Chart.Ticks.formatters.values
11025 * @param value the value to display
11026 * @return {string|string[]} the label to display
11027 */
11028 values: function(value) {
11029 return helpers$1.isArray(value) ? value : '' + value;
11030 },
11031
11032 /**
11033 * Formatter for linear numeric ticks
11034 * @method Chart.Ticks.formatters.linear
11035 * @param tickValue {number} the value to be formatted
11036 * @param index {number} the position of the tickValue parameter in the ticks array
11037 * @param ticks {number[]} the list of ticks being converted
11038 * @return {string} string representation of the tickValue parameter
11039 */
11040 linear: function(tickValue, index, ticks) {
11041 // If we have lots of ticks, don't use the ones
11042 var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
11043
11044 // If we have a number like 2.5 as the delta, figure out how many decimal places we need
11045 if (Math.abs(delta) > 1) {
11046 if (tickValue !== Math.floor(tickValue)) {
11047 // not an integer
11048 delta = tickValue - Math.floor(tickValue);
11049 }
11050 }
11051
11052 var logDelta = helpers$1.log10(Math.abs(delta));
11053 var tickString = '';
11054
11055 if (tickValue !== 0) {
11056 var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1]));
11057 if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation
11058 var logTick = helpers$1.log10(Math.abs(tickValue));
11059 var numExponential = Math.floor(logTick) - Math.floor(logDelta);
11060 numExponential = Math.max(Math.min(numExponential, 20), 0);
11061 tickString = tickValue.toExponential(numExponential);
11062 } else {
11063 var numDecimal = -1 * Math.floor(logDelta);
11064 numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
11065 tickString = tickValue.toFixed(numDecimal);
11066 }
11067 } else {
11068 tickString = '0'; // never show decimal places for 0
11069 }
11070
11071 return tickString;
11072 },
11073
11074 logarithmic: function(tickValue, index, ticks) {
11075 var remain = tickValue / (Math.pow(10, Math.floor(helpers$1.log10(tickValue))));
11076
11077 if (tickValue === 0) {
11078 return '0';
11079 } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
11080 return tickValue.toExponential();
11081 }
11082 return '';
11083 }
11084 }
11085};
11086
11087var isArray = helpers$1.isArray;
11088var isNullOrUndef = helpers$1.isNullOrUndef;
11089var valueOrDefault$a = helpers$1.valueOrDefault;
11090var valueAtIndexOrDefault = helpers$1.valueAtIndexOrDefault;
11091
11092core_defaults._set('scale', {
11093 display: true,
11094 position: 'left',
11095 offset: false,
11096
11097 // grid line settings
11098 gridLines: {
11099 display: true,
11100 color: 'rgba(0,0,0,0.1)',
11101 lineWidth: 1,
11102 drawBorder: true,
11103 drawOnChartArea: true,
11104 drawTicks: true,
11105 tickMarkLength: 10,
11106 zeroLineWidth: 1,
11107 zeroLineColor: 'rgba(0,0,0,0.25)',
11108 zeroLineBorderDash: [],
11109 zeroLineBorderDashOffset: 0.0,
11110 offsetGridLines: false,
11111 borderDash: [],
11112 borderDashOffset: 0.0
11113 },
11114
11115 // scale label
11116 scaleLabel: {
11117 // display property
11118 display: false,
11119
11120 // actual label
11121 labelString: '',
11122
11123 // top/bottom padding
11124 padding: {
11125 top: 4,
11126 bottom: 4
11127 }
11128 },
11129
11130 // label settings
11131 ticks: {
11132 beginAtZero: false,
11133 minRotation: 0,
11134 maxRotation: 50,
11135 mirror: false,
11136 padding: 0,
11137 reverse: false,
11138 display: true,
11139 autoSkip: true,
11140 autoSkipPadding: 0,
11141 labelOffset: 0,
11142 // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
11143 callback: core_ticks.formatters.values,
11144 minor: {},
11145 major: {}
11146 }
11147});
11148
11149/** Returns a new array containing numItems from arr */
11150function sample(arr, numItems) {
11151 var result = [];
11152 var increment = arr.length / numItems;
11153 var i = 0;
11154 var len = arr.length;
11155
11156 for (; i < len; i += increment) {
11157 result.push(arr[Math.floor(i)]);
11158 }
11159 return result;
11160}
11161
11162function getPixelForGridLine(scale, index, offsetGridLines) {
11163 var length = scale.getTicks().length;
11164 var validIndex = Math.min(index, length - 1);
11165 var lineValue = scale.getPixelForTick(validIndex);
11166 var start = scale._startPixel;
11167 var end = scale._endPixel;
11168 var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.
11169 var offset;
11170
11171 if (offsetGridLines) {
11172 if (length === 1) {
11173 offset = Math.max(lineValue - start, end - lineValue);
11174 } else if (index === 0) {
11175 offset = (scale.getPixelForTick(1) - lineValue) / 2;
11176 } else {
11177 offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
11178 }
11179 lineValue += validIndex < index ? offset : -offset;
11180
11181 // Return undefined if the pixel is out of the range
11182 if (lineValue < start - epsilon || lineValue > end + epsilon) {
11183 return;
11184 }
11185 }
11186 return lineValue;
11187}
11188
11189function garbageCollect(caches, length) {
11190 helpers$1.each(caches, function(cache) {
11191 var gc = cache.gc;
11192 var gcLen = gc.length / 2;
11193 var i;
11194 if (gcLen > length) {
11195 for (i = 0; i < gcLen; ++i) {
11196 delete cache.data[gc[i]];
11197 }
11198 gc.splice(0, gcLen);
11199 }
11200 });
11201}
11202
11203/**
11204 * Returns {width, height, offset} objects for the first, last, widest, highest tick
11205 * labels where offset indicates the anchor point offset from the top in pixels.
11206 */
11207function computeLabelSizes(ctx, tickFonts, ticks, caches) {
11208 var length = ticks.length;
11209 var widths = [];
11210 var heights = [];
11211 var offsets = [];
11212 var i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel, widest, highest;
11213
11214 for (i = 0; i < length; ++i) {
11215 label = ticks[i].label;
11216 tickFont = ticks[i].major ? tickFonts.major : tickFonts.minor;
11217 ctx.font = fontString = tickFont.string;
11218 cache = caches[fontString] = caches[fontString] || {data: {}, gc: []};
11219 lineHeight = tickFont.lineHeight;
11220 width = height = 0;
11221 // Undefined labels and arrays should not be measured
11222 if (!isNullOrUndef(label) && !isArray(label)) {
11223 width = helpers$1.measureText(ctx, cache.data, cache.gc, width, label);
11224 height = lineHeight;
11225 } else if (isArray(label)) {
11226 // if it is an array let's measure each element
11227 for (j = 0, jlen = label.length; j < jlen; ++j) {
11228 nestedLabel = label[j];
11229 // Undefined labels and arrays should not be measured
11230 if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
11231 width = helpers$1.measureText(ctx, cache.data, cache.gc, width, nestedLabel);
11232 height += lineHeight;
11233 }
11234 }
11235 }
11236 widths.push(width);
11237 heights.push(height);
11238 offsets.push(lineHeight / 2);
11239 }
11240 garbageCollect(caches, length);
11241
11242 widest = widths.indexOf(Math.max.apply(null, widths));
11243 highest = heights.indexOf(Math.max.apply(null, heights));
11244
11245 function valueAt(idx) {
11246 return {
11247 width: widths[idx] || 0,
11248 height: heights[idx] || 0,
11249 offset: offsets[idx] || 0
11250 };
11251 }
11252
11253 return {
11254 first: valueAt(0),
11255 last: valueAt(length - 1),
11256 widest: valueAt(widest),
11257 highest: valueAt(highest)
11258 };
11259}
11260
11261function getTickMarkLength(options) {
11262 return options.drawTicks ? options.tickMarkLength : 0;
11263}
11264
11265function getScaleLabelHeight(options) {
11266 var font, padding;
11267
11268 if (!options.display) {
11269 return 0;
11270 }
11271
11272 font = helpers$1.options._parseFont(options);
11273 padding = helpers$1.options.toPadding(options.padding);
11274
11275 return font.lineHeight + padding.height;
11276}
11277
11278function parseFontOptions(options, nestedOpts) {
11279 return helpers$1.extend(helpers$1.options._parseFont({
11280 fontFamily: valueOrDefault$a(nestedOpts.fontFamily, options.fontFamily),
11281 fontSize: valueOrDefault$a(nestedOpts.fontSize, options.fontSize),
11282 fontStyle: valueOrDefault$a(nestedOpts.fontStyle, options.fontStyle),
11283 lineHeight: valueOrDefault$a(nestedOpts.lineHeight, options.lineHeight)
11284 }), {
11285 color: helpers$1.options.resolve([nestedOpts.fontColor, options.fontColor, core_defaults.global.defaultFontColor])
11286 });
11287}
11288
11289function parseTickFontOptions(options) {
11290 var minor = parseFontOptions(options, options.minor);
11291 var major = options.major.enabled ? parseFontOptions(options, options.major) : minor;
11292
11293 return {minor: minor, major: major};
11294}
11295
11296function nonSkipped(ticksToFilter) {
11297 var filtered = [];
11298 var item, index, len;
11299 for (index = 0, len = ticksToFilter.length; index < len; ++index) {
11300 item = ticksToFilter[index];
11301 if (typeof item._index !== 'undefined') {
11302 filtered.push(item);
11303 }
11304 }
11305 return filtered;
11306}
11307
11308function getEvenSpacing(arr) {
11309 var len = arr.length;
11310 var i, diff;
11311
11312 if (len < 2) {
11313 return false;
11314 }
11315
11316 for (diff = arr[0], i = 1; i < len; ++i) {
11317 if (arr[i] - arr[i - 1] !== diff) {
11318 return false;
11319 }
11320 }
11321 return diff;
11322}
11323
11324function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) {
11325 var evenMajorSpacing = getEvenSpacing(majorIndices);
11326 var spacing = (ticks.length - 1) / ticksLimit;
11327 var factors, factor, i, ilen;
11328
11329 // If the major ticks are evenly spaced apart, place the minor ticks
11330 // so that they divide the major ticks into even chunks
11331 if (!evenMajorSpacing) {
11332 return Math.max(spacing, 1);
11333 }
11334
11335 factors = helpers$1.math._factorize(evenMajorSpacing);
11336 for (i = 0, ilen = factors.length - 1; i < ilen; i++) {
11337 factor = factors[i];
11338 if (factor > spacing) {
11339 return factor;
11340 }
11341 }
11342 return Math.max(spacing, 1);
11343}
11344
11345function getMajorIndices(ticks) {
11346 var result = [];
11347 var i, ilen;
11348 for (i = 0, ilen = ticks.length; i < ilen; i++) {
11349 if (ticks[i].major) {
11350 result.push(i);
11351 }
11352 }
11353 return result;
11354}
11355
11356function skipMajors(ticks, majorIndices, spacing) {
11357 var count = 0;
11358 var next = majorIndices[0];
11359 var i, tick;
11360
11361 spacing = Math.ceil(spacing);
11362 for (i = 0; i < ticks.length; i++) {
11363 tick = ticks[i];
11364 if (i === next) {
11365 tick._index = i;
11366 count++;
11367 next = majorIndices[count * spacing];
11368 } else {
11369 delete tick.label;
11370 }
11371 }
11372}
11373
11374function skip(ticks, spacing, majorStart, majorEnd) {
11375 var start = valueOrDefault$a(majorStart, 0);
11376 var end = Math.min(valueOrDefault$a(majorEnd, ticks.length), ticks.length);
11377 var count = 0;
11378 var length, i, tick, next;
11379
11380 spacing = Math.ceil(spacing);
11381 if (majorEnd) {
11382 length = majorEnd - majorStart;
11383 spacing = length / Math.floor(length / spacing);
11384 }
11385
11386 next = start;
11387
11388 while (next < 0) {
11389 count++;
11390 next = Math.round(start + count * spacing);
11391 }
11392
11393 for (i = Math.max(start, 0); i < end; i++) {
11394 tick = ticks[i];
11395 if (i === next) {
11396 tick._index = i;
11397 count++;
11398 next = Math.round(start + count * spacing);
11399 } else {
11400 delete tick.label;
11401 }
11402 }
11403}
11404
11405var Scale = core_element.extend({
11406
11407 zeroLineIndex: 0,
11408
11409 /**
11410 * Get the padding needed for the scale
11411 * @method getPadding
11412 * @private
11413 * @returns {Padding} the necessary padding
11414 */
11415 getPadding: function() {
11416 var me = this;
11417 return {
11418 left: me.paddingLeft || 0,
11419 top: me.paddingTop || 0,
11420 right: me.paddingRight || 0,
11421 bottom: me.paddingBottom || 0
11422 };
11423 },
11424
11425 /**
11426 * Returns the scale tick objects ({label, major})
11427 * @since 2.7
11428 */
11429 getTicks: function() {
11430 return this._ticks;
11431 },
11432
11433 /**
11434 * @private
11435 */
11436 _getLabels: function() {
11437 var data = this.chart.data;
11438 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
11439 },
11440
11441 // These methods are ordered by lifecyle. Utilities then follow.
11442 // Any function defined here is inherited by all scale types.
11443 // Any function can be extended by the scale type
11444
11445 /**
11446 * Provided for backward compatibility, not available anymore
11447 * @function Chart.Scale.mergeTicksOptions
11448 * @deprecated since version 2.8.0
11449 * @todo remove at version 3
11450 */
11451 mergeTicksOptions: function() {
11452 // noop
11453 },
11454
11455 beforeUpdate: function() {
11456 helpers$1.callback(this.options.beforeUpdate, [this]);
11457 },
11458
11459 /**
11460 * @param {number} maxWidth - the max width in pixels
11461 * @param {number} maxHeight - the max height in pixels
11462 * @param {object} margins - the space between the edge of the other scales and edge of the chart
11463 * This space comes from two sources:
11464 * - padding - space that's required to show the labels at the edges of the scale
11465 * - thickness of scales or legends in another orientation
11466 */
11467 update: function(maxWidth, maxHeight, margins) {
11468 var me = this;
11469 var tickOpts = me.options.ticks;
11470 var sampleSize = tickOpts.sampleSize;
11471 var i, ilen, labels, ticks, samplingEnabled;
11472
11473 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11474 me.beforeUpdate();
11475
11476 // Absorb the master measurements
11477 me.maxWidth = maxWidth;
11478 me.maxHeight = maxHeight;
11479 me.margins = helpers$1.extend({
11480 left: 0,
11481 right: 0,
11482 top: 0,
11483 bottom: 0
11484 }, margins);
11485
11486 me._ticks = null;
11487 me.ticks = null;
11488 me._labelSizes = null;
11489 me._maxLabelLines = 0;
11490 me.longestLabelWidth = 0;
11491 me.longestTextCache = me.longestTextCache || {};
11492 me._gridLineItems = null;
11493 me._labelItems = null;
11494
11495 // Dimensions
11496 me.beforeSetDimensions();
11497 me.setDimensions();
11498 me.afterSetDimensions();
11499
11500 // Data min/max
11501 me.beforeDataLimits();
11502 me.determineDataLimits();
11503 me.afterDataLimits();
11504
11505 // Ticks - `this.ticks` is now DEPRECATED!
11506 // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
11507 // and must not be accessed directly from outside this class. `this.ticks` being
11508 // around for long time and not marked as private, we can't change its structure
11509 // without unexpected breaking changes. If you need to access the scale ticks,
11510 // use scale.getTicks() instead.
11511
11512 me.beforeBuildTicks();
11513
11514 // New implementations should return an array of objects but for BACKWARD COMPAT,
11515 // we still support no return (`this.ticks` internally set by calling this method).
11516 ticks = me.buildTicks() || [];
11517
11518 // Allow modification of ticks in callback.
11519 ticks = me.afterBuildTicks(ticks) || ticks;
11520
11521 // Ensure ticks contains ticks in new tick format
11522 if ((!ticks || !ticks.length) && me.ticks) {
11523 ticks = [];
11524 for (i = 0, ilen = me.ticks.length; i < ilen; ++i) {
11525 ticks.push({
11526 value: me.ticks[i],
11527 major: false
11528 });
11529 }
11530 }
11531
11532 me._ticks = ticks;
11533
11534 // Compute tick rotation and fit using a sampled subset of labels
11535 // We generally don't need to compute the size of every single label for determining scale size
11536 samplingEnabled = sampleSize < ticks.length;
11537 labels = me._convertTicksToLabels(samplingEnabled ? sample(ticks, sampleSize) : ticks);
11538
11539 // _configure is called twice, once here, once from core.controller.updateLayout.
11540 // Here we haven't been positioned yet, but dimensions are correct.
11541 // Variables set in _configure are needed for calculateTickRotation, and
11542 // it's ok that coordinates are not correct there, only dimensions matter.
11543 me._configure();
11544
11545 // Tick Rotation
11546 me.beforeCalculateTickRotation();
11547 me.calculateTickRotation();
11548 me.afterCalculateTickRotation();
11549
11550 me.beforeFit();
11551 me.fit();
11552 me.afterFit();
11553
11554 // Auto-skip
11555 me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks;
11556
11557 if (samplingEnabled) {
11558 // Generate labels using all non-skipped ticks
11559 labels = me._convertTicksToLabels(me._ticksToDraw);
11560 }
11561
11562 me.ticks = labels; // BACKWARD COMPATIBILITY
11563
11564 // IMPORTANT: after this point, we consider that `this.ticks` will NEVER change!
11565
11566 me.afterUpdate();
11567
11568 // TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused
11569 // make maxWidth and maxHeight private
11570 return me.minSize;
11571 },
11572
11573 /**
11574 * @private
11575 */
11576 _configure: function() {
11577 var me = this;
11578 var reversePixels = me.options.ticks.reverse;
11579 var startPixel, endPixel;
11580
11581 if (me.isHorizontal()) {
11582 startPixel = me.left;
11583 endPixel = me.right;
11584 } else {
11585 startPixel = me.top;
11586 endPixel = me.bottom;
11587 // by default vertical scales are from bottom to top, so pixels are reversed
11588 reversePixels = !reversePixels;
11589 }
11590 me._startPixel = startPixel;
11591 me._endPixel = endPixel;
11592 me._reversePixels = reversePixels;
11593 me._length = endPixel - startPixel;
11594 },
11595
11596 afterUpdate: function() {
11597 helpers$1.callback(this.options.afterUpdate, [this]);
11598 },
11599
11600 //
11601
11602 beforeSetDimensions: function() {
11603 helpers$1.callback(this.options.beforeSetDimensions, [this]);
11604 },
11605 setDimensions: function() {
11606 var me = this;
11607 // Set the unconstrained dimension before label rotation
11608 if (me.isHorizontal()) {
11609 // Reset position before calculating rotation
11610 me.width = me.maxWidth;
11611 me.left = 0;
11612 me.right = me.width;
11613 } else {
11614 me.height = me.maxHeight;
11615
11616 // Reset position before calculating rotation
11617 me.top = 0;
11618 me.bottom = me.height;
11619 }
11620
11621 // Reset padding
11622 me.paddingLeft = 0;
11623 me.paddingTop = 0;
11624 me.paddingRight = 0;
11625 me.paddingBottom = 0;
11626 },
11627 afterSetDimensions: function() {
11628 helpers$1.callback(this.options.afterSetDimensions, [this]);
11629 },
11630
11631 // Data limits
11632 beforeDataLimits: function() {
11633 helpers$1.callback(this.options.beforeDataLimits, [this]);
11634 },
11635 determineDataLimits: helpers$1.noop,
11636 afterDataLimits: function() {
11637 helpers$1.callback(this.options.afterDataLimits, [this]);
11638 },
11639
11640 //
11641 beforeBuildTicks: function() {
11642 helpers$1.callback(this.options.beforeBuildTicks, [this]);
11643 },
11644 buildTicks: helpers$1.noop,
11645 afterBuildTicks: function(ticks) {
11646 var me = this;
11647 // ticks is empty for old axis implementations here
11648 if (isArray(ticks) && ticks.length) {
11649 return helpers$1.callback(me.options.afterBuildTicks, [me, ticks]);
11650 }
11651 // Support old implementations (that modified `this.ticks` directly in buildTicks)
11652 me.ticks = helpers$1.callback(me.options.afterBuildTicks, [me, me.ticks]) || me.ticks;
11653 return ticks;
11654 },
11655
11656 beforeTickToLabelConversion: function() {
11657 helpers$1.callback(this.options.beforeTickToLabelConversion, [this]);
11658 },
11659 convertTicksToLabels: function() {
11660 var me = this;
11661 // Convert ticks to strings
11662 var tickOpts = me.options.ticks;
11663 me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
11664 },
11665 afterTickToLabelConversion: function() {
11666 helpers$1.callback(this.options.afterTickToLabelConversion, [this]);
11667 },
11668
11669 //
11670
11671 beforeCalculateTickRotation: function() {
11672 helpers$1.callback(this.options.beforeCalculateTickRotation, [this]);
11673 },
11674 calculateTickRotation: function() {
11675 var me = this;
11676 var options = me.options;
11677 var tickOpts = options.ticks;
11678 var numTicks = me.getTicks().length;
11679 var minRotation = tickOpts.minRotation || 0;
11680 var maxRotation = tickOpts.maxRotation;
11681 var labelRotation = minRotation;
11682 var labelSizes, maxLabelWidth, maxLabelHeight, maxWidth, tickWidth, maxHeight, maxLabelDiagonal;
11683
11684 if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) {
11685 me.labelRotation = minRotation;
11686 return;
11687 }
11688
11689 labelSizes = me._getLabelSizes();
11690 maxLabelWidth = labelSizes.widest.width;
11691 maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset;
11692
11693 // Estimate the width of each grid based on the canvas width, the maximum
11694 // label width and the number of tick intervals
11695 maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth);
11696 tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1);
11697
11698 // Allow 3 pixels x2 padding either side for label readability
11699 if (maxLabelWidth + 6 > tickWidth) {
11700 tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
11701 maxHeight = me.maxHeight - getTickMarkLength(options.gridLines)
11702 - tickOpts.padding - getScaleLabelHeight(options.scaleLabel);
11703 maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
11704 labelRotation = helpers$1.toDegrees(Math.min(
11705 Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)),
11706 Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal)
11707 ));
11708 labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
11709 }
11710
11711 me.labelRotation = labelRotation;
11712 },
11713 afterCalculateTickRotation: function() {
11714 helpers$1.callback(this.options.afterCalculateTickRotation, [this]);
11715 },
11716
11717 //
11718
11719 beforeFit: function() {
11720 helpers$1.callback(this.options.beforeFit, [this]);
11721 },
11722 fit: function() {
11723 var me = this;
11724 // Reset
11725 var minSize = me.minSize = {
11726 width: 0,
11727 height: 0
11728 };
11729
11730 var chart = me.chart;
11731 var opts = me.options;
11732 var tickOpts = opts.ticks;
11733 var scaleLabelOpts = opts.scaleLabel;
11734 var gridLineOpts = opts.gridLines;
11735 var display = me._isVisible();
11736 var isBottom = opts.position === 'bottom';
11737 var isHorizontal = me.isHorizontal();
11738
11739 // Width
11740 if (isHorizontal) {
11741 minSize.width = me.maxWidth;
11742 } else if (display) {
11743 minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts);
11744 }
11745
11746 // height
11747 if (!isHorizontal) {
11748 minSize.height = me.maxHeight; // fill all the height
11749 } else if (display) {
11750 minSize.height = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts);
11751 }
11752
11753 // Don't bother fitting the ticks if we are not showing the labels
11754 if (tickOpts.display && display) {
11755 var tickFonts = parseTickFontOptions(tickOpts);
11756 var labelSizes = me._getLabelSizes();
11757 var firstLabelSize = labelSizes.first;
11758 var lastLabelSize = labelSizes.last;
11759 var widestLabelSize = labelSizes.widest;
11760 var highestLabelSize = labelSizes.highest;
11761 var lineSpace = tickFonts.minor.lineHeight * 0.4;
11762 var tickPadding = tickOpts.padding;
11763
11764 if (isHorizontal) {
11765 // A horizontal axis is more constrained by the height.
11766 var isRotated = me.labelRotation !== 0;
11767 var angleRadians = helpers$1.toRadians(me.labelRotation);
11768 var cosRotation = Math.cos(angleRadians);
11769 var sinRotation = Math.sin(angleRadians);
11770
11771 var labelHeight = sinRotation * widestLabelSize.width
11772 + cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0))
11773 + (isRotated ? 0 : lineSpace); // padding
11774
11775 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
11776
11777 var offsetLeft = me.getPixelForTick(0) - me.left;
11778 var offsetRight = me.right - me.getPixelForTick(me.getTicks().length - 1);
11779 var paddingLeft, paddingRight;
11780
11781 // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
11782 // which means that the right padding is dominated by the font height
11783 if (isRotated) {
11784 paddingLeft = isBottom ?
11785 cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset :
11786 sinRotation * (firstLabelSize.height - firstLabelSize.offset);
11787 paddingRight = isBottom ?
11788 sinRotation * (lastLabelSize.height - lastLabelSize.offset) :
11789 cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset;
11790 } else {
11791 paddingLeft = firstLabelSize.width / 2;
11792 paddingRight = lastLabelSize.width / 2;
11793 }
11794
11795 // Adjust padding taking into account changes in offsets
11796 // and add 3 px to move away from canvas edges
11797 me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3;
11798 me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3;
11799 } else {
11800 // A vertical axis is more constrained by the width. Labels are the
11801 // dominant factor here, so get that length first and account for padding
11802 var labelWidth = tickOpts.mirror ? 0 :
11803 // use lineSpace for consistency with horizontal axis
11804 // tickPadding is not implemented for horizontal
11805 widestLabelSize.width + tickPadding + lineSpace;
11806
11807 minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth);
11808
11809 me.paddingTop = firstLabelSize.height / 2;
11810 me.paddingBottom = lastLabelSize.height / 2;
11811 }
11812 }
11813
11814 me.handleMargins();
11815
11816 if (isHorizontal) {
11817 me.width = me._length = chart.width - me.margins.left - me.margins.right;
11818 me.height = minSize.height;
11819 } else {
11820 me.width = minSize.width;
11821 me.height = me._length = chart.height - me.margins.top - me.margins.bottom;
11822 }
11823 },
11824
11825 /**
11826 * Handle margins and padding interactions
11827 * @private
11828 */
11829 handleMargins: function() {
11830 var me = this;
11831 if (me.margins) {
11832 me.margins.left = Math.max(me.paddingLeft, me.margins.left);
11833 me.margins.top = Math.max(me.paddingTop, me.margins.top);
11834 me.margins.right = Math.max(me.paddingRight, me.margins.right);
11835 me.margins.bottom = Math.max(me.paddingBottom, me.margins.bottom);
11836 }
11837 },
11838
11839 afterFit: function() {
11840 helpers$1.callback(this.options.afterFit, [this]);
11841 },
11842
11843 // Shared Methods
11844 isHorizontal: function() {
11845 var pos = this.options.position;
11846 return pos === 'top' || pos === 'bottom';
11847 },
11848 isFullWidth: function() {
11849 return this.options.fullWidth;
11850 },
11851
11852 // 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
11853 getRightValue: function(rawValue) {
11854 // Null and undefined values first
11855 if (isNullOrUndef(rawValue)) {
11856 return NaN;
11857 }
11858 // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
11859 if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) {
11860 return NaN;
11861 }
11862
11863 // If it is in fact an object, dive in one more level
11864 if (rawValue) {
11865 if (this.isHorizontal()) {
11866 if (rawValue.x !== undefined) {
11867 return this.getRightValue(rawValue.x);
11868 }
11869 } else if (rawValue.y !== undefined) {
11870 return this.getRightValue(rawValue.y);
11871 }
11872 }
11873
11874 // Value is good, return it
11875 return rawValue;
11876 },
11877
11878 _convertTicksToLabels: function(ticks) {
11879 var me = this;
11880 var labels, i, ilen;
11881
11882 me.ticks = ticks.map(function(tick) {
11883 return tick.value;
11884 });
11885
11886 me.beforeTickToLabelConversion();
11887
11888 // New implementations should return the formatted tick labels but for BACKWARD
11889 // COMPAT, we still support no return (`this.ticks` internally changed by calling
11890 // this method and supposed to contain only string values).
11891 labels = me.convertTicksToLabels(ticks) || me.ticks;
11892
11893 me.afterTickToLabelConversion();
11894
11895 // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
11896 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
11897 ticks[i].label = labels[i];
11898 }
11899
11900 return labels;
11901 },
11902
11903 /**
11904 * @private
11905 */
11906 _getLabelSizes: function() {
11907 var me = this;
11908 var labelSizes = me._labelSizes;
11909
11910 if (!labelSizes) {
11911 me._labelSizes = labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(me.options.ticks), me.getTicks(), me.longestTextCache);
11912 me.longestLabelWidth = labelSizes.widest.width;
11913 }
11914
11915 return labelSizes;
11916 },
11917
11918 /**
11919 * @private
11920 */
11921 _parseValue: function(value) {
11922 var start, end, min, max;
11923
11924 if (isArray(value)) {
11925 start = +this.getRightValue(value[0]);
11926 end = +this.getRightValue(value[1]);
11927 min = Math.min(start, end);
11928 max = Math.max(start, end);
11929 } else {
11930 value = +this.getRightValue(value);
11931 start = undefined;
11932 end = value;
11933 min = value;
11934 max = value;
11935 }
11936
11937 return {
11938 min: min,
11939 max: max,
11940 start: start,
11941 end: end
11942 };
11943 },
11944
11945 /**
11946 * @private
11947 */
11948 _getScaleLabel: function(rawValue) {
11949 var v = this._parseValue(rawValue);
11950 if (v.start !== undefined) {
11951 return '[' + v.start + ', ' + v.end + ']';
11952 }
11953
11954 return +this.getRightValue(rawValue);
11955 },
11956
11957 /**
11958 * Used to get the value to display in the tooltip for the data at the given index
11959 * @param index
11960 * @param datasetIndex
11961 */
11962 getLabelForIndex: helpers$1.noop,
11963
11964 /**
11965 * Returns the location of the given data point. Value can either be an index or a numerical value
11966 * The coordinate (0, 0) is at the upper-left corner of the canvas
11967 * @param value
11968 * @param index
11969 * @param datasetIndex
11970 */
11971 getPixelForValue: helpers$1.noop,
11972
11973 /**
11974 * Used to get the data value from a given pixel. This is the inverse of getPixelForValue
11975 * The coordinate (0, 0) is at the upper-left corner of the canvas
11976 * @param pixel
11977 */
11978 getValueForPixel: helpers$1.noop,
11979
11980 /**
11981 * Returns the location of the tick at the given index
11982 * The coordinate (0, 0) is at the upper-left corner of the canvas
11983 */
11984 getPixelForTick: function(index) {
11985 var me = this;
11986 var offset = me.options.offset;
11987 var numTicks = me._ticks.length;
11988 var tickWidth = 1 / Math.max(numTicks - (offset ? 0 : 1), 1);
11989
11990 return index < 0 || index > numTicks - 1
11991 ? null
11992 : me.getPixelForDecimal(index * tickWidth + (offset ? tickWidth / 2 : 0));
11993 },
11994
11995 /**
11996 * Utility for getting the pixel location of a percentage of scale
11997 * The coordinate (0, 0) is at the upper-left corner of the canvas
11998 */
11999 getPixelForDecimal: function(decimal) {
12000 var me = this;
12001
12002 if (me._reversePixels) {
12003 decimal = 1 - decimal;
12004 }
12005
12006 return me._startPixel + decimal * me._length;
12007 },
12008
12009 getDecimalForPixel: function(pixel) {
12010 var decimal = (pixel - this._startPixel) / this._length;
12011 return this._reversePixels ? 1 - decimal : decimal;
12012 },
12013
12014 /**
12015 * Returns the pixel for the minimum chart value
12016 * The coordinate (0, 0) is at the upper-left corner of the canvas
12017 */
12018 getBasePixel: function() {
12019 return this.getPixelForValue(this.getBaseValue());
12020 },
12021
12022 getBaseValue: function() {
12023 var me = this;
12024 var min = me.min;
12025 var max = me.max;
12026
12027 return me.beginAtZero ? 0 :
12028 min < 0 && max < 0 ? max :
12029 min > 0 && max > 0 ? min :
12030 0;
12031 },
12032
12033 /**
12034 * Returns a subset of ticks to be plotted to avoid overlapping labels.
12035 * @private
12036 */
12037 _autoSkip: function(ticks) {
12038 var me = this;
12039 var tickOpts = me.options.ticks;
12040 var axisLength = me._length;
12041 var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1;
12042 var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
12043 var numMajorIndices = majorIndices.length;
12044 var first = majorIndices[0];
12045 var last = majorIndices[numMajorIndices - 1];
12046 var i, ilen, spacing, avgMajorSpacing;
12047
12048 // If there are too many major ticks to display them all
12049 if (numMajorIndices > ticksLimit) {
12050 skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit);
12051 return nonSkipped(ticks);
12052 }
12053
12054 spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit);
12055
12056 if (numMajorIndices > 0) {
12057 for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
12058 skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]);
12059 }
12060 avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null;
12061 skip(ticks, spacing, helpers$1.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
12062 skip(ticks, spacing, last, helpers$1.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
12063 return nonSkipped(ticks);
12064 }
12065 skip(ticks, spacing);
12066 return nonSkipped(ticks);
12067 },
12068
12069 /**
12070 * @private
12071 */
12072 _tickSize: function() {
12073 var me = this;
12074 var optionTicks = me.options.ticks;
12075
12076 // Calculate space needed by label in axis direction.
12077 var rot = helpers$1.toRadians(me.labelRotation);
12078 var cos = Math.abs(Math.cos(rot));
12079 var sin = Math.abs(Math.sin(rot));
12080
12081 var labelSizes = me._getLabelSizes();
12082 var padding = optionTicks.autoSkipPadding || 0;
12083 var w = labelSizes ? labelSizes.widest.width + padding : 0;
12084 var h = labelSizes ? labelSizes.highest.height + padding : 0;
12085
12086 // Calculate space needed for 1 tick in axis direction.
12087 return me.isHorizontal()
12088 ? h * cos > w * sin ? w / cos : h / sin
12089 : h * sin < w * cos ? h / cos : w / sin;
12090 },
12091
12092 /**
12093 * @private
12094 */
12095 _isVisible: function() {
12096 var me = this;
12097 var chart = me.chart;
12098 var display = me.options.display;
12099 var i, ilen, meta;
12100
12101 if (display !== 'auto') {
12102 return !!display;
12103 }
12104
12105 // When 'auto', the scale is visible if at least one associated dataset is visible.
12106 for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
12107 if (chart.isDatasetVisible(i)) {
12108 meta = chart.getDatasetMeta(i);
12109 if (meta.xAxisID === me.id || meta.yAxisID === me.id) {
12110 return true;
12111 }
12112 }
12113 }
12114
12115 return false;
12116 },
12117
12118 /**
12119 * @private
12120 */
12121 _computeGridLineItems: function(chartArea) {
12122 var me = this;
12123 var chart = me.chart;
12124 var options = me.options;
12125 var gridLines = options.gridLines;
12126 var position = options.position;
12127 var offsetGridLines = gridLines.offsetGridLines;
12128 var isHorizontal = me.isHorizontal();
12129 var ticks = me._ticksToDraw;
12130 var ticksLength = ticks.length + (offsetGridLines ? 1 : 0);
12131
12132 var tl = getTickMarkLength(gridLines);
12133 var items = [];
12134 var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
12135 var axisHalfWidth = axisWidth / 2;
12136 var alignPixel = helpers$1._alignPixel;
12137 var alignBorderValue = function(pixel) {
12138 return alignPixel(chart, pixel, axisWidth);
12139 };
12140 var borderValue, i, tick, lineValue, alignedLineValue;
12141 var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset;
12142
12143 if (position === 'top') {
12144 borderValue = alignBorderValue(me.bottom);
12145 ty1 = me.bottom - tl;
12146 ty2 = borderValue - axisHalfWidth;
12147 y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
12148 y2 = chartArea.bottom;
12149 } else if (position === 'bottom') {
12150 borderValue = alignBorderValue(me.top);
12151 y1 = chartArea.top;
12152 y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
12153 ty1 = borderValue + axisHalfWidth;
12154 ty2 = me.top + tl;
12155 } else if (position === 'left') {
12156 borderValue = alignBorderValue(me.right);
12157 tx1 = me.right - tl;
12158 tx2 = borderValue - axisHalfWidth;
12159 x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
12160 x2 = chartArea.right;
12161 } else {
12162 borderValue = alignBorderValue(me.left);
12163 x1 = chartArea.left;
12164 x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
12165 tx1 = borderValue + axisHalfWidth;
12166 tx2 = me.left + tl;
12167 }
12168
12169 for (i = 0; i < ticksLength; ++i) {
12170 tick = ticks[i] || {};
12171
12172 // autoskipper skipped this tick (#4635)
12173 if (isNullOrUndef(tick.label) && i < ticks.length) {
12174 continue;
12175 }
12176
12177 if (i === me.zeroLineIndex && options.offset === offsetGridLines) {
12178 // Draw the first index specially
12179 lineWidth = gridLines.zeroLineWidth;
12180 lineColor = gridLines.zeroLineColor;
12181 borderDash = gridLines.zeroLineBorderDash || [];
12182 borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0;
12183 } else {
12184 lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, i, 1);
12185 lineColor = valueAtIndexOrDefault(gridLines.color, i, 'rgba(0,0,0,0.1)');
12186 borderDash = gridLines.borderDash || [];
12187 borderDashOffset = gridLines.borderDashOffset || 0.0;
12188 }
12189
12190 lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines);
12191
12192 // Skip if the pixel is out of the range
12193 if (lineValue === undefined) {
12194 continue;
12195 }
12196
12197 alignedLineValue = alignPixel(chart, lineValue, lineWidth);
12198
12199 if (isHorizontal) {
12200 tx1 = tx2 = x1 = x2 = alignedLineValue;
12201 } else {
12202 ty1 = ty2 = y1 = y2 = alignedLineValue;
12203 }
12204
12205 items.push({
12206 tx1: tx1,
12207 ty1: ty1,
12208 tx2: tx2,
12209 ty2: ty2,
12210 x1: x1,
12211 y1: y1,
12212 x2: x2,
12213 y2: y2,
12214 width: lineWidth,
12215 color: lineColor,
12216 borderDash: borderDash,
12217 borderDashOffset: borderDashOffset,
12218 });
12219 }
12220
12221 items.ticksLength = ticksLength;
12222 items.borderValue = borderValue;
12223
12224 return items;
12225 },
12226
12227 /**
12228 * @private
12229 */
12230 _computeLabelItems: function() {
12231 var me = this;
12232 var options = me.options;
12233 var optionTicks = options.ticks;
12234 var position = options.position;
12235 var isMirrored = optionTicks.mirror;
12236 var isHorizontal = me.isHorizontal();
12237 var ticks = me._ticksToDraw;
12238 var fonts = parseTickFontOptions(optionTicks);
12239 var tickPadding = optionTicks.padding;
12240 var tl = getTickMarkLength(options.gridLines);
12241 var rotation = -helpers$1.toRadians(me.labelRotation);
12242 var items = [];
12243 var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
12244
12245 if (position === 'top') {
12246 y = me.bottom - tl - tickPadding;
12247 textAlign = !rotation ? 'center' : 'left';
12248 } else if (position === 'bottom') {
12249 y = me.top + tl + tickPadding;
12250 textAlign = !rotation ? 'center' : 'right';
12251 } else if (position === 'left') {
12252 x = me.right - (isMirrored ? 0 : tl) - tickPadding;
12253 textAlign = isMirrored ? 'left' : 'right';
12254 } else {
12255 x = me.left + (isMirrored ? 0 : tl) + tickPadding;
12256 textAlign = isMirrored ? 'right' : 'left';
12257 }
12258
12259 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
12260 tick = ticks[i];
12261 label = tick.label;
12262
12263 // autoskipper skipped this tick (#4635)
12264 if (isNullOrUndef(label)) {
12265 continue;
12266 }
12267
12268 pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset;
12269 font = tick.major ? fonts.major : fonts.minor;
12270 lineHeight = font.lineHeight;
12271 lineCount = isArray(label) ? label.length : 1;
12272
12273 if (isHorizontal) {
12274 x = pixel;
12275 textOffset = position === 'top'
12276 ? ((!rotation ? 0.5 : 1) - lineCount) * lineHeight
12277 : (!rotation ? 0.5 : 0) * lineHeight;
12278 } else {
12279 y = pixel;
12280 textOffset = (1 - lineCount) * lineHeight / 2;
12281 }
12282
12283 items.push({
12284 x: x,
12285 y: y,
12286 rotation: rotation,
12287 label: label,
12288 font: font,
12289 textOffset: textOffset,
12290 textAlign: textAlign
12291 });
12292 }
12293
12294 return items;
12295 },
12296
12297 /**
12298 * @private
12299 */
12300 _drawGrid: function(chartArea) {
12301 var me = this;
12302 var gridLines = me.options.gridLines;
12303
12304 if (!gridLines.display) {
12305 return;
12306 }
12307
12308 var ctx = me.ctx;
12309 var chart = me.chart;
12310 var alignPixel = helpers$1._alignPixel;
12311 var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
12312 var items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea));
12313 var width, color, i, ilen, item;
12314
12315 for (i = 0, ilen = items.length; i < ilen; ++i) {
12316 item = items[i];
12317 width = item.width;
12318 color = item.color;
12319
12320 if (width && color) {
12321 ctx.save();
12322 ctx.lineWidth = width;
12323 ctx.strokeStyle = color;
12324 if (ctx.setLineDash) {
12325 ctx.setLineDash(item.borderDash);
12326 ctx.lineDashOffset = item.borderDashOffset;
12327 }
12328
12329 ctx.beginPath();
12330
12331 if (gridLines.drawTicks) {
12332 ctx.moveTo(item.tx1, item.ty1);
12333 ctx.lineTo(item.tx2, item.ty2);
12334 }
12335
12336 if (gridLines.drawOnChartArea) {
12337 ctx.moveTo(item.x1, item.y1);
12338 ctx.lineTo(item.x2, item.y2);
12339 }
12340
12341 ctx.stroke();
12342 ctx.restore();
12343 }
12344 }
12345
12346 if (axisWidth) {
12347 // Draw the line at the edge of the axis
12348 var firstLineWidth = axisWidth;
12349 var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, items.ticksLength - 1, 1);
12350 var borderValue = items.borderValue;
12351 var x1, x2, y1, y2;
12352
12353 if (me.isHorizontal()) {
12354 x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2;
12355 x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2;
12356 y1 = y2 = borderValue;
12357 } else {
12358 y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2;
12359 y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2;
12360 x1 = x2 = borderValue;
12361 }
12362
12363 ctx.lineWidth = axisWidth;
12364 ctx.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0);
12365 ctx.beginPath();
12366 ctx.moveTo(x1, y1);
12367 ctx.lineTo(x2, y2);
12368 ctx.stroke();
12369 }
12370 },
12371
12372 /**
12373 * @private
12374 */
12375 _drawLabels: function() {
12376 var me = this;
12377 var optionTicks = me.options.ticks;
12378
12379 if (!optionTicks.display) {
12380 return;
12381 }
12382
12383 var ctx = me.ctx;
12384 var items = me._labelItems || (me._labelItems = me._computeLabelItems());
12385 var i, j, ilen, jlen, item, tickFont, label, y;
12386
12387 for (i = 0, ilen = items.length; i < ilen; ++i) {
12388 item = items[i];
12389 tickFont = item.font;
12390
12391 // Make sure we draw text in the correct color and font
12392 ctx.save();
12393 ctx.translate(item.x, item.y);
12394 ctx.rotate(item.rotation);
12395 ctx.font = tickFont.string;
12396 ctx.fillStyle = tickFont.color;
12397 ctx.textBaseline = 'middle';
12398 ctx.textAlign = item.textAlign;
12399
12400 label = item.label;
12401 y = item.textOffset;
12402 if (isArray(label)) {
12403 for (j = 0, jlen = label.length; j < jlen; ++j) {
12404 // We just make sure the multiline element is a string here..
12405 ctx.fillText('' + label[j], 0, y);
12406 y += tickFont.lineHeight;
12407 }
12408 } else {
12409 ctx.fillText(label, 0, y);
12410 }
12411 ctx.restore();
12412 }
12413 },
12414
12415 /**
12416 * @private
12417 */
12418 _drawTitle: function() {
12419 var me = this;
12420 var ctx = me.ctx;
12421 var options = me.options;
12422 var scaleLabel = options.scaleLabel;
12423
12424 if (!scaleLabel.display) {
12425 return;
12426 }
12427
12428 var scaleLabelFontColor = valueOrDefault$a(scaleLabel.fontColor, core_defaults.global.defaultFontColor);
12429 var scaleLabelFont = helpers$1.options._parseFont(scaleLabel);
12430 var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding);
12431 var halfLineHeight = scaleLabelFont.lineHeight / 2;
12432 var position = options.position;
12433 var rotation = 0;
12434 var scaleLabelX, scaleLabelY;
12435
12436 if (me.isHorizontal()) {
12437 scaleLabelX = me.left + me.width / 2; // midpoint of the width
12438 scaleLabelY = position === 'bottom'
12439 ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
12440 : me.top + halfLineHeight + scaleLabelPadding.top;
12441 } else {
12442 var isLeft = position === 'left';
12443 scaleLabelX = isLeft
12444 ? me.left + halfLineHeight + scaleLabelPadding.top
12445 : me.right - halfLineHeight - scaleLabelPadding.top;
12446 scaleLabelY = me.top + me.height / 2;
12447 rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
12448 }
12449
12450 ctx.save();
12451 ctx.translate(scaleLabelX, scaleLabelY);
12452 ctx.rotate(rotation);
12453 ctx.textAlign = 'center';
12454 ctx.textBaseline = 'middle';
12455 ctx.fillStyle = scaleLabelFontColor; // render in correct colour
12456 ctx.font = scaleLabelFont.string;
12457 ctx.fillText(scaleLabel.labelString, 0, 0);
12458 ctx.restore();
12459 },
12460
12461 draw: function(chartArea) {
12462 var me = this;
12463
12464 if (!me._isVisible()) {
12465 return;
12466 }
12467
12468 me._drawGrid(chartArea);
12469 me._drawTitle();
12470 me._drawLabels();
12471 },
12472
12473 /**
12474 * @private
12475 */
12476 _layers: function() {
12477 var me = this;
12478 var opts = me.options;
12479 var tz = opts.ticks && opts.ticks.z || 0;
12480 var gz = opts.gridLines && opts.gridLines.z || 0;
12481
12482 if (!me._isVisible() || tz === gz || me.draw !== me._draw) {
12483 // backward compatibility: draw has been overridden by custom scale
12484 return [{
12485 z: tz,
12486 draw: function() {
12487 me.draw.apply(me, arguments);
12488 }
12489 }];
12490 }
12491
12492 return [{
12493 z: gz,
12494 draw: function() {
12495 me._drawGrid.apply(me, arguments);
12496 me._drawTitle.apply(me, arguments);
12497 }
12498 }, {
12499 z: tz,
12500 draw: function() {
12501 me._drawLabels.apply(me, arguments);
12502 }
12503 }];
12504 },
12505
12506 /**
12507 * @private
12508 */
12509 _getMatchingVisibleMetas: function(type) {
12510 var me = this;
12511 var isHorizontal = me.isHorizontal();
12512 return me.chart._getSortedVisibleDatasetMetas()
12513 .filter(function(meta) {
12514 return (!type || meta.type === type)
12515 && (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id);
12516 });
12517 }
12518});
12519
12520Scale.prototype._draw = Scale.prototype.draw;
12521
12522var core_scale = Scale;
12523
12524var isNullOrUndef$1 = helpers$1.isNullOrUndef;
12525
12526var defaultConfig = {
12527 position: 'bottom'
12528};
12529
12530var scale_category = core_scale.extend({
12531 determineDataLimits: function() {
12532 var me = this;
12533 var labels = me._getLabels();
12534 var ticksOpts = me.options.ticks;
12535 var min = ticksOpts.min;
12536 var max = ticksOpts.max;
12537 var minIndex = 0;
12538 var maxIndex = labels.length - 1;
12539 var findIndex;
12540
12541 if (min !== undefined) {
12542 // user specified min value
12543 findIndex = labels.indexOf(min);
12544 if (findIndex >= 0) {
12545 minIndex = findIndex;
12546 }
12547 }
12548
12549 if (max !== undefined) {
12550 // user specified max value
12551 findIndex = labels.indexOf(max);
12552 if (findIndex >= 0) {
12553 maxIndex = findIndex;
12554 }
12555 }
12556
12557 me.minIndex = minIndex;
12558 me.maxIndex = maxIndex;
12559 me.min = labels[minIndex];
12560 me.max = labels[maxIndex];
12561 },
12562
12563 buildTicks: function() {
12564 var me = this;
12565 var labels = me._getLabels();
12566 var minIndex = me.minIndex;
12567 var maxIndex = me.maxIndex;
12568
12569 // If we are viewing some subset of labels, slice the original array
12570 me.ticks = (minIndex === 0 && maxIndex === labels.length - 1) ? labels : labels.slice(minIndex, maxIndex + 1);
12571 },
12572
12573 getLabelForIndex: function(index, datasetIndex) {
12574 var me = this;
12575 var chart = me.chart;
12576
12577 if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) {
12578 return me.getRightValue(chart.data.datasets[datasetIndex].data[index]);
12579 }
12580
12581 return me._getLabels()[index];
12582 },
12583
12584 _configure: function() {
12585 var me = this;
12586 var offset = me.options.offset;
12587 var ticks = me.ticks;
12588
12589 core_scale.prototype._configure.call(me);
12590
12591 if (!me.isHorizontal()) {
12592 // For backward compatibility, vertical category scale reverse is inverted.
12593 me._reversePixels = !me._reversePixels;
12594 }
12595
12596 if (!ticks) {
12597 return;
12598 }
12599
12600 me._startValue = me.minIndex - (offset ? 0.5 : 0);
12601 me._valueRange = Math.max(ticks.length - (offset ? 0 : 1), 1);
12602 },
12603
12604 // Used to get data value locations. Value can either be an index or a numerical value
12605 getPixelForValue: function(value, index, datasetIndex) {
12606 var me = this;
12607 var valueCategory, labels, idx;
12608
12609 if (!isNullOrUndef$1(index) && !isNullOrUndef$1(datasetIndex)) {
12610 value = me.chart.data.datasets[datasetIndex].data[index];
12611 }
12612
12613 // If value is a data object, then index is the index in the data array,
12614 // not the index of the scale. We need to change that.
12615 if (!isNullOrUndef$1(value)) {
12616 valueCategory = me.isHorizontal() ? value.x : value.y;
12617 }
12618 if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
12619 labels = me._getLabels();
12620 value = helpers$1.valueOrDefault(valueCategory, value);
12621 idx = labels.indexOf(value);
12622 index = idx !== -1 ? idx : index;
12623 if (isNaN(index)) {
12624 index = value;
12625 }
12626 }
12627 return me.getPixelForDecimal((index - me._startValue) / me._valueRange);
12628 },
12629
12630 getPixelForTick: function(index) {
12631 var ticks = this.ticks;
12632 return index < 0 || index > ticks.length - 1
12633 ? null
12634 : this.getPixelForValue(ticks[index], index + this.minIndex);
12635 },
12636
12637 getValueForPixel: function(pixel) {
12638 var me = this;
12639 var value = Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange);
12640 return Math.min(Math.max(value, 0), me.ticks.length - 1);
12641 },
12642
12643 getBasePixel: function() {
12644 return this.bottom;
12645 }
12646});
12647
12648// INTERNAL: static default options, registered in src/index.js
12649var _defaults = defaultConfig;
12650scale_category._defaults = _defaults;
12651
12652var noop = helpers$1.noop;
12653var isNullOrUndef$2 = helpers$1.isNullOrUndef;
12654
12655/**
12656 * Generate a set of linear ticks
12657 * @param generationOptions the options used to generate the ticks
12658 * @param dataRange the range of the data
12659 * @returns {number[]} array of tick values
12660 */
12661function generateTicks(generationOptions, dataRange) {
12662 var ticks = [];
12663 // To get a "nice" value for the tick spacing, we will use the appropriately named
12664 // "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
12665 // for details.
12666
12667 var MIN_SPACING = 1e-14;
12668 var stepSize = generationOptions.stepSize;
12669 var unit = stepSize || 1;
12670 var maxNumSpaces = generationOptions.maxTicks - 1;
12671 var min = generationOptions.min;
12672 var max = generationOptions.max;
12673 var precision = generationOptions.precision;
12674 var rmin = dataRange.min;
12675 var rmax = dataRange.max;
12676 var spacing = helpers$1.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit;
12677 var factor, niceMin, niceMax, numSpaces;
12678
12679 // Beyond MIN_SPACING floating point numbers being to lose precision
12680 // such that we can't do the math necessary to generate ticks
12681 if (spacing < MIN_SPACING && isNullOrUndef$2(min) && isNullOrUndef$2(max)) {
12682 return [rmin, rmax];
12683 }
12684
12685 numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
12686 if (numSpaces > maxNumSpaces) {
12687 // If the calculated num of spaces exceeds maxNumSpaces, recalculate it
12688 spacing = helpers$1.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit;
12689 }
12690
12691 if (stepSize || isNullOrUndef$2(precision)) {
12692 // If a precision is not specified, calculate factor based on spacing
12693 factor = Math.pow(10, helpers$1._decimalPlaces(spacing));
12694 } else {
12695 // If the user specified a precision, round to that number of decimal places
12696 factor = Math.pow(10, precision);
12697 spacing = Math.ceil(spacing * factor) / factor;
12698 }
12699
12700 niceMin = Math.floor(rmin / spacing) * spacing;
12701 niceMax = Math.ceil(rmax / spacing) * spacing;
12702
12703 // If min, max and stepSize is set and they make an evenly spaced scale use it.
12704 if (stepSize) {
12705 // If very close to our whole number, use it.
12706 if (!isNullOrUndef$2(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) {
12707 niceMin = min;
12708 }
12709 if (!isNullOrUndef$2(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) {
12710 niceMax = max;
12711 }
12712 }
12713
12714 numSpaces = (niceMax - niceMin) / spacing;
12715 // If very close to our rounded value, use it.
12716 if (helpers$1.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
12717 numSpaces = Math.round(numSpaces);
12718 } else {
12719 numSpaces = Math.ceil(numSpaces);
12720 }
12721
12722 niceMin = Math.round(niceMin * factor) / factor;
12723 niceMax = Math.round(niceMax * factor) / factor;
12724 ticks.push(isNullOrUndef$2(min) ? niceMin : min);
12725 for (var j = 1; j < numSpaces; ++j) {
12726 ticks.push(Math.round((niceMin + j * spacing) * factor) / factor);
12727 }
12728 ticks.push(isNullOrUndef$2(max) ? niceMax : max);
12729
12730 return ticks;
12731}
12732
12733var scale_linearbase = core_scale.extend({
12734 getRightValue: function(value) {
12735 if (typeof value === 'string') {
12736 return +value;
12737 }
12738 return core_scale.prototype.getRightValue.call(this, value);
12739 },
12740
12741 handleTickRangeOptions: function() {
12742 var me = this;
12743 var opts = me.options;
12744 var tickOpts = opts.ticks;
12745
12746 // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
12747 // do nothing since that would make the chart weird. If the user really wants a weird chart
12748 // axis, they can manually override it
12749 if (tickOpts.beginAtZero) {
12750 var minSign = helpers$1.sign(me.min);
12751 var maxSign = helpers$1.sign(me.max);
12752
12753 if (minSign < 0 && maxSign < 0) {
12754 // move the top up to 0
12755 me.max = 0;
12756 } else if (minSign > 0 && maxSign > 0) {
12757 // move the bottom down to 0
12758 me.min = 0;
12759 }
12760 }
12761
12762 var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
12763 var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
12764
12765 if (tickOpts.min !== undefined) {
12766 me.min = tickOpts.min;
12767 } else if (tickOpts.suggestedMin !== undefined) {
12768 if (me.min === null) {
12769 me.min = tickOpts.suggestedMin;
12770 } else {
12771 me.min = Math.min(me.min, tickOpts.suggestedMin);
12772 }
12773 }
12774
12775 if (tickOpts.max !== undefined) {
12776 me.max = tickOpts.max;
12777 } else if (tickOpts.suggestedMax !== undefined) {
12778 if (me.max === null) {
12779 me.max = tickOpts.suggestedMax;
12780 } else {
12781 me.max = Math.max(me.max, tickOpts.suggestedMax);
12782 }
12783 }
12784
12785 if (setMin !== setMax) {
12786 // We set the min or the max but not both.
12787 // So ensure that our range is good
12788 // Inverted or 0 length range can happen when
12789 // ticks.min is set, and no datasets are visible
12790 if (me.min >= me.max) {
12791 if (setMin) {
12792 me.max = me.min + 1;
12793 } else {
12794 me.min = me.max - 1;
12795 }
12796 }
12797 }
12798
12799 if (me.min === me.max) {
12800 me.max++;
12801
12802 if (!tickOpts.beginAtZero) {
12803 me.min--;
12804 }
12805 }
12806 },
12807
12808 getTickLimit: function() {
12809 var me = this;
12810 var tickOpts = me.options.ticks;
12811 var stepSize = tickOpts.stepSize;
12812 var maxTicksLimit = tickOpts.maxTicksLimit;
12813 var maxTicks;
12814
12815 if (stepSize) {
12816 maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1;
12817 } else {
12818 maxTicks = me._computeTickLimit();
12819 maxTicksLimit = maxTicksLimit || 11;
12820 }
12821
12822 if (maxTicksLimit) {
12823 maxTicks = Math.min(maxTicksLimit, maxTicks);
12824 }
12825
12826 return maxTicks;
12827 },
12828
12829 _computeTickLimit: function() {
12830 return Number.POSITIVE_INFINITY;
12831 },
12832
12833 handleDirectionalChanges: noop,
12834
12835 buildTicks: function() {
12836 var me = this;
12837 var opts = me.options;
12838 var tickOpts = opts.ticks;
12839
12840 // Figure out what the max number of ticks we can support it is based on the size of
12841 // the axis area. For now, we say that the minimum tick spacing in pixels must be 40
12842 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
12843 // the graph. Make sure we always have at least 2 ticks
12844 var maxTicks = me.getTickLimit();
12845 maxTicks = Math.max(2, maxTicks);
12846
12847 var numericGeneratorOptions = {
12848 maxTicks: maxTicks,
12849 min: tickOpts.min,
12850 max: tickOpts.max,
12851 precision: tickOpts.precision,
12852 stepSize: helpers$1.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
12853 };
12854 var ticks = me.ticks = generateTicks(numericGeneratorOptions, me);
12855
12856 me.handleDirectionalChanges();
12857
12858 // At this point, we need to update our max and min given the tick values since we have expanded the
12859 // range of the scale
12860 me.max = helpers$1.max(ticks);
12861 me.min = helpers$1.min(ticks);
12862
12863 if (tickOpts.reverse) {
12864 ticks.reverse();
12865
12866 me.start = me.max;
12867 me.end = me.min;
12868 } else {
12869 me.start = me.min;
12870 me.end = me.max;
12871 }
12872 },
12873
12874 convertTicksToLabels: function() {
12875 var me = this;
12876 me.ticksAsNumbers = me.ticks.slice();
12877 me.zeroLineIndex = me.ticks.indexOf(0);
12878
12879 core_scale.prototype.convertTicksToLabels.call(me);
12880 },
12881
12882 _configure: function() {
12883 var me = this;
12884 var ticks = me.getTicks();
12885 var start = me.min;
12886 var end = me.max;
12887 var offset;
12888
12889 core_scale.prototype._configure.call(me);
12890
12891 if (me.options.offset && ticks.length) {
12892 offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
12893 start -= offset;
12894 end += offset;
12895 }
12896 me._startValue = start;
12897 me._endValue = end;
12898 me._valueRange = end - start;
12899 }
12900});
12901
12902var defaultConfig$1 = {
12903 position: 'left',
12904 ticks: {
12905 callback: core_ticks.formatters.linear
12906 }
12907};
12908
12909var DEFAULT_MIN = 0;
12910var DEFAULT_MAX = 1;
12911
12912function getOrCreateStack(stacks, stacked, meta) {
12913 var key = [
12914 meta.type,
12915 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12916 stacked === undefined && meta.stack === undefined ? meta.index : '',
12917 meta.stack
12918 ].join('.');
12919
12920 if (stacks[key] === undefined) {
12921 stacks[key] = {
12922 pos: [],
12923 neg: []
12924 };
12925 }
12926
12927 return stacks[key];
12928}
12929
12930function stackData(scale, stacks, meta, data) {
12931 var opts = scale.options;
12932 var stacked = opts.stacked;
12933 var stack = getOrCreateStack(stacks, stacked, meta);
12934 var pos = stack.pos;
12935 var neg = stack.neg;
12936 var ilen = data.length;
12937 var i, value;
12938
12939 for (i = 0; i < ilen; ++i) {
12940 value = scale._parseValue(data[i]);
12941 if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) {
12942 continue;
12943 }
12944
12945 pos[i] = pos[i] || 0;
12946 neg[i] = neg[i] || 0;
12947
12948 if (opts.relativePoints) {
12949 pos[i] = 100;
12950 } else if (value.min < 0 || value.max < 0) {
12951 neg[i] += value.min;
12952 } else {
12953 pos[i] += value.max;
12954 }
12955 }
12956}
12957
12958function updateMinMax(scale, meta, data) {
12959 var ilen = data.length;
12960 var i, value;
12961
12962 for (i = 0; i < ilen; ++i) {
12963 value = scale._parseValue(data[i]);
12964 if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) {
12965 continue;
12966 }
12967
12968 scale.min = Math.min(scale.min, value.min);
12969 scale.max = Math.max(scale.max, value.max);
12970 }
12971}
12972
12973var scale_linear = scale_linearbase.extend({
12974 determineDataLimits: function() {
12975 var me = this;
12976 var opts = me.options;
12977 var chart = me.chart;
12978 var datasets = chart.data.datasets;
12979 var metasets = me._getMatchingVisibleMetas();
12980 var hasStacks = opts.stacked;
12981 var stacks = {};
12982 var ilen = metasets.length;
12983 var i, meta, data, values;
12984
12985 me.min = Number.POSITIVE_INFINITY;
12986 me.max = Number.NEGATIVE_INFINITY;
12987
12988 if (hasStacks === undefined) {
12989 for (i = 0; !hasStacks && i < ilen; ++i) {
12990 meta = metasets[i];
12991 hasStacks = meta.stack !== undefined;
12992 }
12993 }
12994
12995 for (i = 0; i < ilen; ++i) {
12996 meta = metasets[i];
12997 data = datasets[meta.index].data;
12998 if (hasStacks) {
12999 stackData(me, stacks, meta, data);
13000 } else {
13001 updateMinMax(me, meta, data);
13002 }
13003 }
13004
13005 helpers$1.each(stacks, function(stackValues) {
13006 values = stackValues.pos.concat(stackValues.neg);
13007 me.min = Math.min(me.min, helpers$1.min(values));
13008 me.max = Math.max(me.max, helpers$1.max(values));
13009 });
13010
13011 me.min = helpers$1.isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
13012 me.max = helpers$1.isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
13013
13014 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
13015 me.handleTickRangeOptions();
13016 },
13017
13018 // Returns the maximum number of ticks based on the scale dimension
13019 _computeTickLimit: function() {
13020 var me = this;
13021 var tickFont;
13022
13023 if (me.isHorizontal()) {
13024 return Math.ceil(me.width / 40);
13025 }
13026 tickFont = helpers$1.options._parseFont(me.options.ticks);
13027 return Math.ceil(me.height / tickFont.lineHeight);
13028 },
13029
13030 // Called after the ticks are built. We need
13031 handleDirectionalChanges: function() {
13032 if (!this.isHorizontal()) {
13033 // We are in a vertical orientation. The top value is the highest. So reverse the array
13034 this.ticks.reverse();
13035 }
13036 },
13037
13038 getLabelForIndex: function(index, datasetIndex) {
13039 return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
13040 },
13041
13042 // Utils
13043 getPixelForValue: function(value) {
13044 var me = this;
13045 return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange);
13046 },
13047
13048 getValueForPixel: function(pixel) {
13049 return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
13050 },
13051
13052 getPixelForTick: function(index) {
13053 var ticks = this.ticksAsNumbers;
13054 if (index < 0 || index > ticks.length - 1) {
13055 return null;
13056 }
13057 return this.getPixelForValue(ticks[index]);
13058 }
13059});
13060
13061// INTERNAL: static default options, registered in src/index.js
13062var _defaults$1 = defaultConfig$1;
13063scale_linear._defaults = _defaults$1;
13064
13065var valueOrDefault$b = helpers$1.valueOrDefault;
13066var log10 = helpers$1.math.log10;
13067
13068/**
13069 * Generate a set of logarithmic ticks
13070 * @param generationOptions the options used to generate the ticks
13071 * @param dataRange the range of the data
13072 * @returns {number[]} array of tick values
13073 */
13074function generateTicks$1(generationOptions, dataRange) {
13075 var ticks = [];
13076
13077 var tickVal = valueOrDefault$b(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min))));
13078
13079 var endExp = Math.floor(log10(dataRange.max));
13080 var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
13081 var exp, significand;
13082
13083 if (tickVal === 0) {
13084 exp = Math.floor(log10(dataRange.minNotZero));
13085 significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
13086
13087 ticks.push(tickVal);
13088 tickVal = significand * Math.pow(10, exp);
13089 } else {
13090 exp = Math.floor(log10(tickVal));
13091 significand = Math.floor(tickVal / Math.pow(10, exp));
13092 }
13093 var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
13094
13095 do {
13096 ticks.push(tickVal);
13097
13098 ++significand;
13099 if (significand === 10) {
13100 significand = 1;
13101 ++exp;
13102 precision = exp >= 0 ? 1 : precision;
13103 }
13104
13105 tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
13106 } while (exp < endExp || (exp === endExp && significand < endSignificand));
13107
13108 var lastTick = valueOrDefault$b(generationOptions.max, tickVal);
13109 ticks.push(lastTick);
13110
13111 return ticks;
13112}
13113
13114var defaultConfig$2 = {
13115 position: 'left',
13116
13117 // label settings
13118 ticks: {
13119 callback: core_ticks.formatters.logarithmic
13120 }
13121};
13122
13123// TODO(v3): change this to positiveOrDefault
13124function nonNegativeOrDefault(value, defaultValue) {
13125 return helpers$1.isFinite(value) && value >= 0 ? value : defaultValue;
13126}
13127
13128var scale_logarithmic = core_scale.extend({
13129 determineDataLimits: function() {
13130 var me = this;
13131 var opts = me.options;
13132 var chart = me.chart;
13133 var datasets = chart.data.datasets;
13134 var isHorizontal = me.isHorizontal();
13135 function IDMatches(meta) {
13136 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
13137 }
13138 var datasetIndex, meta, value, data, i, ilen;
13139
13140 // Calculate Range
13141 me.min = Number.POSITIVE_INFINITY;
13142 me.max = Number.NEGATIVE_INFINITY;
13143 me.minNotZero = Number.POSITIVE_INFINITY;
13144
13145 var hasStacks = opts.stacked;
13146 if (hasStacks === undefined) {
13147 for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
13148 meta = chart.getDatasetMeta(datasetIndex);
13149 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
13150 meta.stack !== undefined) {
13151 hasStacks = true;
13152 break;
13153 }
13154 }
13155 }
13156
13157 if (opts.stacked || hasStacks) {
13158 var valuesPerStack = {};
13159
13160 for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
13161 meta = chart.getDatasetMeta(datasetIndex);
13162 var key = [
13163 meta.type,
13164 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
13165 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
13166 meta.stack
13167 ].join('.');
13168
13169 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
13170 if (valuesPerStack[key] === undefined) {
13171 valuesPerStack[key] = [];
13172 }
13173
13174 data = datasets[datasetIndex].data;
13175 for (i = 0, ilen = data.length; i < ilen; i++) {
13176 var values = valuesPerStack[key];
13177 value = me._parseValue(data[i]);
13178 // invalid, hidden and negative values are ignored
13179 if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
13180 continue;
13181 }
13182 values[i] = values[i] || 0;
13183 values[i] += value.max;
13184 }
13185 }
13186 }
13187
13188 helpers$1.each(valuesPerStack, function(valuesForType) {
13189 if (valuesForType.length > 0) {
13190 var minVal = helpers$1.min(valuesForType);
13191 var maxVal = helpers$1.max(valuesForType);
13192 me.min = Math.min(me.min, minVal);
13193 me.max = Math.max(me.max, maxVal);
13194 }
13195 });
13196
13197 } else {
13198 for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
13199 meta = chart.getDatasetMeta(datasetIndex);
13200 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
13201 data = datasets[datasetIndex].data;
13202 for (i = 0, ilen = data.length; i < ilen; i++) {
13203 value = me._parseValue(data[i]);
13204 // invalid, hidden and negative values are ignored
13205 if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
13206 continue;
13207 }
13208
13209 me.min = Math.min(value.min, me.min);
13210 me.max = Math.max(value.max, me.max);
13211
13212 if (value.min !== 0) {
13213 me.minNotZero = Math.min(value.min, me.minNotZero);
13214 }
13215 }
13216 }
13217 }
13218 }
13219
13220 me.min = helpers$1.isFinite(me.min) ? me.min : null;
13221 me.max = helpers$1.isFinite(me.max) ? me.max : null;
13222 me.minNotZero = helpers$1.isFinite(me.minNotZero) ? me.minNotZero : null;
13223
13224 // Common base implementation to handle ticks.min, ticks.max
13225 this.handleTickRangeOptions();
13226 },
13227
13228 handleTickRangeOptions: function() {
13229 var me = this;
13230 var tickOpts = me.options.ticks;
13231 var DEFAULT_MIN = 1;
13232 var DEFAULT_MAX = 10;
13233
13234 me.min = nonNegativeOrDefault(tickOpts.min, me.min);
13235 me.max = nonNegativeOrDefault(tickOpts.max, me.max);
13236
13237 if (me.min === me.max) {
13238 if (me.min !== 0 && me.min !== null) {
13239 me.min = Math.pow(10, Math.floor(log10(me.min)) - 1);
13240 me.max = Math.pow(10, Math.floor(log10(me.max)) + 1);
13241 } else {
13242 me.min = DEFAULT_MIN;
13243 me.max = DEFAULT_MAX;
13244 }
13245 }
13246 if (me.min === null) {
13247 me.min = Math.pow(10, Math.floor(log10(me.max)) - 1);
13248 }
13249 if (me.max === null) {
13250 me.max = me.min !== 0
13251 ? Math.pow(10, Math.floor(log10(me.min)) + 1)
13252 : DEFAULT_MAX;
13253 }
13254 if (me.minNotZero === null) {
13255 if (me.min > 0) {
13256 me.minNotZero = me.min;
13257 } else if (me.max < 1) {
13258 me.minNotZero = Math.pow(10, Math.floor(log10(me.max)));
13259 } else {
13260 me.minNotZero = DEFAULT_MIN;
13261 }
13262 }
13263 },
13264
13265 buildTicks: function() {
13266 var me = this;
13267 var tickOpts = me.options.ticks;
13268 var reverse = !me.isHorizontal();
13269
13270 var generationOptions = {
13271 min: nonNegativeOrDefault(tickOpts.min),
13272 max: nonNegativeOrDefault(tickOpts.max)
13273 };
13274 var ticks = me.ticks = generateTicks$1(generationOptions, me);
13275
13276 // At this point, we need to update our max and min given the tick values since we have expanded the
13277 // range of the scale
13278 me.max = helpers$1.max(ticks);
13279 me.min = helpers$1.min(ticks);
13280
13281 if (tickOpts.reverse) {
13282 reverse = !reverse;
13283 me.start = me.max;
13284 me.end = me.min;
13285 } else {
13286 me.start = me.min;
13287 me.end = me.max;
13288 }
13289 if (reverse) {
13290 ticks.reverse();
13291 }
13292 },
13293
13294 convertTicksToLabels: function() {
13295 this.tickValues = this.ticks.slice();
13296
13297 core_scale.prototype.convertTicksToLabels.call(this);
13298 },
13299
13300 // Get the correct tooltip label
13301 getLabelForIndex: function(index, datasetIndex) {
13302 return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
13303 },
13304
13305 getPixelForTick: function(index) {
13306 var ticks = this.tickValues;
13307 if (index < 0 || index > ticks.length - 1) {
13308 return null;
13309 }
13310 return this.getPixelForValue(ticks[index]);
13311 },
13312
13313 /**
13314 * Returns the value of the first tick.
13315 * @param {number} value - The minimum not zero value.
13316 * @return {number} The first tick value.
13317 * @private
13318 */
13319 _getFirstTickValue: function(value) {
13320 var exp = Math.floor(log10(value));
13321 var significand = Math.floor(value / Math.pow(10, exp));
13322
13323 return significand * Math.pow(10, exp);
13324 },
13325
13326 _configure: function() {
13327 var me = this;
13328 var start = me.min;
13329 var offset = 0;
13330
13331 core_scale.prototype._configure.call(me);
13332
13333 if (start === 0) {
13334 start = me._getFirstTickValue(me.minNotZero);
13335 offset = valueOrDefault$b(me.options.ticks.fontSize, core_defaults.global.defaultFontSize) / me._length;
13336 }
13337
13338 me._startValue = log10(start);
13339 me._valueOffset = offset;
13340 me._valueRange = (log10(me.max) - log10(start)) / (1 - offset);
13341 },
13342
13343 getPixelForValue: function(value) {
13344 var me = this;
13345 var decimal = 0;
13346
13347 value = +me.getRightValue(value);
13348
13349 if (value > me.min && value > 0) {
13350 decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset;
13351 }
13352 return me.getPixelForDecimal(decimal);
13353 },
13354
13355 getValueForPixel: function(pixel) {
13356 var me = this;
13357 var decimal = me.getDecimalForPixel(pixel);
13358 return decimal === 0 && me.min === 0
13359 ? 0
13360 : Math.pow(10, me._startValue + (decimal - me._valueOffset) * me._valueRange);
13361 }
13362});
13363
13364// INTERNAL: static default options, registered in src/index.js
13365var _defaults$2 = defaultConfig$2;
13366scale_logarithmic._defaults = _defaults$2;
13367
13368var valueOrDefault$c = helpers$1.valueOrDefault;
13369var valueAtIndexOrDefault$1 = helpers$1.valueAtIndexOrDefault;
13370var resolve$4 = helpers$1.options.resolve;
13371
13372var defaultConfig$3 = {
13373 display: true,
13374
13375 // Boolean - Whether to animate scaling the chart from the centre
13376 animate: true,
13377 position: 'chartArea',
13378
13379 angleLines: {
13380 display: true,
13381 color: 'rgba(0,0,0,0.1)',
13382 lineWidth: 1,
13383 borderDash: [],
13384 borderDashOffset: 0.0
13385 },
13386
13387 gridLines: {
13388 circular: false
13389 },
13390
13391 // label settings
13392 ticks: {
13393 // Boolean - Show a backdrop to the scale label
13394 showLabelBackdrop: true,
13395
13396 // String - The colour of the label backdrop
13397 backdropColor: 'rgba(255,255,255,0.75)',
13398
13399 // Number - The backdrop padding above & below the label in pixels
13400 backdropPaddingY: 2,
13401
13402 // Number - The backdrop padding to the side of the label in pixels
13403 backdropPaddingX: 2,
13404
13405 callback: core_ticks.formatters.linear
13406 },
13407
13408 pointLabels: {
13409 // Boolean - if true, show point labels
13410 display: true,
13411
13412 // Number - Point label font size in pixels
13413 fontSize: 10,
13414
13415 // Function - Used to convert point labels
13416 callback: function(label) {
13417 return label;
13418 }
13419 }
13420};
13421
13422function getTickBackdropHeight(opts) {
13423 var tickOpts = opts.ticks;
13424
13425 if (tickOpts.display && opts.display) {
13426 return valueOrDefault$c(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2;
13427 }
13428 return 0;
13429}
13430
13431function measureLabelSize(ctx, lineHeight, label) {
13432 if (helpers$1.isArray(label)) {
13433 return {
13434 w: helpers$1.longestText(ctx, ctx.font, label),
13435 h: label.length * lineHeight
13436 };
13437 }
13438
13439 return {
13440 w: ctx.measureText(label).width,
13441 h: lineHeight
13442 };
13443}
13444
13445function determineLimits(angle, pos, size, min, max) {
13446 if (angle === min || angle === max) {
13447 return {
13448 start: pos - (size / 2),
13449 end: pos + (size / 2)
13450 };
13451 } else if (angle < min || angle > max) {
13452 return {
13453 start: pos - size,
13454 end: pos
13455 };
13456 }
13457
13458 return {
13459 start: pos,
13460 end: pos + size
13461 };
13462}
13463
13464/**
13465 * Helper function to fit a radial linear scale with point labels
13466 */
13467function fitWithPointLabels(scale) {
13468
13469 // Right, this is really confusing and there is a lot of maths going on here
13470 // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
13471 //
13472 // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
13473 //
13474 // Solution:
13475 //
13476 // We assume the radius of the polygon is half the size of the canvas at first
13477 // at each index we check if the text overlaps.
13478 //
13479 // Where it does, we store that angle and that index.
13480 //
13481 // After finding the largest index and angle we calculate how much we need to remove
13482 // from the shape radius to move the point inwards by that x.
13483 //
13484 // We average the left and right distances to get the maximum shape radius that can fit in the box
13485 // along with labels.
13486 //
13487 // Once we have that, we can find the centre point for the chart, by taking the x text protrusion
13488 // on each side, removing that from the size, halving it and adding the left x protrusion width.
13489 //
13490 // This will mean we have a shape fitted to the canvas, as large as it can be with the labels
13491 // and position it in the most space efficient manner
13492 //
13493 // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
13494
13495 var plFont = helpers$1.options._parseFont(scale.options.pointLabels);
13496
13497 // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
13498 // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
13499 var furthestLimits = {
13500 l: 0,
13501 r: scale.width,
13502 t: 0,
13503 b: scale.height - scale.paddingTop
13504 };
13505 var furthestAngles = {};
13506 var i, textSize, pointPosition;
13507
13508 scale.ctx.font = plFont.string;
13509 scale._pointLabelSizes = [];
13510
13511 var valueCount = scale.chart.data.labels.length;
13512 for (i = 0; i < valueCount; i++) {
13513 pointPosition = scale.getPointPosition(i, scale.drawingArea + 5);
13514 textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]);
13515 scale._pointLabelSizes[i] = textSize;
13516
13517 // Add quarter circle to make degree 0 mean top of circle
13518 var angleRadians = scale.getIndexAngle(i);
13519 var angle = helpers$1.toDegrees(angleRadians) % 360;
13520 var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
13521 var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
13522
13523 if (hLimits.start < furthestLimits.l) {
13524 furthestLimits.l = hLimits.start;
13525 furthestAngles.l = angleRadians;
13526 }
13527
13528 if (hLimits.end > furthestLimits.r) {
13529 furthestLimits.r = hLimits.end;
13530 furthestAngles.r = angleRadians;
13531 }
13532
13533 if (vLimits.start < furthestLimits.t) {
13534 furthestLimits.t = vLimits.start;
13535 furthestAngles.t = angleRadians;
13536 }
13537
13538 if (vLimits.end > furthestLimits.b) {
13539 furthestLimits.b = vLimits.end;
13540 furthestAngles.b = angleRadians;
13541 }
13542 }
13543
13544 scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles);
13545}
13546
13547function getTextAlignForAngle(angle) {
13548 if (angle === 0 || angle === 180) {
13549 return 'center';
13550 } else if (angle < 180) {
13551 return 'left';
13552 }
13553
13554 return 'right';
13555}
13556
13557function fillText(ctx, text, position, lineHeight) {
13558 var y = position.y + lineHeight / 2;
13559 var i, ilen;
13560
13561 if (helpers$1.isArray(text)) {
13562 for (i = 0, ilen = text.length; i < ilen; ++i) {
13563 ctx.fillText(text[i], position.x, y);
13564 y += lineHeight;
13565 }
13566 } else {
13567 ctx.fillText(text, position.x, y);
13568 }
13569}
13570
13571function adjustPointPositionForLabelHeight(angle, textSize, position) {
13572 if (angle === 90 || angle === 270) {
13573 position.y -= (textSize.h / 2);
13574 } else if (angle > 270 || angle < 90) {
13575 position.y -= textSize.h;
13576 }
13577}
13578
13579function drawPointLabels(scale) {
13580 var ctx = scale.ctx;
13581 var opts = scale.options;
13582 var pointLabelOpts = opts.pointLabels;
13583 var tickBackdropHeight = getTickBackdropHeight(opts);
13584 var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
13585 var plFont = helpers$1.options._parseFont(pointLabelOpts);
13586
13587 ctx.save();
13588
13589 ctx.font = plFont.string;
13590 ctx.textBaseline = 'middle';
13591
13592 for (var i = scale.chart.data.labels.length - 1; i >= 0; i--) {
13593 // Extra pixels out for some label spacing
13594 var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
13595 var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
13596
13597 // Keep this in loop since we may support array properties here
13598 var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor);
13599 ctx.fillStyle = pointLabelFontColor;
13600
13601 var angleRadians = scale.getIndexAngle(i);
13602 var angle = helpers$1.toDegrees(angleRadians);
13603 ctx.textAlign = getTextAlignForAngle(angle);
13604 adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
13605 fillText(ctx, scale.pointLabels[i], pointLabelPosition, plFont.lineHeight);
13606 }
13607 ctx.restore();
13608}
13609
13610function drawRadiusLine(scale, gridLineOpts, radius, index) {
13611 var ctx = scale.ctx;
13612 var circular = gridLineOpts.circular;
13613 var valueCount = scale.chart.data.labels.length;
13614 var lineColor = valueAtIndexOrDefault$1(gridLineOpts.color, index - 1);
13615 var lineWidth = valueAtIndexOrDefault$1(gridLineOpts.lineWidth, index - 1);
13616 var pointPosition;
13617
13618 if ((!circular && !valueCount) || !lineColor || !lineWidth) {
13619 return;
13620 }
13621
13622 ctx.save();
13623 ctx.strokeStyle = lineColor;
13624 ctx.lineWidth = lineWidth;
13625 if (ctx.setLineDash) {
13626 ctx.setLineDash(gridLineOpts.borderDash || []);
13627 ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0;
13628 }
13629
13630 ctx.beginPath();
13631 if (circular) {
13632 // Draw circular arcs between the points
13633 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
13634 } else {
13635 // Draw straight lines connecting each index
13636 pointPosition = scale.getPointPosition(0, radius);
13637 ctx.moveTo(pointPosition.x, pointPosition.y);
13638
13639 for (var i = 1; i < valueCount; i++) {
13640 pointPosition = scale.getPointPosition(i, radius);
13641 ctx.lineTo(pointPosition.x, pointPosition.y);
13642 }
13643 }
13644 ctx.closePath();
13645 ctx.stroke();
13646 ctx.restore();
13647}
13648
13649function numberOrZero(param) {
13650 return helpers$1.isNumber(param) ? param : 0;
13651}
13652
13653var scale_radialLinear = scale_linearbase.extend({
13654 setDimensions: function() {
13655 var me = this;
13656
13657 // Set the unconstrained dimension before label rotation
13658 me.width = me.maxWidth;
13659 me.height = me.maxHeight;
13660 me.paddingTop = getTickBackdropHeight(me.options) / 2;
13661 me.xCenter = Math.floor(me.width / 2);
13662 me.yCenter = Math.floor((me.height - me.paddingTop) / 2);
13663 me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2;
13664 },
13665
13666 determineDataLimits: function() {
13667 var me = this;
13668 var chart = me.chart;
13669 var min = Number.POSITIVE_INFINITY;
13670 var max = Number.NEGATIVE_INFINITY;
13671
13672 helpers$1.each(chart.data.datasets, function(dataset, datasetIndex) {
13673 if (chart.isDatasetVisible(datasetIndex)) {
13674 var meta = chart.getDatasetMeta(datasetIndex);
13675
13676 helpers$1.each(dataset.data, function(rawValue, index) {
13677 var value = +me.getRightValue(rawValue);
13678 if (isNaN(value) || meta.data[index].hidden) {
13679 return;
13680 }
13681
13682 min = Math.min(value, min);
13683 max = Math.max(value, max);
13684 });
13685 }
13686 });
13687
13688 me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
13689 me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
13690
13691 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
13692 me.handleTickRangeOptions();
13693 },
13694
13695 // Returns the maximum number of ticks based on the scale dimension
13696 _computeTickLimit: function() {
13697 return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
13698 },
13699
13700 convertTicksToLabels: function() {
13701 var me = this;
13702
13703 scale_linearbase.prototype.convertTicksToLabels.call(me);
13704
13705 // Point labels
13706 me.pointLabels = me.chart.data.labels.map(function() {
13707 var label = helpers$1.callback(me.options.pointLabels.callback, arguments, me);
13708 return label || label === 0 ? label : '';
13709 });
13710 },
13711
13712 getLabelForIndex: function(index, datasetIndex) {
13713 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
13714 },
13715
13716 fit: function() {
13717 var me = this;
13718 var opts = me.options;
13719
13720 if (opts.display && opts.pointLabels.display) {
13721 fitWithPointLabels(me);
13722 } else {
13723 me.setCenterPoint(0, 0, 0, 0);
13724 }
13725 },
13726
13727 /**
13728 * Set radius reductions and determine new radius and center point
13729 * @private
13730 */
13731 setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
13732 var me = this;
13733 var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
13734 var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
13735 var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
13736 var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b);
13737
13738 radiusReductionLeft = numberOrZero(radiusReductionLeft);
13739 radiusReductionRight = numberOrZero(radiusReductionRight);
13740 radiusReductionTop = numberOrZero(radiusReductionTop);
13741 radiusReductionBottom = numberOrZero(radiusReductionBottom);
13742
13743 me.drawingArea = Math.min(
13744 Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
13745 Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
13746 me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
13747 },
13748
13749 setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
13750 var me = this;
13751 var maxRight = me.width - rightMovement - me.drawingArea;
13752 var maxLeft = leftMovement + me.drawingArea;
13753 var maxTop = topMovement + me.drawingArea;
13754 var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea;
13755
13756 me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left);
13757 me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop);
13758 },
13759
13760 getIndexAngle: function(index) {
13761 var chart = this.chart;
13762 var angleMultiplier = 360 / chart.data.labels.length;
13763 var options = chart.options || {};
13764 var startAngle = options.startAngle || 0;
13765
13766 // Start from the top instead of right, so remove a quarter of the circle
13767 var angle = (index * angleMultiplier + startAngle) % 360;
13768
13769 return (angle < 0 ? angle + 360 : angle) * Math.PI * 2 / 360;
13770 },
13771
13772 getDistanceFromCenterForValue: function(value) {
13773 var me = this;
13774
13775 if (helpers$1.isNullOrUndef(value)) {
13776 return NaN;
13777 }
13778
13779 // Take into account half font size + the yPadding of the top value
13780 var scalingFactor = me.drawingArea / (me.max - me.min);
13781 if (me.options.ticks.reverse) {
13782 return (me.max - value) * scalingFactor;
13783 }
13784 return (value - me.min) * scalingFactor;
13785 },
13786
13787 getPointPosition: function(index, distanceFromCenter) {
13788 var me = this;
13789 var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
13790 return {
13791 x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter,
13792 y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter
13793 };
13794 },
13795
13796 getPointPositionForValue: function(index, value) {
13797 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
13798 },
13799
13800 getBasePosition: function(index) {
13801 var me = this;
13802 var min = me.min;
13803 var max = me.max;
13804
13805 return me.getPointPositionForValue(index || 0,
13806 me.beginAtZero ? 0 :
13807 min < 0 && max < 0 ? max :
13808 min > 0 && max > 0 ? min :
13809 0);
13810 },
13811
13812 /**
13813 * @private
13814 */
13815 _drawGrid: function() {
13816 var me = this;
13817 var ctx = me.ctx;
13818 var opts = me.options;
13819 var gridLineOpts = opts.gridLines;
13820 var angleLineOpts = opts.angleLines;
13821 var lineWidth = valueOrDefault$c(angleLineOpts.lineWidth, gridLineOpts.lineWidth);
13822 var lineColor = valueOrDefault$c(angleLineOpts.color, gridLineOpts.color);
13823 var i, offset, position;
13824
13825 if (opts.pointLabels.display) {
13826 drawPointLabels(me);
13827 }
13828
13829 if (gridLineOpts.display) {
13830 helpers$1.each(me.ticks, function(label, index) {
13831 if (index !== 0) {
13832 offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
13833 drawRadiusLine(me, gridLineOpts, offset, index);
13834 }
13835 });
13836 }
13837
13838 if (angleLineOpts.display && lineWidth && lineColor) {
13839 ctx.save();
13840 ctx.lineWidth = lineWidth;
13841 ctx.strokeStyle = lineColor;
13842 if (ctx.setLineDash) {
13843 ctx.setLineDash(resolve$4([angleLineOpts.borderDash, gridLineOpts.borderDash, []]));
13844 ctx.lineDashOffset = resolve$4([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]);
13845 }
13846
13847 for (i = me.chart.data.labels.length - 1; i >= 0; i--) {
13848 offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max);
13849 position = me.getPointPosition(i, offset);
13850 ctx.beginPath();
13851 ctx.moveTo(me.xCenter, me.yCenter);
13852 ctx.lineTo(position.x, position.y);
13853 ctx.stroke();
13854 }
13855
13856 ctx.restore();
13857 }
13858 },
13859
13860 /**
13861 * @private
13862 */
13863 _drawLabels: function() {
13864 var me = this;
13865 var ctx = me.ctx;
13866 var opts = me.options;
13867 var tickOpts = opts.ticks;
13868
13869 if (!tickOpts.display) {
13870 return;
13871 }
13872
13873 var startAngle = me.getIndexAngle(0);
13874 var tickFont = helpers$1.options._parseFont(tickOpts);
13875 var tickFontColor = valueOrDefault$c(tickOpts.fontColor, core_defaults.global.defaultFontColor);
13876 var offset, width;
13877
13878 ctx.save();
13879 ctx.font = tickFont.string;
13880 ctx.translate(me.xCenter, me.yCenter);
13881 ctx.rotate(startAngle);
13882 ctx.textAlign = 'center';
13883 ctx.textBaseline = 'middle';
13884
13885 helpers$1.each(me.ticks, function(label, index) {
13886 if (index === 0 && !tickOpts.reverse) {
13887 return;
13888 }
13889
13890 offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
13891
13892 if (tickOpts.showLabelBackdrop) {
13893 width = ctx.measureText(label).width;
13894 ctx.fillStyle = tickOpts.backdropColor;
13895
13896 ctx.fillRect(
13897 -width / 2 - tickOpts.backdropPaddingX,
13898 -offset - tickFont.size / 2 - tickOpts.backdropPaddingY,
13899 width + tickOpts.backdropPaddingX * 2,
13900 tickFont.size + tickOpts.backdropPaddingY * 2
13901 );
13902 }
13903
13904 ctx.fillStyle = tickFontColor;
13905 ctx.fillText(label, 0, -offset);
13906 });
13907
13908 ctx.restore();
13909 },
13910
13911 /**
13912 * @private
13913 */
13914 _drawTitle: helpers$1.noop
13915});
13916
13917// INTERNAL: static default options, registered in src/index.js
13918var _defaults$3 = defaultConfig$3;
13919scale_radialLinear._defaults = _defaults$3;
13920
13921var deprecated$1 = helpers$1._deprecated;
13922var resolve$5 = helpers$1.options.resolve;
13923var valueOrDefault$d = helpers$1.valueOrDefault;
13924
13925// Integer constants are from the ES6 spec.
13926var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
13927var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
13928
13929var INTERVALS = {
13930 millisecond: {
13931 common: true,
13932 size: 1,
13933 steps: 1000
13934 },
13935 second: {
13936 common: true,
13937 size: 1000,
13938 steps: 60
13939 },
13940 minute: {
13941 common: true,
13942 size: 60000,
13943 steps: 60
13944 },
13945 hour: {
13946 common: true,
13947 size: 3600000,
13948 steps: 24
13949 },
13950 day: {
13951 common: true,
13952 size: 86400000,
13953 steps: 30
13954 },
13955 week: {
13956 common: false,
13957 size: 604800000,
13958 steps: 4
13959 },
13960 month: {
13961 common: true,
13962 size: 2.628e9,
13963 steps: 12
13964 },
13965 quarter: {
13966 common: false,
13967 size: 7.884e9,
13968 steps: 4
13969 },
13970 year: {
13971 common: true,
13972 size: 3.154e10
13973 }
13974};
13975
13976var UNITS = Object.keys(INTERVALS);
13977
13978function sorter(a, b) {
13979 return a - b;
13980}
13981
13982function arrayUnique(items) {
13983 var hash = {};
13984 var out = [];
13985 var i, ilen, item;
13986
13987 for (i = 0, ilen = items.length; i < ilen; ++i) {
13988 item = items[i];
13989 if (!hash[item]) {
13990 hash[item] = true;
13991 out.push(item);
13992 }
13993 }
13994
13995 return out;
13996}
13997
13998function getMin(options) {
13999 return helpers$1.valueOrDefault(options.time.min, options.ticks.min);
14000}
14001
14002function getMax(options) {
14003 return helpers$1.valueOrDefault(options.time.max, options.ticks.max);
14004}
14005
14006/**
14007 * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
14008 * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
14009 * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
14010 * extremity (left + width or top + height). Note that it would be more optimized to directly
14011 * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
14012 * to create the lookup table. The table ALWAYS contains at least two items: min and max.
14013 *
14014 * @param {number[]} timestamps - timestamps sorted from lowest to highest.
14015 * @param {string} distribution - If 'linear', timestamps will be spread linearly along the min
14016 * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
14017 * If 'series', timestamps will be positioned at the same distance from each other. In this
14018 * case, only timestamps that break the time linearity are registered, meaning that in the
14019 * best case, all timestamps are linear, the table contains only min and max.
14020 */
14021function buildLookupTable(timestamps, min, max, distribution) {
14022 if (distribution === 'linear' || !timestamps.length) {
14023 return [
14024 {time: min, pos: 0},
14025 {time: max, pos: 1}
14026 ];
14027 }
14028
14029 var table = [];
14030 var items = [min];
14031 var i, ilen, prev, curr, next;
14032
14033 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
14034 curr = timestamps[i];
14035 if (curr > min && curr < max) {
14036 items.push(curr);
14037 }
14038 }
14039
14040 items.push(max);
14041
14042 for (i = 0, ilen = items.length; i < ilen; ++i) {
14043 next = items[i + 1];
14044 prev = items[i - 1];
14045 curr = items[i];
14046
14047 // only add points that breaks the scale linearity
14048 if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
14049 table.push({time: curr, pos: i / (ilen - 1)});
14050 }
14051 }
14052
14053 return table;
14054}
14055
14056// @see adapted from https://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
14057function lookup(table, key, value) {
14058 var lo = 0;
14059 var hi = table.length - 1;
14060 var mid, i0, i1;
14061
14062 while (lo >= 0 && lo <= hi) {
14063 mid = (lo + hi) >> 1;
14064 i0 = table[mid - 1] || null;
14065 i1 = table[mid];
14066
14067 if (!i0) {
14068 // given value is outside table (before first item)
14069 return {lo: null, hi: i1};
14070 } else if (i1[key] < value) {
14071 lo = mid + 1;
14072 } else if (i0[key] > value) {
14073 hi = mid - 1;
14074 } else {
14075 return {lo: i0, hi: i1};
14076 }
14077 }
14078
14079 // given value is outside table (after last item)
14080 return {lo: i1, hi: null};
14081}
14082
14083/**
14084 * Linearly interpolates the given source `value` using the table items `skey` values and
14085 * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
14086 * returns the position for a timestamp equal to 42. If value is out of bounds, values at
14087 * index [0, 1] or [n - 1, n] are used for the interpolation.
14088 */
14089function interpolate$1(table, skey, sval, tkey) {
14090 var range = lookup(table, skey, sval);
14091
14092 // Note: the lookup table ALWAYS contains at least 2 items (min and max)
14093 var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
14094 var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
14095
14096 var span = next[skey] - prev[skey];
14097 var ratio = span ? (sval - prev[skey]) / span : 0;
14098 var offset = (next[tkey] - prev[tkey]) * ratio;
14099
14100 return prev[tkey] + offset;
14101}
14102
14103function toTimestamp(scale, input) {
14104 var adapter = scale._adapter;
14105 var options = scale.options.time;
14106 var parser = options.parser;
14107 var format = parser || options.format;
14108 var value = input;
14109
14110 if (typeof parser === 'function') {
14111 value = parser(value);
14112 }
14113
14114 // Only parse if its not a timestamp already
14115 if (!helpers$1.isFinite(value)) {
14116 value = typeof format === 'string'
14117 ? adapter.parse(value, format)
14118 : adapter.parse(value);
14119 }
14120
14121 if (value !== null) {
14122 return +value;
14123 }
14124
14125 // Labels are in an incompatible format and no `parser` has been provided.
14126 // The user might still use the deprecated `format` option for parsing.
14127 if (!parser && typeof format === 'function') {
14128 value = format(input);
14129
14130 // `format` could return something else than a timestamp, if so, parse it
14131 if (!helpers$1.isFinite(value)) {
14132 value = adapter.parse(value);
14133 }
14134 }
14135
14136 return value;
14137}
14138
14139function parse(scale, input) {
14140 if (helpers$1.isNullOrUndef(input)) {
14141 return null;
14142 }
14143
14144 var options = scale.options.time;
14145 var value = toTimestamp(scale, scale.getRightValue(input));
14146 if (value === null) {
14147 return value;
14148 }
14149
14150 if (options.round) {
14151 value = +scale._adapter.startOf(value, options.round);
14152 }
14153
14154 return value;
14155}
14156
14157/**
14158 * Figures out what unit results in an appropriate number of auto-generated ticks
14159 */
14160function determineUnitForAutoTicks(minUnit, min, max, capacity) {
14161 var ilen = UNITS.length;
14162 var i, interval, factor;
14163
14164 for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
14165 interval = INTERVALS[UNITS[i]];
14166 factor = interval.steps ? interval.steps : MAX_INTEGER;
14167
14168 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
14169 return UNITS[i];
14170 }
14171 }
14172
14173 return UNITS[ilen - 1];
14174}
14175
14176/**
14177 * Figures out what unit to format a set of ticks with
14178 */
14179function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
14180 var i, unit;
14181
14182 for (i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) {
14183 unit = UNITS[i];
14184 if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
14185 return unit;
14186 }
14187 }
14188
14189 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
14190}
14191
14192function determineMajorUnit(unit) {
14193 for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
14194 if (INTERVALS[UNITS[i]].common) {
14195 return UNITS[i];
14196 }
14197 }
14198}
14199
14200/**
14201 * Generates a maximum of `capacity` timestamps between min and max, rounded to the
14202 * `minor` unit using the given scale time `options`.
14203 * Important: this method can return ticks outside the min and max range, it's the
14204 * responsibility of the calling code to clamp values if needed.
14205 */
14206function generate(scale, min, max, capacity) {
14207 var adapter = scale._adapter;
14208 var options = scale.options;
14209 var timeOpts = options.time;
14210 var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
14211 var stepSize = resolve$5([timeOpts.stepSize, timeOpts.unitStepSize, 1]);
14212 var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
14213 var first = min;
14214 var ticks = [];
14215 var time;
14216
14217 // For 'week' unit, handle the first day of week option
14218 if (weekday) {
14219 first = +adapter.startOf(first, 'isoWeek', weekday);
14220 }
14221
14222 // Align first ticks on unit
14223 first = +adapter.startOf(first, weekday ? 'day' : minor);
14224
14225 // Prevent browser from freezing in case user options request millions of milliseconds
14226 if (adapter.diff(max, min, minor) > 100000 * stepSize) {
14227 throw min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor;
14228 }
14229
14230 for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) {
14231 ticks.push(time);
14232 }
14233
14234 if (time === max || options.bounds === 'ticks') {
14235 ticks.push(time);
14236 }
14237
14238 return ticks;
14239}
14240
14241/**
14242 * Returns the start and end offsets from edges in the form of {start, end}
14243 * where each value is a relative width to the scale and ranges between 0 and 1.
14244 * They add extra margins on the both sides by scaling down the original scale.
14245 * Offsets are added when the `offset` option is true.
14246 */
14247function computeOffsets(table, ticks, min, max, options) {
14248 var start = 0;
14249 var end = 0;
14250 var first, last;
14251
14252 if (options.offset && ticks.length) {
14253 first = interpolate$1(table, 'time', ticks[0], 'pos');
14254 if (ticks.length === 1) {
14255 start = 1 - first;
14256 } else {
14257 start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2;
14258 }
14259 last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos');
14260 if (ticks.length === 1) {
14261 end = last;
14262 } else {
14263 end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2;
14264 }
14265 }
14266
14267 return {start: start, end: end, factor: 1 / (start + 1 + end)};
14268}
14269
14270function setMajorTicks(scale, ticks, map, majorUnit) {
14271 var adapter = scale._adapter;
14272 var first = +adapter.startOf(ticks[0].value, majorUnit);
14273 var last = ticks[ticks.length - 1].value;
14274 var major, index;
14275
14276 for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) {
14277 index = map[major];
14278 if (index >= 0) {
14279 ticks[index].major = true;
14280 }
14281 }
14282 return ticks;
14283}
14284
14285function ticksFromTimestamps(scale, values, majorUnit) {
14286 var ticks = [];
14287 var map = {};
14288 var ilen = values.length;
14289 var i, value;
14290
14291 for (i = 0; i < ilen; ++i) {
14292 value = values[i];
14293 map[value] = i;
14294
14295 ticks.push({
14296 value: value,
14297 major: false
14298 });
14299 }
14300
14301 // We set the major ticks separately from the above loop because calling startOf for every tick
14302 // is expensive when there is a large number of ticks
14303 return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
14304}
14305
14306var defaultConfig$4 = {
14307 position: 'bottom',
14308
14309 /**
14310 * Data distribution along the scale:
14311 * - 'linear': data are spread according to their time (distances can vary),
14312 * - 'series': data are spread at the same distance from each other.
14313 * @see https://github.com/chartjs/Chart.js/pull/4507
14314 * @since 2.7.0
14315 */
14316 distribution: 'linear',
14317
14318 /**
14319 * Scale boundary strategy (bypassed by min/max time options)
14320 * - `data`: make sure data are fully visible, ticks outside are removed
14321 * - `ticks`: make sure ticks are fully visible, data outside are truncated
14322 * @see https://github.com/chartjs/Chart.js/pull/4556
14323 * @since 2.7.0
14324 */
14325 bounds: 'data',
14326
14327 adapters: {},
14328 time: {
14329 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
14330 unit: false, // false == automatic or override with week, month, year, etc.
14331 round: false, // none, or override with week, month, year, etc.
14332 displayFormat: false, // DEPRECATED
14333 isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/
14334 minUnit: 'millisecond',
14335 displayFormats: {}
14336 },
14337 ticks: {
14338 autoSkip: false,
14339
14340 /**
14341 * Ticks generation input values:
14342 * - 'auto': generates "optimal" ticks based on scale size and time options.
14343 * - 'data': generates ticks from data (including labels from data {t|x|y} objects).
14344 * - 'labels': generates ticks from user given `data.labels` values ONLY.
14345 * @see https://github.com/chartjs/Chart.js/pull/4507
14346 * @since 2.7.0
14347 */
14348 source: 'auto',
14349
14350 major: {
14351 enabled: false
14352 }
14353 }
14354};
14355
14356var scale_time = core_scale.extend({
14357 initialize: function() {
14358 this.mergeTicksOptions();
14359 core_scale.prototype.initialize.call(this);
14360 },
14361
14362 update: function() {
14363 var me = this;
14364 var options = me.options;
14365 var time = options.time || (options.time = {});
14366 var adapter = me._adapter = new core_adapters._date(options.adapters.date);
14367
14368 // DEPRECATIONS: output a message only one time per update
14369 deprecated$1('time scale', time.format, 'time.format', 'time.parser');
14370 deprecated$1('time scale', time.min, 'time.min', 'ticks.min');
14371 deprecated$1('time scale', time.max, 'time.max', 'ticks.max');
14372
14373 // Backward compatibility: before introducing adapter, `displayFormats` was
14374 // supposed to contain *all* unit/string pairs but this can't be resolved
14375 // when loading the scale (adapters are loaded afterward), so let's populate
14376 // missing formats on update
14377 helpers$1.mergeIf(time.displayFormats, adapter.formats());
14378
14379 return core_scale.prototype.update.apply(me, arguments);
14380 },
14381
14382 /**
14383 * Allows data to be referenced via 't' attribute
14384 */
14385 getRightValue: function(rawValue) {
14386 if (rawValue && rawValue.t !== undefined) {
14387 rawValue = rawValue.t;
14388 }
14389 return core_scale.prototype.getRightValue.call(this, rawValue);
14390 },
14391
14392 determineDataLimits: function() {
14393 var me = this;
14394 var chart = me.chart;
14395 var adapter = me._adapter;
14396 var options = me.options;
14397 var unit = options.time.unit || 'day';
14398 var min = MAX_INTEGER;
14399 var max = MIN_INTEGER;
14400 var timestamps = [];
14401 var datasets = [];
14402 var labels = [];
14403 var i, j, ilen, jlen, data, timestamp, labelsAdded;
14404 var dataLabels = me._getLabels();
14405
14406 for (i = 0, ilen = dataLabels.length; i < ilen; ++i) {
14407 labels.push(parse(me, dataLabels[i]));
14408 }
14409
14410 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
14411 if (chart.isDatasetVisible(i)) {
14412 data = chart.data.datasets[i].data;
14413
14414 // Let's consider that all data have the same format.
14415 if (helpers$1.isObject(data[0])) {
14416 datasets[i] = [];
14417
14418 for (j = 0, jlen = data.length; j < jlen; ++j) {
14419 timestamp = parse(me, data[j]);
14420 timestamps.push(timestamp);
14421 datasets[i][j] = timestamp;
14422 }
14423 } else {
14424 datasets[i] = labels.slice(0);
14425 if (!labelsAdded) {
14426 timestamps = timestamps.concat(labels);
14427 labelsAdded = true;
14428 }
14429 }
14430 } else {
14431 datasets[i] = [];
14432 }
14433 }
14434
14435 if (labels.length) {
14436 min = Math.min(min, labels[0]);
14437 max = Math.max(max, labels[labels.length - 1]);
14438 }
14439
14440 if (timestamps.length) {
14441 timestamps = ilen > 1 ? arrayUnique(timestamps).sort(sorter) : timestamps.sort(sorter);
14442 min = Math.min(min, timestamps[0]);
14443 max = Math.max(max, timestamps[timestamps.length - 1]);
14444 }
14445
14446 min = parse(me, getMin(options)) || min;
14447 max = parse(me, getMax(options)) || max;
14448
14449 // In case there is no valid min/max, set limits based on unit time option
14450 min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min;
14451 max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), unit) + 1 : max;
14452
14453 // Make sure that max is strictly higher than min (required by the lookup table)
14454 me.min = Math.min(min, max);
14455 me.max = Math.max(min + 1, max);
14456
14457 // PRIVATE
14458 me._table = [];
14459 me._timestamps = {
14460 data: timestamps,
14461 datasets: datasets,
14462 labels: labels
14463 };
14464 },
14465
14466 buildTicks: function() {
14467 var me = this;
14468 var min = me.min;
14469 var max = me.max;
14470 var options = me.options;
14471 var tickOpts = options.ticks;
14472 var timeOpts = options.time;
14473 var timestamps = me._timestamps;
14474 var ticks = [];
14475 var capacity = me.getLabelCapacity(min);
14476 var source = tickOpts.source;
14477 var distribution = options.distribution;
14478 var i, ilen, timestamp;
14479
14480 if (source === 'data' || (source === 'auto' && distribution === 'series')) {
14481 timestamps = timestamps.data;
14482 } else if (source === 'labels') {
14483 timestamps = timestamps.labels;
14484 } else {
14485 timestamps = generate(me, min, max, capacity);
14486 }
14487
14488 if (options.bounds === 'ticks' && timestamps.length) {
14489 min = timestamps[0];
14490 max = timestamps[timestamps.length - 1];
14491 }
14492
14493 // Enforce limits with user min/max options
14494 min = parse(me, getMin(options)) || min;
14495 max = parse(me, getMax(options)) || max;
14496
14497 // Remove ticks outside the min/max range
14498 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
14499 timestamp = timestamps[i];
14500 if (timestamp >= min && timestamp <= max) {
14501 ticks.push(timestamp);
14502 }
14503 }
14504
14505 me.min = min;
14506 me.max = max;
14507
14508 // PRIVATE
14509 // determineUnitForFormatting relies on the number of ticks so we don't use it when
14510 // autoSkip is enabled because we don't yet know what the final number of ticks will be
14511 me._unit = timeOpts.unit || (tickOpts.autoSkip
14512 ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, capacity)
14513 : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max));
14514 me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined
14515 : determineMajorUnit(me._unit);
14516 me._table = buildLookupTable(me._timestamps.data, min, max, distribution);
14517 me._offsets = computeOffsets(me._table, ticks, min, max, options);
14518
14519 if (tickOpts.reverse) {
14520 ticks.reverse();
14521 }
14522
14523 return ticksFromTimestamps(me, ticks, me._majorUnit);
14524 },
14525
14526 getLabelForIndex: function(index, datasetIndex) {
14527 var me = this;
14528 var adapter = me._adapter;
14529 var data = me.chart.data;
14530 var timeOpts = me.options.time;
14531 var label = data.labels && index < data.labels.length ? data.labels[index] : '';
14532 var value = data.datasets[datasetIndex].data[index];
14533
14534 if (helpers$1.isObject(value)) {
14535 label = me.getRightValue(value);
14536 }
14537 if (timeOpts.tooltipFormat) {
14538 return adapter.format(toTimestamp(me, label), timeOpts.tooltipFormat);
14539 }
14540 if (typeof label === 'string') {
14541 return label;
14542 }
14543 return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime);
14544 },
14545
14546 /**
14547 * Function to format an individual tick mark
14548 * @private
14549 */
14550 tickFormatFunction: function(time, index, ticks, format) {
14551 var me = this;
14552 var adapter = me._adapter;
14553 var options = me.options;
14554 var formats = options.time.displayFormats;
14555 var minorFormat = formats[me._unit];
14556 var majorUnit = me._majorUnit;
14557 var majorFormat = formats[majorUnit];
14558 var tick = ticks[index];
14559 var tickOpts = options.ticks;
14560 var major = majorUnit && majorFormat && tick && tick.major;
14561 var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat);
14562 var nestedTickOpts = major ? tickOpts.major : tickOpts.minor;
14563 var formatter = resolve$5([
14564 nestedTickOpts.callback,
14565 nestedTickOpts.userCallback,
14566 tickOpts.callback,
14567 tickOpts.userCallback
14568 ]);
14569
14570 return formatter ? formatter(label, index, ticks) : label;
14571 },
14572
14573 convertTicksToLabels: function(ticks) {
14574 var labels = [];
14575 var i, ilen;
14576
14577 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
14578 labels.push(this.tickFormatFunction(ticks[i].value, i, ticks));
14579 }
14580
14581 return labels;
14582 },
14583
14584 /**
14585 * @private
14586 */
14587 getPixelForOffset: function(time) {
14588 var me = this;
14589 var offsets = me._offsets;
14590 var pos = interpolate$1(me._table, 'time', time, 'pos');
14591 return me.getPixelForDecimal((offsets.start + pos) * offsets.factor);
14592 },
14593
14594 getPixelForValue: function(value, index, datasetIndex) {
14595 var me = this;
14596 var time = null;
14597
14598 if (index !== undefined && datasetIndex !== undefined) {
14599 time = me._timestamps.datasets[datasetIndex][index];
14600 }
14601
14602 if (time === null) {
14603 time = parse(me, value);
14604 }
14605
14606 if (time !== null) {
14607 return me.getPixelForOffset(time);
14608 }
14609 },
14610
14611 getPixelForTick: function(index) {
14612 var ticks = this.getTicks();
14613 return index >= 0 && index < ticks.length ?
14614 this.getPixelForOffset(ticks[index].value) :
14615 null;
14616 },
14617
14618 getValueForPixel: function(pixel) {
14619 var me = this;
14620 var offsets = me._offsets;
14621 var pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
14622 var time = interpolate$1(me._table, 'pos', pos, 'time');
14623
14624 // DEPRECATION, we should return time directly
14625 return me._adapter._create(time);
14626 },
14627
14628 /**
14629 * @private
14630 */
14631 _getLabelSize: function(label) {
14632 var me = this;
14633 var ticksOpts = me.options.ticks;
14634 var tickLabelWidth = me.ctx.measureText(label).width;
14635 var angle = helpers$1.toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
14636 var cosRotation = Math.cos(angle);
14637 var sinRotation = Math.sin(angle);
14638 var tickFontSize = valueOrDefault$d(ticksOpts.fontSize, core_defaults.global.defaultFontSize);
14639
14640 return {
14641 w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation),
14642 h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation)
14643 };
14644 },
14645
14646 /**
14647 * Crude approximation of what the label width might be
14648 * @private
14649 */
14650 getLabelWidth: function(label) {
14651 return this._getLabelSize(label).w;
14652 },
14653
14654 /**
14655 * @private
14656 */
14657 getLabelCapacity: function(exampleTime) {
14658 var me = this;
14659 var timeOpts = me.options.time;
14660 var displayFormats = timeOpts.displayFormats;
14661
14662 // pick the longest format (milliseconds) for guestimation
14663 var format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
14664 var exampleLabel = me.tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format);
14665 var size = me._getLabelSize(exampleLabel);
14666 var capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h);
14667
14668 if (me.options.offset) {
14669 capacity--;
14670 }
14671
14672 return capacity > 0 ? capacity : 1;
14673 }
14674});
14675
14676// INTERNAL: static default options, registered in src/index.js
14677var _defaults$4 = defaultConfig$4;
14678scale_time._defaults = _defaults$4;
14679
14680var scales = {
14681 category: scale_category,
14682 linear: scale_linear,
14683 logarithmic: scale_logarithmic,
14684 radialLinear: scale_radialLinear,
14685 time: scale_time
14686};
14687
14688var FORMATS = {
14689 datetime: 'MMM D, YYYY, h:mm:ss a',
14690 millisecond: 'h:mm:ss.SSS a',
14691 second: 'h:mm:ss a',
14692 minute: 'h:mm a',
14693 hour: 'hA',
14694 day: 'MMM D',
14695 week: 'll',
14696 month: 'MMM YYYY',
14697 quarter: '[Q]Q - YYYY',
14698 year: 'YYYY'
14699};
14700
14701core_adapters._date.override(typeof moment === 'function' ? {
14702 _id: 'moment', // DEBUG ONLY
14703
14704 formats: function() {
14705 return FORMATS;
14706 },
14707
14708 parse: function(value, format) {
14709 if (typeof value === 'string' && typeof format === 'string') {
14710 value = moment(value, format);
14711 } else if (!(value instanceof moment)) {
14712 value = moment(value);
14713 }
14714 return value.isValid() ? value.valueOf() : null;
14715 },
14716
14717 format: function(time, format) {
14718 return moment(time).format(format);
14719 },
14720
14721 add: function(time, amount, unit) {
14722 return moment(time).add(amount, unit).valueOf();
14723 },
14724
14725 diff: function(max, min, unit) {
14726 return moment(max).diff(moment(min), unit);
14727 },
14728
14729 startOf: function(time, unit, weekday) {
14730 time = moment(time);
14731 if (unit === 'isoWeek') {
14732 return time.isoWeekday(weekday).valueOf();
14733 }
14734 return time.startOf(unit).valueOf();
14735 },
14736
14737 endOf: function(time, unit) {
14738 return moment(time).endOf(unit).valueOf();
14739 },
14740
14741 // DEPRECATIONS
14742
14743 /**
14744 * Provided for backward compatibility with scale.getValueForPixel().
14745 * @deprecated since version 2.8.0
14746 * @todo remove at version 3
14747 * @private
14748 */
14749 _create: function(time) {
14750 return moment(time);
14751 },
14752} : {});
14753
14754core_defaults._set('global', {
14755 plugins: {
14756 filler: {
14757 propagate: true
14758 }
14759 }
14760});
14761
14762var mappers = {
14763 dataset: function(source) {
14764 var index = source.fill;
14765 var chart = source.chart;
14766 var meta = chart.getDatasetMeta(index);
14767 var visible = meta && chart.isDatasetVisible(index);
14768 var points = (visible && meta.dataset._children) || [];
14769 var length = points.length || 0;
14770
14771 return !length ? null : function(point, i) {
14772 return (i < length && points[i]._view) || null;
14773 };
14774 },
14775
14776 boundary: function(source) {
14777 var boundary = source.boundary;
14778 var x = boundary ? boundary.x : null;
14779 var y = boundary ? boundary.y : null;
14780
14781 if (helpers$1.isArray(boundary)) {
14782 return function(point, i) {
14783 return boundary[i];
14784 };
14785 }
14786
14787 return function(point) {
14788 return {
14789 x: x === null ? point.x : x,
14790 y: y === null ? point.y : y,
14791 };
14792 };
14793 }
14794};
14795
14796// @todo if (fill[0] === '#')
14797function decodeFill(el, index, count) {
14798 var model = el._model || {};
14799 var fill = model.fill;
14800 var target;
14801
14802 if (fill === undefined) {
14803 fill = !!model.backgroundColor;
14804 }
14805
14806 if (fill === false || fill === null) {
14807 return false;
14808 }
14809
14810 if (fill === true) {
14811 return 'origin';
14812 }
14813
14814 target = parseFloat(fill, 10);
14815 if (isFinite(target) && Math.floor(target) === target) {
14816 if (fill[0] === '-' || fill[0] === '+') {
14817 target = index + target;
14818 }
14819
14820 if (target === index || target < 0 || target >= count) {
14821 return false;
14822 }
14823
14824 return target;
14825 }
14826
14827 switch (fill) {
14828 // compatibility
14829 case 'bottom':
14830 return 'start';
14831 case 'top':
14832 return 'end';
14833 case 'zero':
14834 return 'origin';
14835 // supported boundaries
14836 case 'origin':
14837 case 'start':
14838 case 'end':
14839 return fill;
14840 // invalid fill values
14841 default:
14842 return false;
14843 }
14844}
14845
14846function computeLinearBoundary(source) {
14847 var model = source.el._model || {};
14848 var scale = source.el._scale || {};
14849 var fill = source.fill;
14850 var target = null;
14851 var horizontal;
14852
14853 if (isFinite(fill)) {
14854 return null;
14855 }
14856
14857 // Backward compatibility: until v3, we still need to support boundary values set on
14858 // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
14859 // controllers might still use it (e.g. the Smith chart).
14860
14861 if (fill === 'start') {
14862 target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
14863 } else if (fill === 'end') {
14864 target = model.scaleTop === undefined ? scale.top : model.scaleTop;
14865 } else if (model.scaleZero !== undefined) {
14866 target = model.scaleZero;
14867 } else if (scale.getBasePixel) {
14868 target = scale.getBasePixel();
14869 }
14870
14871 if (target !== undefined && target !== null) {
14872 if (target.x !== undefined && target.y !== undefined) {
14873 return target;
14874 }
14875
14876 if (helpers$1.isFinite(target)) {
14877 horizontal = scale.isHorizontal();
14878 return {
14879 x: horizontal ? target : null,
14880 y: horizontal ? null : target
14881 };
14882 }
14883 }
14884
14885 return null;
14886}
14887
14888function computeCircularBoundary(source) {
14889 var scale = source.el._scale;
14890 var options = scale.options;
14891 var length = scale.chart.data.labels.length;
14892 var fill = source.fill;
14893 var target = [];
14894 var start, end, center, i, point;
14895
14896 if (!length) {
14897 return null;
14898 }
14899
14900 start = options.ticks.reverse ? scale.max : scale.min;
14901 end = options.ticks.reverse ? scale.min : scale.max;
14902 center = scale.getPointPositionForValue(0, start);
14903 for (i = 0; i < length; ++i) {
14904 point = fill === 'start' || fill === 'end'
14905 ? scale.getPointPositionForValue(i, fill === 'start' ? start : end)
14906 : scale.getBasePosition(i);
14907 if (options.gridLines.circular) {
14908 point.cx = center.x;
14909 point.cy = center.y;
14910 point.angle = scale.getIndexAngle(i) - Math.PI / 2;
14911 }
14912 target.push(point);
14913 }
14914 return target;
14915}
14916
14917function computeBoundary(source) {
14918 var scale = source.el._scale || {};
14919
14920 if (scale.getPointPositionForValue) {
14921 return computeCircularBoundary(source);
14922 }
14923 return computeLinearBoundary(source);
14924}
14925
14926function resolveTarget(sources, index, propagate) {
14927 var source = sources[index];
14928 var fill = source.fill;
14929 var visited = [index];
14930 var target;
14931
14932 if (!propagate) {
14933 return fill;
14934 }
14935
14936 while (fill !== false && visited.indexOf(fill) === -1) {
14937 if (!isFinite(fill)) {
14938 return fill;
14939 }
14940
14941 target = sources[fill];
14942 if (!target) {
14943 return false;
14944 }
14945
14946 if (target.visible) {
14947 return fill;
14948 }
14949
14950 visited.push(fill);
14951 fill = target.fill;
14952 }
14953
14954 return false;
14955}
14956
14957function createMapper(source) {
14958 var fill = source.fill;
14959 var type = 'dataset';
14960
14961 if (fill === false) {
14962 return null;
14963 }
14964
14965 if (!isFinite(fill)) {
14966 type = 'boundary';
14967 }
14968
14969 return mappers[type](source);
14970}
14971
14972function isDrawable(point) {
14973 return point && !point.skip;
14974}
14975
14976function drawArea(ctx, curve0, curve1, len0, len1) {
14977 var i, cx, cy, r;
14978
14979 if (!len0 || !len1) {
14980 return;
14981 }
14982
14983 // building first area curve (normal)
14984 ctx.moveTo(curve0[0].x, curve0[0].y);
14985 for (i = 1; i < len0; ++i) {
14986 helpers$1.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
14987 }
14988
14989 if (curve1[0].angle !== undefined) {
14990 cx = curve1[0].cx;
14991 cy = curve1[0].cy;
14992 r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2));
14993 for (i = len1 - 1; i > 0; --i) {
14994 ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true);
14995 }
14996 return;
14997 }
14998
14999 // joining the two area curves
15000 ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
15001
15002 // building opposite area curve (reverse)
15003 for (i = len1 - 1; i > 0; --i) {
15004 helpers$1.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
15005 }
15006}
15007
15008function doFill(ctx, points, mapper, view, color, loop) {
15009 var count = points.length;
15010 var span = view.spanGaps;
15011 var curve0 = [];
15012 var curve1 = [];
15013 var len0 = 0;
15014 var len1 = 0;
15015 var i, ilen, index, p0, p1, d0, d1, loopOffset;
15016
15017 ctx.beginPath();
15018
15019 for (i = 0, ilen = count; i < ilen; ++i) {
15020 index = i % count;
15021 p0 = points[index]._view;
15022 p1 = mapper(p0, index, view);
15023 d0 = isDrawable(p0);
15024 d1 = isDrawable(p1);
15025
15026 if (loop && loopOffset === undefined && d0) {
15027 loopOffset = i + 1;
15028 ilen = count + loopOffset;
15029 }
15030
15031 if (d0 && d1) {
15032 len0 = curve0.push(p0);
15033 len1 = curve1.push(p1);
15034 } else if (len0 && len1) {
15035 if (!span) {
15036 drawArea(ctx, curve0, curve1, len0, len1);
15037 len0 = len1 = 0;
15038 curve0 = [];
15039 curve1 = [];
15040 } else {
15041 if (d0) {
15042 curve0.push(p0);
15043 }
15044 if (d1) {
15045 curve1.push(p1);
15046 }
15047 }
15048 }
15049 }
15050
15051 drawArea(ctx, curve0, curve1, len0, len1);
15052
15053 ctx.closePath();
15054 ctx.fillStyle = color;
15055 ctx.fill();
15056}
15057
15058var plugin_filler = {
15059 id: 'filler',
15060
15061 afterDatasetsUpdate: function(chart, options) {
15062 var count = (chart.data.datasets || []).length;
15063 var propagate = options.propagate;
15064 var sources = [];
15065 var meta, i, el, source;
15066
15067 for (i = 0; i < count; ++i) {
15068 meta = chart.getDatasetMeta(i);
15069 el = meta.dataset;
15070 source = null;
15071
15072 if (el && el._model && el instanceof elements.Line) {
15073 source = {
15074 visible: chart.isDatasetVisible(i),
15075 fill: decodeFill(el, i, count),
15076 chart: chart,
15077 el: el
15078 };
15079 }
15080
15081 meta.$filler = source;
15082 sources.push(source);
15083 }
15084
15085 for (i = 0; i < count; ++i) {
15086 source = sources[i];
15087 if (!source) {
15088 continue;
15089 }
15090
15091 source.fill = resolveTarget(sources, i, propagate);
15092 source.boundary = computeBoundary(source);
15093 source.mapper = createMapper(source);
15094 }
15095 },
15096
15097 beforeDatasetsDraw: function(chart) {
15098 var metasets = chart._getSortedVisibleDatasetMetas();
15099 var ctx = chart.ctx;
15100 var meta, i, el, view, points, mapper, color;
15101
15102 for (i = metasets.length - 1; i >= 0; --i) {
15103 meta = metasets[i].$filler;
15104
15105 if (!meta || !meta.visible) {
15106 continue;
15107 }
15108
15109 el = meta.el;
15110 view = el._view;
15111 points = el._children || [];
15112 mapper = meta.mapper;
15113 color = view.backgroundColor || core_defaults.global.defaultColor;
15114
15115 if (mapper && color && points.length) {
15116 helpers$1.canvas.clipArea(ctx, chart.chartArea);
15117 doFill(ctx, points, mapper, view, color, el._loop);
15118 helpers$1.canvas.unclipArea(ctx);
15119 }
15120 }
15121 }
15122};
15123
15124var getRtlHelper$1 = helpers$1.rtl.getRtlAdapter;
15125var noop$1 = helpers$1.noop;
15126var valueOrDefault$e = helpers$1.valueOrDefault;
15127
15128core_defaults._set('global', {
15129 legend: {
15130 display: true,
15131 position: 'top',
15132 align: 'center',
15133 fullWidth: true,
15134 reverse: false,
15135 weight: 1000,
15136
15137 // a callback that will handle
15138 onClick: function(e, legendItem) {
15139 var index = legendItem.datasetIndex;
15140 var ci = this.chart;
15141 var meta = ci.getDatasetMeta(index);
15142
15143 // See controller.isDatasetVisible comment
15144 meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
15145
15146 // We hid a dataset ... rerender the chart
15147 ci.update();
15148 },
15149
15150 onHover: null,
15151 onLeave: null,
15152
15153 labels: {
15154 boxWidth: 40,
15155 padding: 10,
15156 // Generates labels shown in the legend
15157 // Valid properties to return:
15158 // text : text to display
15159 // fillStyle : fill of coloured box
15160 // strokeStyle: stroke of coloured box
15161 // hidden : if this legend item refers to a hidden item
15162 // lineCap : cap style for line
15163 // lineDash
15164 // lineDashOffset :
15165 // lineJoin :
15166 // lineWidth :
15167 generateLabels: function(chart) {
15168 var datasets = chart.data.datasets;
15169 var options = chart.options.legend || {};
15170 var usePointStyle = options.labels && options.labels.usePointStyle;
15171
15172 return chart._getSortedDatasetMetas().map(function(meta) {
15173 var style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
15174
15175 return {
15176 text: datasets[meta.index].label,
15177 fillStyle: style.backgroundColor,
15178 hidden: !chart.isDatasetVisible(meta.index),
15179 lineCap: style.borderCapStyle,
15180 lineDash: style.borderDash,
15181 lineDashOffset: style.borderDashOffset,
15182 lineJoin: style.borderJoinStyle,
15183 lineWidth: style.borderWidth,
15184 strokeStyle: style.borderColor,
15185 pointStyle: style.pointStyle,
15186 rotation: style.rotation,
15187
15188 // Below is extra data used for toggling the datasets
15189 datasetIndex: meta.index
15190 };
15191 }, this);
15192 }
15193 }
15194 },
15195
15196 legendCallback: function(chart) {
15197 var list = document.createElement('ul');
15198 var datasets = chart.data.datasets;
15199 var i, ilen, listItem, listItemSpan;
15200
15201 list.setAttribute('class', chart.id + '-legend');
15202
15203 for (i = 0, ilen = datasets.length; i < ilen; i++) {
15204 listItem = list.appendChild(document.createElement('li'));
15205 listItemSpan = listItem.appendChild(document.createElement('span'));
15206 listItemSpan.style.backgroundColor = datasets[i].backgroundColor;
15207 if (datasets[i].label) {
15208 listItem.appendChild(document.createTextNode(datasets[i].label));
15209 }
15210 }
15211
15212 return list.outerHTML;
15213 }
15214});
15215
15216/**
15217 * Helper function to get the box width based on the usePointStyle option
15218 * @param {object} labelopts - the label options on the legend
15219 * @param {number} fontSize - the label font size
15220 * @return {number} width of the color box area
15221 */
15222function getBoxWidth(labelOpts, fontSize) {
15223 return labelOpts.usePointStyle && labelOpts.boxWidth > fontSize ?
15224 fontSize :
15225 labelOpts.boxWidth;
15226}
15227
15228/**
15229 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
15230 */
15231var Legend = core_element.extend({
15232
15233 initialize: function(config) {
15234 var me = this;
15235 helpers$1.extend(me, config);
15236
15237 // Contains hit boxes for each dataset (in dataset order)
15238 me.legendHitBoxes = [];
15239
15240 /**
15241 * @private
15242 */
15243 me._hoveredItem = null;
15244
15245 // Are we in doughnut mode which has a different data type
15246 me.doughnutMode = false;
15247 },
15248
15249 // These methods are ordered by lifecycle. Utilities then follow.
15250 // Any function defined here is inherited by all legend types.
15251 // Any function can be extended by the legend type
15252
15253 beforeUpdate: noop$1,
15254 update: function(maxWidth, maxHeight, margins) {
15255 var me = this;
15256
15257 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
15258 me.beforeUpdate();
15259
15260 // Absorb the master measurements
15261 me.maxWidth = maxWidth;
15262 me.maxHeight = maxHeight;
15263 me.margins = margins;
15264
15265 // Dimensions
15266 me.beforeSetDimensions();
15267 me.setDimensions();
15268 me.afterSetDimensions();
15269 // Labels
15270 me.beforeBuildLabels();
15271 me.buildLabels();
15272 me.afterBuildLabels();
15273
15274 // Fit
15275 me.beforeFit();
15276 me.fit();
15277 me.afterFit();
15278 //
15279 me.afterUpdate();
15280
15281 return me.minSize;
15282 },
15283 afterUpdate: noop$1,
15284
15285 //
15286
15287 beforeSetDimensions: noop$1,
15288 setDimensions: function() {
15289 var me = this;
15290 // Set the unconstrained dimension before label rotation
15291 if (me.isHorizontal()) {
15292 // Reset position before calculating rotation
15293 me.width = me.maxWidth;
15294 me.left = 0;
15295 me.right = me.width;
15296 } else {
15297 me.height = me.maxHeight;
15298
15299 // Reset position before calculating rotation
15300 me.top = 0;
15301 me.bottom = me.height;
15302 }
15303
15304 // Reset padding
15305 me.paddingLeft = 0;
15306 me.paddingTop = 0;
15307 me.paddingRight = 0;
15308 me.paddingBottom = 0;
15309
15310 // Reset minSize
15311 me.minSize = {
15312 width: 0,
15313 height: 0
15314 };
15315 },
15316 afterSetDimensions: noop$1,
15317
15318 //
15319
15320 beforeBuildLabels: noop$1,
15321 buildLabels: function() {
15322 var me = this;
15323 var labelOpts = me.options.labels || {};
15324 var legendItems = helpers$1.callback(labelOpts.generateLabels, [me.chart], me) || [];
15325
15326 if (labelOpts.filter) {
15327 legendItems = legendItems.filter(function(item) {
15328 return labelOpts.filter(item, me.chart.data);
15329 });
15330 }
15331
15332 if (me.options.reverse) {
15333 legendItems.reverse();
15334 }
15335
15336 me.legendItems = legendItems;
15337 },
15338 afterBuildLabels: noop$1,
15339
15340 //
15341
15342 beforeFit: noop$1,
15343 fit: function() {
15344 var me = this;
15345 var opts = me.options;
15346 var labelOpts = opts.labels;
15347 var display = opts.display;
15348
15349 var ctx = me.ctx;
15350
15351 var labelFont = helpers$1.options._parseFont(labelOpts);
15352 var fontSize = labelFont.size;
15353
15354 // Reset hit boxes
15355 var hitboxes = me.legendHitBoxes = [];
15356
15357 var minSize = me.minSize;
15358 var isHorizontal = me.isHorizontal();
15359
15360 if (isHorizontal) {
15361 minSize.width = me.maxWidth; // fill all the width
15362 minSize.height = display ? 10 : 0;
15363 } else {
15364 minSize.width = display ? 10 : 0;
15365 minSize.height = me.maxHeight; // fill all the height
15366 }
15367
15368 // Increase sizes here
15369 if (!display) {
15370 me.width = minSize.width = me.height = minSize.height = 0;
15371 return;
15372 }
15373 ctx.font = labelFont.string;
15374
15375 if (isHorizontal) {
15376 // Labels
15377
15378 // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
15379 var lineWidths = me.lineWidths = [0];
15380 var totalHeight = 0;
15381
15382 ctx.textAlign = 'left';
15383 ctx.textBaseline = 'middle';
15384
15385 helpers$1.each(me.legendItems, function(legendItem, i) {
15386 var boxWidth = getBoxWidth(labelOpts, fontSize);
15387 var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
15388
15389 if (i === 0 || lineWidths[lineWidths.length - 1] + width + 2 * labelOpts.padding > minSize.width) {
15390 totalHeight += fontSize + labelOpts.padding;
15391 lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
15392 }
15393
15394 // Store the hitbox width and height here. Final position will be updated in `draw`
15395 hitboxes[i] = {
15396 left: 0,
15397 top: 0,
15398 width: width,
15399 height: fontSize
15400 };
15401
15402 lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
15403 });
15404
15405 minSize.height += totalHeight;
15406
15407 } else {
15408 var vPadding = labelOpts.padding;
15409 var columnWidths = me.columnWidths = [];
15410 var columnHeights = me.columnHeights = [];
15411 var totalWidth = labelOpts.padding;
15412 var currentColWidth = 0;
15413 var currentColHeight = 0;
15414
15415 helpers$1.each(me.legendItems, function(legendItem, i) {
15416 var boxWidth = getBoxWidth(labelOpts, fontSize);
15417 var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
15418
15419 // If too tall, go to new column
15420 if (i > 0 && currentColHeight + fontSize + 2 * vPadding > minSize.height) {
15421 totalWidth += currentColWidth + labelOpts.padding;
15422 columnWidths.push(currentColWidth); // previous column width
15423 columnHeights.push(currentColHeight);
15424 currentColWidth = 0;
15425 currentColHeight = 0;
15426 }
15427
15428 // Get max width
15429 currentColWidth = Math.max(currentColWidth, itemWidth);
15430 currentColHeight += fontSize + vPadding;
15431
15432 // Store the hitbox width and height here. Final position will be updated in `draw`
15433 hitboxes[i] = {
15434 left: 0,
15435 top: 0,
15436 width: itemWidth,
15437 height: fontSize
15438 };
15439 });
15440
15441 totalWidth += currentColWidth;
15442 columnWidths.push(currentColWidth);
15443 columnHeights.push(currentColHeight);
15444 minSize.width += totalWidth;
15445 }
15446
15447 me.width = minSize.width;
15448 me.height = minSize.height;
15449 },
15450 afterFit: noop$1,
15451
15452 // Shared Methods
15453 isHorizontal: function() {
15454 return this.options.position === 'top' || this.options.position === 'bottom';
15455 },
15456
15457 // Actually draw the legend on the canvas
15458 draw: function() {
15459 var me = this;
15460 var opts = me.options;
15461 var labelOpts = opts.labels;
15462 var globalDefaults = core_defaults.global;
15463 var defaultColor = globalDefaults.defaultColor;
15464 var lineDefault = globalDefaults.elements.line;
15465 var legendHeight = me.height;
15466 var columnHeights = me.columnHeights;
15467 var legendWidth = me.width;
15468 var lineWidths = me.lineWidths;
15469
15470 if (!opts.display) {
15471 return;
15472 }
15473
15474 var rtlHelper = getRtlHelper$1(opts.rtl, me.left, me.minSize.width);
15475 var ctx = me.ctx;
15476 var fontColor = valueOrDefault$e(labelOpts.fontColor, globalDefaults.defaultFontColor);
15477 var labelFont = helpers$1.options._parseFont(labelOpts);
15478 var fontSize = labelFont.size;
15479 var cursor;
15480
15481 // Canvas setup
15482 ctx.textAlign = rtlHelper.textAlign('left');
15483 ctx.textBaseline = 'middle';
15484 ctx.lineWidth = 0.5;
15485 ctx.strokeStyle = fontColor; // for strikethrough effect
15486 ctx.fillStyle = fontColor; // render in correct colour
15487 ctx.font = labelFont.string;
15488
15489 var boxWidth = getBoxWidth(labelOpts, fontSize);
15490 var hitboxes = me.legendHitBoxes;
15491
15492 // current position
15493 var drawLegendBox = function(x, y, legendItem) {
15494 if (isNaN(boxWidth) || boxWidth <= 0) {
15495 return;
15496 }
15497
15498 // Set the ctx for the box
15499 ctx.save();
15500
15501 var lineWidth = valueOrDefault$e(legendItem.lineWidth, lineDefault.borderWidth);
15502 ctx.fillStyle = valueOrDefault$e(legendItem.fillStyle, defaultColor);
15503 ctx.lineCap = valueOrDefault$e(legendItem.lineCap, lineDefault.borderCapStyle);
15504 ctx.lineDashOffset = valueOrDefault$e(legendItem.lineDashOffset, lineDefault.borderDashOffset);
15505 ctx.lineJoin = valueOrDefault$e(legendItem.lineJoin, lineDefault.borderJoinStyle);
15506 ctx.lineWidth = lineWidth;
15507 ctx.strokeStyle = valueOrDefault$e(legendItem.strokeStyle, defaultColor);
15508
15509 if (ctx.setLineDash) {
15510 // IE 9 and 10 do not support line dash
15511 ctx.setLineDash(valueOrDefault$e(legendItem.lineDash, lineDefault.borderDash));
15512 }
15513
15514 if (labelOpts && labelOpts.usePointStyle) {
15515 // Recalculate x and y for drawPoint() because its expecting
15516 // x and y to be center of figure (instead of top left)
15517 var radius = boxWidth * Math.SQRT2 / 2;
15518 var centerX = rtlHelper.xPlus(x, boxWidth / 2);
15519 var centerY = y + fontSize / 2;
15520
15521 // Draw pointStyle as legend symbol
15522 helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY, legendItem.rotation);
15523 } else {
15524 // Draw box as legend symbol
15525 ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize);
15526 if (lineWidth !== 0) {
15527 ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize);
15528 }
15529 }
15530
15531 ctx.restore();
15532 };
15533
15534 var fillText = function(x, y, legendItem, textWidth) {
15535 var halfFontSize = fontSize / 2;
15536 var xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize);
15537 var yMiddle = y + halfFontSize;
15538
15539 ctx.fillText(legendItem.text, xLeft, yMiddle);
15540
15541 if (legendItem.hidden) {
15542 // Strikethrough the text if hidden
15543 ctx.beginPath();
15544 ctx.lineWidth = 2;
15545 ctx.moveTo(xLeft, yMiddle);
15546 ctx.lineTo(rtlHelper.xPlus(xLeft, textWidth), yMiddle);
15547 ctx.stroke();
15548 }
15549 };
15550
15551 var alignmentOffset = function(dimension, blockSize) {
15552 switch (opts.align) {
15553 case 'start':
15554 return labelOpts.padding;
15555 case 'end':
15556 return dimension - blockSize;
15557 default: // center
15558 return (dimension - blockSize + labelOpts.padding) / 2;
15559 }
15560 };
15561
15562 // Horizontal
15563 var isHorizontal = me.isHorizontal();
15564 if (isHorizontal) {
15565 cursor = {
15566 x: me.left + alignmentOffset(legendWidth, lineWidths[0]),
15567 y: me.top + labelOpts.padding,
15568 line: 0
15569 };
15570 } else {
15571 cursor = {
15572 x: me.left + labelOpts.padding,
15573 y: me.top + alignmentOffset(legendHeight, columnHeights[0]),
15574 line: 0
15575 };
15576 }
15577
15578 helpers$1.rtl.overrideTextDirection(me.ctx, opts.textDirection);
15579
15580 var itemHeight = fontSize + labelOpts.padding;
15581 helpers$1.each(me.legendItems, function(legendItem, i) {
15582 var textWidth = ctx.measureText(legendItem.text).width;
15583 var width = boxWidth + (fontSize / 2) + textWidth;
15584 var x = cursor.x;
15585 var y = cursor.y;
15586
15587 rtlHelper.setWidth(me.minSize.width);
15588
15589 // Use (me.left + me.minSize.width) and (me.top + me.minSize.height)
15590 // instead of me.right and me.bottom because me.width and me.height
15591 // may have been changed since me.minSize was calculated
15592 if (isHorizontal) {
15593 if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) {
15594 y = cursor.y += itemHeight;
15595 cursor.line++;
15596 x = cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]);
15597 }
15598 } else if (i > 0 && y + itemHeight > me.top + me.minSize.height) {
15599 x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
15600 cursor.line++;
15601 y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]);
15602 }
15603
15604 var realX = rtlHelper.x(x);
15605
15606 drawLegendBox(realX, y, legendItem);
15607
15608 hitboxes[i].left = rtlHelper.leftForLtr(realX, hitboxes[i].width);
15609 hitboxes[i].top = y;
15610
15611 // Fill the actual label
15612 fillText(realX, y, legendItem, textWidth);
15613
15614 if (isHorizontal) {
15615 cursor.x += width + labelOpts.padding;
15616 } else {
15617 cursor.y += itemHeight;
15618 }
15619 });
15620
15621 helpers$1.rtl.restoreTextDirection(me.ctx, opts.textDirection);
15622 },
15623
15624 /**
15625 * @private
15626 */
15627 _getLegendItemAt: function(x, y) {
15628 var me = this;
15629 var i, hitBox, lh;
15630
15631 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
15632 // See if we are touching one of the dataset boxes
15633 lh = me.legendHitBoxes;
15634 for (i = 0; i < lh.length; ++i) {
15635 hitBox = lh[i];
15636
15637 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
15638 // Touching an element
15639 return me.legendItems[i];
15640 }
15641 }
15642 }
15643
15644 return null;
15645 },
15646
15647 /**
15648 * Handle an event
15649 * @private
15650 * @param {IEvent} event - The event to handle
15651 */
15652 handleEvent: function(e) {
15653 var me = this;
15654 var opts = me.options;
15655 var type = e.type === 'mouseup' ? 'click' : e.type;
15656 var hoveredItem;
15657
15658 if (type === 'mousemove') {
15659 if (!opts.onHover && !opts.onLeave) {
15660 return;
15661 }
15662 } else if (type === 'click') {
15663 if (!opts.onClick) {
15664 return;
15665 }
15666 } else {
15667 return;
15668 }
15669
15670 // Chart event already has relative position in it
15671 hoveredItem = me._getLegendItemAt(e.x, e.y);
15672
15673 if (type === 'click') {
15674 if (hoveredItem && opts.onClick) {
15675 // use e.native for backwards compatibility
15676 opts.onClick.call(me, e.native, hoveredItem);
15677 }
15678 } else {
15679 if (opts.onLeave && hoveredItem !== me._hoveredItem) {
15680 if (me._hoveredItem) {
15681 opts.onLeave.call(me, e.native, me._hoveredItem);
15682 }
15683 me._hoveredItem = hoveredItem;
15684 }
15685
15686 if (opts.onHover && hoveredItem) {
15687 // use e.native for backwards compatibility
15688 opts.onHover.call(me, e.native, hoveredItem);
15689 }
15690 }
15691 }
15692});
15693
15694function createNewLegendAndAttach(chart, legendOpts) {
15695 var legend = new Legend({
15696 ctx: chart.ctx,
15697 options: legendOpts,
15698 chart: chart
15699 });
15700
15701 core_layouts.configure(chart, legend, legendOpts);
15702 core_layouts.addBox(chart, legend);
15703 chart.legend = legend;
15704}
15705
15706var plugin_legend = {
15707 id: 'legend',
15708
15709 /**
15710 * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making
15711 * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of
15712 * the plugin, which one will be re-exposed in the chart.js file.
15713 * https://github.com/chartjs/Chart.js/pull/2640
15714 * @private
15715 */
15716 _element: Legend,
15717
15718 beforeInit: function(chart) {
15719 var legendOpts = chart.options.legend;
15720
15721 if (legendOpts) {
15722 createNewLegendAndAttach(chart, legendOpts);
15723 }
15724 },
15725
15726 beforeUpdate: function(chart) {
15727 var legendOpts = chart.options.legend;
15728 var legend = chart.legend;
15729
15730 if (legendOpts) {
15731 helpers$1.mergeIf(legendOpts, core_defaults.global.legend);
15732
15733 if (legend) {
15734 core_layouts.configure(chart, legend, legendOpts);
15735 legend.options = legendOpts;
15736 } else {
15737 createNewLegendAndAttach(chart, legendOpts);
15738 }
15739 } else if (legend) {
15740 core_layouts.removeBox(chart, legend);
15741 delete chart.legend;
15742 }
15743 },
15744
15745 afterEvent: function(chart, e) {
15746 var legend = chart.legend;
15747 if (legend) {
15748 legend.handleEvent(e);
15749 }
15750 }
15751};
15752
15753var noop$2 = helpers$1.noop;
15754
15755core_defaults._set('global', {
15756 title: {
15757 display: false,
15758 fontStyle: 'bold',
15759 fullWidth: true,
15760 padding: 10,
15761 position: 'top',
15762 text: '',
15763 weight: 2000 // by default greater than legend (1000) to be above
15764 }
15765});
15766
15767/**
15768 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
15769 */
15770var Title = core_element.extend({
15771 initialize: function(config) {
15772 var me = this;
15773 helpers$1.extend(me, config);
15774
15775 // Contains hit boxes for each dataset (in dataset order)
15776 me.legendHitBoxes = [];
15777 },
15778
15779 // These methods are ordered by lifecycle. Utilities then follow.
15780
15781 beforeUpdate: noop$2,
15782 update: function(maxWidth, maxHeight, margins) {
15783 var me = this;
15784
15785 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
15786 me.beforeUpdate();
15787
15788 // Absorb the master measurements
15789 me.maxWidth = maxWidth;
15790 me.maxHeight = maxHeight;
15791 me.margins = margins;
15792
15793 // Dimensions
15794 me.beforeSetDimensions();
15795 me.setDimensions();
15796 me.afterSetDimensions();
15797 // Labels
15798 me.beforeBuildLabels();
15799 me.buildLabels();
15800 me.afterBuildLabels();
15801
15802 // Fit
15803 me.beforeFit();
15804 me.fit();
15805 me.afterFit();
15806 //
15807 me.afterUpdate();
15808
15809 return me.minSize;
15810
15811 },
15812 afterUpdate: noop$2,
15813
15814 //
15815
15816 beforeSetDimensions: noop$2,
15817 setDimensions: function() {
15818 var me = this;
15819 // Set the unconstrained dimension before label rotation
15820 if (me.isHorizontal()) {
15821 // Reset position before calculating rotation
15822 me.width = me.maxWidth;
15823 me.left = 0;
15824 me.right = me.width;
15825 } else {
15826 me.height = me.maxHeight;
15827
15828 // Reset position before calculating rotation
15829 me.top = 0;
15830 me.bottom = me.height;
15831 }
15832
15833 // Reset padding
15834 me.paddingLeft = 0;
15835 me.paddingTop = 0;
15836 me.paddingRight = 0;
15837 me.paddingBottom = 0;
15838
15839 // Reset minSize
15840 me.minSize = {
15841 width: 0,
15842 height: 0
15843 };
15844 },
15845 afterSetDimensions: noop$2,
15846
15847 //
15848
15849 beforeBuildLabels: noop$2,
15850 buildLabels: noop$2,
15851 afterBuildLabels: noop$2,
15852
15853 //
15854
15855 beforeFit: noop$2,
15856 fit: function() {
15857 var me = this;
15858 var opts = me.options;
15859 var minSize = me.minSize = {};
15860 var isHorizontal = me.isHorizontal();
15861 var lineCount, textSize;
15862
15863 if (!opts.display) {
15864 me.width = minSize.width = me.height = minSize.height = 0;
15865 return;
15866 }
15867
15868 lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1;
15869 textSize = lineCount * helpers$1.options._parseFont(opts).lineHeight + opts.padding * 2;
15870
15871 me.width = minSize.width = isHorizontal ? me.maxWidth : textSize;
15872 me.height = minSize.height = isHorizontal ? textSize : me.maxHeight;
15873 },
15874 afterFit: noop$2,
15875
15876 // Shared Methods
15877 isHorizontal: function() {
15878 var pos = this.options.position;
15879 return pos === 'top' || pos === 'bottom';
15880 },
15881
15882 // Actually draw the title block on the canvas
15883 draw: function() {
15884 var me = this;
15885 var ctx = me.ctx;
15886 var opts = me.options;
15887
15888 if (!opts.display) {
15889 return;
15890 }
15891
15892 var fontOpts = helpers$1.options._parseFont(opts);
15893 var lineHeight = fontOpts.lineHeight;
15894 var offset = lineHeight / 2 + opts.padding;
15895 var rotation = 0;
15896 var top = me.top;
15897 var left = me.left;
15898 var bottom = me.bottom;
15899 var right = me.right;
15900 var maxWidth, titleX, titleY;
15901
15902 ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour
15903 ctx.font = fontOpts.string;
15904
15905 // Horizontal
15906 if (me.isHorizontal()) {
15907 titleX = left + ((right - left) / 2); // midpoint of the width
15908 titleY = top + offset;
15909 maxWidth = right - left;
15910 } else {
15911 titleX = opts.position === 'left' ? left + offset : right - offset;
15912 titleY = top + ((bottom - top) / 2);
15913 maxWidth = bottom - top;
15914 rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
15915 }
15916
15917 ctx.save();
15918 ctx.translate(titleX, titleY);
15919 ctx.rotate(rotation);
15920 ctx.textAlign = 'center';
15921 ctx.textBaseline = 'middle';
15922
15923 var text = opts.text;
15924 if (helpers$1.isArray(text)) {
15925 var y = 0;
15926 for (var i = 0; i < text.length; ++i) {
15927 ctx.fillText(text[i], 0, y, maxWidth);
15928 y += lineHeight;
15929 }
15930 } else {
15931 ctx.fillText(text, 0, 0, maxWidth);
15932 }
15933
15934 ctx.restore();
15935 }
15936});
15937
15938function createNewTitleBlockAndAttach(chart, titleOpts) {
15939 var title = new Title({
15940 ctx: chart.ctx,
15941 options: titleOpts,
15942 chart: chart
15943 });
15944
15945 core_layouts.configure(chart, title, titleOpts);
15946 core_layouts.addBox(chart, title);
15947 chart.titleBlock = title;
15948}
15949
15950var plugin_title = {
15951 id: 'title',
15952
15953 /**
15954 * Backward compatibility: since 2.1.5, the title is registered as a plugin, making
15955 * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of
15956 * the plugin, which one will be re-exposed in the chart.js file.
15957 * https://github.com/chartjs/Chart.js/pull/2640
15958 * @private
15959 */
15960 _element: Title,
15961
15962 beforeInit: function(chart) {
15963 var titleOpts = chart.options.title;
15964
15965 if (titleOpts) {
15966 createNewTitleBlockAndAttach(chart, titleOpts);
15967 }
15968 },
15969
15970 beforeUpdate: function(chart) {
15971 var titleOpts = chart.options.title;
15972 var titleBlock = chart.titleBlock;
15973
15974 if (titleOpts) {
15975 helpers$1.mergeIf(titleOpts, core_defaults.global.title);
15976
15977 if (titleBlock) {
15978 core_layouts.configure(chart, titleBlock, titleOpts);
15979 titleBlock.options = titleOpts;
15980 } else {
15981 createNewTitleBlockAndAttach(chart, titleOpts);
15982 }
15983 } else if (titleBlock) {
15984 core_layouts.removeBox(chart, titleBlock);
15985 delete chart.titleBlock;
15986 }
15987 }
15988};
15989
15990var plugins = {};
15991var filler = plugin_filler;
15992var legend = plugin_legend;
15993var title = plugin_title;
15994plugins.filler = filler;
15995plugins.legend = legend;
15996plugins.title = title;
15997
15998/**
15999 * @namespace Chart
16000 */
16001
16002
16003core_controller.helpers = helpers$1;
16004
16005// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
16006core_helpers();
16007
16008core_controller._adapters = core_adapters;
16009core_controller.Animation = core_animation;
16010core_controller.animationService = core_animations;
16011core_controller.controllers = controllers;
16012core_controller.DatasetController = core_datasetController;
16013core_controller.defaults = core_defaults;
16014core_controller.Element = core_element;
16015core_controller.elements = elements;
16016core_controller.Interaction = core_interaction;
16017core_controller.layouts = core_layouts;
16018core_controller.platform = platform;
16019core_controller.plugins = core_plugins;
16020core_controller.Scale = core_scale;
16021core_controller.scaleService = core_scaleService;
16022core_controller.Ticks = core_ticks;
16023core_controller.Tooltip = core_tooltip;
16024
16025// Register built-in scales
16026
16027core_controller.helpers.each(scales, function(scale, type) {
16028 core_controller.scaleService.registerScaleType(type, scale, scale._defaults);
16029});
16030
16031// Load to register built-in adapters (as side effects)
16032
16033
16034// Loading built-in plugins
16035
16036for (var k in plugins) {
16037 if (plugins.hasOwnProperty(k)) {
16038 core_controller.plugins.register(plugins[k]);
16039 }
16040}
16041
16042core_controller.platform.initialize();
16043
16044var src = core_controller;
16045if (typeof window !== 'undefined') {
16046 window.Chart = core_controller;
16047}
16048
16049// DEPRECATIONS
16050
16051/**
16052 * Provided for backward compatibility, not available anymore
16053 * @namespace Chart.Chart
16054 * @deprecated since version 2.8.0
16055 * @todo remove at version 3
16056 * @private
16057 */
16058core_controller.Chart = core_controller;
16059
16060/**
16061 * Provided for backward compatibility, not available anymore
16062 * @namespace Chart.Legend
16063 * @deprecated since version 2.1.5
16064 * @todo remove at version 3
16065 * @private
16066 */
16067core_controller.Legend = plugins.legend._element;
16068
16069/**
16070 * Provided for backward compatibility, not available anymore
16071 * @namespace Chart.Title
16072 * @deprecated since version 2.1.5
16073 * @todo remove at version 3
16074 * @private
16075 */
16076core_controller.Title = plugins.title._element;
16077
16078/**
16079 * Provided for backward compatibility, use Chart.plugins instead
16080 * @namespace Chart.pluginService
16081 * @deprecated since version 2.1.5
16082 * @todo remove at version 3
16083 * @private
16084 */
16085core_controller.pluginService = core_controller.plugins;
16086
16087/**
16088 * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
16089 * effect, instead simply create/register plugins via plain JavaScript objects.
16090 * @interface Chart.PluginBase
16091 * @deprecated since version 2.5.0
16092 * @todo remove at version 3
16093 * @private
16094 */
16095core_controller.PluginBase = core_controller.Element.extend({});
16096
16097/**
16098 * Provided for backward compatibility, use Chart.helpers.canvas instead.
16099 * @namespace Chart.canvasHelpers
16100 * @deprecated since version 2.6.0
16101 * @todo remove at version 3
16102 * @private
16103 */
16104core_controller.canvasHelpers = core_controller.helpers.canvas;
16105
16106/**
16107 * Provided for backward compatibility, use Chart.layouts instead.
16108 * @namespace Chart.layoutService
16109 * @deprecated since version 2.7.3
16110 * @todo remove at version 3
16111 * @private
16112 */
16113core_controller.layoutService = core_controller.layouts;
16114
16115/**
16116 * Provided for backward compatibility, not available anymore.
16117 * @namespace Chart.LinearScaleBase
16118 * @deprecated since version 2.8
16119 * @todo remove at version 3
16120 * @private
16121 */
16122core_controller.LinearScaleBase = scale_linearbase;
16123
16124/**
16125 * Provided for backward compatibility, instead we should create a new Chart
16126 * by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`).
16127 * @deprecated since version 2.8.0
16128 * @todo remove at version 3
16129 */
16130core_controller.helpers.each(
16131 [
16132 'Bar',
16133 'Bubble',
16134 'Doughnut',
16135 'Line',
16136 'PolarArea',
16137 'Radar',
16138 'Scatter'
16139 ],
16140 function(klass) {
16141 core_controller[klass] = function(ctx, cfg) {
16142 return new core_controller(ctx, core_controller.helpers.merge(cfg || {}, {
16143 type: klass.charAt(0).toLowerCase() + klass.slice(1)
16144 }));
16145 };
16146 }
16147);
16148
16149return src;
16150
16151})));