blob: 5899061600afaa9b5bec3b6ab9cc635c6b5a15c9 [file] [log] [blame]
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001/*!
2 * Chart.js v3.7.1
3 * https://www.chartjs.org
4 * (c) 2022 Chart.js Contributors
5 * Released under the MIT License
6 */
7(function (global, factory) {
8typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
9typeof define === 'function' && define.amd ? define(factory) :
10(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chart = factory());
11})(this, (function () { 'use strict';
12
13function fontString(pixelSize, fontStyle, fontFamily) {
14 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
15}
16const requestAnimFrame = (function() {
17 if (typeof window === 'undefined') {
18 return function(callback) {
19 return callback();
20 };
21 }
22 return window.requestAnimationFrame;
23}());
24function throttled(fn, thisArg, updateFn) {
25 const updateArgs = updateFn || ((args) => Array.prototype.slice.call(args));
26 let ticking = false;
27 let args = [];
28 return function(...rest) {
29 args = updateArgs(rest);
30 if (!ticking) {
31 ticking = true;
32 requestAnimFrame.call(window, () => {
33 ticking = false;
34 fn.apply(thisArg, args);
35 });
36 }
37 };
38}
39function debounce(fn, delay) {
40 let timeout;
41 return function(...args) {
42 if (delay) {
43 clearTimeout(timeout);
44 timeout = setTimeout(fn, delay, args);
45 } else {
46 fn.apply(this, args);
47 }
48 return delay;
49 };
50}
51const _toLeftRightCenter = (align) => align === 'start' ? 'left' : align === 'end' ? 'right' : 'center';
52const _alignStartEnd = (align, start, end) => align === 'start' ? start : align === 'end' ? end : (start + end) / 2;
53const _textX = (align, left, right, rtl) => {
54 const check = rtl ? 'left' : 'right';
55 return align === check ? right : align === 'center' ? (left + right) / 2 : left;
56};
57
58class Animator {
59 constructor() {
60 this._request = null;
61 this._charts = new Map();
62 this._running = false;
63 this._lastDate = undefined;
64 }
65 _notify(chart, anims, date, type) {
66 const callbacks = anims.listeners[type];
67 const numSteps = anims.duration;
68 callbacks.forEach(fn => fn({
69 chart,
70 initial: anims.initial,
71 numSteps,
72 currentStep: Math.min(date - anims.start, numSteps)
73 }));
74 }
75 _refresh() {
76 if (this._request) {
77 return;
78 }
79 this._running = true;
80 this._request = requestAnimFrame.call(window, () => {
81 this._update();
82 this._request = null;
83 if (this._running) {
84 this._refresh();
85 }
86 });
87 }
88 _update(date = Date.now()) {
89 let remaining = 0;
90 this._charts.forEach((anims, chart) => {
91 if (!anims.running || !anims.items.length) {
92 return;
93 }
94 const items = anims.items;
95 let i = items.length - 1;
96 let draw = false;
97 let item;
98 for (; i >= 0; --i) {
99 item = items[i];
100 if (item._active) {
101 if (item._total > anims.duration) {
102 anims.duration = item._total;
103 }
104 item.tick(date);
105 draw = true;
106 } else {
107 items[i] = items[items.length - 1];
108 items.pop();
109 }
110 }
111 if (draw) {
112 chart.draw();
113 this._notify(chart, anims, date, 'progress');
114 }
115 if (!items.length) {
116 anims.running = false;
117 this._notify(chart, anims, date, 'complete');
118 anims.initial = false;
119 }
120 remaining += items.length;
121 });
122 this._lastDate = date;
123 if (remaining === 0) {
124 this._running = false;
125 }
126 }
127 _getAnims(chart) {
128 const charts = this._charts;
129 let anims = charts.get(chart);
130 if (!anims) {
131 anims = {
132 running: false,
133 initial: true,
134 items: [],
135 listeners: {
136 complete: [],
137 progress: []
138 }
139 };
140 charts.set(chart, anims);
141 }
142 return anims;
143 }
144 listen(chart, event, cb) {
145 this._getAnims(chart).listeners[event].push(cb);
146 }
147 add(chart, items) {
148 if (!items || !items.length) {
149 return;
150 }
151 this._getAnims(chart).items.push(...items);
152 }
153 has(chart) {
154 return this._getAnims(chart).items.length > 0;
155 }
156 start(chart) {
157 const anims = this._charts.get(chart);
158 if (!anims) {
159 return;
160 }
161 anims.running = true;
162 anims.start = Date.now();
163 anims.duration = anims.items.reduce((acc, cur) => Math.max(acc, cur._duration), 0);
164 this._refresh();
165 }
166 running(chart) {
167 if (!this._running) {
168 return false;
169 }
170 const anims = this._charts.get(chart);
171 if (!anims || !anims.running || !anims.items.length) {
172 return false;
173 }
174 return true;
175 }
176 stop(chart) {
177 const anims = this._charts.get(chart);
178 if (!anims || !anims.items.length) {
179 return;
180 }
181 const items = anims.items;
182 let i = items.length - 1;
183 for (; i >= 0; --i) {
184 items[i].cancel();
185 }
186 anims.items = [];
187 this._notify(chart, anims, Date.now(), 'complete');
188 }
189 remove(chart) {
190 return this._charts.delete(chart);
191 }
192}
193var animator = new Animator();
194
195/*!
196 * @kurkle/color v0.1.9
197 * https://github.com/kurkle/color#readme
198 * (c) 2020 Jukka Kurkela
199 * Released under the MIT License
200 */
201const map$1 = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, A: 10, B: 11, C: 12, D: 13, E: 14, F: 15, a: 10, b: 11, c: 12, d: 13, e: 14, f: 15};
202const hex = '0123456789ABCDEF';
203const h1 = (b) => hex[b & 0xF];
204const h2 = (b) => hex[(b & 0xF0) >> 4] + hex[b & 0xF];
205const eq = (b) => (((b & 0xF0) >> 4) === (b & 0xF));
206function isShort(v) {
207 return eq(v.r) && eq(v.g) && eq(v.b) && eq(v.a);
208}
209function hexParse(str) {
210 var len = str.length;
211 var ret;
212 if (str[0] === '#') {
213 if (len === 4 || len === 5) {
214 ret = {
215 r: 255 & map$1[str[1]] * 17,
216 g: 255 & map$1[str[2]] * 17,
217 b: 255 & map$1[str[3]] * 17,
218 a: len === 5 ? map$1[str[4]] * 17 : 255
219 };
220 } else if (len === 7 || len === 9) {
221 ret = {
222 r: map$1[str[1]] << 4 | map$1[str[2]],
223 g: map$1[str[3]] << 4 | map$1[str[4]],
224 b: map$1[str[5]] << 4 | map$1[str[6]],
225 a: len === 9 ? (map$1[str[7]] << 4 | map$1[str[8]]) : 255
226 };
227 }
228 }
229 return ret;
230}
231function hexString(v) {
232 var f = isShort(v) ? h1 : h2;
233 return v
234 ? '#' + f(v.r) + f(v.g) + f(v.b) + (v.a < 255 ? f(v.a) : '')
235 : v;
236}
237function round(v) {
238 return v + 0.5 | 0;
239}
240const lim = (v, l, h) => Math.max(Math.min(v, h), l);
241function p2b(v) {
242 return lim(round(v * 2.55), 0, 255);
243}
244function n2b(v) {
245 return lim(round(v * 255), 0, 255);
246}
247function b2n(v) {
248 return lim(round(v / 2.55) / 100, 0, 1);
249}
250function n2p(v) {
251 return lim(round(v * 100), 0, 100);
252}
253const RGB_RE = /^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;
254function rgbParse(str) {
255 const m = RGB_RE.exec(str);
256 let a = 255;
257 let r, g, b;
258 if (!m) {
259 return;
260 }
261 if (m[7] !== r) {
262 const v = +m[7];
263 a = 255 & (m[8] ? p2b(v) : v * 255);
264 }
265 r = +m[1];
266 g = +m[3];
267 b = +m[5];
268 r = 255 & (m[2] ? p2b(r) : r);
269 g = 255 & (m[4] ? p2b(g) : g);
270 b = 255 & (m[6] ? p2b(b) : b);
271 return {
272 r: r,
273 g: g,
274 b: b,
275 a: a
276 };
277}
278function rgbString(v) {
279 return v && (
280 v.a < 255
281 ? `rgba(${v.r}, ${v.g}, ${v.b}, ${b2n(v.a)})`
282 : `rgb(${v.r}, ${v.g}, ${v.b})`
283 );
284}
285const HUE_RE = /^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;
286function hsl2rgbn(h, s, l) {
287 const a = s * Math.min(l, 1 - l);
288 const f = (n, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
289 return [f(0), f(8), f(4)];
290}
291function hsv2rgbn(h, s, v) {
292 const f = (n, k = (n + h / 60) % 6) => v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
293 return [f(5), f(3), f(1)];
294}
295function hwb2rgbn(h, w, b) {
296 const rgb = hsl2rgbn(h, 1, 0.5);
297 let i;
298 if (w + b > 1) {
299 i = 1 / (w + b);
300 w *= i;
301 b *= i;
302 }
303 for (i = 0; i < 3; i++) {
304 rgb[i] *= 1 - w - b;
305 rgb[i] += w;
306 }
307 return rgb;
308}
309function rgb2hsl(v) {
310 const range = 255;
311 const r = v.r / range;
312 const g = v.g / range;
313 const b = v.b / range;
314 const max = Math.max(r, g, b);
315 const min = Math.min(r, g, b);
316 const l = (max + min) / 2;
317 let h, s, d;
318 if (max !== min) {
319 d = max - min;
320 s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
321 h = max === r
322 ? ((g - b) / d) + (g < b ? 6 : 0)
323 : max === g
324 ? (b - r) / d + 2
325 : (r - g) / d + 4;
326 h = h * 60 + 0.5;
327 }
328 return [h | 0, s || 0, l];
329}
330function calln(f, a, b, c) {
331 return (
332 Array.isArray(a)
333 ? f(a[0], a[1], a[2])
334 : f(a, b, c)
335 ).map(n2b);
336}
337function hsl2rgb(h, s, l) {
338 return calln(hsl2rgbn, h, s, l);
339}
340function hwb2rgb(h, w, b) {
341 return calln(hwb2rgbn, h, w, b);
342}
343function hsv2rgb(h, s, v) {
344 return calln(hsv2rgbn, h, s, v);
345}
346function hue(h) {
347 return (h % 360 + 360) % 360;
348}
349function hueParse(str) {
350 const m = HUE_RE.exec(str);
351 let a = 255;
352 let v;
353 if (!m) {
354 return;
355 }
356 if (m[5] !== v) {
357 a = m[6] ? p2b(+m[5]) : n2b(+m[5]);
358 }
359 const h = hue(+m[2]);
360 const p1 = +m[3] / 100;
361 const p2 = +m[4] / 100;
362 if (m[1] === 'hwb') {
363 v = hwb2rgb(h, p1, p2);
364 } else if (m[1] === 'hsv') {
365 v = hsv2rgb(h, p1, p2);
366 } else {
367 v = hsl2rgb(h, p1, p2);
368 }
369 return {
370 r: v[0],
371 g: v[1],
372 b: v[2],
373 a: a
374 };
375}
376function rotate(v, deg) {
377 var h = rgb2hsl(v);
378 h[0] = hue(h[0] + deg);
379 h = hsl2rgb(h);
380 v.r = h[0];
381 v.g = h[1];
382 v.b = h[2];
383}
384function hslString(v) {
385 if (!v) {
386 return;
387 }
388 const a = rgb2hsl(v);
389 const h = a[0];
390 const s = n2p(a[1]);
391 const l = n2p(a[2]);
392 return v.a < 255
393 ? `hsla(${h}, ${s}%, ${l}%, ${b2n(v.a)})`
394 : `hsl(${h}, ${s}%, ${l}%)`;
395}
396const map$1$1 = {
397 x: 'dark',
398 Z: 'light',
399 Y: 're',
400 X: 'blu',
401 W: 'gr',
402 V: 'medium',
403 U: 'slate',
404 A: 'ee',
405 T: 'ol',
406 S: 'or',
407 B: 'ra',
408 C: 'lateg',
409 D: 'ights',
410 R: 'in',
411 Q: 'turquois',
412 E: 'hi',
413 P: 'ro',
414 O: 'al',
415 N: 'le',
416 M: 'de',
417 L: 'yello',
418 F: 'en',
419 K: 'ch',
420 G: 'arks',
421 H: 'ea',
422 I: 'ightg',
423 J: 'wh'
424};
425const names = {
426 OiceXe: 'f0f8ff',
427 antiquewEte: 'faebd7',
428 aqua: 'ffff',
429 aquamarRe: '7fffd4',
430 azuY: 'f0ffff',
431 beige: 'f5f5dc',
432 bisque: 'ffe4c4',
433 black: '0',
434 blanKedOmond: 'ffebcd',
435 Xe: 'ff',
436 XeviTet: '8a2be2',
437 bPwn: 'a52a2a',
438 burlywood: 'deb887',
439 caMtXe: '5f9ea0',
440 KartYuse: '7fff00',
441 KocTate: 'd2691e',
442 cSO: 'ff7f50',
443 cSnflowerXe: '6495ed',
444 cSnsilk: 'fff8dc',
445 crimson: 'dc143c',
446 cyan: 'ffff',
447 xXe: '8b',
448 xcyan: '8b8b',
449 xgTMnPd: 'b8860b',
450 xWay: 'a9a9a9',
451 xgYF: '6400',
452 xgYy: 'a9a9a9',
453 xkhaki: 'bdb76b',
454 xmagFta: '8b008b',
455 xTivegYF: '556b2f',
456 xSange: 'ff8c00',
457 xScEd: '9932cc',
458 xYd: '8b0000',
459 xsOmon: 'e9967a',
460 xsHgYF: '8fbc8f',
461 xUXe: '483d8b',
462 xUWay: '2f4f4f',
463 xUgYy: '2f4f4f',
464 xQe: 'ced1',
465 xviTet: '9400d3',
466 dAppRk: 'ff1493',
467 dApskyXe: 'bfff',
468 dimWay: '696969',
469 dimgYy: '696969',
470 dodgerXe: '1e90ff',
471 fiYbrick: 'b22222',
472 flSOwEte: 'fffaf0',
473 foYstWAn: '228b22',
474 fuKsia: 'ff00ff',
475 gaRsbSo: 'dcdcdc',
476 ghostwEte: 'f8f8ff',
477 gTd: 'ffd700',
478 gTMnPd: 'daa520',
479 Way: '808080',
480 gYF: '8000',
481 gYFLw: 'adff2f',
482 gYy: '808080',
483 honeyMw: 'f0fff0',
484 hotpRk: 'ff69b4',
485 RdianYd: 'cd5c5c',
486 Rdigo: '4b0082',
487 ivSy: 'fffff0',
488 khaki: 'f0e68c',
489 lavFMr: 'e6e6fa',
490 lavFMrXsh: 'fff0f5',
491 lawngYF: '7cfc00',
492 NmoncEffon: 'fffacd',
493 ZXe: 'add8e6',
494 ZcSO: 'f08080',
495 Zcyan: 'e0ffff',
496 ZgTMnPdLw: 'fafad2',
497 ZWay: 'd3d3d3',
498 ZgYF: '90ee90',
499 ZgYy: 'd3d3d3',
500 ZpRk: 'ffb6c1',
501 ZsOmon: 'ffa07a',
502 ZsHgYF: '20b2aa',
503 ZskyXe: '87cefa',
504 ZUWay: '778899',
505 ZUgYy: '778899',
506 ZstAlXe: 'b0c4de',
507 ZLw: 'ffffe0',
508 lime: 'ff00',
509 limegYF: '32cd32',
510 lRF: 'faf0e6',
511 magFta: 'ff00ff',
512 maPon: '800000',
513 VaquamarRe: '66cdaa',
514 VXe: 'cd',
515 VScEd: 'ba55d3',
516 VpurpN: '9370db',
517 VsHgYF: '3cb371',
518 VUXe: '7b68ee',
519 VsprRggYF: 'fa9a',
520 VQe: '48d1cc',
521 VviTetYd: 'c71585',
522 midnightXe: '191970',
523 mRtcYam: 'f5fffa',
524 mistyPse: 'ffe4e1',
525 moccasR: 'ffe4b5',
526 navajowEte: 'ffdead',
527 navy: '80',
528 Tdlace: 'fdf5e6',
529 Tive: '808000',
530 TivedBb: '6b8e23',
531 Sange: 'ffa500',
532 SangeYd: 'ff4500',
533 ScEd: 'da70d6',
534 pOegTMnPd: 'eee8aa',
535 pOegYF: '98fb98',
536 pOeQe: 'afeeee',
537 pOeviTetYd: 'db7093',
538 papayawEp: 'ffefd5',
539 pHKpuff: 'ffdab9',
540 peru: 'cd853f',
541 pRk: 'ffc0cb',
542 plum: 'dda0dd',
543 powMrXe: 'b0e0e6',
544 purpN: '800080',
545 YbeccapurpN: '663399',
546 Yd: 'ff0000',
547 Psybrown: 'bc8f8f',
548 PyOXe: '4169e1',
549 saddNbPwn: '8b4513',
550 sOmon: 'fa8072',
551 sandybPwn: 'f4a460',
552 sHgYF: '2e8b57',
553 sHshell: 'fff5ee',
554 siFna: 'a0522d',
555 silver: 'c0c0c0',
556 skyXe: '87ceeb',
557 UXe: '6a5acd',
558 UWay: '708090',
559 UgYy: '708090',
560 snow: 'fffafa',
561 sprRggYF: 'ff7f',
562 stAlXe: '4682b4',
563 tan: 'd2b48c',
564 teO: '8080',
565 tEstN: 'd8bfd8',
566 tomato: 'ff6347',
567 Qe: '40e0d0',
568 viTet: 'ee82ee',
569 JHt: 'f5deb3',
570 wEte: 'ffffff',
571 wEtesmoke: 'f5f5f5',
572 Lw: 'ffff00',
573 LwgYF: '9acd32'
574};
575function unpack() {
576 const unpacked = {};
577 const keys = Object.keys(names);
578 const tkeys = Object.keys(map$1$1);
579 let i, j, k, ok, nk;
580 for (i = 0; i < keys.length; i++) {
581 ok = nk = keys[i];
582 for (j = 0; j < tkeys.length; j++) {
583 k = tkeys[j];
584 nk = nk.replace(k, map$1$1[k]);
585 }
586 k = parseInt(names[ok], 16);
587 unpacked[nk] = [k >> 16 & 0xFF, k >> 8 & 0xFF, k & 0xFF];
588 }
589 return unpacked;
590}
591let names$1;
592function nameParse(str) {
593 if (!names$1) {
594 names$1 = unpack();
595 names$1.transparent = [0, 0, 0, 0];
596 }
597 const a = names$1[str.toLowerCase()];
598 return a && {
599 r: a[0],
600 g: a[1],
601 b: a[2],
602 a: a.length === 4 ? a[3] : 255
603 };
604}
605function modHSL(v, i, ratio) {
606 if (v) {
607 let tmp = rgb2hsl(v);
608 tmp[i] = Math.max(0, Math.min(tmp[i] + tmp[i] * ratio, i === 0 ? 360 : 1));
609 tmp = hsl2rgb(tmp);
610 v.r = tmp[0];
611 v.g = tmp[1];
612 v.b = tmp[2];
613 }
614}
615function clone$1(v, proto) {
616 return v ? Object.assign(proto || {}, v) : v;
617}
618function fromObject(input) {
619 var v = {r: 0, g: 0, b: 0, a: 255};
620 if (Array.isArray(input)) {
621 if (input.length >= 3) {
622 v = {r: input[0], g: input[1], b: input[2], a: 255};
623 if (input.length > 3) {
624 v.a = n2b(input[3]);
625 }
626 }
627 } else {
628 v = clone$1(input, {r: 0, g: 0, b: 0, a: 1});
629 v.a = n2b(v.a);
630 }
631 return v;
632}
633function functionParse(str) {
634 if (str.charAt(0) === 'r') {
635 return rgbParse(str);
636 }
637 return hueParse(str);
638}
639class Color {
640 constructor(input) {
641 if (input instanceof Color) {
642 return input;
643 }
644 const type = typeof input;
645 let v;
646 if (type === 'object') {
647 v = fromObject(input);
648 } else if (type === 'string') {
649 v = hexParse(input) || nameParse(input) || functionParse(input);
650 }
651 this._rgb = v;
652 this._valid = !!v;
653 }
654 get valid() {
655 return this._valid;
656 }
657 get rgb() {
658 var v = clone$1(this._rgb);
659 if (v) {
660 v.a = b2n(v.a);
661 }
662 return v;
663 }
664 set rgb(obj) {
665 this._rgb = fromObject(obj);
666 }
667 rgbString() {
668 return this._valid ? rgbString(this._rgb) : this._rgb;
669 }
670 hexString() {
671 return this._valid ? hexString(this._rgb) : this._rgb;
672 }
673 hslString() {
674 return this._valid ? hslString(this._rgb) : this._rgb;
675 }
676 mix(color, weight) {
677 const me = this;
678 if (color) {
679 const c1 = me.rgb;
680 const c2 = color.rgb;
681 let w2;
682 const p = weight === w2 ? 0.5 : weight;
683 const w = 2 * p - 1;
684 const a = c1.a - c2.a;
685 const w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
686 w2 = 1 - w1;
687 c1.r = 0xFF & w1 * c1.r + w2 * c2.r + 0.5;
688 c1.g = 0xFF & w1 * c1.g + w2 * c2.g + 0.5;
689 c1.b = 0xFF & w1 * c1.b + w2 * c2.b + 0.5;
690 c1.a = p * c1.a + (1 - p) * c2.a;
691 me.rgb = c1;
692 }
693 return me;
694 }
695 clone() {
696 return new Color(this.rgb);
697 }
698 alpha(a) {
699 this._rgb.a = n2b(a);
700 return this;
701 }
702 clearer(ratio) {
703 const rgb = this._rgb;
704 rgb.a *= 1 - ratio;
705 return this;
706 }
707 greyscale() {
708 const rgb = this._rgb;
709 const val = round(rgb.r * 0.3 + rgb.g * 0.59 + rgb.b * 0.11);
710 rgb.r = rgb.g = rgb.b = val;
711 return this;
712 }
713 opaquer(ratio) {
714 const rgb = this._rgb;
715 rgb.a *= 1 + ratio;
716 return this;
717 }
718 negate() {
719 const v = this._rgb;
720 v.r = 255 - v.r;
721 v.g = 255 - v.g;
722 v.b = 255 - v.b;
723 return this;
724 }
725 lighten(ratio) {
726 modHSL(this._rgb, 2, ratio);
727 return this;
728 }
729 darken(ratio) {
730 modHSL(this._rgb, 2, -ratio);
731 return this;
732 }
733 saturate(ratio) {
734 modHSL(this._rgb, 1, ratio);
735 return this;
736 }
737 desaturate(ratio) {
738 modHSL(this._rgb, 1, -ratio);
739 return this;
740 }
741 rotate(deg) {
742 rotate(this._rgb, deg);
743 return this;
744 }
745}
746function index_esm(input) {
747 return new Color(input);
748}
749
750const isPatternOrGradient = (value) => value instanceof CanvasGradient || value instanceof CanvasPattern;
751function color(value) {
752 return isPatternOrGradient(value) ? value : index_esm(value);
753}
754function getHoverColor(value) {
755 return isPatternOrGradient(value)
756 ? value
757 : index_esm(value).saturate(0.5).darken(0.1).hexString();
758}
759
760function noop() {}
761const uid = (function() {
762 let id = 0;
763 return function() {
764 return id++;
765 };
766}());
767function isNullOrUndef(value) {
768 return value === null || typeof value === 'undefined';
769}
770function isArray(value) {
771 if (Array.isArray && Array.isArray(value)) {
772 return true;
773 }
774 const type = Object.prototype.toString.call(value);
775 if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') {
776 return true;
777 }
778 return false;
779}
780function isObject(value) {
781 return value !== null && Object.prototype.toString.call(value) === '[object Object]';
782}
783const isNumberFinite = (value) => (typeof value === 'number' || value instanceof Number) && isFinite(+value);
784function finiteOrDefault(value, defaultValue) {
785 return isNumberFinite(value) ? value : defaultValue;
786}
787function valueOrDefault(value, defaultValue) {
788 return typeof value === 'undefined' ? defaultValue : value;
789}
790const toPercentage = (value, dimension) =>
791 typeof value === 'string' && value.endsWith('%') ?
792 parseFloat(value) / 100
793 : value / dimension;
794const toDimension = (value, dimension) =>
795 typeof value === 'string' && value.endsWith('%') ?
796 parseFloat(value) / 100 * dimension
797 : +value;
798function callback(fn, args, thisArg) {
799 if (fn && typeof fn.call === 'function') {
800 return fn.apply(thisArg, args);
801 }
802}
803function each(loopable, fn, thisArg, reverse) {
804 let i, len, keys;
805 if (isArray(loopable)) {
806 len = loopable.length;
807 if (reverse) {
808 for (i = len - 1; i >= 0; i--) {
809 fn.call(thisArg, loopable[i], i);
810 }
811 } else {
812 for (i = 0; i < len; i++) {
813 fn.call(thisArg, loopable[i], i);
814 }
815 }
816 } else if (isObject(loopable)) {
817 keys = Object.keys(loopable);
818 len = keys.length;
819 for (i = 0; i < len; i++) {
820 fn.call(thisArg, loopable[keys[i]], keys[i]);
821 }
822 }
823}
824function _elementsEqual(a0, a1) {
825 let i, ilen, v0, v1;
826 if (!a0 || !a1 || a0.length !== a1.length) {
827 return false;
828 }
829 for (i = 0, ilen = a0.length; i < ilen; ++i) {
830 v0 = a0[i];
831 v1 = a1[i];
832 if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) {
833 return false;
834 }
835 }
836 return true;
837}
838function clone(source) {
839 if (isArray(source)) {
840 return source.map(clone);
841 }
842 if (isObject(source)) {
843 const target = Object.create(null);
844 const keys = Object.keys(source);
845 const klen = keys.length;
846 let k = 0;
847 for (; k < klen; ++k) {
848 target[keys[k]] = clone(source[keys[k]]);
849 }
850 return target;
851 }
852 return source;
853}
854function isValidKey(key) {
855 return ['__proto__', 'prototype', 'constructor'].indexOf(key) === -1;
856}
857function _merger(key, target, source, options) {
858 if (!isValidKey(key)) {
859 return;
860 }
861 const tval = target[key];
862 const sval = source[key];
863 if (isObject(tval) && isObject(sval)) {
864 merge(tval, sval, options);
865 } else {
866 target[key] = clone(sval);
867 }
868}
869function merge(target, source, options) {
870 const sources = isArray(source) ? source : [source];
871 const ilen = sources.length;
872 if (!isObject(target)) {
873 return target;
874 }
875 options = options || {};
876 const merger = options.merger || _merger;
877 for (let i = 0; i < ilen; ++i) {
878 source = sources[i];
879 if (!isObject(source)) {
880 continue;
881 }
882 const keys = Object.keys(source);
883 for (let k = 0, klen = keys.length; k < klen; ++k) {
884 merger(keys[k], target, source, options);
885 }
886 }
887 return target;
888}
889function mergeIf(target, source) {
890 return merge(target, source, {merger: _mergerIf});
891}
892function _mergerIf(key, target, source) {
893 if (!isValidKey(key)) {
894 return;
895 }
896 const tval = target[key];
897 const sval = source[key];
898 if (isObject(tval) && isObject(sval)) {
899 mergeIf(tval, sval);
900 } else if (!Object.prototype.hasOwnProperty.call(target, key)) {
901 target[key] = clone(sval);
902 }
903}
904function _deprecated(scope, value, previous, current) {
905 if (value !== undefined) {
906 console.warn(scope + ': "' + previous +
907 '" is deprecated. Please use "' + current + '" instead');
908 }
909}
910const emptyString = '';
911const dot = '.';
912function indexOfDotOrLength(key, start) {
913 const idx = key.indexOf(dot, start);
914 return idx === -1 ? key.length : idx;
915}
916function resolveObjectKey(obj, key) {
917 if (key === emptyString) {
918 return obj;
919 }
920 let pos = 0;
921 let idx = indexOfDotOrLength(key, pos);
922 while (obj && idx > pos) {
923 obj = obj[key.substr(pos, idx - pos)];
924 pos = idx + 1;
925 idx = indexOfDotOrLength(key, pos);
926 }
927 return obj;
928}
929function _capitalize(str) {
930 return str.charAt(0).toUpperCase() + str.slice(1);
931}
932const defined = (value) => typeof value !== 'undefined';
933const isFunction = (value) => typeof value === 'function';
934const setsEqual = (a, b) => {
935 if (a.size !== b.size) {
936 return false;
937 }
938 for (const item of a) {
939 if (!b.has(item)) {
940 return false;
941 }
942 }
943 return true;
944};
945function _isClickEvent(e) {
946 return e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu';
947}
948
949const overrides = Object.create(null);
950const descriptors = Object.create(null);
951function getScope$1(node, key) {
952 if (!key) {
953 return node;
954 }
955 const keys = key.split('.');
956 for (let i = 0, n = keys.length; i < n; ++i) {
957 const k = keys[i];
958 node = node[k] || (node[k] = Object.create(null));
959 }
960 return node;
961}
962function set(root, scope, values) {
963 if (typeof scope === 'string') {
964 return merge(getScope$1(root, scope), values);
965 }
966 return merge(getScope$1(root, ''), scope);
967}
968class Defaults {
969 constructor(_descriptors) {
970 this.animation = undefined;
971 this.backgroundColor = 'rgba(0,0,0,0.1)';
972 this.borderColor = 'rgba(0,0,0,0.1)';
973 this.color = '#666';
974 this.datasets = {};
975 this.devicePixelRatio = (context) => context.chart.platform.getDevicePixelRatio();
976 this.elements = {};
977 this.events = [
978 'mousemove',
979 'mouseout',
980 'click',
981 'touchstart',
982 'touchmove'
983 ];
984 this.font = {
985 family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
986 size: 12,
987 style: 'normal',
988 lineHeight: 1.2,
989 weight: null
990 };
991 this.hover = {};
992 this.hoverBackgroundColor = (ctx, options) => getHoverColor(options.backgroundColor);
993 this.hoverBorderColor = (ctx, options) => getHoverColor(options.borderColor);
994 this.hoverColor = (ctx, options) => getHoverColor(options.color);
995 this.indexAxis = 'x';
996 this.interaction = {
997 mode: 'nearest',
998 intersect: true
999 };
1000 this.maintainAspectRatio = true;
1001 this.onHover = null;
1002 this.onClick = null;
1003 this.parsing = true;
1004 this.plugins = {};
1005 this.responsive = true;
1006 this.scale = undefined;
1007 this.scales = {};
1008 this.showLine = true;
1009 this.drawActiveElementsOnTop = true;
1010 this.describe(_descriptors);
1011 }
1012 set(scope, values) {
1013 return set(this, scope, values);
1014 }
1015 get(scope) {
1016 return getScope$1(this, scope);
1017 }
1018 describe(scope, values) {
1019 return set(descriptors, scope, values);
1020 }
1021 override(scope, values) {
1022 return set(overrides, scope, values);
1023 }
1024 route(scope, name, targetScope, targetName) {
1025 const scopeObject = getScope$1(this, scope);
1026 const targetScopeObject = getScope$1(this, targetScope);
1027 const privateName = '_' + name;
1028 Object.defineProperties(scopeObject, {
1029 [privateName]: {
1030 value: scopeObject[name],
1031 writable: true
1032 },
1033 [name]: {
1034 enumerable: true,
1035 get() {
1036 const local = this[privateName];
1037 const target = targetScopeObject[targetName];
1038 if (isObject(local)) {
1039 return Object.assign({}, target, local);
1040 }
1041 return valueOrDefault(local, target);
1042 },
1043 set(value) {
1044 this[privateName] = value;
1045 }
1046 }
1047 });
1048 }
1049}
1050var defaults = new Defaults({
1051 _scriptable: (name) => !name.startsWith('on'),
1052 _indexable: (name) => name !== 'events',
1053 hover: {
1054 _fallback: 'interaction'
1055 },
1056 interaction: {
1057 _scriptable: false,
1058 _indexable: false,
1059 }
1060});
1061
1062const PI = Math.PI;
1063const TAU = 2 * PI;
1064const PITAU = TAU + PI;
1065const INFINITY = Number.POSITIVE_INFINITY;
1066const RAD_PER_DEG = PI / 180;
1067const HALF_PI = PI / 2;
1068const QUARTER_PI = PI / 4;
1069const TWO_THIRDS_PI = PI * 2 / 3;
1070const log10 = Math.log10;
1071const sign = Math.sign;
1072function niceNum(range) {
1073 const roundedRange = Math.round(range);
1074 range = almostEquals(range, roundedRange, range / 1000) ? roundedRange : range;
1075 const niceRange = Math.pow(10, Math.floor(log10(range)));
1076 const fraction = range / niceRange;
1077 const niceFraction = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 5 ? 5 : 10;
1078 return niceFraction * niceRange;
1079}
1080function _factorize(value) {
1081 const result = [];
1082 const sqrt = Math.sqrt(value);
1083 let i;
1084 for (i = 1; i < sqrt; i++) {
1085 if (value % i === 0) {
1086 result.push(i);
1087 result.push(value / i);
1088 }
1089 }
1090 if (sqrt === (sqrt | 0)) {
1091 result.push(sqrt);
1092 }
1093 result.sort((a, b) => a - b).pop();
1094 return result;
1095}
1096function isNumber(n) {
1097 return !isNaN(parseFloat(n)) && isFinite(n);
1098}
1099function almostEquals(x, y, epsilon) {
1100 return Math.abs(x - y) < epsilon;
1101}
1102function almostWhole(x, epsilon) {
1103 const rounded = Math.round(x);
1104 return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x);
1105}
1106function _setMinAndMaxByKey(array, target, property) {
1107 let i, ilen, value;
1108 for (i = 0, ilen = array.length; i < ilen; i++) {
1109 value = array[i][property];
1110 if (!isNaN(value)) {
1111 target.min = Math.min(target.min, value);
1112 target.max = Math.max(target.max, value);
1113 }
1114 }
1115}
1116function toRadians(degrees) {
1117 return degrees * (PI / 180);
1118}
1119function toDegrees(radians) {
1120 return radians * (180 / PI);
1121}
1122function _decimalPlaces(x) {
1123 if (!isNumberFinite(x)) {
1124 return;
1125 }
1126 let e = 1;
1127 let p = 0;
1128 while (Math.round(x * e) / e !== x) {
1129 e *= 10;
1130 p++;
1131 }
1132 return p;
1133}
1134function getAngleFromPoint(centrePoint, anglePoint) {
1135 const distanceFromXCenter = anglePoint.x - centrePoint.x;
1136 const distanceFromYCenter = anglePoint.y - centrePoint.y;
1137 const radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
1138 let angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
1139 if (angle < (-0.5 * PI)) {
1140 angle += TAU;
1141 }
1142 return {
1143 angle,
1144 distance: radialDistanceFromCenter
1145 };
1146}
1147function distanceBetweenPoints(pt1, pt2) {
1148 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
1149}
1150function _angleDiff(a, b) {
1151 return (a - b + PITAU) % TAU - PI;
1152}
1153function _normalizeAngle(a) {
1154 return (a % TAU + TAU) % TAU;
1155}
1156function _angleBetween(angle, start, end, sameAngleIsFullCircle) {
1157 const a = _normalizeAngle(angle);
1158 const s = _normalizeAngle(start);
1159 const e = _normalizeAngle(end);
1160 const angleToStart = _normalizeAngle(s - a);
1161 const angleToEnd = _normalizeAngle(e - a);
1162 const startToAngle = _normalizeAngle(a - s);
1163 const endToAngle = _normalizeAngle(a - e);
1164 return a === s || a === e || (sameAngleIsFullCircle && s === e)
1165 || (angleToStart > angleToEnd && startToAngle < endToAngle);
1166}
1167function _limitValue(value, min, max) {
1168 return Math.max(min, Math.min(max, value));
1169}
1170function _int16Range(value) {
1171 return _limitValue(value, -32768, 32767);
1172}
1173function _isBetween(value, start, end, epsilon = 1e-6) {
1174 return value >= Math.min(start, end) - epsilon && value <= Math.max(start, end) + epsilon;
1175}
1176
1177function toFontString(font) {
1178 if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) {
1179 return null;
1180 }
1181 return (font.style ? font.style + ' ' : '')
1182 + (font.weight ? font.weight + ' ' : '')
1183 + font.size + 'px '
1184 + font.family;
1185}
1186function _measureText(ctx, data, gc, longest, string) {
1187 let textWidth = data[string];
1188 if (!textWidth) {
1189 textWidth = data[string] = ctx.measureText(string).width;
1190 gc.push(string);
1191 }
1192 if (textWidth > longest) {
1193 longest = textWidth;
1194 }
1195 return longest;
1196}
1197function _longestText(ctx, font, arrayOfThings, cache) {
1198 cache = cache || {};
1199 let data = cache.data = cache.data || {};
1200 let gc = cache.garbageCollect = cache.garbageCollect || [];
1201 if (cache.font !== font) {
1202 data = cache.data = {};
1203 gc = cache.garbageCollect = [];
1204 cache.font = font;
1205 }
1206 ctx.save();
1207 ctx.font = font;
1208 let longest = 0;
1209 const ilen = arrayOfThings.length;
1210 let i, j, jlen, thing, nestedThing;
1211 for (i = 0; i < ilen; i++) {
1212 thing = arrayOfThings[i];
1213 if (thing !== undefined && thing !== null && isArray(thing) !== true) {
1214 longest = _measureText(ctx, data, gc, longest, thing);
1215 } else if (isArray(thing)) {
1216 for (j = 0, jlen = thing.length; j < jlen; j++) {
1217 nestedThing = thing[j];
1218 if (nestedThing !== undefined && nestedThing !== null && !isArray(nestedThing)) {
1219 longest = _measureText(ctx, data, gc, longest, nestedThing);
1220 }
1221 }
1222 }
1223 }
1224 ctx.restore();
1225 const gcLen = gc.length / 2;
1226 if (gcLen > arrayOfThings.length) {
1227 for (i = 0; i < gcLen; i++) {
1228 delete data[gc[i]];
1229 }
1230 gc.splice(0, gcLen);
1231 }
1232 return longest;
1233}
1234function _alignPixel(chart, pixel, width) {
1235 const devicePixelRatio = chart.currentDevicePixelRatio;
1236 const halfWidth = width !== 0 ? Math.max(width / 2, 0.5) : 0;
1237 return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
1238}
1239function clearCanvas(canvas, ctx) {
1240 ctx = ctx || canvas.getContext('2d');
1241 ctx.save();
1242 ctx.resetTransform();
1243 ctx.clearRect(0, 0, canvas.width, canvas.height);
1244 ctx.restore();
1245}
1246function drawPoint(ctx, options, x, y) {
1247 let type, xOffset, yOffset, size, cornerRadius;
1248 const style = options.pointStyle;
1249 const rotation = options.rotation;
1250 const radius = options.radius;
1251 let rad = (rotation || 0) * RAD_PER_DEG;
1252 if (style && typeof style === 'object') {
1253 type = style.toString();
1254 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
1255 ctx.save();
1256 ctx.translate(x, y);
1257 ctx.rotate(rad);
1258 ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);
1259 ctx.restore();
1260 return;
1261 }
1262 }
1263 if (isNaN(radius) || radius <= 0) {
1264 return;
1265 }
1266 ctx.beginPath();
1267 switch (style) {
1268 default:
1269 ctx.arc(x, y, radius, 0, TAU);
1270 ctx.closePath();
1271 break;
1272 case 'triangle':
1273 ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
1274 rad += TWO_THIRDS_PI;
1275 ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
1276 rad += TWO_THIRDS_PI;
1277 ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
1278 ctx.closePath();
1279 break;
1280 case 'rectRounded':
1281 cornerRadius = radius * 0.516;
1282 size = radius - cornerRadius;
1283 xOffset = Math.cos(rad + QUARTER_PI) * size;
1284 yOffset = Math.sin(rad + QUARTER_PI) * size;
1285 ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
1286 ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad);
1287 ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI);
1288 ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
1289 ctx.closePath();
1290 break;
1291 case 'rect':
1292 if (!rotation) {
1293 size = Math.SQRT1_2 * radius;
1294 ctx.rect(x - size, y - size, 2 * size, 2 * size);
1295 break;
1296 }
1297 rad += QUARTER_PI;
1298 case 'rectRot':
1299 xOffset = Math.cos(rad) * radius;
1300 yOffset = Math.sin(rad) * radius;
1301 ctx.moveTo(x - xOffset, y - yOffset);
1302 ctx.lineTo(x + yOffset, y - xOffset);
1303 ctx.lineTo(x + xOffset, y + yOffset);
1304 ctx.lineTo(x - yOffset, y + xOffset);
1305 ctx.closePath();
1306 break;
1307 case 'crossRot':
1308 rad += QUARTER_PI;
1309 case 'cross':
1310 xOffset = Math.cos(rad) * radius;
1311 yOffset = Math.sin(rad) * radius;
1312 ctx.moveTo(x - xOffset, y - yOffset);
1313 ctx.lineTo(x + xOffset, y + yOffset);
1314 ctx.moveTo(x + yOffset, y - xOffset);
1315 ctx.lineTo(x - yOffset, y + xOffset);
1316 break;
1317 case 'star':
1318 xOffset = Math.cos(rad) * radius;
1319 yOffset = Math.sin(rad) * radius;
1320 ctx.moveTo(x - xOffset, y - yOffset);
1321 ctx.lineTo(x + xOffset, y + yOffset);
1322 ctx.moveTo(x + yOffset, y - xOffset);
1323 ctx.lineTo(x - yOffset, y + xOffset);
1324 rad += QUARTER_PI;
1325 xOffset = Math.cos(rad) * radius;
1326 yOffset = Math.sin(rad) * radius;
1327 ctx.moveTo(x - xOffset, y - yOffset);
1328 ctx.lineTo(x + xOffset, y + yOffset);
1329 ctx.moveTo(x + yOffset, y - xOffset);
1330 ctx.lineTo(x - yOffset, y + xOffset);
1331 break;
1332 case 'line':
1333 xOffset = Math.cos(rad) * radius;
1334 yOffset = Math.sin(rad) * radius;
1335 ctx.moveTo(x - xOffset, y - yOffset);
1336 ctx.lineTo(x + xOffset, y + yOffset);
1337 break;
1338 case 'dash':
1339 ctx.moveTo(x, y);
1340 ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius);
1341 break;
1342 }
1343 ctx.fill();
1344 if (options.borderWidth > 0) {
1345 ctx.stroke();
1346 }
1347}
1348function _isPointInArea(point, area, margin) {
1349 margin = margin || 0.5;
1350 return !area || (point && point.x > area.left - margin && point.x < area.right + margin &&
1351 point.y > area.top - margin && point.y < area.bottom + margin);
1352}
1353function clipArea(ctx, area) {
1354 ctx.save();
1355 ctx.beginPath();
1356 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
1357 ctx.clip();
1358}
1359function unclipArea(ctx) {
1360 ctx.restore();
1361}
1362function _steppedLineTo(ctx, previous, target, flip, mode) {
1363 if (!previous) {
1364 return ctx.lineTo(target.x, target.y);
1365 }
1366 if (mode === 'middle') {
1367 const midpoint = (previous.x + target.x) / 2.0;
1368 ctx.lineTo(midpoint, previous.y);
1369 ctx.lineTo(midpoint, target.y);
1370 } else if (mode === 'after' !== !!flip) {
1371 ctx.lineTo(previous.x, target.y);
1372 } else {
1373 ctx.lineTo(target.x, previous.y);
1374 }
1375 ctx.lineTo(target.x, target.y);
1376}
1377function _bezierCurveTo(ctx, previous, target, flip) {
1378 if (!previous) {
1379 return ctx.lineTo(target.x, target.y);
1380 }
1381 ctx.bezierCurveTo(
1382 flip ? previous.cp1x : previous.cp2x,
1383 flip ? previous.cp1y : previous.cp2y,
1384 flip ? target.cp2x : target.cp1x,
1385 flip ? target.cp2y : target.cp1y,
1386 target.x,
1387 target.y);
1388}
1389function renderText(ctx, text, x, y, font, opts = {}) {
1390 const lines = isArray(text) ? text : [text];
1391 const stroke = opts.strokeWidth > 0 && opts.strokeColor !== '';
1392 let i, line;
1393 ctx.save();
1394 ctx.font = font.string;
1395 setRenderOpts(ctx, opts);
1396 for (i = 0; i < lines.length; ++i) {
1397 line = lines[i];
1398 if (stroke) {
1399 if (opts.strokeColor) {
1400 ctx.strokeStyle = opts.strokeColor;
1401 }
1402 if (!isNullOrUndef(opts.strokeWidth)) {
1403 ctx.lineWidth = opts.strokeWidth;
1404 }
1405 ctx.strokeText(line, x, y, opts.maxWidth);
1406 }
1407 ctx.fillText(line, x, y, opts.maxWidth);
1408 decorateText(ctx, x, y, line, opts);
1409 y += font.lineHeight;
1410 }
1411 ctx.restore();
1412}
1413function setRenderOpts(ctx, opts) {
1414 if (opts.translation) {
1415 ctx.translate(opts.translation[0], opts.translation[1]);
1416 }
1417 if (!isNullOrUndef(opts.rotation)) {
1418 ctx.rotate(opts.rotation);
1419 }
1420 if (opts.color) {
1421 ctx.fillStyle = opts.color;
1422 }
1423 if (opts.textAlign) {
1424 ctx.textAlign = opts.textAlign;
1425 }
1426 if (opts.textBaseline) {
1427 ctx.textBaseline = opts.textBaseline;
1428 }
1429}
1430function decorateText(ctx, x, y, line, opts) {
1431 if (opts.strikethrough || opts.underline) {
1432 const metrics = ctx.measureText(line);
1433 const left = x - metrics.actualBoundingBoxLeft;
1434 const right = x + metrics.actualBoundingBoxRight;
1435 const top = y - metrics.actualBoundingBoxAscent;
1436 const bottom = y + metrics.actualBoundingBoxDescent;
1437 const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;
1438 ctx.strokeStyle = ctx.fillStyle;
1439 ctx.beginPath();
1440 ctx.lineWidth = opts.decorationWidth || 2;
1441 ctx.moveTo(left, yDecoration);
1442 ctx.lineTo(right, yDecoration);
1443 ctx.stroke();
1444 }
1445}
1446function addRoundedRectPath(ctx, rect) {
1447 const {x, y, w, h, radius} = rect;
1448 ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, -HALF_PI, PI, true);
1449 ctx.lineTo(x, y + h - radius.bottomLeft);
1450 ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true);
1451 ctx.lineTo(x + w - radius.bottomRight, y + h);
1452 ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true);
1453 ctx.lineTo(x + w, y + radius.topRight);
1454 ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true);
1455 ctx.lineTo(x + radius.topLeft, y);
1456}
1457
1458function _lookup(table, value, cmp) {
1459 cmp = cmp || ((index) => table[index] < value);
1460 let hi = table.length - 1;
1461 let lo = 0;
1462 let mid;
1463 while (hi - lo > 1) {
1464 mid = (lo + hi) >> 1;
1465 if (cmp(mid)) {
1466 lo = mid;
1467 } else {
1468 hi = mid;
1469 }
1470 }
1471 return {lo, hi};
1472}
1473const _lookupByKey = (table, key, value) =>
1474 _lookup(table, value, index => table[index][key] < value);
1475const _rlookupByKey = (table, key, value) =>
1476 _lookup(table, value, index => table[index][key] >= value);
1477function _filterBetween(values, min, max) {
1478 let start = 0;
1479 let end = values.length;
1480 while (start < end && values[start] < min) {
1481 start++;
1482 }
1483 while (end > start && values[end - 1] > max) {
1484 end--;
1485 }
1486 return start > 0 || end < values.length
1487 ? values.slice(start, end)
1488 : values;
1489}
1490const arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
1491function listenArrayEvents(array, listener) {
1492 if (array._chartjs) {
1493 array._chartjs.listeners.push(listener);
1494 return;
1495 }
1496 Object.defineProperty(array, '_chartjs', {
1497 configurable: true,
1498 enumerable: false,
1499 value: {
1500 listeners: [listener]
1501 }
1502 });
1503 arrayEvents.forEach((key) => {
1504 const method = '_onData' + _capitalize(key);
1505 const base = array[key];
1506 Object.defineProperty(array, key, {
1507 configurable: true,
1508 enumerable: false,
1509 value(...args) {
1510 const res = base.apply(this, args);
1511 array._chartjs.listeners.forEach((object) => {
1512 if (typeof object[method] === 'function') {
1513 object[method](...args);
1514 }
1515 });
1516 return res;
1517 }
1518 });
1519 });
1520}
1521function unlistenArrayEvents(array, listener) {
1522 const stub = array._chartjs;
1523 if (!stub) {
1524 return;
1525 }
1526 const listeners = stub.listeners;
1527 const index = listeners.indexOf(listener);
1528 if (index !== -1) {
1529 listeners.splice(index, 1);
1530 }
1531 if (listeners.length > 0) {
1532 return;
1533 }
1534 arrayEvents.forEach((key) => {
1535 delete array[key];
1536 });
1537 delete array._chartjs;
1538}
1539function _arrayUnique(items) {
1540 const set = new Set();
1541 let i, ilen;
1542 for (i = 0, ilen = items.length; i < ilen; ++i) {
1543 set.add(items[i]);
1544 }
1545 if (set.size === ilen) {
1546 return items;
1547 }
1548 return Array.from(set);
1549}
1550
1551function _isDomSupported() {
1552 return typeof window !== 'undefined' && typeof document !== 'undefined';
1553}
1554function _getParentNode(domNode) {
1555 let parent = domNode.parentNode;
1556 if (parent && parent.toString() === '[object ShadowRoot]') {
1557 parent = parent.host;
1558 }
1559 return parent;
1560}
1561function parseMaxStyle(styleValue, node, parentProperty) {
1562 let valueInPixels;
1563 if (typeof styleValue === 'string') {
1564 valueInPixels = parseInt(styleValue, 10);
1565 if (styleValue.indexOf('%') !== -1) {
1566 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
1567 }
1568 } else {
1569 valueInPixels = styleValue;
1570 }
1571 return valueInPixels;
1572}
1573const getComputedStyle = (element) => window.getComputedStyle(element, null);
1574function getStyle(el, property) {
1575 return getComputedStyle(el).getPropertyValue(property);
1576}
1577const positions = ['top', 'right', 'bottom', 'left'];
1578function getPositionedStyle(styles, style, suffix) {
1579 const result = {};
1580 suffix = suffix ? '-' + suffix : '';
1581 for (let i = 0; i < 4; i++) {
1582 const pos = positions[i];
1583 result[pos] = parseFloat(styles[style + '-' + pos + suffix]) || 0;
1584 }
1585 result.width = result.left + result.right;
1586 result.height = result.top + result.bottom;
1587 return result;
1588}
1589const useOffsetPos = (x, y, target) => (x > 0 || y > 0) && (!target || !target.shadowRoot);
1590function getCanvasPosition(evt, canvas) {
1591 const e = evt.native || evt;
1592 const touches = e.touches;
1593 const source = touches && touches.length ? touches[0] : e;
1594 const {offsetX, offsetY} = source;
1595 let box = false;
1596 let x, y;
1597 if (useOffsetPos(offsetX, offsetY, e.target)) {
1598 x = offsetX;
1599 y = offsetY;
1600 } else {
1601 const rect = canvas.getBoundingClientRect();
1602 x = source.clientX - rect.left;
1603 y = source.clientY - rect.top;
1604 box = true;
1605 }
1606 return {x, y, box};
1607}
1608function getRelativePosition$1(evt, chart) {
1609 const {canvas, currentDevicePixelRatio} = chart;
1610 const style = getComputedStyle(canvas);
1611 const borderBox = style.boxSizing === 'border-box';
1612 const paddings = getPositionedStyle(style, 'padding');
1613 const borders = getPositionedStyle(style, 'border', 'width');
1614 const {x, y, box} = getCanvasPosition(evt, canvas);
1615 const xOffset = paddings.left + (box && borders.left);
1616 const yOffset = paddings.top + (box && borders.top);
1617 let {width, height} = chart;
1618 if (borderBox) {
1619 width -= paddings.width + borders.width;
1620 height -= paddings.height + borders.height;
1621 }
1622 return {
1623 x: Math.round((x - xOffset) / width * canvas.width / currentDevicePixelRatio),
1624 y: Math.round((y - yOffset) / height * canvas.height / currentDevicePixelRatio)
1625 };
1626}
1627function getContainerSize(canvas, width, height) {
1628 let maxWidth, maxHeight;
1629 if (width === undefined || height === undefined) {
1630 const container = _getParentNode(canvas);
1631 if (!container) {
1632 width = canvas.clientWidth;
1633 height = canvas.clientHeight;
1634 } else {
1635 const rect = container.getBoundingClientRect();
1636 const containerStyle = getComputedStyle(container);
1637 const containerBorder = getPositionedStyle(containerStyle, 'border', 'width');
1638 const containerPadding = getPositionedStyle(containerStyle, 'padding');
1639 width = rect.width - containerPadding.width - containerBorder.width;
1640 height = rect.height - containerPadding.height - containerBorder.height;
1641 maxWidth = parseMaxStyle(containerStyle.maxWidth, container, 'clientWidth');
1642 maxHeight = parseMaxStyle(containerStyle.maxHeight, container, 'clientHeight');
1643 }
1644 }
1645 return {
1646 width,
1647 height,
1648 maxWidth: maxWidth || INFINITY,
1649 maxHeight: maxHeight || INFINITY
1650 };
1651}
1652const round1 = v => Math.round(v * 10) / 10;
1653function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) {
1654 const style = getComputedStyle(canvas);
1655 const margins = getPositionedStyle(style, 'margin');
1656 const maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY;
1657 const maxHeight = parseMaxStyle(style.maxHeight, canvas, 'clientHeight') || INFINITY;
1658 const containerSize = getContainerSize(canvas, bbWidth, bbHeight);
1659 let {width, height} = containerSize;
1660 if (style.boxSizing === 'content-box') {
1661 const borders = getPositionedStyle(style, 'border', 'width');
1662 const paddings = getPositionedStyle(style, 'padding');
1663 width -= paddings.width + borders.width;
1664 height -= paddings.height + borders.height;
1665 }
1666 width = Math.max(0, width - margins.width);
1667 height = Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height - margins.height);
1668 width = round1(Math.min(width, maxWidth, containerSize.maxWidth));
1669 height = round1(Math.min(height, maxHeight, containerSize.maxHeight));
1670 if (width && !height) {
1671 height = round1(width / 2);
1672 }
1673 return {
1674 width,
1675 height
1676 };
1677}
1678function retinaScale(chart, forceRatio, forceStyle) {
1679 const pixelRatio = forceRatio || 1;
1680 const deviceHeight = Math.floor(chart.height * pixelRatio);
1681 const deviceWidth = Math.floor(chart.width * pixelRatio);
1682 chart.height = deviceHeight / pixelRatio;
1683 chart.width = deviceWidth / pixelRatio;
1684 const canvas = chart.canvas;
1685 if (canvas.style && (forceStyle || (!canvas.style.height && !canvas.style.width))) {
1686 canvas.style.height = `${chart.height}px`;
1687 canvas.style.width = `${chart.width}px`;
1688 }
1689 if (chart.currentDevicePixelRatio !== pixelRatio
1690 || canvas.height !== deviceHeight
1691 || canvas.width !== deviceWidth) {
1692 chart.currentDevicePixelRatio = pixelRatio;
1693 canvas.height = deviceHeight;
1694 canvas.width = deviceWidth;
1695 chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
1696 return true;
1697 }
1698 return false;
1699}
1700const supportsEventListenerOptions = (function() {
1701 let passiveSupported = false;
1702 try {
1703 const options = {
1704 get passive() {
1705 passiveSupported = true;
1706 return false;
1707 }
1708 };
1709 window.addEventListener('test', null, options);
1710 window.removeEventListener('test', null, options);
1711 } catch (e) {
1712 }
1713 return passiveSupported;
1714}());
1715function readUsedSize(element, property) {
1716 const value = getStyle(element, property);
1717 const matches = value && value.match(/^(\d+)(\.\d+)?px$/);
1718 return matches ? +matches[1] : undefined;
1719}
1720
1721function getRelativePosition(e, chart) {
1722 if ('native' in e) {
1723 return {
1724 x: e.x,
1725 y: e.y
1726 };
1727 }
1728 return getRelativePosition$1(e, chart);
1729}
1730function evaluateAllVisibleItems(chart, handler) {
1731 const metasets = chart.getSortedVisibleDatasetMetas();
1732 let index, data, element;
1733 for (let i = 0, ilen = metasets.length; i < ilen; ++i) {
1734 ({index, data} = metasets[i]);
1735 for (let j = 0, jlen = data.length; j < jlen; ++j) {
1736 element = data[j];
1737 if (!element.skip) {
1738 handler(element, index, j);
1739 }
1740 }
1741 }
1742}
1743function binarySearch(metaset, axis, value, intersect) {
1744 const {controller, data, _sorted} = metaset;
1745 const iScale = controller._cachedMeta.iScale;
1746 if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {
1747 const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey;
1748 if (!intersect) {
1749 return lookupMethod(data, axis, value);
1750 } else if (controller._sharedOptions) {
1751 const el = data[0];
1752 const range = typeof el.getRange === 'function' && el.getRange(axis);
1753 if (range) {
1754 const start = lookupMethod(data, axis, value - range);
1755 const end = lookupMethod(data, axis, value + range);
1756 return {lo: start.lo, hi: end.hi};
1757 }
1758 }
1759 }
1760 return {lo: 0, hi: data.length - 1};
1761}
1762function optimizedEvaluateItems(chart, axis, position, handler, intersect) {
1763 const metasets = chart.getSortedVisibleDatasetMetas();
1764 const value = position[axis];
1765 for (let i = 0, ilen = metasets.length; i < ilen; ++i) {
1766 const {index, data} = metasets[i];
1767 const {lo, hi} = binarySearch(metasets[i], axis, value, intersect);
1768 for (let j = lo; j <= hi; ++j) {
1769 const element = data[j];
1770 if (!element.skip) {
1771 handler(element, index, j);
1772 }
1773 }
1774 }
1775}
1776function getDistanceMetricForAxis(axis) {
1777 const useX = axis.indexOf('x') !== -1;
1778 const useY = axis.indexOf('y') !== -1;
1779 return function(pt1, pt2) {
1780 const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
1781 const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
1782 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
1783 };
1784}
1785function getIntersectItems(chart, position, axis, useFinalPosition) {
1786 const items = [];
1787 if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) {
1788 return items;
1789 }
1790 const evaluationFunc = function(element, datasetIndex, index) {
1791 if (element.inRange(position.x, position.y, useFinalPosition)) {
1792 items.push({element, datasetIndex, index});
1793 }
1794 };
1795 optimizedEvaluateItems(chart, axis, position, evaluationFunc, true);
1796 return items;
1797}
1798function getNearestRadialItems(chart, position, axis, useFinalPosition) {
1799 let items = [];
1800 function evaluationFunc(element, datasetIndex, index) {
1801 const {startAngle, endAngle} = element.getProps(['startAngle', 'endAngle'], useFinalPosition);
1802 const {angle} = getAngleFromPoint(element, {x: position.x, y: position.y});
1803 if (_angleBetween(angle, startAngle, endAngle)) {
1804 items.push({element, datasetIndex, index});
1805 }
1806 }
1807 optimizedEvaluateItems(chart, axis, position, evaluationFunc);
1808 return items;
1809}
1810function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition) {
1811 let items = [];
1812 const distanceMetric = getDistanceMetricForAxis(axis);
1813 let minDistance = Number.POSITIVE_INFINITY;
1814 function evaluationFunc(element, datasetIndex, index) {
1815 const inRange = element.inRange(position.x, position.y, useFinalPosition);
1816 if (intersect && !inRange) {
1817 return;
1818 }
1819 const center = element.getCenterPoint(useFinalPosition);
1820 const pointInArea = _isPointInArea(center, chart.chartArea, chart._minPadding);
1821 if (!pointInArea && !inRange) {
1822 return;
1823 }
1824 const distance = distanceMetric(position, center);
1825 if (distance < minDistance) {
1826 items = [{element, datasetIndex, index}];
1827 minDistance = distance;
1828 } else if (distance === minDistance) {
1829 items.push({element, datasetIndex, index});
1830 }
1831 }
1832 optimizedEvaluateItems(chart, axis, position, evaluationFunc);
1833 return items;
1834}
1835function getNearestItems(chart, position, axis, intersect, useFinalPosition) {
1836 if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) {
1837 return [];
1838 }
1839 return axis === 'r' && !intersect
1840 ? getNearestRadialItems(chart, position, axis, useFinalPosition)
1841 : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition);
1842}
1843function getAxisItems(chart, e, options, useFinalPosition) {
1844 const position = getRelativePosition(e, chart);
1845 const items = [];
1846 const axis = options.axis;
1847 const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
1848 let intersectsItem = false;
1849 evaluateAllVisibleItems(chart, (element, datasetIndex, index) => {
1850 if (element[rangeMethod](position[axis], useFinalPosition)) {
1851 items.push({element, datasetIndex, index});
1852 }
1853 if (element.inRange(position.x, position.y, useFinalPosition)) {
1854 intersectsItem = true;
1855 }
1856 });
1857 if (options.intersect && !intersectsItem) {
1858 return [];
1859 }
1860 return items;
1861}
1862var Interaction = {
1863 modes: {
1864 index(chart, e, options, useFinalPosition) {
1865 const position = getRelativePosition(e, chart);
1866 const axis = options.axis || 'x';
1867 const items = options.intersect
1868 ? getIntersectItems(chart, position, axis, useFinalPosition)
1869 : getNearestItems(chart, position, axis, false, useFinalPosition);
1870 const elements = [];
1871 if (!items.length) {
1872 return [];
1873 }
1874 chart.getSortedVisibleDatasetMetas().forEach((meta) => {
1875 const index = items[0].index;
1876 const element = meta.data[index];
1877 if (element && !element.skip) {
1878 elements.push({element, datasetIndex: meta.index, index});
1879 }
1880 });
1881 return elements;
1882 },
1883 dataset(chart, e, options, useFinalPosition) {
1884 const position = getRelativePosition(e, chart);
1885 const axis = options.axis || 'xy';
1886 let items = options.intersect
1887 ? getIntersectItems(chart, position, axis, useFinalPosition) :
1888 getNearestItems(chart, position, axis, false, useFinalPosition);
1889 if (items.length > 0) {
1890 const datasetIndex = items[0].datasetIndex;
1891 const data = chart.getDatasetMeta(datasetIndex).data;
1892 items = [];
1893 for (let i = 0; i < data.length; ++i) {
1894 items.push({element: data[i], datasetIndex, index: i});
1895 }
1896 }
1897 return items;
1898 },
1899 point(chart, e, options, useFinalPosition) {
1900 const position = getRelativePosition(e, chart);
1901 const axis = options.axis || 'xy';
1902 return getIntersectItems(chart, position, axis, useFinalPosition);
1903 },
1904 nearest(chart, e, options, useFinalPosition) {
1905 const position = getRelativePosition(e, chart);
1906 const axis = options.axis || 'xy';
1907 return getNearestItems(chart, position, axis, options.intersect, useFinalPosition);
1908 },
1909 x(chart, e, options, useFinalPosition) {
1910 return getAxisItems(chart, e, {axis: 'x', intersect: options.intersect}, useFinalPosition);
1911 },
1912 y(chart, e, options, useFinalPosition) {
1913 return getAxisItems(chart, e, {axis: 'y', intersect: options.intersect}, useFinalPosition);
1914 }
1915 }
1916};
1917
1918const LINE_HEIGHT = new RegExp(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
1919const FONT_STYLE = new RegExp(/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/);
1920function toLineHeight(value, size) {
1921 const matches = ('' + value).match(LINE_HEIGHT);
1922 if (!matches || matches[1] === 'normal') {
1923 return size * 1.2;
1924 }
1925 value = +matches[2];
1926 switch (matches[3]) {
1927 case 'px':
1928 return value;
1929 case '%':
1930 value /= 100;
1931 break;
1932 }
1933 return size * value;
1934}
1935const numberOrZero = v => +v || 0;
1936function _readValueToProps(value, props) {
1937 const ret = {};
1938 const objProps = isObject(props);
1939 const keys = objProps ? Object.keys(props) : props;
1940 const read = isObject(value)
1941 ? objProps
1942 ? prop => valueOrDefault(value[prop], value[props[prop]])
1943 : prop => value[prop]
1944 : () => value;
1945 for (const prop of keys) {
1946 ret[prop] = numberOrZero(read(prop));
1947 }
1948 return ret;
1949}
1950function toTRBL(value) {
1951 return _readValueToProps(value, {top: 'y', right: 'x', bottom: 'y', left: 'x'});
1952}
1953function toTRBLCorners(value) {
1954 return _readValueToProps(value, ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']);
1955}
1956function toPadding(value) {
1957 const obj = toTRBL(value);
1958 obj.width = obj.left + obj.right;
1959 obj.height = obj.top + obj.bottom;
1960 return obj;
1961}
1962function toFont(options, fallback) {
1963 options = options || {};
1964 fallback = fallback || defaults.font;
1965 let size = valueOrDefault(options.size, fallback.size);
1966 if (typeof size === 'string') {
1967 size = parseInt(size, 10);
1968 }
1969 let style = valueOrDefault(options.style, fallback.style);
1970 if (style && !('' + style).match(FONT_STYLE)) {
1971 console.warn('Invalid font style specified: "' + style + '"');
1972 style = '';
1973 }
1974 const font = {
1975 family: valueOrDefault(options.family, fallback.family),
1976 lineHeight: toLineHeight(valueOrDefault(options.lineHeight, fallback.lineHeight), size),
1977 size,
1978 style,
1979 weight: valueOrDefault(options.weight, fallback.weight),
1980 string: ''
1981 };
1982 font.string = toFontString(font);
1983 return font;
1984}
1985function resolve(inputs, context, index, info) {
1986 let cacheable = true;
1987 let i, ilen, value;
1988 for (i = 0, ilen = inputs.length; i < ilen; ++i) {
1989 value = inputs[i];
1990 if (value === undefined) {
1991 continue;
1992 }
1993 if (context !== undefined && typeof value === 'function') {
1994 value = value(context);
1995 cacheable = false;
1996 }
1997 if (index !== undefined && isArray(value)) {
1998 value = value[index % value.length];
1999 cacheable = false;
2000 }
2001 if (value !== undefined) {
2002 if (info && !cacheable) {
2003 info.cacheable = false;
2004 }
2005 return value;
2006 }
2007 }
2008}
2009function _addGrace(minmax, grace, beginAtZero) {
2010 const {min, max} = minmax;
2011 const change = toDimension(grace, (max - min) / 2);
2012 const keepZero = (value, add) => beginAtZero && value === 0 ? 0 : value + add;
2013 return {
2014 min: keepZero(min, -Math.abs(change)),
2015 max: keepZero(max, change)
2016 };
2017}
2018function createContext(parentContext, context) {
2019 return Object.assign(Object.create(parentContext), context);
2020}
2021
2022const STATIC_POSITIONS = ['left', 'top', 'right', 'bottom'];
2023function filterByPosition(array, position) {
2024 return array.filter(v => v.pos === position);
2025}
2026function filterDynamicPositionByAxis(array, axis) {
2027 return array.filter(v => STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);
2028}
2029function sortByWeight(array, reverse) {
2030 return array.sort((a, b) => {
2031 const v0 = reverse ? b : a;
2032 const v1 = reverse ? a : b;
2033 return v0.weight === v1.weight ?
2034 v0.index - v1.index :
2035 v0.weight - v1.weight;
2036 });
2037}
2038function wrapBoxes(boxes) {
2039 const layoutBoxes = [];
2040 let i, ilen, box, pos, stack, stackWeight;
2041 for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) {
2042 box = boxes[i];
2043 ({position: pos, options: {stack, stackWeight = 1}} = box);
2044 layoutBoxes.push({
2045 index: i,
2046 box,
2047 pos,
2048 horizontal: box.isHorizontal(),
2049 weight: box.weight,
2050 stack: stack && (pos + stack),
2051 stackWeight
2052 });
2053 }
2054 return layoutBoxes;
2055}
2056function buildStacks(layouts) {
2057 const stacks = {};
2058 for (const wrap of layouts) {
2059 const {stack, pos, stackWeight} = wrap;
2060 if (!stack || !STATIC_POSITIONS.includes(pos)) {
2061 continue;
2062 }
2063 const _stack = stacks[stack] || (stacks[stack] = {count: 0, placed: 0, weight: 0, size: 0});
2064 _stack.count++;
2065 _stack.weight += stackWeight;
2066 }
2067 return stacks;
2068}
2069function setLayoutDims(layouts, params) {
2070 const stacks = buildStacks(layouts);
2071 const {vBoxMaxWidth, hBoxMaxHeight} = params;
2072 let i, ilen, layout;
2073 for (i = 0, ilen = layouts.length; i < ilen; ++i) {
2074 layout = layouts[i];
2075 const {fullSize} = layout.box;
2076 const stack = stacks[layout.stack];
2077 const factor = stack && layout.stackWeight / stack.weight;
2078 if (layout.horizontal) {
2079 layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth;
2080 layout.height = hBoxMaxHeight;
2081 } else {
2082 layout.width = vBoxMaxWidth;
2083 layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight;
2084 }
2085 }
2086 return stacks;
2087}
2088function buildLayoutBoxes(boxes) {
2089 const layoutBoxes = wrapBoxes(boxes);
2090 const fullSize = sortByWeight(layoutBoxes.filter(wrap => wrap.box.fullSize), true);
2091 const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
2092 const right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
2093 const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
2094 const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
2095 const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');
2096 const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');
2097 return {
2098 fullSize,
2099 leftAndTop: left.concat(top),
2100 rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),
2101 chartArea: filterByPosition(layoutBoxes, 'chartArea'),
2102 vertical: left.concat(right).concat(centerVertical),
2103 horizontal: top.concat(bottom).concat(centerHorizontal)
2104 };
2105}
2106function getCombinedMax(maxPadding, chartArea, a, b) {
2107 return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
2108}
2109function updateMaxPadding(maxPadding, boxPadding) {
2110 maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
2111 maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
2112 maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
2113 maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
2114}
2115function updateDims(chartArea, params, layout, stacks) {
2116 const {pos, box} = layout;
2117 const maxPadding = chartArea.maxPadding;
2118 if (!isObject(pos)) {
2119 if (layout.size) {
2120 chartArea[pos] -= layout.size;
2121 }
2122 const stack = stacks[layout.stack] || {size: 0, count: 1};
2123 stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width);
2124 layout.size = stack.size / stack.count;
2125 chartArea[pos] += layout.size;
2126 }
2127 if (box.getPadding) {
2128 updateMaxPadding(maxPadding, box.getPadding());
2129 }
2130 const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));
2131 const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));
2132 const widthChanged = newWidth !== chartArea.w;
2133 const heightChanged = newHeight !== chartArea.h;
2134 chartArea.w = newWidth;
2135 chartArea.h = newHeight;
2136 return layout.horizontal
2137 ? {same: widthChanged, other: heightChanged}
2138 : {same: heightChanged, other: widthChanged};
2139}
2140function handleMaxPadding(chartArea) {
2141 const maxPadding = chartArea.maxPadding;
2142 function updatePos(pos) {
2143 const change = Math.max(maxPadding[pos] - chartArea[pos], 0);
2144 chartArea[pos] += change;
2145 return change;
2146 }
2147 chartArea.y += updatePos('top');
2148 chartArea.x += updatePos('left');
2149 updatePos('right');
2150 updatePos('bottom');
2151}
2152function getMargins(horizontal, chartArea) {
2153 const maxPadding = chartArea.maxPadding;
2154 function marginForPositions(positions) {
2155 const margin = {left: 0, top: 0, right: 0, bottom: 0};
2156 positions.forEach((pos) => {
2157 margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
2158 });
2159 return margin;
2160 }
2161 return horizontal
2162 ? marginForPositions(['left', 'right'])
2163 : marginForPositions(['top', 'bottom']);
2164}
2165function fitBoxes(boxes, chartArea, params, stacks) {
2166 const refitBoxes = [];
2167 let i, ilen, layout, box, refit, changed;
2168 for (i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i) {
2169 layout = boxes[i];
2170 box = layout.box;
2171 box.update(
2172 layout.width || chartArea.w,
2173 layout.height || chartArea.h,
2174 getMargins(layout.horizontal, chartArea)
2175 );
2176 const {same, other} = updateDims(chartArea, params, layout, stacks);
2177 refit |= same && refitBoxes.length;
2178 changed = changed || other;
2179 if (!box.fullSize) {
2180 refitBoxes.push(layout);
2181 }
2182 }
2183 return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed;
2184}
2185function setBoxDims(box, left, top, width, height) {
2186 box.top = top;
2187 box.left = left;
2188 box.right = left + width;
2189 box.bottom = top + height;
2190 box.width = width;
2191 box.height = height;
2192}
2193function placeBoxes(boxes, chartArea, params, stacks) {
2194 const userPadding = params.padding;
2195 let {x, y} = chartArea;
2196 for (const layout of boxes) {
2197 const box = layout.box;
2198 const stack = stacks[layout.stack] || {count: 1, placed: 0, weight: 1};
2199 const weight = (layout.stackWeight / stack.weight) || 1;
2200 if (layout.horizontal) {
2201 const width = chartArea.w * weight;
2202 const height = stack.size || box.height;
2203 if (defined(stack.start)) {
2204 y = stack.start;
2205 }
2206 if (box.fullSize) {
2207 setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height);
2208 } else {
2209 setBoxDims(box, chartArea.left + stack.placed, y, width, height);
2210 }
2211 stack.start = y;
2212 stack.placed += width;
2213 y = box.bottom;
2214 } else {
2215 const height = chartArea.h * weight;
2216 const width = stack.size || box.width;
2217 if (defined(stack.start)) {
2218 x = stack.start;
2219 }
2220 if (box.fullSize) {
2221 setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top);
2222 } else {
2223 setBoxDims(box, x, chartArea.top + stack.placed, width, height);
2224 }
2225 stack.start = x;
2226 stack.placed += height;
2227 x = box.right;
2228 }
2229 }
2230 chartArea.x = x;
2231 chartArea.y = y;
2232}
2233defaults.set('layout', {
2234 autoPadding: true,
2235 padding: {
2236 top: 0,
2237 right: 0,
2238 bottom: 0,
2239 left: 0
2240 }
2241});
2242var layouts = {
2243 addBox(chart, item) {
2244 if (!chart.boxes) {
2245 chart.boxes = [];
2246 }
2247 item.fullSize = item.fullSize || false;
2248 item.position = item.position || 'top';
2249 item.weight = item.weight || 0;
2250 item._layers = item._layers || function() {
2251 return [{
2252 z: 0,
2253 draw(chartArea) {
2254 item.draw(chartArea);
2255 }
2256 }];
2257 };
2258 chart.boxes.push(item);
2259 },
2260 removeBox(chart, layoutItem) {
2261 const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
2262 if (index !== -1) {
2263 chart.boxes.splice(index, 1);
2264 }
2265 },
2266 configure(chart, item, options) {
2267 item.fullSize = options.fullSize;
2268 item.position = options.position;
2269 item.weight = options.weight;
2270 },
2271 update(chart, width, height, minPadding) {
2272 if (!chart) {
2273 return;
2274 }
2275 const padding = toPadding(chart.options.layout.padding);
2276 const availableWidth = Math.max(width - padding.width, 0);
2277 const availableHeight = Math.max(height - padding.height, 0);
2278 const boxes = buildLayoutBoxes(chart.boxes);
2279 const verticalBoxes = boxes.vertical;
2280 const horizontalBoxes = boxes.horizontal;
2281 each(chart.boxes, box => {
2282 if (typeof box.beforeLayout === 'function') {
2283 box.beforeLayout();
2284 }
2285 });
2286 const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap) =>
2287 wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1;
2288 const params = Object.freeze({
2289 outerWidth: width,
2290 outerHeight: height,
2291 padding,
2292 availableWidth,
2293 availableHeight,
2294 vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
2295 hBoxMaxHeight: availableHeight / 2
2296 });
2297 const maxPadding = Object.assign({}, padding);
2298 updateMaxPadding(maxPadding, toPadding(minPadding));
2299 const chartArea = Object.assign({
2300 maxPadding,
2301 w: availableWidth,
2302 h: availableHeight,
2303 x: padding.left,
2304 y: padding.top
2305 }, padding);
2306 const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
2307 fitBoxes(boxes.fullSize, chartArea, params, stacks);
2308 fitBoxes(verticalBoxes, chartArea, params, stacks);
2309 if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) {
2310 fitBoxes(verticalBoxes, chartArea, params, stacks);
2311 }
2312 handleMaxPadding(chartArea);
2313 placeBoxes(boxes.leftAndTop, chartArea, params, stacks);
2314 chartArea.x += chartArea.w;
2315 chartArea.y += chartArea.h;
2316 placeBoxes(boxes.rightAndBottom, chartArea, params, stacks);
2317 chart.chartArea = {
2318 left: chartArea.left,
2319 top: chartArea.top,
2320 right: chartArea.left + chartArea.w,
2321 bottom: chartArea.top + chartArea.h,
2322 height: chartArea.h,
2323 width: chartArea.w,
2324 };
2325 each(boxes.chartArea, (layout) => {
2326 const box = layout.box;
2327 Object.assign(box, chart.chartArea);
2328 box.update(chartArea.w, chartArea.h, {left: 0, top: 0, right: 0, bottom: 0});
2329 });
2330 }
2331};
2332
2333function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fallback, getTarget = () => scopes[0]) {
2334 if (!defined(fallback)) {
2335 fallback = _resolve('_fallback', scopes);
2336 }
2337 const cache = {
2338 [Symbol.toStringTag]: 'Object',
2339 _cacheable: true,
2340 _scopes: scopes,
2341 _rootScopes: rootScopes,
2342 _fallback: fallback,
2343 _getTarget: getTarget,
2344 override: (scope) => _createResolver([scope, ...scopes], prefixes, rootScopes, fallback),
2345 };
2346 return new Proxy(cache, {
2347 deleteProperty(target, prop) {
2348 delete target[prop];
2349 delete target._keys;
2350 delete scopes[0][prop];
2351 return true;
2352 },
2353 get(target, prop) {
2354 return _cached(target, prop,
2355 () => _resolveWithPrefixes(prop, prefixes, scopes, target));
2356 },
2357 getOwnPropertyDescriptor(target, prop) {
2358 return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop);
2359 },
2360 getPrototypeOf() {
2361 return Reflect.getPrototypeOf(scopes[0]);
2362 },
2363 has(target, prop) {
2364 return getKeysFromAllScopes(target).includes(prop);
2365 },
2366 ownKeys(target) {
2367 return getKeysFromAllScopes(target);
2368 },
2369 set(target, prop, value) {
2370 const storage = target._storage || (target._storage = getTarget());
2371 target[prop] = storage[prop] = value;
2372 delete target._keys;
2373 return true;
2374 }
2375 });
2376}
2377function _attachContext(proxy, context, subProxy, descriptorDefaults) {
2378 const cache = {
2379 _cacheable: false,
2380 _proxy: proxy,
2381 _context: context,
2382 _subProxy: subProxy,
2383 _stack: new Set(),
2384 _descriptors: _descriptors(proxy, descriptorDefaults),
2385 setContext: (ctx) => _attachContext(proxy, ctx, subProxy, descriptorDefaults),
2386 override: (scope) => _attachContext(proxy.override(scope), context, subProxy, descriptorDefaults)
2387 };
2388 return new Proxy(cache, {
2389 deleteProperty(target, prop) {
2390 delete target[prop];
2391 delete proxy[prop];
2392 return true;
2393 },
2394 get(target, prop, receiver) {
2395 return _cached(target, prop,
2396 () => _resolveWithContext(target, prop, receiver));
2397 },
2398 getOwnPropertyDescriptor(target, prop) {
2399 return target._descriptors.allKeys
2400 ? Reflect.has(proxy, prop) ? {enumerable: true, configurable: true} : undefined
2401 : Reflect.getOwnPropertyDescriptor(proxy, prop);
2402 },
2403 getPrototypeOf() {
2404 return Reflect.getPrototypeOf(proxy);
2405 },
2406 has(target, prop) {
2407 return Reflect.has(proxy, prop);
2408 },
2409 ownKeys() {
2410 return Reflect.ownKeys(proxy);
2411 },
2412 set(target, prop, value) {
2413 proxy[prop] = value;
2414 delete target[prop];
2415 return true;
2416 }
2417 });
2418}
2419function _descriptors(proxy, defaults = {scriptable: true, indexable: true}) {
2420 const {_scriptable = defaults.scriptable, _indexable = defaults.indexable, _allKeys = defaults.allKeys} = proxy;
2421 return {
2422 allKeys: _allKeys,
2423 scriptable: _scriptable,
2424 indexable: _indexable,
2425 isScriptable: isFunction(_scriptable) ? _scriptable : () => _scriptable,
2426 isIndexable: isFunction(_indexable) ? _indexable : () => _indexable
2427 };
2428}
2429const readKey = (prefix, name) => prefix ? prefix + _capitalize(name) : name;
2430const needsSubResolver = (prop, value) => isObject(value) && prop !== 'adapters' &&
2431 (Object.getPrototypeOf(value) === null || value.constructor === Object);
2432function _cached(target, prop, resolve) {
2433 if (Object.prototype.hasOwnProperty.call(target, prop)) {
2434 return target[prop];
2435 }
2436 const value = resolve();
2437 target[prop] = value;
2438 return value;
2439}
2440function _resolveWithContext(target, prop, receiver) {
2441 const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;
2442 let value = _proxy[prop];
2443 if (isFunction(value) && descriptors.isScriptable(prop)) {
2444 value = _resolveScriptable(prop, value, target, receiver);
2445 }
2446 if (isArray(value) && value.length) {
2447 value = _resolveArray(prop, value, target, descriptors.isIndexable);
2448 }
2449 if (needsSubResolver(prop, value)) {
2450 value = _attachContext(value, _context, _subProxy && _subProxy[prop], descriptors);
2451 }
2452 return value;
2453}
2454function _resolveScriptable(prop, value, target, receiver) {
2455 const {_proxy, _context, _subProxy, _stack} = target;
2456 if (_stack.has(prop)) {
2457 throw new Error('Recursion detected: ' + Array.from(_stack).join('->') + '->' + prop);
2458 }
2459 _stack.add(prop);
2460 value = value(_context, _subProxy || receiver);
2461 _stack.delete(prop);
2462 if (needsSubResolver(prop, value)) {
2463 value = createSubResolver(_proxy._scopes, _proxy, prop, value);
2464 }
2465 return value;
2466}
2467function _resolveArray(prop, value, target, isIndexable) {
2468 const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;
2469 if (defined(_context.index) && isIndexable(prop)) {
2470 value = value[_context.index % value.length];
2471 } else if (isObject(value[0])) {
2472 const arr = value;
2473 const scopes = _proxy._scopes.filter(s => s !== arr);
2474 value = [];
2475 for (const item of arr) {
2476 const resolver = createSubResolver(scopes, _proxy, prop, item);
2477 value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop], descriptors));
2478 }
2479 }
2480 return value;
2481}
2482function resolveFallback(fallback, prop, value) {
2483 return isFunction(fallback) ? fallback(prop, value) : fallback;
2484}
2485const getScope = (key, parent) => key === true ? parent
2486 : typeof key === 'string' ? resolveObjectKey(parent, key) : undefined;
2487function addScopes(set, parentScopes, key, parentFallback, value) {
2488 for (const parent of parentScopes) {
2489 const scope = getScope(key, parent);
2490 if (scope) {
2491 set.add(scope);
2492 const fallback = resolveFallback(scope._fallback, key, value);
2493 if (defined(fallback) && fallback !== key && fallback !== parentFallback) {
2494 return fallback;
2495 }
2496 } else if (scope === false && defined(parentFallback) && key !== parentFallback) {
2497 return null;
2498 }
2499 }
2500 return false;
2501}
2502function createSubResolver(parentScopes, resolver, prop, value) {
2503 const rootScopes = resolver._rootScopes;
2504 const fallback = resolveFallback(resolver._fallback, prop, value);
2505 const allScopes = [...parentScopes, ...rootScopes];
2506 const set = new Set();
2507 set.add(value);
2508 let key = addScopesFromKey(set, allScopes, prop, fallback || prop, value);
2509 if (key === null) {
2510 return false;
2511 }
2512 if (defined(fallback) && fallback !== prop) {
2513 key = addScopesFromKey(set, allScopes, fallback, key, value);
2514 if (key === null) {
2515 return false;
2516 }
2517 }
2518 return _createResolver(Array.from(set), [''], rootScopes, fallback,
2519 () => subGetTarget(resolver, prop, value));
2520}
2521function addScopesFromKey(set, allScopes, key, fallback, item) {
2522 while (key) {
2523 key = addScopes(set, allScopes, key, fallback, item);
2524 }
2525 return key;
2526}
2527function subGetTarget(resolver, prop, value) {
2528 const parent = resolver._getTarget();
2529 if (!(prop in parent)) {
2530 parent[prop] = {};
2531 }
2532 const target = parent[prop];
2533 if (isArray(target) && isObject(value)) {
2534 return value;
2535 }
2536 return target;
2537}
2538function _resolveWithPrefixes(prop, prefixes, scopes, proxy) {
2539 let value;
2540 for (const prefix of prefixes) {
2541 value = _resolve(readKey(prefix, prop), scopes);
2542 if (defined(value)) {
2543 return needsSubResolver(prop, value)
2544 ? createSubResolver(scopes, proxy, prop, value)
2545 : value;
2546 }
2547 }
2548}
2549function _resolve(key, scopes) {
2550 for (const scope of scopes) {
2551 if (!scope) {
2552 continue;
2553 }
2554 const value = scope[key];
2555 if (defined(value)) {
2556 return value;
2557 }
2558 }
2559}
2560function getKeysFromAllScopes(target) {
2561 let keys = target._keys;
2562 if (!keys) {
2563 keys = target._keys = resolveKeysFromAllScopes(target._scopes);
2564 }
2565 return keys;
2566}
2567function resolveKeysFromAllScopes(scopes) {
2568 const set = new Set();
2569 for (const scope of scopes) {
2570 for (const key of Object.keys(scope).filter(k => !k.startsWith('_'))) {
2571 set.add(key);
2572 }
2573 }
2574 return Array.from(set);
2575}
2576
2577const EPSILON = Number.EPSILON || 1e-14;
2578const getPoint = (points, i) => i < points.length && !points[i].skip && points[i];
2579const getValueAxis = (indexAxis) => indexAxis === 'x' ? 'y' : 'x';
2580function splineCurve(firstPoint, middlePoint, afterPoint, t) {
2581 const previous = firstPoint.skip ? middlePoint : firstPoint;
2582 const current = middlePoint;
2583 const next = afterPoint.skip ? middlePoint : afterPoint;
2584 const d01 = distanceBetweenPoints(current, previous);
2585 const d12 = distanceBetweenPoints(next, current);
2586 let s01 = d01 / (d01 + d12);
2587 let s12 = d12 / (d01 + d12);
2588 s01 = isNaN(s01) ? 0 : s01;
2589 s12 = isNaN(s12) ? 0 : s12;
2590 const fa = t * s01;
2591 const fb = t * s12;
2592 return {
2593 previous: {
2594 x: current.x - fa * (next.x - previous.x),
2595 y: current.y - fa * (next.y - previous.y)
2596 },
2597 next: {
2598 x: current.x + fb * (next.x - previous.x),
2599 y: current.y + fb * (next.y - previous.y)
2600 }
2601 };
2602}
2603function monotoneAdjust(points, deltaK, mK) {
2604 const pointsLen = points.length;
2605 let alphaK, betaK, tauK, squaredMagnitude, pointCurrent;
2606 let pointAfter = getPoint(points, 0);
2607 for (let i = 0; i < pointsLen - 1; ++i) {
2608 pointCurrent = pointAfter;
2609 pointAfter = getPoint(points, i + 1);
2610 if (!pointCurrent || !pointAfter) {
2611 continue;
2612 }
2613 if (almostEquals(deltaK[i], 0, EPSILON)) {
2614 mK[i] = mK[i + 1] = 0;
2615 continue;
2616 }
2617 alphaK = mK[i] / deltaK[i];
2618 betaK = mK[i + 1] / deltaK[i];
2619 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
2620 if (squaredMagnitude <= 9) {
2621 continue;
2622 }
2623 tauK = 3 / Math.sqrt(squaredMagnitude);
2624 mK[i] = alphaK * tauK * deltaK[i];
2625 mK[i + 1] = betaK * tauK * deltaK[i];
2626 }
2627}
2628function monotoneCompute(points, mK, indexAxis = 'x') {
2629 const valueAxis = getValueAxis(indexAxis);
2630 const pointsLen = points.length;
2631 let delta, pointBefore, pointCurrent;
2632 let pointAfter = getPoint(points, 0);
2633 for (let i = 0; i < pointsLen; ++i) {
2634 pointBefore = pointCurrent;
2635 pointCurrent = pointAfter;
2636 pointAfter = getPoint(points, i + 1);
2637 if (!pointCurrent) {
2638 continue;
2639 }
2640 const iPixel = pointCurrent[indexAxis];
2641 const vPixel = pointCurrent[valueAxis];
2642 if (pointBefore) {
2643 delta = (iPixel - pointBefore[indexAxis]) / 3;
2644 pointCurrent[`cp1${indexAxis}`] = iPixel - delta;
2645 pointCurrent[`cp1${valueAxis}`] = vPixel - delta * mK[i];
2646 }
2647 if (pointAfter) {
2648 delta = (pointAfter[indexAxis] - iPixel) / 3;
2649 pointCurrent[`cp2${indexAxis}`] = iPixel + delta;
2650 pointCurrent[`cp2${valueAxis}`] = vPixel + delta * mK[i];
2651 }
2652 }
2653}
2654function splineCurveMonotone(points, indexAxis = 'x') {
2655 const valueAxis = getValueAxis(indexAxis);
2656 const pointsLen = points.length;
2657 const deltaK = Array(pointsLen).fill(0);
2658 const mK = Array(pointsLen);
2659 let i, pointBefore, pointCurrent;
2660 let pointAfter = getPoint(points, 0);
2661 for (i = 0; i < pointsLen; ++i) {
2662 pointBefore = pointCurrent;
2663 pointCurrent = pointAfter;
2664 pointAfter = getPoint(points, i + 1);
2665 if (!pointCurrent) {
2666 continue;
2667 }
2668 if (pointAfter) {
2669 const slopeDelta = pointAfter[indexAxis] - pointCurrent[indexAxis];
2670 deltaK[i] = slopeDelta !== 0 ? (pointAfter[valueAxis] - pointCurrent[valueAxis]) / slopeDelta : 0;
2671 }
2672 mK[i] = !pointBefore ? deltaK[i]
2673 : !pointAfter ? deltaK[i - 1]
2674 : (sign(deltaK[i - 1]) !== sign(deltaK[i])) ? 0
2675 : (deltaK[i - 1] + deltaK[i]) / 2;
2676 }
2677 monotoneAdjust(points, deltaK, mK);
2678 monotoneCompute(points, mK, indexAxis);
2679}
2680function capControlPoint(pt, min, max) {
2681 return Math.max(Math.min(pt, max), min);
2682}
2683function capBezierPoints(points, area) {
2684 let i, ilen, point, inArea, inAreaPrev;
2685 let inAreaNext = _isPointInArea(points[0], area);
2686 for (i = 0, ilen = points.length; i < ilen; ++i) {
2687 inAreaPrev = inArea;
2688 inArea = inAreaNext;
2689 inAreaNext = i < ilen - 1 && _isPointInArea(points[i + 1], area);
2690 if (!inArea) {
2691 continue;
2692 }
2693 point = points[i];
2694 if (inAreaPrev) {
2695 point.cp1x = capControlPoint(point.cp1x, area.left, area.right);
2696 point.cp1y = capControlPoint(point.cp1y, area.top, area.bottom);
2697 }
2698 if (inAreaNext) {
2699 point.cp2x = capControlPoint(point.cp2x, area.left, area.right);
2700 point.cp2y = capControlPoint(point.cp2y, area.top, area.bottom);
2701 }
2702 }
2703}
2704function _updateBezierControlPoints(points, options, area, loop, indexAxis) {
2705 let i, ilen, point, controlPoints;
2706 if (options.spanGaps) {
2707 points = points.filter((pt) => !pt.skip);
2708 }
2709 if (options.cubicInterpolationMode === 'monotone') {
2710 splineCurveMonotone(points, indexAxis);
2711 } else {
2712 let prev = loop ? points[points.length - 1] : points[0];
2713 for (i = 0, ilen = points.length; i < ilen; ++i) {
2714 point = points[i];
2715 controlPoints = splineCurve(
2716 prev,
2717 point,
2718 points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen],
2719 options.tension
2720 );
2721 point.cp1x = controlPoints.previous.x;
2722 point.cp1y = controlPoints.previous.y;
2723 point.cp2x = controlPoints.next.x;
2724 point.cp2y = controlPoints.next.y;
2725 prev = point;
2726 }
2727 }
2728 if (options.capBezierPoints) {
2729 capBezierPoints(points, area);
2730 }
2731}
2732
2733const atEdge = (t) => t === 0 || t === 1;
2734const elasticIn = (t, s, p) => -(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p));
2735const elasticOut = (t, s, p) => Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1;
2736const effects = {
2737 linear: t => t,
2738 easeInQuad: t => t * t,
2739 easeOutQuad: t => -t * (t - 2),
2740 easeInOutQuad: t => ((t /= 0.5) < 1)
2741 ? 0.5 * t * t
2742 : -0.5 * ((--t) * (t - 2) - 1),
2743 easeInCubic: t => t * t * t,
2744 easeOutCubic: t => (t -= 1) * t * t + 1,
2745 easeInOutCubic: t => ((t /= 0.5) < 1)
2746 ? 0.5 * t * t * t
2747 : 0.5 * ((t -= 2) * t * t + 2),
2748 easeInQuart: t => t * t * t * t,
2749 easeOutQuart: t => -((t -= 1) * t * t * t - 1),
2750 easeInOutQuart: t => ((t /= 0.5) < 1)
2751 ? 0.5 * t * t * t * t
2752 : -0.5 * ((t -= 2) * t * t * t - 2),
2753 easeInQuint: t => t * t * t * t * t,
2754 easeOutQuint: t => (t -= 1) * t * t * t * t + 1,
2755 easeInOutQuint: t => ((t /= 0.5) < 1)
2756 ? 0.5 * t * t * t * t * t
2757 : 0.5 * ((t -= 2) * t * t * t * t + 2),
2758 easeInSine: t => -Math.cos(t * HALF_PI) + 1,
2759 easeOutSine: t => Math.sin(t * HALF_PI),
2760 easeInOutSine: t => -0.5 * (Math.cos(PI * t) - 1),
2761 easeInExpo: t => (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)),
2762 easeOutExpo: t => (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1,
2763 easeInOutExpo: t => atEdge(t) ? t : t < 0.5
2764 ? 0.5 * Math.pow(2, 10 * (t * 2 - 1))
2765 : 0.5 * (-Math.pow(2, -10 * (t * 2 - 1)) + 2),
2766 easeInCirc: t => (t >= 1) ? t : -(Math.sqrt(1 - t * t) - 1),
2767 easeOutCirc: t => Math.sqrt(1 - (t -= 1) * t),
2768 easeInOutCirc: t => ((t /= 0.5) < 1)
2769 ? -0.5 * (Math.sqrt(1 - t * t) - 1)
2770 : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1),
2771 easeInElastic: t => atEdge(t) ? t : elasticIn(t, 0.075, 0.3),
2772 easeOutElastic: t => atEdge(t) ? t : elasticOut(t, 0.075, 0.3),
2773 easeInOutElastic(t) {
2774 const s = 0.1125;
2775 const p = 0.45;
2776 return atEdge(t) ? t :
2777 t < 0.5
2778 ? 0.5 * elasticIn(t * 2, s, p)
2779 : 0.5 + 0.5 * elasticOut(t * 2 - 1, s, p);
2780 },
2781 easeInBack(t) {
2782 const s = 1.70158;
2783 return t * t * ((s + 1) * t - s);
2784 },
2785 easeOutBack(t) {
2786 const s = 1.70158;
2787 return (t -= 1) * t * ((s + 1) * t + s) + 1;
2788 },
2789 easeInOutBack(t) {
2790 let s = 1.70158;
2791 if ((t /= 0.5) < 1) {
2792 return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
2793 }
2794 return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
2795 },
2796 easeInBounce: t => 1 - effects.easeOutBounce(1 - t),
2797 easeOutBounce(t) {
2798 const m = 7.5625;
2799 const d = 2.75;
2800 if (t < (1 / d)) {
2801 return m * t * t;
2802 }
2803 if (t < (2 / d)) {
2804 return m * (t -= (1.5 / d)) * t + 0.75;
2805 }
2806 if (t < (2.5 / d)) {
2807 return m * (t -= (2.25 / d)) * t + 0.9375;
2808 }
2809 return m * (t -= (2.625 / d)) * t + 0.984375;
2810 },
2811 easeInOutBounce: t => (t < 0.5)
2812 ? effects.easeInBounce(t * 2) * 0.5
2813 : effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5,
2814};
2815
2816function _pointInLine(p1, p2, t, mode) {
2817 return {
2818 x: p1.x + t * (p2.x - p1.x),
2819 y: p1.y + t * (p2.y - p1.y)
2820 };
2821}
2822function _steppedInterpolation(p1, p2, t, mode) {
2823 return {
2824 x: p1.x + t * (p2.x - p1.x),
2825 y: mode === 'middle' ? t < 0.5 ? p1.y : p2.y
2826 : mode === 'after' ? t < 1 ? p1.y : p2.y
2827 : t > 0 ? p2.y : p1.y
2828 };
2829}
2830function _bezierInterpolation(p1, p2, t, mode) {
2831 const cp1 = {x: p1.cp2x, y: p1.cp2y};
2832 const cp2 = {x: p2.cp1x, y: p2.cp1y};
2833 const a = _pointInLine(p1, cp1, t);
2834 const b = _pointInLine(cp1, cp2, t);
2835 const c = _pointInLine(cp2, p2, t);
2836 const d = _pointInLine(a, b, t);
2837 const e = _pointInLine(b, c, t);
2838 return _pointInLine(d, e, t);
2839}
2840
2841const intlCache = new Map();
2842function getNumberFormat(locale, options) {
2843 options = options || {};
2844 const cacheKey = locale + JSON.stringify(options);
2845 let formatter = intlCache.get(cacheKey);
2846 if (!formatter) {
2847 formatter = new Intl.NumberFormat(locale, options);
2848 intlCache.set(cacheKey, formatter);
2849 }
2850 return formatter;
2851}
2852function formatNumber(num, locale, options) {
2853 return getNumberFormat(locale, options).format(num);
2854}
2855
2856const getRightToLeftAdapter = function(rectX, width) {
2857 return {
2858 x(x) {
2859 return rectX + rectX + width - x;
2860 },
2861 setWidth(w) {
2862 width = w;
2863 },
2864 textAlign(align) {
2865 if (align === 'center') {
2866 return align;
2867 }
2868 return align === 'right' ? 'left' : 'right';
2869 },
2870 xPlus(x, value) {
2871 return x - value;
2872 },
2873 leftForLtr(x, itemWidth) {
2874 return x - itemWidth;
2875 },
2876 };
2877};
2878const getLeftToRightAdapter = function() {
2879 return {
2880 x(x) {
2881 return x;
2882 },
2883 setWidth(w) {
2884 },
2885 textAlign(align) {
2886 return align;
2887 },
2888 xPlus(x, value) {
2889 return x + value;
2890 },
2891 leftForLtr(x, _itemWidth) {
2892 return x;
2893 },
2894 };
2895};
2896function getRtlAdapter(rtl, rectX, width) {
2897 return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter();
2898}
2899function overrideTextDirection(ctx, direction) {
2900 let style, original;
2901 if (direction === 'ltr' || direction === 'rtl') {
2902 style = ctx.canvas.style;
2903 original = [
2904 style.getPropertyValue('direction'),
2905 style.getPropertyPriority('direction'),
2906 ];
2907 style.setProperty('direction', direction, 'important');
2908 ctx.prevTextDirection = original;
2909 }
2910}
2911function restoreTextDirection(ctx, original) {
2912 if (original !== undefined) {
2913 delete ctx.prevTextDirection;
2914 ctx.canvas.style.setProperty('direction', original[0], original[1]);
2915 }
2916}
2917
2918function propertyFn(property) {
2919 if (property === 'angle') {
2920 return {
2921 between: _angleBetween,
2922 compare: _angleDiff,
2923 normalize: _normalizeAngle,
2924 };
2925 }
2926 return {
2927 between: _isBetween,
2928 compare: (a, b) => a - b,
2929 normalize: x => x
2930 };
2931}
2932function normalizeSegment({start, end, count, loop, style}) {
2933 return {
2934 start: start % count,
2935 end: end % count,
2936 loop: loop && (end - start + 1) % count === 0,
2937 style
2938 };
2939}
2940function getSegment(segment, points, bounds) {
2941 const {property, start: startBound, end: endBound} = bounds;
2942 const {between, normalize} = propertyFn(property);
2943 const count = points.length;
2944 let {start, end, loop} = segment;
2945 let i, ilen;
2946 if (loop) {
2947 start += count;
2948 end += count;
2949 for (i = 0, ilen = count; i < ilen; ++i) {
2950 if (!between(normalize(points[start % count][property]), startBound, endBound)) {
2951 break;
2952 }
2953 start--;
2954 end--;
2955 }
2956 start %= count;
2957 end %= count;
2958 }
2959 if (end < start) {
2960 end += count;
2961 }
2962 return {start, end, loop, style: segment.style};
2963}
2964function _boundSegment(segment, points, bounds) {
2965 if (!bounds) {
2966 return [segment];
2967 }
2968 const {property, start: startBound, end: endBound} = bounds;
2969 const count = points.length;
2970 const {compare, between, normalize} = propertyFn(property);
2971 const {start, end, loop, style} = getSegment(segment, points, bounds);
2972 const result = [];
2973 let inside = false;
2974 let subStart = null;
2975 let value, point, prevValue;
2976 const startIsBefore = () => between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0;
2977 const endIsBefore = () => compare(endBound, value) === 0 || between(endBound, prevValue, value);
2978 const shouldStart = () => inside || startIsBefore();
2979 const shouldStop = () => !inside || endIsBefore();
2980 for (let i = start, prev = start; i <= end; ++i) {
2981 point = points[i % count];
2982 if (point.skip) {
2983 continue;
2984 }
2985 value = normalize(point[property]);
2986 if (value === prevValue) {
2987 continue;
2988 }
2989 inside = between(value, startBound, endBound);
2990 if (subStart === null && shouldStart()) {
2991 subStart = compare(value, startBound) === 0 ? i : prev;
2992 }
2993 if (subStart !== null && shouldStop()) {
2994 result.push(normalizeSegment({start: subStart, end: i, loop, count, style}));
2995 subStart = null;
2996 }
2997 prev = i;
2998 prevValue = value;
2999 }
3000 if (subStart !== null) {
3001 result.push(normalizeSegment({start: subStart, end, loop, count, style}));
3002 }
3003 return result;
3004}
3005function _boundSegments(line, bounds) {
3006 const result = [];
3007 const segments = line.segments;
3008 for (let i = 0; i < segments.length; i++) {
3009 const sub = _boundSegment(segments[i], line.points, bounds);
3010 if (sub.length) {
3011 result.push(...sub);
3012 }
3013 }
3014 return result;
3015}
3016function findStartAndEnd(points, count, loop, spanGaps) {
3017 let start = 0;
3018 let end = count - 1;
3019 if (loop && !spanGaps) {
3020 while (start < count && !points[start].skip) {
3021 start++;
3022 }
3023 }
3024 while (start < count && points[start].skip) {
3025 start++;
3026 }
3027 start %= count;
3028 if (loop) {
3029 end += start;
3030 }
3031 while (end > start && points[end % count].skip) {
3032 end--;
3033 }
3034 end %= count;
3035 return {start, end};
3036}
3037function solidSegments(points, start, max, loop) {
3038 const count = points.length;
3039 const result = [];
3040 let last = start;
3041 let prev = points[start];
3042 let end;
3043 for (end = start + 1; end <= max; ++end) {
3044 const cur = points[end % count];
3045 if (cur.skip || cur.stop) {
3046 if (!prev.skip) {
3047 loop = false;
3048 result.push({start: start % count, end: (end - 1) % count, loop});
3049 start = last = cur.stop ? end : null;
3050 }
3051 } else {
3052 last = end;
3053 if (prev.skip) {
3054 start = end;
3055 }
3056 }
3057 prev = cur;
3058 }
3059 if (last !== null) {
3060 result.push({start: start % count, end: last % count, loop});
3061 }
3062 return result;
3063}
3064function _computeSegments(line, segmentOptions) {
3065 const points = line.points;
3066 const spanGaps = line.options.spanGaps;
3067 const count = points.length;
3068 if (!count) {
3069 return [];
3070 }
3071 const loop = !!line._loop;
3072 const {start, end} = findStartAndEnd(points, count, loop, spanGaps);
3073 if (spanGaps === true) {
3074 return splitByStyles(line, [{start, end, loop}], points, segmentOptions);
3075 }
3076 const max = end < start ? end + count : end;
3077 const completeLoop = !!line._fullLoop && start === 0 && end === count - 1;
3078 return splitByStyles(line, solidSegments(points, start, max, completeLoop), points, segmentOptions);
3079}
3080function splitByStyles(line, segments, points, segmentOptions) {
3081 if (!segmentOptions || !segmentOptions.setContext || !points) {
3082 return segments;
3083 }
3084 return doSplitByStyles(line, segments, points, segmentOptions);
3085}
3086function doSplitByStyles(line, segments, points, segmentOptions) {
3087 const chartContext = line._chart.getContext();
3088 const baseStyle = readStyle(line.options);
3089 const {_datasetIndex: datasetIndex, options: {spanGaps}} = line;
3090 const count = points.length;
3091 const result = [];
3092 let prevStyle = baseStyle;
3093 let start = segments[0].start;
3094 let i = start;
3095 function addStyle(s, e, l, st) {
3096 const dir = spanGaps ? -1 : 1;
3097 if (s === e) {
3098 return;
3099 }
3100 s += count;
3101 while (points[s % count].skip) {
3102 s -= dir;
3103 }
3104 while (points[e % count].skip) {
3105 e += dir;
3106 }
3107 if (s % count !== e % count) {
3108 result.push({start: s % count, end: e % count, loop: l, style: st});
3109 prevStyle = st;
3110 start = e % count;
3111 }
3112 }
3113 for (const segment of segments) {
3114 start = spanGaps ? start : segment.start;
3115 let prev = points[start % count];
3116 let style;
3117 for (i = start + 1; i <= segment.end; i++) {
3118 const pt = points[i % count];
3119 style = readStyle(segmentOptions.setContext(createContext(chartContext, {
3120 type: 'segment',
3121 p0: prev,
3122 p1: pt,
3123 p0DataIndex: (i - 1) % count,
3124 p1DataIndex: i % count,
3125 datasetIndex
3126 })));
3127 if (styleChanged(style, prevStyle)) {
3128 addStyle(start, i - 1, segment.loop, prevStyle);
3129 }
3130 prev = pt;
3131 prevStyle = style;
3132 }
3133 if (start < i - 1) {
3134 addStyle(start, i - 1, segment.loop, prevStyle);
3135 }
3136 }
3137 return result;
3138}
3139function readStyle(options) {
3140 return {
3141 backgroundColor: options.backgroundColor,
3142 borderCapStyle: options.borderCapStyle,
3143 borderDash: options.borderDash,
3144 borderDashOffset: options.borderDashOffset,
3145 borderJoinStyle: options.borderJoinStyle,
3146 borderWidth: options.borderWidth,
3147 borderColor: options.borderColor
3148 };
3149}
3150function styleChanged(style, prevStyle) {
3151 return prevStyle && JSON.stringify(style) !== JSON.stringify(prevStyle);
3152}
3153
3154var helpers = /*#__PURE__*/Object.freeze({
3155__proto__: null,
3156easingEffects: effects,
3157color: color,
3158getHoverColor: getHoverColor,
3159noop: noop,
3160uid: uid,
3161isNullOrUndef: isNullOrUndef,
3162isArray: isArray,
3163isObject: isObject,
3164isFinite: isNumberFinite,
3165finiteOrDefault: finiteOrDefault,
3166valueOrDefault: valueOrDefault,
3167toPercentage: toPercentage,
3168toDimension: toDimension,
3169callback: callback,
3170each: each,
3171_elementsEqual: _elementsEqual,
3172clone: clone,
3173_merger: _merger,
3174merge: merge,
3175mergeIf: mergeIf,
3176_mergerIf: _mergerIf,
3177_deprecated: _deprecated,
3178resolveObjectKey: resolveObjectKey,
3179_capitalize: _capitalize,
3180defined: defined,
3181isFunction: isFunction,
3182setsEqual: setsEqual,
3183_isClickEvent: _isClickEvent,
3184toFontString: toFontString,
3185_measureText: _measureText,
3186_longestText: _longestText,
3187_alignPixel: _alignPixel,
3188clearCanvas: clearCanvas,
3189drawPoint: drawPoint,
3190_isPointInArea: _isPointInArea,
3191clipArea: clipArea,
3192unclipArea: unclipArea,
3193_steppedLineTo: _steppedLineTo,
3194_bezierCurveTo: _bezierCurveTo,
3195renderText: renderText,
3196addRoundedRectPath: addRoundedRectPath,
3197_lookup: _lookup,
3198_lookupByKey: _lookupByKey,
3199_rlookupByKey: _rlookupByKey,
3200_filterBetween: _filterBetween,
3201listenArrayEvents: listenArrayEvents,
3202unlistenArrayEvents: unlistenArrayEvents,
3203_arrayUnique: _arrayUnique,
3204_createResolver: _createResolver,
3205_attachContext: _attachContext,
3206_descriptors: _descriptors,
3207splineCurve: splineCurve,
3208splineCurveMonotone: splineCurveMonotone,
3209_updateBezierControlPoints: _updateBezierControlPoints,
3210_isDomSupported: _isDomSupported,
3211_getParentNode: _getParentNode,
3212getStyle: getStyle,
3213getRelativePosition: getRelativePosition$1,
3214getMaximumSize: getMaximumSize,
3215retinaScale: retinaScale,
3216supportsEventListenerOptions: supportsEventListenerOptions,
3217readUsedSize: readUsedSize,
3218fontString: fontString,
3219requestAnimFrame: requestAnimFrame,
3220throttled: throttled,
3221debounce: debounce,
3222_toLeftRightCenter: _toLeftRightCenter,
3223_alignStartEnd: _alignStartEnd,
3224_textX: _textX,
3225_pointInLine: _pointInLine,
3226_steppedInterpolation: _steppedInterpolation,
3227_bezierInterpolation: _bezierInterpolation,
3228formatNumber: formatNumber,
3229toLineHeight: toLineHeight,
3230_readValueToProps: _readValueToProps,
3231toTRBL: toTRBL,
3232toTRBLCorners: toTRBLCorners,
3233toPadding: toPadding,
3234toFont: toFont,
3235resolve: resolve,
3236_addGrace: _addGrace,
3237createContext: createContext,
3238PI: PI,
3239TAU: TAU,
3240PITAU: PITAU,
3241INFINITY: INFINITY,
3242RAD_PER_DEG: RAD_PER_DEG,
3243HALF_PI: HALF_PI,
3244QUARTER_PI: QUARTER_PI,
3245TWO_THIRDS_PI: TWO_THIRDS_PI,
3246log10: log10,
3247sign: sign,
3248niceNum: niceNum,
3249_factorize: _factorize,
3250isNumber: isNumber,
3251almostEquals: almostEquals,
3252almostWhole: almostWhole,
3253_setMinAndMaxByKey: _setMinAndMaxByKey,
3254toRadians: toRadians,
3255toDegrees: toDegrees,
3256_decimalPlaces: _decimalPlaces,
3257getAngleFromPoint: getAngleFromPoint,
3258distanceBetweenPoints: distanceBetweenPoints,
3259_angleDiff: _angleDiff,
3260_normalizeAngle: _normalizeAngle,
3261_angleBetween: _angleBetween,
3262_limitValue: _limitValue,
3263_int16Range: _int16Range,
3264_isBetween: _isBetween,
3265getRtlAdapter: getRtlAdapter,
3266overrideTextDirection: overrideTextDirection,
3267restoreTextDirection: restoreTextDirection,
3268_boundSegment: _boundSegment,
3269_boundSegments: _boundSegments,
3270_computeSegments: _computeSegments
3271});
3272
3273class BasePlatform {
3274 acquireContext(canvas, aspectRatio) {}
3275 releaseContext(context) {
3276 return false;
3277 }
3278 addEventListener(chart, type, listener) {}
3279 removeEventListener(chart, type, listener) {}
3280 getDevicePixelRatio() {
3281 return 1;
3282 }
3283 getMaximumSize(element, width, height, aspectRatio) {
3284 width = Math.max(0, width || element.width);
3285 height = height || element.height;
3286 return {
3287 width,
3288 height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)
3289 };
3290 }
3291 isAttached(canvas) {
3292 return true;
3293 }
3294 updateConfig(config) {
3295 }
3296}
3297
3298class BasicPlatform extends BasePlatform {
3299 acquireContext(item) {
3300 return item && item.getContext && item.getContext('2d') || null;
3301 }
3302 updateConfig(config) {
3303 config.options.animation = false;
3304 }
3305}
3306
3307const EXPANDO_KEY = '$chartjs';
3308const EVENT_TYPES = {
3309 touchstart: 'mousedown',
3310 touchmove: 'mousemove',
3311 touchend: 'mouseup',
3312 pointerenter: 'mouseenter',
3313 pointerdown: 'mousedown',
3314 pointermove: 'mousemove',
3315 pointerup: 'mouseup',
3316 pointerleave: 'mouseout',
3317 pointerout: 'mouseout'
3318};
3319const isNullOrEmpty = value => value === null || value === '';
3320function initCanvas(canvas, aspectRatio) {
3321 const style = canvas.style;
3322 const renderHeight = canvas.getAttribute('height');
3323 const renderWidth = canvas.getAttribute('width');
3324 canvas[EXPANDO_KEY] = {
3325 initial: {
3326 height: renderHeight,
3327 width: renderWidth,
3328 style: {
3329 display: style.display,
3330 height: style.height,
3331 width: style.width
3332 }
3333 }
3334 };
3335 style.display = style.display || 'block';
3336 style.boxSizing = style.boxSizing || 'border-box';
3337 if (isNullOrEmpty(renderWidth)) {
3338 const displayWidth = readUsedSize(canvas, 'width');
3339 if (displayWidth !== undefined) {
3340 canvas.width = displayWidth;
3341 }
3342 }
3343 if (isNullOrEmpty(renderHeight)) {
3344 if (canvas.style.height === '') {
3345 canvas.height = canvas.width / (aspectRatio || 2);
3346 } else {
3347 const displayHeight = readUsedSize(canvas, 'height');
3348 if (displayHeight !== undefined) {
3349 canvas.height = displayHeight;
3350 }
3351 }
3352 }
3353 return canvas;
3354}
3355const eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
3356function addListener(node, type, listener) {
3357 node.addEventListener(type, listener, eventListenerOptions);
3358}
3359function removeListener(chart, type, listener) {
3360 chart.canvas.removeEventListener(type, listener, eventListenerOptions);
3361}
3362function fromNativeEvent(event, chart) {
3363 const type = EVENT_TYPES[event.type] || event.type;
3364 const {x, y} = getRelativePosition$1(event, chart);
3365 return {
3366 type,
3367 chart,
3368 native: event,
3369 x: x !== undefined ? x : null,
3370 y: y !== undefined ? y : null,
3371 };
3372}
3373function nodeListContains(nodeList, canvas) {
3374 for (const node of nodeList) {
3375 if (node === canvas || node.contains(canvas)) {
3376 return true;
3377 }
3378 }
3379}
3380function createAttachObserver(chart, type, listener) {
3381 const canvas = chart.canvas;
3382 const observer = new MutationObserver(entries => {
3383 let trigger = false;
3384 for (const entry of entries) {
3385 trigger = trigger || nodeListContains(entry.addedNodes, canvas);
3386 trigger = trigger && !nodeListContains(entry.removedNodes, canvas);
3387 }
3388 if (trigger) {
3389 listener();
3390 }
3391 });
3392 observer.observe(document, {childList: true, subtree: true});
3393 return observer;
3394}
3395function createDetachObserver(chart, type, listener) {
3396 const canvas = chart.canvas;
3397 const observer = new MutationObserver(entries => {
3398 let trigger = false;
3399 for (const entry of entries) {
3400 trigger = trigger || nodeListContains(entry.removedNodes, canvas);
3401 trigger = trigger && !nodeListContains(entry.addedNodes, canvas);
3402 }
3403 if (trigger) {
3404 listener();
3405 }
3406 });
3407 observer.observe(document, {childList: true, subtree: true});
3408 return observer;
3409}
3410const drpListeningCharts = new Map();
3411let oldDevicePixelRatio = 0;
3412function onWindowResize() {
3413 const dpr = window.devicePixelRatio;
3414 if (dpr === oldDevicePixelRatio) {
3415 return;
3416 }
3417 oldDevicePixelRatio = dpr;
3418 drpListeningCharts.forEach((resize, chart) => {
3419 if (chart.currentDevicePixelRatio !== dpr) {
3420 resize();
3421 }
3422 });
3423}
3424function listenDevicePixelRatioChanges(chart, resize) {
3425 if (!drpListeningCharts.size) {
3426 window.addEventListener('resize', onWindowResize);
3427 }
3428 drpListeningCharts.set(chart, resize);
3429}
3430function unlistenDevicePixelRatioChanges(chart) {
3431 drpListeningCharts.delete(chart);
3432 if (!drpListeningCharts.size) {
3433 window.removeEventListener('resize', onWindowResize);
3434 }
3435}
3436function createResizeObserver(chart, type, listener) {
3437 const canvas = chart.canvas;
3438 const container = canvas && _getParentNode(canvas);
3439 if (!container) {
3440 return;
3441 }
3442 const resize = throttled((width, height) => {
3443 const w = container.clientWidth;
3444 listener(width, height);
3445 if (w < container.clientWidth) {
3446 listener();
3447 }
3448 }, window);
3449 const observer = new ResizeObserver(entries => {
3450 const entry = entries[0];
3451 const width = entry.contentRect.width;
3452 const height = entry.contentRect.height;
3453 if (width === 0 && height === 0) {
3454 return;
3455 }
3456 resize(width, height);
3457 });
3458 observer.observe(container);
3459 listenDevicePixelRatioChanges(chart, resize);
3460 return observer;
3461}
3462function releaseObserver(chart, type, observer) {
3463 if (observer) {
3464 observer.disconnect();
3465 }
3466 if (type === 'resize') {
3467 unlistenDevicePixelRatioChanges(chart);
3468 }
3469}
3470function createProxyAndListen(chart, type, listener) {
3471 const canvas = chart.canvas;
3472 const proxy = throttled((event) => {
3473 if (chart.ctx !== null) {
3474 listener(fromNativeEvent(event, chart));
3475 }
3476 }, chart, (args) => {
3477 const event = args[0];
3478 return [event, event.offsetX, event.offsetY];
3479 });
3480 addListener(canvas, type, proxy);
3481 return proxy;
3482}
3483class DomPlatform extends BasePlatform {
3484 acquireContext(canvas, aspectRatio) {
3485 const context = canvas && canvas.getContext && canvas.getContext('2d');
3486 if (context && context.canvas === canvas) {
3487 initCanvas(canvas, aspectRatio);
3488 return context;
3489 }
3490 return null;
3491 }
3492 releaseContext(context) {
3493 const canvas = context.canvas;
3494 if (!canvas[EXPANDO_KEY]) {
3495 return false;
3496 }
3497 const initial = canvas[EXPANDO_KEY].initial;
3498 ['height', 'width'].forEach((prop) => {
3499 const value = initial[prop];
3500 if (isNullOrUndef(value)) {
3501 canvas.removeAttribute(prop);
3502 } else {
3503 canvas.setAttribute(prop, value);
3504 }
3505 });
3506 const style = initial.style || {};
3507 Object.keys(style).forEach((key) => {
3508 canvas.style[key] = style[key];
3509 });
3510 canvas.width = canvas.width;
3511 delete canvas[EXPANDO_KEY];
3512 return true;
3513 }
3514 addEventListener(chart, type, listener) {
3515 this.removeEventListener(chart, type);
3516 const proxies = chart.$proxies || (chart.$proxies = {});
3517 const handlers = {
3518 attach: createAttachObserver,
3519 detach: createDetachObserver,
3520 resize: createResizeObserver
3521 };
3522 const handler = handlers[type] || createProxyAndListen;
3523 proxies[type] = handler(chart, type, listener);
3524 }
3525 removeEventListener(chart, type) {
3526 const proxies = chart.$proxies || (chart.$proxies = {});
3527 const proxy = proxies[type];
3528 if (!proxy) {
3529 return;
3530 }
3531 const handlers = {
3532 attach: releaseObserver,
3533 detach: releaseObserver,
3534 resize: releaseObserver
3535 };
3536 const handler = handlers[type] || removeListener;
3537 handler(chart, type, proxy);
3538 proxies[type] = undefined;
3539 }
3540 getDevicePixelRatio() {
3541 return window.devicePixelRatio;
3542 }
3543 getMaximumSize(canvas, width, height, aspectRatio) {
3544 return getMaximumSize(canvas, width, height, aspectRatio);
3545 }
3546 isAttached(canvas) {
3547 const container = _getParentNode(canvas);
3548 return !!(container && container.isConnected);
3549 }
3550}
3551
3552function _detectPlatform(canvas) {
3553 if (!_isDomSupported() || (typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)) {
3554 return BasicPlatform;
3555 }
3556 return DomPlatform;
3557}
3558
3559var platforms = /*#__PURE__*/Object.freeze({
3560__proto__: null,
3561_detectPlatform: _detectPlatform,
3562BasePlatform: BasePlatform,
3563BasicPlatform: BasicPlatform,
3564DomPlatform: DomPlatform
3565});
3566
3567const transparent = 'transparent';
3568const interpolators = {
3569 boolean(from, to, factor) {
3570 return factor > 0.5 ? to : from;
3571 },
3572 color(from, to, factor) {
3573 const c0 = color(from || transparent);
3574 const c1 = c0.valid && color(to || transparent);
3575 return c1 && c1.valid
3576 ? c1.mix(c0, factor).hexString()
3577 : to;
3578 },
3579 number(from, to, factor) {
3580 return from + (to - from) * factor;
3581 }
3582};
3583class Animation {
3584 constructor(cfg, target, prop, to) {
3585 const currentValue = target[prop];
3586 to = resolve([cfg.to, to, currentValue, cfg.from]);
3587 const from = resolve([cfg.from, currentValue, to]);
3588 this._active = true;
3589 this._fn = cfg.fn || interpolators[cfg.type || typeof from];
3590 this._easing = effects[cfg.easing] || effects.linear;
3591 this._start = Math.floor(Date.now() + (cfg.delay || 0));
3592 this._duration = this._total = Math.floor(cfg.duration);
3593 this._loop = !!cfg.loop;
3594 this._target = target;
3595 this._prop = prop;
3596 this._from = from;
3597 this._to = to;
3598 this._promises = undefined;
3599 }
3600 active() {
3601 return this._active;
3602 }
3603 update(cfg, to, date) {
3604 if (this._active) {
3605 this._notify(false);
3606 const currentValue = this._target[this._prop];
3607 const elapsed = date - this._start;
3608 const remain = this._duration - elapsed;
3609 this._start = date;
3610 this._duration = Math.floor(Math.max(remain, cfg.duration));
3611 this._total += elapsed;
3612 this._loop = !!cfg.loop;
3613 this._to = resolve([cfg.to, to, currentValue, cfg.from]);
3614 this._from = resolve([cfg.from, currentValue, to]);
3615 }
3616 }
3617 cancel() {
3618 if (this._active) {
3619 this.tick(Date.now());
3620 this._active = false;
3621 this._notify(false);
3622 }
3623 }
3624 tick(date) {
3625 const elapsed = date - this._start;
3626 const duration = this._duration;
3627 const prop = this._prop;
3628 const from = this._from;
3629 const loop = this._loop;
3630 const to = this._to;
3631 let factor;
3632 this._active = from !== to && (loop || (elapsed < duration));
3633 if (!this._active) {
3634 this._target[prop] = to;
3635 this._notify(true);
3636 return;
3637 }
3638 if (elapsed < 0) {
3639 this._target[prop] = from;
3640 return;
3641 }
3642 factor = (elapsed / duration) % 2;
3643 factor = loop && factor > 1 ? 2 - factor : factor;
3644 factor = this._easing(Math.min(1, Math.max(0, factor)));
3645 this._target[prop] = this._fn(from, to, factor);
3646 }
3647 wait() {
3648 const promises = this._promises || (this._promises = []);
3649 return new Promise((res, rej) => {
3650 promises.push({res, rej});
3651 });
3652 }
3653 _notify(resolved) {
3654 const method = resolved ? 'res' : 'rej';
3655 const promises = this._promises || [];
3656 for (let i = 0; i < promises.length; i++) {
3657 promises[i][method]();
3658 }
3659 }
3660}
3661
3662const numbers = ['x', 'y', 'borderWidth', 'radius', 'tension'];
3663const colors = ['color', 'borderColor', 'backgroundColor'];
3664defaults.set('animation', {
3665 delay: undefined,
3666 duration: 1000,
3667 easing: 'easeOutQuart',
3668 fn: undefined,
3669 from: undefined,
3670 loop: undefined,
3671 to: undefined,
3672 type: undefined,
3673});
3674const animationOptions = Object.keys(defaults.animation);
3675defaults.describe('animation', {
3676 _fallback: false,
3677 _indexable: false,
3678 _scriptable: (name) => name !== 'onProgress' && name !== 'onComplete' && name !== 'fn',
3679});
3680defaults.set('animations', {
3681 colors: {
3682 type: 'color',
3683 properties: colors
3684 },
3685 numbers: {
3686 type: 'number',
3687 properties: numbers
3688 },
3689});
3690defaults.describe('animations', {
3691 _fallback: 'animation',
3692});
3693defaults.set('transitions', {
3694 active: {
3695 animation: {
3696 duration: 400
3697 }
3698 },
3699 resize: {
3700 animation: {
3701 duration: 0
3702 }
3703 },
3704 show: {
3705 animations: {
3706 colors: {
3707 from: 'transparent'
3708 },
3709 visible: {
3710 type: 'boolean',
3711 duration: 0
3712 },
3713 }
3714 },
3715 hide: {
3716 animations: {
3717 colors: {
3718 to: 'transparent'
3719 },
3720 visible: {
3721 type: 'boolean',
3722 easing: 'linear',
3723 fn: v => v | 0
3724 },
3725 }
3726 }
3727});
3728class Animations {
3729 constructor(chart, config) {
3730 this._chart = chart;
3731 this._properties = new Map();
3732 this.configure(config);
3733 }
3734 configure(config) {
3735 if (!isObject(config)) {
3736 return;
3737 }
3738 const animatedProps = this._properties;
3739 Object.getOwnPropertyNames(config).forEach(key => {
3740 const cfg = config[key];
3741 if (!isObject(cfg)) {
3742 return;
3743 }
3744 const resolved = {};
3745 for (const option of animationOptions) {
3746 resolved[option] = cfg[option];
3747 }
3748 (isArray(cfg.properties) && cfg.properties || [key]).forEach((prop) => {
3749 if (prop === key || !animatedProps.has(prop)) {
3750 animatedProps.set(prop, resolved);
3751 }
3752 });
3753 });
3754 }
3755 _animateOptions(target, values) {
3756 const newOptions = values.options;
3757 const options = resolveTargetOptions(target, newOptions);
3758 if (!options) {
3759 return [];
3760 }
3761 const animations = this._createAnimations(options, newOptions);
3762 if (newOptions.$shared) {
3763 awaitAll(target.options.$animations, newOptions).then(() => {
3764 target.options = newOptions;
3765 }, () => {
3766 });
3767 }
3768 return animations;
3769 }
3770 _createAnimations(target, values) {
3771 const animatedProps = this._properties;
3772 const animations = [];
3773 const running = target.$animations || (target.$animations = {});
3774 const props = Object.keys(values);
3775 const date = Date.now();
3776 let i;
3777 for (i = props.length - 1; i >= 0; --i) {
3778 const prop = props[i];
3779 if (prop.charAt(0) === '$') {
3780 continue;
3781 }
3782 if (prop === 'options') {
3783 animations.push(...this._animateOptions(target, values));
3784 continue;
3785 }
3786 const value = values[prop];
3787 let animation = running[prop];
3788 const cfg = animatedProps.get(prop);
3789 if (animation) {
3790 if (cfg && animation.active()) {
3791 animation.update(cfg, value, date);
3792 continue;
3793 } else {
3794 animation.cancel();
3795 }
3796 }
3797 if (!cfg || !cfg.duration) {
3798 target[prop] = value;
3799 continue;
3800 }
3801 running[prop] = animation = new Animation(cfg, target, prop, value);
3802 animations.push(animation);
3803 }
3804 return animations;
3805 }
3806 update(target, values) {
3807 if (this._properties.size === 0) {
3808 Object.assign(target, values);
3809 return;
3810 }
3811 const animations = this._createAnimations(target, values);
3812 if (animations.length) {
3813 animator.add(this._chart, animations);
3814 return true;
3815 }
3816 }
3817}
3818function awaitAll(animations, properties) {
3819 const running = [];
3820 const keys = Object.keys(properties);
3821 for (let i = 0; i < keys.length; i++) {
3822 const anim = animations[keys[i]];
3823 if (anim && anim.active()) {
3824 running.push(anim.wait());
3825 }
3826 }
3827 return Promise.all(running);
3828}
3829function resolveTargetOptions(target, newOptions) {
3830 if (!newOptions) {
3831 return;
3832 }
3833 let options = target.options;
3834 if (!options) {
3835 target.options = newOptions;
3836 return;
3837 }
3838 if (options.$shared) {
3839 target.options = options = Object.assign({}, options, {$shared: false, $animations: {}});
3840 }
3841 return options;
3842}
3843
3844function scaleClip(scale, allowedOverflow) {
3845 const opts = scale && scale.options || {};
3846 const reverse = opts.reverse;
3847 const min = opts.min === undefined ? allowedOverflow : 0;
3848 const max = opts.max === undefined ? allowedOverflow : 0;
3849 return {
3850 start: reverse ? max : min,
3851 end: reverse ? min : max
3852 };
3853}
3854function defaultClip(xScale, yScale, allowedOverflow) {
3855 if (allowedOverflow === false) {
3856 return false;
3857 }
3858 const x = scaleClip(xScale, allowedOverflow);
3859 const y = scaleClip(yScale, allowedOverflow);
3860 return {
3861 top: y.end,
3862 right: x.end,
3863 bottom: y.start,
3864 left: x.start
3865 };
3866}
3867function toClip(value) {
3868 let t, r, b, l;
3869 if (isObject(value)) {
3870 t = value.top;
3871 r = value.right;
3872 b = value.bottom;
3873 l = value.left;
3874 } else {
3875 t = r = b = l = value;
3876 }
3877 return {
3878 top: t,
3879 right: r,
3880 bottom: b,
3881 left: l,
3882 disabled: value === false
3883 };
3884}
3885function getSortedDatasetIndices(chart, filterVisible) {
3886 const keys = [];
3887 const metasets = chart._getSortedDatasetMetas(filterVisible);
3888 let i, ilen;
3889 for (i = 0, ilen = metasets.length; i < ilen; ++i) {
3890 keys.push(metasets[i].index);
3891 }
3892 return keys;
3893}
3894function applyStack(stack, value, dsIndex, options = {}) {
3895 const keys = stack.keys;
3896 const singleMode = options.mode === 'single';
3897 let i, ilen, datasetIndex, otherValue;
3898 if (value === null) {
3899 return;
3900 }
3901 for (i = 0, ilen = keys.length; i < ilen; ++i) {
3902 datasetIndex = +keys[i];
3903 if (datasetIndex === dsIndex) {
3904 if (options.all) {
3905 continue;
3906 }
3907 break;
3908 }
3909 otherValue = stack.values[datasetIndex];
3910 if (isNumberFinite(otherValue) && (singleMode || (value === 0 || sign(value) === sign(otherValue)))) {
3911 value += otherValue;
3912 }
3913 }
3914 return value;
3915}
3916function convertObjectDataToArray(data) {
3917 const keys = Object.keys(data);
3918 const adata = new Array(keys.length);
3919 let i, ilen, key;
3920 for (i = 0, ilen = keys.length; i < ilen; ++i) {
3921 key = keys[i];
3922 adata[i] = {
3923 x: key,
3924 y: data[key]
3925 };
3926 }
3927 return adata;
3928}
3929function isStacked(scale, meta) {
3930 const stacked = scale && scale.options.stacked;
3931 return stacked || (stacked === undefined && meta.stack !== undefined);
3932}
3933function getStackKey(indexScale, valueScale, meta) {
3934 return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;
3935}
3936function getUserBounds(scale) {
3937 const {min, max, minDefined, maxDefined} = scale.getUserBounds();
3938 return {
3939 min: minDefined ? min : Number.NEGATIVE_INFINITY,
3940 max: maxDefined ? max : Number.POSITIVE_INFINITY
3941 };
3942}
3943function getOrCreateStack(stacks, stackKey, indexValue) {
3944 const subStack = stacks[stackKey] || (stacks[stackKey] = {});
3945 return subStack[indexValue] || (subStack[indexValue] = {});
3946}
3947function getLastIndexInStack(stack, vScale, positive, type) {
3948 for (const meta of vScale.getMatchingVisibleMetas(type).reverse()) {
3949 const value = stack[meta.index];
3950 if ((positive && value > 0) || (!positive && value < 0)) {
3951 return meta.index;
3952 }
3953 }
3954 return null;
3955}
3956function updateStacks(controller, parsed) {
3957 const {chart, _cachedMeta: meta} = controller;
3958 const stacks = chart._stacks || (chart._stacks = {});
3959 const {iScale, vScale, index: datasetIndex} = meta;
3960 const iAxis = iScale.axis;
3961 const vAxis = vScale.axis;
3962 const key = getStackKey(iScale, vScale, meta);
3963 const ilen = parsed.length;
3964 let stack;
3965 for (let i = 0; i < ilen; ++i) {
3966 const item = parsed[i];
3967 const {[iAxis]: index, [vAxis]: value} = item;
3968 const itemStacks = item._stacks || (item._stacks = {});
3969 stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index);
3970 stack[datasetIndex] = value;
3971 stack._top = getLastIndexInStack(stack, vScale, true, meta.type);
3972 stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type);
3973 }
3974}
3975function getFirstScaleId(chart, axis) {
3976 const scales = chart.scales;
3977 return Object.keys(scales).filter(key => scales[key].axis === axis).shift();
3978}
3979function createDatasetContext(parent, index) {
3980 return createContext(parent,
3981 {
3982 active: false,
3983 dataset: undefined,
3984 datasetIndex: index,
3985 index,
3986 mode: 'default',
3987 type: 'dataset'
3988 }
3989 );
3990}
3991function createDataContext(parent, index, element) {
3992 return createContext(parent, {
3993 active: false,
3994 dataIndex: index,
3995 parsed: undefined,
3996 raw: undefined,
3997 element,
3998 index,
3999 mode: 'default',
4000 type: 'data'
4001 });
4002}
4003function clearStacks(meta, items) {
4004 const datasetIndex = meta.controller.index;
4005 const axis = meta.vScale && meta.vScale.axis;
4006 if (!axis) {
4007 return;
4008 }
4009 items = items || meta._parsed;
4010 for (const parsed of items) {
4011 const stacks = parsed._stacks;
4012 if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) {
4013 return;
4014 }
4015 delete stacks[axis][datasetIndex];
4016 }
4017}
4018const isDirectUpdateMode = (mode) => mode === 'reset' || mode === 'none';
4019const cloneIfNotShared = (cached, shared) => shared ? cached : Object.assign({}, cached);
4020const createStack = (canStack, meta, chart) => canStack && !meta.hidden && meta._stacked
4021 && {keys: getSortedDatasetIndices(chart, true), values: null};
4022class DatasetController {
4023 constructor(chart, datasetIndex) {
4024 this.chart = chart;
4025 this._ctx = chart.ctx;
4026 this.index = datasetIndex;
4027 this._cachedDataOpts = {};
4028 this._cachedMeta = this.getMeta();
4029 this._type = this._cachedMeta.type;
4030 this.options = undefined;
4031 this._parsing = false;
4032 this._data = undefined;
4033 this._objectData = undefined;
4034 this._sharedOptions = undefined;
4035 this._drawStart = undefined;
4036 this._drawCount = undefined;
4037 this.enableOptionSharing = false;
4038 this.$context = undefined;
4039 this._syncList = [];
4040 this.initialize();
4041 }
4042 initialize() {
4043 const meta = this._cachedMeta;
4044 this.configure();
4045 this.linkScales();
4046 meta._stacked = isStacked(meta.vScale, meta);
4047 this.addElements();
4048 }
4049 updateIndex(datasetIndex) {
4050 if (this.index !== datasetIndex) {
4051 clearStacks(this._cachedMeta);
4052 }
4053 this.index = datasetIndex;
4054 }
4055 linkScales() {
4056 const chart = this.chart;
4057 const meta = this._cachedMeta;
4058 const dataset = this.getDataset();
4059 const chooseId = (axis, x, y, r) => axis === 'x' ? x : axis === 'r' ? r : y;
4060 const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));
4061 const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));
4062 const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));
4063 const indexAxis = meta.indexAxis;
4064 const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);
4065 const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);
4066 meta.xScale = this.getScaleForId(xid);
4067 meta.yScale = this.getScaleForId(yid);
4068 meta.rScale = this.getScaleForId(rid);
4069 meta.iScale = this.getScaleForId(iid);
4070 meta.vScale = this.getScaleForId(vid);
4071 }
4072 getDataset() {
4073 return this.chart.data.datasets[this.index];
4074 }
4075 getMeta() {
4076 return this.chart.getDatasetMeta(this.index);
4077 }
4078 getScaleForId(scaleID) {
4079 return this.chart.scales[scaleID];
4080 }
4081 _getOtherScale(scale) {
4082 const meta = this._cachedMeta;
4083 return scale === meta.iScale
4084 ? meta.vScale
4085 : meta.iScale;
4086 }
4087 reset() {
4088 this._update('reset');
4089 }
4090 _destroy() {
4091 const meta = this._cachedMeta;
4092 if (this._data) {
4093 unlistenArrayEvents(this._data, this);
4094 }
4095 if (meta._stacked) {
4096 clearStacks(meta);
4097 }
4098 }
4099 _dataCheck() {
4100 const dataset = this.getDataset();
4101 const data = dataset.data || (dataset.data = []);
4102 const _data = this._data;
4103 if (isObject(data)) {
4104 this._data = convertObjectDataToArray(data);
4105 } else if (_data !== data) {
4106 if (_data) {
4107 unlistenArrayEvents(_data, this);
4108 const meta = this._cachedMeta;
4109 clearStacks(meta);
4110 meta._parsed = [];
4111 }
4112 if (data && Object.isExtensible(data)) {
4113 listenArrayEvents(data, this);
4114 }
4115 this._syncList = [];
4116 this._data = data;
4117 }
4118 }
4119 addElements() {
4120 const meta = this._cachedMeta;
4121 this._dataCheck();
4122 if (this.datasetElementType) {
4123 meta.dataset = new this.datasetElementType();
4124 }
4125 }
4126 buildOrUpdateElements(resetNewElements) {
4127 const meta = this._cachedMeta;
4128 const dataset = this.getDataset();
4129 let stackChanged = false;
4130 this._dataCheck();
4131 const oldStacked = meta._stacked;
4132 meta._stacked = isStacked(meta.vScale, meta);
4133 if (meta.stack !== dataset.stack) {
4134 stackChanged = true;
4135 clearStacks(meta);
4136 meta.stack = dataset.stack;
4137 }
4138 this._resyncElements(resetNewElements);
4139 if (stackChanged || oldStacked !== meta._stacked) {
4140 updateStacks(this, meta._parsed);
4141 }
4142 }
4143 configure() {
4144 const config = this.chart.config;
4145 const scopeKeys = config.datasetScopeKeys(this._type);
4146 const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true);
4147 this.options = config.createResolver(scopes, this.getContext());
4148 this._parsing = this.options.parsing;
4149 this._cachedDataOpts = {};
4150 }
4151 parse(start, count) {
4152 const {_cachedMeta: meta, _data: data} = this;
4153 const {iScale, _stacked} = meta;
4154 const iAxis = iScale.axis;
4155 let sorted = start === 0 && count === data.length ? true : meta._sorted;
4156 let prev = start > 0 && meta._parsed[start - 1];
4157 let i, cur, parsed;
4158 if (this._parsing === false) {
4159 meta._parsed = data;
4160 meta._sorted = true;
4161 parsed = data;
4162 } else {
4163 if (isArray(data[start])) {
4164 parsed = this.parseArrayData(meta, data, start, count);
4165 } else if (isObject(data[start])) {
4166 parsed = this.parseObjectData(meta, data, start, count);
4167 } else {
4168 parsed = this.parsePrimitiveData(meta, data, start, count);
4169 }
4170 const isNotInOrderComparedToPrev = () => cur[iAxis] === null || (prev && cur[iAxis] < prev[iAxis]);
4171 for (i = 0; i < count; ++i) {
4172 meta._parsed[i + start] = cur = parsed[i];
4173 if (sorted) {
4174 if (isNotInOrderComparedToPrev()) {
4175 sorted = false;
4176 }
4177 prev = cur;
4178 }
4179 }
4180 meta._sorted = sorted;
4181 }
4182 if (_stacked) {
4183 updateStacks(this, parsed);
4184 }
4185 }
4186 parsePrimitiveData(meta, data, start, count) {
4187 const {iScale, vScale} = meta;
4188 const iAxis = iScale.axis;
4189 const vAxis = vScale.axis;
4190 const labels = iScale.getLabels();
4191 const singleScale = iScale === vScale;
4192 const parsed = new Array(count);
4193 let i, ilen, index;
4194 for (i = 0, ilen = count; i < ilen; ++i) {
4195 index = i + start;
4196 parsed[i] = {
4197 [iAxis]: singleScale || iScale.parse(labels[index], index),
4198 [vAxis]: vScale.parse(data[index], index)
4199 };
4200 }
4201 return parsed;
4202 }
4203 parseArrayData(meta, data, start, count) {
4204 const {xScale, yScale} = meta;
4205 const parsed = new Array(count);
4206 let i, ilen, index, item;
4207 for (i = 0, ilen = count; i < ilen; ++i) {
4208 index = i + start;
4209 item = data[index];
4210 parsed[i] = {
4211 x: xScale.parse(item[0], index),
4212 y: yScale.parse(item[1], index)
4213 };
4214 }
4215 return parsed;
4216 }
4217 parseObjectData(meta, data, start, count) {
4218 const {xScale, yScale} = meta;
4219 const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
4220 const parsed = new Array(count);
4221 let i, ilen, index, item;
4222 for (i = 0, ilen = count; i < ilen; ++i) {
4223 index = i + start;
4224 item = data[index];
4225 parsed[i] = {
4226 x: xScale.parse(resolveObjectKey(item, xAxisKey), index),
4227 y: yScale.parse(resolveObjectKey(item, yAxisKey), index)
4228 };
4229 }
4230 return parsed;
4231 }
4232 getParsed(index) {
4233 return this._cachedMeta._parsed[index];
4234 }
4235 getDataElement(index) {
4236 return this._cachedMeta.data[index];
4237 }
4238 applyStack(scale, parsed, mode) {
4239 const chart = this.chart;
4240 const meta = this._cachedMeta;
4241 const value = parsed[scale.axis];
4242 const stack = {
4243 keys: getSortedDatasetIndices(chart, true),
4244 values: parsed._stacks[scale.axis]
4245 };
4246 return applyStack(stack, value, meta.index, {mode});
4247 }
4248 updateRangeFromParsed(range, scale, parsed, stack) {
4249 const parsedValue = parsed[scale.axis];
4250 let value = parsedValue === null ? NaN : parsedValue;
4251 const values = stack && parsed._stacks[scale.axis];
4252 if (stack && values) {
4253 stack.values = values;
4254 value = applyStack(stack, parsedValue, this._cachedMeta.index);
4255 }
4256 range.min = Math.min(range.min, value);
4257 range.max = Math.max(range.max, value);
4258 }
4259 getMinMax(scale, canStack) {
4260 const meta = this._cachedMeta;
4261 const _parsed = meta._parsed;
4262 const sorted = meta._sorted && scale === meta.iScale;
4263 const ilen = _parsed.length;
4264 const otherScale = this._getOtherScale(scale);
4265 const stack = createStack(canStack, meta, this.chart);
4266 const range = {min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY};
4267 const {min: otherMin, max: otherMax} = getUserBounds(otherScale);
4268 let i, parsed;
4269 function _skip() {
4270 parsed = _parsed[i];
4271 const otherValue = parsed[otherScale.axis];
4272 return !isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue;
4273 }
4274 for (i = 0; i < ilen; ++i) {
4275 if (_skip()) {
4276 continue;
4277 }
4278 this.updateRangeFromParsed(range, scale, parsed, stack);
4279 if (sorted) {
4280 break;
4281 }
4282 }
4283 if (sorted) {
4284 for (i = ilen - 1; i >= 0; --i) {
4285 if (_skip()) {
4286 continue;
4287 }
4288 this.updateRangeFromParsed(range, scale, parsed, stack);
4289 break;
4290 }
4291 }
4292 return range;
4293 }
4294 getAllParsedValues(scale) {
4295 const parsed = this._cachedMeta._parsed;
4296 const values = [];
4297 let i, ilen, value;
4298 for (i = 0, ilen = parsed.length; i < ilen; ++i) {
4299 value = parsed[i][scale.axis];
4300 if (isNumberFinite(value)) {
4301 values.push(value);
4302 }
4303 }
4304 return values;
4305 }
4306 getMaxOverflow() {
4307 return false;
4308 }
4309 getLabelAndValue(index) {
4310 const meta = this._cachedMeta;
4311 const iScale = meta.iScale;
4312 const vScale = meta.vScale;
4313 const parsed = this.getParsed(index);
4314 return {
4315 label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',
4316 value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''
4317 };
4318 }
4319 _update(mode) {
4320 const meta = this._cachedMeta;
4321 this.update(mode || 'default');
4322 meta._clip = toClip(valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow())));
4323 }
4324 update(mode) {}
4325 draw() {
4326 const ctx = this._ctx;
4327 const chart = this.chart;
4328 const meta = this._cachedMeta;
4329 const elements = meta.data || [];
4330 const area = chart.chartArea;
4331 const active = [];
4332 const start = this._drawStart || 0;
4333 const count = this._drawCount || (elements.length - start);
4334 const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop;
4335 let i;
4336 if (meta.dataset) {
4337 meta.dataset.draw(ctx, area, start, count);
4338 }
4339 for (i = start; i < start + count; ++i) {
4340 const element = elements[i];
4341 if (element.hidden) {
4342 continue;
4343 }
4344 if (element.active && drawActiveElementsOnTop) {
4345 active.push(element);
4346 } else {
4347 element.draw(ctx, area);
4348 }
4349 }
4350 for (i = 0; i < active.length; ++i) {
4351 active[i].draw(ctx, area);
4352 }
4353 }
4354 getStyle(index, active) {
4355 const mode = active ? 'active' : 'default';
4356 return index === undefined && this._cachedMeta.dataset
4357 ? this.resolveDatasetElementOptions(mode)
4358 : this.resolveDataElementOptions(index || 0, mode);
4359 }
4360 getContext(index, active, mode) {
4361 const dataset = this.getDataset();
4362 let context;
4363 if (index >= 0 && index < this._cachedMeta.data.length) {
4364 const element = this._cachedMeta.data[index];
4365 context = element.$context ||
4366 (element.$context = createDataContext(this.getContext(), index, element));
4367 context.parsed = this.getParsed(index);
4368 context.raw = dataset.data[index];
4369 context.index = context.dataIndex = index;
4370 } else {
4371 context = this.$context ||
4372 (this.$context = createDatasetContext(this.chart.getContext(), this.index));
4373 context.dataset = dataset;
4374 context.index = context.datasetIndex = this.index;
4375 }
4376 context.active = !!active;
4377 context.mode = mode;
4378 return context;
4379 }
4380 resolveDatasetElementOptions(mode) {
4381 return this._resolveElementOptions(this.datasetElementType.id, mode);
4382 }
4383 resolveDataElementOptions(index, mode) {
4384 return this._resolveElementOptions(this.dataElementType.id, mode, index);
4385 }
4386 _resolveElementOptions(elementType, mode = 'default', index) {
4387 const active = mode === 'active';
4388 const cache = this._cachedDataOpts;
4389 const cacheKey = elementType + '-' + mode;
4390 const cached = cache[cacheKey];
4391 const sharing = this.enableOptionSharing && defined(index);
4392 if (cached) {
4393 return cloneIfNotShared(cached, sharing);
4394 }
4395 const config = this.chart.config;
4396 const scopeKeys = config.datasetElementScopeKeys(this._type, elementType);
4397 const prefixes = active ? [`${elementType}Hover`, 'hover', elementType, ''] : [elementType, ''];
4398 const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
4399 const names = Object.keys(defaults.elements[elementType]);
4400 const context = () => this.getContext(index, active);
4401 const values = config.resolveNamedOptions(scopes, names, context, prefixes);
4402 if (values.$shared) {
4403 values.$shared = sharing;
4404 cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));
4405 }
4406 return values;
4407 }
4408 _resolveAnimations(index, transition, active) {
4409 const chart = this.chart;
4410 const cache = this._cachedDataOpts;
4411 const cacheKey = `animation-${transition}`;
4412 const cached = cache[cacheKey];
4413 if (cached) {
4414 return cached;
4415 }
4416 let options;
4417 if (chart.options.animation !== false) {
4418 const config = this.chart.config;
4419 const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition);
4420 const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
4421 options = config.createResolver(scopes, this.getContext(index, active, transition));
4422 }
4423 const animations = new Animations(chart, options && options.animations);
4424 if (options && options._cacheable) {
4425 cache[cacheKey] = Object.freeze(animations);
4426 }
4427 return animations;
4428 }
4429 getSharedOptions(options) {
4430 if (!options.$shared) {
4431 return;
4432 }
4433 return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
4434 }
4435 includeOptions(mode, sharedOptions) {
4436 return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;
4437 }
4438 updateElement(element, index, properties, mode) {
4439 if (isDirectUpdateMode(mode)) {
4440 Object.assign(element, properties);
4441 } else {
4442 this._resolveAnimations(index, mode).update(element, properties);
4443 }
4444 }
4445 updateSharedOptions(sharedOptions, mode, newOptions) {
4446 if (sharedOptions && !isDirectUpdateMode(mode)) {
4447 this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);
4448 }
4449 }
4450 _setStyle(element, index, mode, active) {
4451 element.active = active;
4452 const options = this.getStyle(index, active);
4453 this._resolveAnimations(index, mode, active).update(element, {
4454 options: (!active && this.getSharedOptions(options)) || options
4455 });
4456 }
4457 removeHoverStyle(element, datasetIndex, index) {
4458 this._setStyle(element, index, 'active', false);
4459 }
4460 setHoverStyle(element, datasetIndex, index) {
4461 this._setStyle(element, index, 'active', true);
4462 }
4463 _removeDatasetHoverStyle() {
4464 const element = this._cachedMeta.dataset;
4465 if (element) {
4466 this._setStyle(element, undefined, 'active', false);
4467 }
4468 }
4469 _setDatasetHoverStyle() {
4470 const element = this._cachedMeta.dataset;
4471 if (element) {
4472 this._setStyle(element, undefined, 'active', true);
4473 }
4474 }
4475 _resyncElements(resetNewElements) {
4476 const data = this._data;
4477 const elements = this._cachedMeta.data;
4478 for (const [method, arg1, arg2] of this._syncList) {
4479 this[method](arg1, arg2);
4480 }
4481 this._syncList = [];
4482 const numMeta = elements.length;
4483 const numData = data.length;
4484 const count = Math.min(numData, numMeta);
4485 if (count) {
4486 this.parse(0, count);
4487 }
4488 if (numData > numMeta) {
4489 this._insertElements(numMeta, numData - numMeta, resetNewElements);
4490 } else if (numData < numMeta) {
4491 this._removeElements(numData, numMeta - numData);
4492 }
4493 }
4494 _insertElements(start, count, resetNewElements = true) {
4495 const meta = this._cachedMeta;
4496 const data = meta.data;
4497 const end = start + count;
4498 let i;
4499 const move = (arr) => {
4500 arr.length += count;
4501 for (i = arr.length - 1; i >= end; i--) {
4502 arr[i] = arr[i - count];
4503 }
4504 };
4505 move(data);
4506 for (i = start; i < end; ++i) {
4507 data[i] = new this.dataElementType();
4508 }
4509 if (this._parsing) {
4510 move(meta._parsed);
4511 }
4512 this.parse(start, count);
4513 if (resetNewElements) {
4514 this.updateElements(data, start, count, 'reset');
4515 }
4516 }
4517 updateElements(element, start, count, mode) {}
4518 _removeElements(start, count) {
4519 const meta = this._cachedMeta;
4520 if (this._parsing) {
4521 const removed = meta._parsed.splice(start, count);
4522 if (meta._stacked) {
4523 clearStacks(meta, removed);
4524 }
4525 }
4526 meta.data.splice(start, count);
4527 }
4528 _sync(args) {
4529 if (this._parsing) {
4530 this._syncList.push(args);
4531 } else {
4532 const [method, arg1, arg2] = args;
4533 this[method](arg1, arg2);
4534 }
4535 this.chart._dataChanges.push([this.index, ...args]);
4536 }
4537 _onDataPush() {
4538 const count = arguments.length;
4539 this._sync(['_insertElements', this.getDataset().data.length - count, count]);
4540 }
4541 _onDataPop() {
4542 this._sync(['_removeElements', this._cachedMeta.data.length - 1, 1]);
4543 }
4544 _onDataShift() {
4545 this._sync(['_removeElements', 0, 1]);
4546 }
4547 _onDataSplice(start, count) {
4548 if (count) {
4549 this._sync(['_removeElements', start, count]);
4550 }
4551 const newCount = arguments.length - 2;
4552 if (newCount) {
4553 this._sync(['_insertElements', start, newCount]);
4554 }
4555 }
4556 _onDataUnshift() {
4557 this._sync(['_insertElements', 0, arguments.length]);
4558 }
4559}
4560DatasetController.defaults = {};
4561DatasetController.prototype.datasetElementType = null;
4562DatasetController.prototype.dataElementType = null;
4563
4564class Element {
4565 constructor() {
4566 this.x = undefined;
4567 this.y = undefined;
4568 this.active = false;
4569 this.options = undefined;
4570 this.$animations = undefined;
4571 }
4572 tooltipPosition(useFinalPosition) {
4573 const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
4574 return {x, y};
4575 }
4576 hasValue() {
4577 return isNumber(this.x) && isNumber(this.y);
4578 }
4579 getProps(props, final) {
4580 const anims = this.$animations;
4581 if (!final || !anims) {
4582 return this;
4583 }
4584 const ret = {};
4585 props.forEach(prop => {
4586 ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop];
4587 });
4588 return ret;
4589 }
4590}
4591Element.defaults = {};
4592Element.defaultRoutes = undefined;
4593
4594const formatters = {
4595 values(value) {
4596 return isArray(value) ? value : '' + value;
4597 },
4598 numeric(tickValue, index, ticks) {
4599 if (tickValue === 0) {
4600 return '0';
4601 }
4602 const locale = this.chart.options.locale;
4603 let notation;
4604 let delta = tickValue;
4605 if (ticks.length > 1) {
4606 const maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value));
4607 if (maxTick < 1e-4 || maxTick > 1e+15) {
4608 notation = 'scientific';
4609 }
4610 delta = calculateDelta(tickValue, ticks);
4611 }
4612 const logDelta = log10(Math.abs(delta));
4613 const numDecimal = Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0);
4614 const options = {notation, minimumFractionDigits: numDecimal, maximumFractionDigits: numDecimal};
4615 Object.assign(options, this.options.ticks.format);
4616 return formatNumber(tickValue, locale, options);
4617 },
4618 logarithmic(tickValue, index, ticks) {
4619 if (tickValue === 0) {
4620 return '0';
4621 }
4622 const remain = tickValue / (Math.pow(10, Math.floor(log10(tickValue))));
4623 if (remain === 1 || remain === 2 || remain === 5) {
4624 return formatters.numeric.call(this, tickValue, index, ticks);
4625 }
4626 return '';
4627 }
4628};
4629function calculateDelta(tickValue, ticks) {
4630 let delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value;
4631 if (Math.abs(delta) >= 1 && tickValue !== Math.floor(tickValue)) {
4632 delta = tickValue - Math.floor(tickValue);
4633 }
4634 return delta;
4635}
4636var Ticks = {formatters};
4637
4638defaults.set('scale', {
4639 display: true,
4640 offset: false,
4641 reverse: false,
4642 beginAtZero: false,
4643 bounds: 'ticks',
4644 grace: 0,
4645 grid: {
4646 display: true,
4647 lineWidth: 1,
4648 drawBorder: true,
4649 drawOnChartArea: true,
4650 drawTicks: true,
4651 tickLength: 8,
4652 tickWidth: (_ctx, options) => options.lineWidth,
4653 tickColor: (_ctx, options) => options.color,
4654 offset: false,
4655 borderDash: [],
4656 borderDashOffset: 0.0,
4657 borderWidth: 1
4658 },
4659 title: {
4660 display: false,
4661 text: '',
4662 padding: {
4663 top: 4,
4664 bottom: 4
4665 }
4666 },
4667 ticks: {
4668 minRotation: 0,
4669 maxRotation: 50,
4670 mirror: false,
4671 textStrokeWidth: 0,
4672 textStrokeColor: '',
4673 padding: 3,
4674 display: true,
4675 autoSkip: true,
4676 autoSkipPadding: 3,
4677 labelOffset: 0,
4678 callback: Ticks.formatters.values,
4679 minor: {},
4680 major: {},
4681 align: 'center',
4682 crossAlign: 'near',
4683 showLabelBackdrop: false,
4684 backdropColor: 'rgba(255, 255, 255, 0.75)',
4685 backdropPadding: 2,
4686 }
4687});
4688defaults.route('scale.ticks', 'color', '', 'color');
4689defaults.route('scale.grid', 'color', '', 'borderColor');
4690defaults.route('scale.grid', 'borderColor', '', 'borderColor');
4691defaults.route('scale.title', 'color', '', 'color');
4692defaults.describe('scale', {
4693 _fallback: false,
4694 _scriptable: (name) => !name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser',
4695 _indexable: (name) => name !== 'borderDash' && name !== 'tickBorderDash',
4696});
4697defaults.describe('scales', {
4698 _fallback: 'scale',
4699});
4700defaults.describe('scale.ticks', {
4701 _scriptable: (name) => name !== 'backdropPadding' && name !== 'callback',
4702 _indexable: (name) => name !== 'backdropPadding',
4703});
4704
4705function autoSkip(scale, ticks) {
4706 const tickOpts = scale.options.ticks;
4707 const ticksLimit = tickOpts.maxTicksLimit || determineMaxTicks(scale);
4708 const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
4709 const numMajorIndices = majorIndices.length;
4710 const first = majorIndices[0];
4711 const last = majorIndices[numMajorIndices - 1];
4712 const newTicks = [];
4713 if (numMajorIndices > ticksLimit) {
4714 skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);
4715 return newTicks;
4716 }
4717 const spacing = calculateSpacing(majorIndices, ticks, ticksLimit);
4718 if (numMajorIndices > 0) {
4719 let i, ilen;
4720 const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;
4721 skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
4722 for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
4723 skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);
4724 }
4725 skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
4726 return newTicks;
4727 }
4728 skip(ticks, newTicks, spacing);
4729 return newTicks;
4730}
4731function determineMaxTicks(scale) {
4732 const offset = scale.options.offset;
4733 const tickLength = scale._tickSize();
4734 const maxScale = scale._length / tickLength + (offset ? 0 : 1);
4735 const maxChart = scale._maxLength / tickLength;
4736 return Math.floor(Math.min(maxScale, maxChart));
4737}
4738function calculateSpacing(majorIndices, ticks, ticksLimit) {
4739 const evenMajorSpacing = getEvenSpacing(majorIndices);
4740 const spacing = ticks.length / ticksLimit;
4741 if (!evenMajorSpacing) {
4742 return Math.max(spacing, 1);
4743 }
4744 const factors = _factorize(evenMajorSpacing);
4745 for (let i = 0, ilen = factors.length - 1; i < ilen; i++) {
4746 const factor = factors[i];
4747 if (factor > spacing) {
4748 return factor;
4749 }
4750 }
4751 return Math.max(spacing, 1);
4752}
4753function getMajorIndices(ticks) {
4754 const result = [];
4755 let i, ilen;
4756 for (i = 0, ilen = ticks.length; i < ilen; i++) {
4757 if (ticks[i].major) {
4758 result.push(i);
4759 }
4760 }
4761 return result;
4762}
4763function skipMajors(ticks, newTicks, majorIndices, spacing) {
4764 let count = 0;
4765 let next = majorIndices[0];
4766 let i;
4767 spacing = Math.ceil(spacing);
4768 for (i = 0; i < ticks.length; i++) {
4769 if (i === next) {
4770 newTicks.push(ticks[i]);
4771 count++;
4772 next = majorIndices[count * spacing];
4773 }
4774 }
4775}
4776function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
4777 const start = valueOrDefault(majorStart, 0);
4778 const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
4779 let count = 0;
4780 let length, i, next;
4781 spacing = Math.ceil(spacing);
4782 if (majorEnd) {
4783 length = majorEnd - majorStart;
4784 spacing = length / Math.floor(length / spacing);
4785 }
4786 next = start;
4787 while (next < 0) {
4788 count++;
4789 next = Math.round(start + count * spacing);
4790 }
4791 for (i = Math.max(start, 0); i < end; i++) {
4792 if (i === next) {
4793 newTicks.push(ticks[i]);
4794 count++;
4795 next = Math.round(start + count * spacing);
4796 }
4797 }
4798}
4799function getEvenSpacing(arr) {
4800 const len = arr.length;
4801 let i, diff;
4802 if (len < 2) {
4803 return false;
4804 }
4805 for (diff = arr[0], i = 1; i < len; ++i) {
4806 if (arr[i] - arr[i - 1] !== diff) {
4807 return false;
4808 }
4809 }
4810 return diff;
4811}
4812
4813const reverseAlign = (align) => align === 'left' ? 'right' : align === 'right' ? 'left' : align;
4814const offsetFromEdge = (scale, edge, offset) => edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
4815function sample(arr, numItems) {
4816 const result = [];
4817 const increment = arr.length / numItems;
4818 const len = arr.length;
4819 let i = 0;
4820 for (; i < len; i += increment) {
4821 result.push(arr[Math.floor(i)]);
4822 }
4823 return result;
4824}
4825function getPixelForGridLine(scale, index, offsetGridLines) {
4826 const length = scale.ticks.length;
4827 const validIndex = Math.min(index, length - 1);
4828 const start = scale._startPixel;
4829 const end = scale._endPixel;
4830 const epsilon = 1e-6;
4831 let lineValue = scale.getPixelForTick(validIndex);
4832 let offset;
4833 if (offsetGridLines) {
4834 if (length === 1) {
4835 offset = Math.max(lineValue - start, end - lineValue);
4836 } else if (index === 0) {
4837 offset = (scale.getPixelForTick(1) - lineValue) / 2;
4838 } else {
4839 offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
4840 }
4841 lineValue += validIndex < index ? offset : -offset;
4842 if (lineValue < start - epsilon || lineValue > end + epsilon) {
4843 return;
4844 }
4845 }
4846 return lineValue;
4847}
4848function garbageCollect(caches, length) {
4849 each(caches, (cache) => {
4850 const gc = cache.gc;
4851 const gcLen = gc.length / 2;
4852 let i;
4853 if (gcLen > length) {
4854 for (i = 0; i < gcLen; ++i) {
4855 delete cache.data[gc[i]];
4856 }
4857 gc.splice(0, gcLen);
4858 }
4859 });
4860}
4861function getTickMarkLength(options) {
4862 return options.drawTicks ? options.tickLength : 0;
4863}
4864function getTitleHeight(options, fallback) {
4865 if (!options.display) {
4866 return 0;
4867 }
4868 const font = toFont(options.font, fallback);
4869 const padding = toPadding(options.padding);
4870 const lines = isArray(options.text) ? options.text.length : 1;
4871 return (lines * font.lineHeight) + padding.height;
4872}
4873function createScaleContext(parent, scale) {
4874 return createContext(parent, {
4875 scale,
4876 type: 'scale'
4877 });
4878}
4879function createTickContext(parent, index, tick) {
4880 return createContext(parent, {
4881 tick,
4882 index,
4883 type: 'tick'
4884 });
4885}
4886function titleAlign(align, position, reverse) {
4887 let ret = _toLeftRightCenter(align);
4888 if ((reverse && position !== 'right') || (!reverse && position === 'right')) {
4889 ret = reverseAlign(ret);
4890 }
4891 return ret;
4892}
4893function titleArgs(scale, offset, position, align) {
4894 const {top, left, bottom, right, chart} = scale;
4895 const {chartArea, scales} = chart;
4896 let rotation = 0;
4897 let maxWidth, titleX, titleY;
4898 const height = bottom - top;
4899 const width = right - left;
4900 if (scale.isHorizontal()) {
4901 titleX = _alignStartEnd(align, left, right);
4902 if (isObject(position)) {
4903 const positionAxisID = Object.keys(position)[0];
4904 const value = position[positionAxisID];
4905 titleY = scales[positionAxisID].getPixelForValue(value) + height - offset;
4906 } else if (position === 'center') {
4907 titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset;
4908 } else {
4909 titleY = offsetFromEdge(scale, position, offset);
4910 }
4911 maxWidth = right - left;
4912 } else {
4913 if (isObject(position)) {
4914 const positionAxisID = Object.keys(position)[0];
4915 const value = position[positionAxisID];
4916 titleX = scales[positionAxisID].getPixelForValue(value) - width + offset;
4917 } else if (position === 'center') {
4918 titleX = (chartArea.left + chartArea.right) / 2 - width + offset;
4919 } else {
4920 titleX = offsetFromEdge(scale, position, offset);
4921 }
4922 titleY = _alignStartEnd(align, bottom, top);
4923 rotation = position === 'left' ? -HALF_PI : HALF_PI;
4924 }
4925 return {titleX, titleY, maxWidth, rotation};
4926}
4927class Scale extends Element {
4928 constructor(cfg) {
4929 super();
4930 this.id = cfg.id;
4931 this.type = cfg.type;
4932 this.options = undefined;
4933 this.ctx = cfg.ctx;
4934 this.chart = cfg.chart;
4935 this.top = undefined;
4936 this.bottom = undefined;
4937 this.left = undefined;
4938 this.right = undefined;
4939 this.width = undefined;
4940 this.height = undefined;
4941 this._margins = {
4942 left: 0,
4943 right: 0,
4944 top: 0,
4945 bottom: 0
4946 };
4947 this.maxWidth = undefined;
4948 this.maxHeight = undefined;
4949 this.paddingTop = undefined;
4950 this.paddingBottom = undefined;
4951 this.paddingLeft = undefined;
4952 this.paddingRight = undefined;
4953 this.axis = undefined;
4954 this.labelRotation = undefined;
4955 this.min = undefined;
4956 this.max = undefined;
4957 this._range = undefined;
4958 this.ticks = [];
4959 this._gridLineItems = null;
4960 this._labelItems = null;
4961 this._labelSizes = null;
4962 this._length = 0;
4963 this._maxLength = 0;
4964 this._longestTextCache = {};
4965 this._startPixel = undefined;
4966 this._endPixel = undefined;
4967 this._reversePixels = false;
4968 this._userMax = undefined;
4969 this._userMin = undefined;
4970 this._suggestedMax = undefined;
4971 this._suggestedMin = undefined;
4972 this._ticksLength = 0;
4973 this._borderValue = 0;
4974 this._cache = {};
4975 this._dataLimitsCached = false;
4976 this.$context = undefined;
4977 }
4978 init(options) {
4979 this.options = options.setContext(this.getContext());
4980 this.axis = options.axis;
4981 this._userMin = this.parse(options.min);
4982 this._userMax = this.parse(options.max);
4983 this._suggestedMin = this.parse(options.suggestedMin);
4984 this._suggestedMax = this.parse(options.suggestedMax);
4985 }
4986 parse(raw, index) {
4987 return raw;
4988 }
4989 getUserBounds() {
4990 let {_userMin, _userMax, _suggestedMin, _suggestedMax} = this;
4991 _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);
4992 _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);
4993 _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);
4994 _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);
4995 return {
4996 min: finiteOrDefault(_userMin, _suggestedMin),
4997 max: finiteOrDefault(_userMax, _suggestedMax),
4998 minDefined: isNumberFinite(_userMin),
4999 maxDefined: isNumberFinite(_userMax)
5000 };
5001 }
5002 getMinMax(canStack) {
5003 let {min, max, minDefined, maxDefined} = this.getUserBounds();
5004 let range;
5005 if (minDefined && maxDefined) {
5006 return {min, max};
5007 }
5008 const metas = this.getMatchingVisibleMetas();
5009 for (let i = 0, ilen = metas.length; i < ilen; ++i) {
5010 range = metas[i].controller.getMinMax(this, canStack);
5011 if (!minDefined) {
5012 min = Math.min(min, range.min);
5013 }
5014 if (!maxDefined) {
5015 max = Math.max(max, range.max);
5016 }
5017 }
5018 min = maxDefined && min > max ? max : min;
5019 max = minDefined && min > max ? min : max;
5020 return {
5021 min: finiteOrDefault(min, finiteOrDefault(max, min)),
5022 max: finiteOrDefault(max, finiteOrDefault(min, max))
5023 };
5024 }
5025 getPadding() {
5026 return {
5027 left: this.paddingLeft || 0,
5028 top: this.paddingTop || 0,
5029 right: this.paddingRight || 0,
5030 bottom: this.paddingBottom || 0
5031 };
5032 }
5033 getTicks() {
5034 return this.ticks;
5035 }
5036 getLabels() {
5037 const data = this.chart.data;
5038 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
5039 }
5040 beforeLayout() {
5041 this._cache = {};
5042 this._dataLimitsCached = false;
5043 }
5044 beforeUpdate() {
5045 callback(this.options.beforeUpdate, [this]);
5046 }
5047 update(maxWidth, maxHeight, margins) {
5048 const {beginAtZero, grace, ticks: tickOpts} = this.options;
5049 const sampleSize = tickOpts.sampleSize;
5050 this.beforeUpdate();
5051 this.maxWidth = maxWidth;
5052 this.maxHeight = maxHeight;
5053 this._margins = margins = Object.assign({
5054 left: 0,
5055 right: 0,
5056 top: 0,
5057 bottom: 0
5058 }, margins);
5059 this.ticks = null;
5060 this._labelSizes = null;
5061 this._gridLineItems = null;
5062 this._labelItems = null;
5063 this.beforeSetDimensions();
5064 this.setDimensions();
5065 this.afterSetDimensions();
5066 this._maxLength = this.isHorizontal()
5067 ? this.width + margins.left + margins.right
5068 : this.height + margins.top + margins.bottom;
5069 if (!this._dataLimitsCached) {
5070 this.beforeDataLimits();
5071 this.determineDataLimits();
5072 this.afterDataLimits();
5073 this._range = _addGrace(this, grace, beginAtZero);
5074 this._dataLimitsCached = true;
5075 }
5076 this.beforeBuildTicks();
5077 this.ticks = this.buildTicks() || [];
5078 this.afterBuildTicks();
5079 const samplingEnabled = sampleSize < this.ticks.length;
5080 this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks);
5081 this.configure();
5082 this.beforeCalculateLabelRotation();
5083 this.calculateLabelRotation();
5084 this.afterCalculateLabelRotation();
5085 if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
5086 this.ticks = autoSkip(this, this.ticks);
5087 this._labelSizes = null;
5088 }
5089 if (samplingEnabled) {
5090 this._convertTicksToLabels(this.ticks);
5091 }
5092 this.beforeFit();
5093 this.fit();
5094 this.afterFit();
5095 this.afterUpdate();
5096 }
5097 configure() {
5098 let reversePixels = this.options.reverse;
5099 let startPixel, endPixel;
5100 if (this.isHorizontal()) {
5101 startPixel = this.left;
5102 endPixel = this.right;
5103 } else {
5104 startPixel = this.top;
5105 endPixel = this.bottom;
5106 reversePixels = !reversePixels;
5107 }
5108 this._startPixel = startPixel;
5109 this._endPixel = endPixel;
5110 this._reversePixels = reversePixels;
5111 this._length = endPixel - startPixel;
5112 this._alignToPixels = this.options.alignToPixels;
5113 }
5114 afterUpdate() {
5115 callback(this.options.afterUpdate, [this]);
5116 }
5117 beforeSetDimensions() {
5118 callback(this.options.beforeSetDimensions, [this]);
5119 }
5120 setDimensions() {
5121 if (this.isHorizontal()) {
5122 this.width = this.maxWidth;
5123 this.left = 0;
5124 this.right = this.width;
5125 } else {
5126 this.height = this.maxHeight;
5127 this.top = 0;
5128 this.bottom = this.height;
5129 }
5130 this.paddingLeft = 0;
5131 this.paddingTop = 0;
5132 this.paddingRight = 0;
5133 this.paddingBottom = 0;
5134 }
5135 afterSetDimensions() {
5136 callback(this.options.afterSetDimensions, [this]);
5137 }
5138 _callHooks(name) {
5139 this.chart.notifyPlugins(name, this.getContext());
5140 callback(this.options[name], [this]);
5141 }
5142 beforeDataLimits() {
5143 this._callHooks('beforeDataLimits');
5144 }
5145 determineDataLimits() {}
5146 afterDataLimits() {
5147 this._callHooks('afterDataLimits');
5148 }
5149 beforeBuildTicks() {
5150 this._callHooks('beforeBuildTicks');
5151 }
5152 buildTicks() {
5153 return [];
5154 }
5155 afterBuildTicks() {
5156 this._callHooks('afterBuildTicks');
5157 }
5158 beforeTickToLabelConversion() {
5159 callback(this.options.beforeTickToLabelConversion, [this]);
5160 }
5161 generateTickLabels(ticks) {
5162 const tickOpts = this.options.ticks;
5163 let i, ilen, tick;
5164 for (i = 0, ilen = ticks.length; i < ilen; i++) {
5165 tick = ticks[i];
5166 tick.label = callback(tickOpts.callback, [tick.value, i, ticks], this);
5167 }
5168 }
5169 afterTickToLabelConversion() {
5170 callback(this.options.afterTickToLabelConversion, [this]);
5171 }
5172 beforeCalculateLabelRotation() {
5173 callback(this.options.beforeCalculateLabelRotation, [this]);
5174 }
5175 calculateLabelRotation() {
5176 const options = this.options;
5177 const tickOpts = options.ticks;
5178 const numTicks = this.ticks.length;
5179 const minRotation = tickOpts.minRotation || 0;
5180 const maxRotation = tickOpts.maxRotation;
5181 let labelRotation = minRotation;
5182 let tickWidth, maxHeight, maxLabelDiagonal;
5183 if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) {
5184 this.labelRotation = minRotation;
5185 return;
5186 }
5187 const labelSizes = this._getLabelSizes();
5188 const maxLabelWidth = labelSizes.widest.width;
5189 const maxLabelHeight = labelSizes.highest.height;
5190 const maxWidth = _limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth);
5191 tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1);
5192 if (maxLabelWidth + 6 > tickWidth) {
5193 tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
5194 maxHeight = this.maxHeight - getTickMarkLength(options.grid)
5195 - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font);
5196 maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
5197 labelRotation = toDegrees(Math.min(
5198 Math.asin(_limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)),
5199 Math.asin(_limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(_limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1))
5200 ));
5201 labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
5202 }
5203 this.labelRotation = labelRotation;
5204 }
5205 afterCalculateLabelRotation() {
5206 callback(this.options.afterCalculateLabelRotation, [this]);
5207 }
5208 beforeFit() {
5209 callback(this.options.beforeFit, [this]);
5210 }
5211 fit() {
5212 const minSize = {
5213 width: 0,
5214 height: 0
5215 };
5216 const {chart, options: {ticks: tickOpts, title: titleOpts, grid: gridOpts}} = this;
5217 const display = this._isVisible();
5218 const isHorizontal = this.isHorizontal();
5219 if (display) {
5220 const titleHeight = getTitleHeight(titleOpts, chart.options.font);
5221 if (isHorizontal) {
5222 minSize.width = this.maxWidth;
5223 minSize.height = getTickMarkLength(gridOpts) + titleHeight;
5224 } else {
5225 minSize.height = this.maxHeight;
5226 minSize.width = getTickMarkLength(gridOpts) + titleHeight;
5227 }
5228 if (tickOpts.display && this.ticks.length) {
5229 const {first, last, widest, highest} = this._getLabelSizes();
5230 const tickPadding = tickOpts.padding * 2;
5231 const angleRadians = toRadians(this.labelRotation);
5232 const cos = Math.cos(angleRadians);
5233 const sin = Math.sin(angleRadians);
5234 if (isHorizontal) {
5235 const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;
5236 minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding);
5237 } else {
5238 const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
5239 minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding);
5240 }
5241 this._calculatePadding(first, last, sin, cos);
5242 }
5243 }
5244 this._handleMargins();
5245 if (isHorizontal) {
5246 this.width = this._length = chart.width - this._margins.left - this._margins.right;
5247 this.height = minSize.height;
5248 } else {
5249 this.width = minSize.width;
5250 this.height = this._length = chart.height - this._margins.top - this._margins.bottom;
5251 }
5252 }
5253 _calculatePadding(first, last, sin, cos) {
5254 const {ticks: {align, padding}, position} = this.options;
5255 const isRotated = this.labelRotation !== 0;
5256 const labelsBelowTicks = position !== 'top' && this.axis === 'x';
5257 if (this.isHorizontal()) {
5258 const offsetLeft = this.getPixelForTick(0) - this.left;
5259 const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1);
5260 let paddingLeft = 0;
5261 let paddingRight = 0;
5262 if (isRotated) {
5263 if (labelsBelowTicks) {
5264 paddingLeft = cos * first.width;
5265 paddingRight = sin * last.height;
5266 } else {
5267 paddingLeft = sin * first.height;
5268 paddingRight = cos * last.width;
5269 }
5270 } else if (align === 'start') {
5271 paddingRight = last.width;
5272 } else if (align === 'end') {
5273 paddingLeft = first.width;
5274 } else {
5275 paddingLeft = first.width / 2;
5276 paddingRight = last.width / 2;
5277 }
5278 this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0);
5279 this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0);
5280 } else {
5281 let paddingTop = last.height / 2;
5282 let paddingBottom = first.height / 2;
5283 if (align === 'start') {
5284 paddingTop = 0;
5285 paddingBottom = first.height;
5286 } else if (align === 'end') {
5287 paddingTop = last.height;
5288 paddingBottom = 0;
5289 }
5290 this.paddingTop = paddingTop + padding;
5291 this.paddingBottom = paddingBottom + padding;
5292 }
5293 }
5294 _handleMargins() {
5295 if (this._margins) {
5296 this._margins.left = Math.max(this.paddingLeft, this._margins.left);
5297 this._margins.top = Math.max(this.paddingTop, this._margins.top);
5298 this._margins.right = Math.max(this.paddingRight, this._margins.right);
5299 this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom);
5300 }
5301 }
5302 afterFit() {
5303 callback(this.options.afterFit, [this]);
5304 }
5305 isHorizontal() {
5306 const {axis, position} = this.options;
5307 return position === 'top' || position === 'bottom' || axis === 'x';
5308 }
5309 isFullSize() {
5310 return this.options.fullSize;
5311 }
5312 _convertTicksToLabels(ticks) {
5313 this.beforeTickToLabelConversion();
5314 this.generateTickLabels(ticks);
5315 let i, ilen;
5316 for (i = 0, ilen = ticks.length; i < ilen; i++) {
5317 if (isNullOrUndef(ticks[i].label)) {
5318 ticks.splice(i, 1);
5319 ilen--;
5320 i--;
5321 }
5322 }
5323 this.afterTickToLabelConversion();
5324 }
5325 _getLabelSizes() {
5326 let labelSizes = this._labelSizes;
5327 if (!labelSizes) {
5328 const sampleSize = this.options.ticks.sampleSize;
5329 let ticks = this.ticks;
5330 if (sampleSize < ticks.length) {
5331 ticks = sample(ticks, sampleSize);
5332 }
5333 this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length);
5334 }
5335 return labelSizes;
5336 }
5337 _computeLabelSizes(ticks, length) {
5338 const {ctx, _longestTextCache: caches} = this;
5339 const widths = [];
5340 const heights = [];
5341 let widestLabelSize = 0;
5342 let highestLabelSize = 0;
5343 let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
5344 for (i = 0; i < length; ++i) {
5345 label = ticks[i].label;
5346 tickFont = this._resolveTickFontOptions(i);
5347 ctx.font = fontString = tickFont.string;
5348 cache = caches[fontString] = caches[fontString] || {data: {}, gc: []};
5349 lineHeight = tickFont.lineHeight;
5350 width = height = 0;
5351 if (!isNullOrUndef(label) && !isArray(label)) {
5352 width = _measureText(ctx, cache.data, cache.gc, width, label);
5353 height = lineHeight;
5354 } else if (isArray(label)) {
5355 for (j = 0, jlen = label.length; j < jlen; ++j) {
5356 nestedLabel = label[j];
5357 if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
5358 width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel);
5359 height += lineHeight;
5360 }
5361 }
5362 }
5363 widths.push(width);
5364 heights.push(height);
5365 widestLabelSize = Math.max(width, widestLabelSize);
5366 highestLabelSize = Math.max(height, highestLabelSize);
5367 }
5368 garbageCollect(caches, length);
5369 const widest = widths.indexOf(widestLabelSize);
5370 const highest = heights.indexOf(highestLabelSize);
5371 const valueAt = (idx) => ({width: widths[idx] || 0, height: heights[idx] || 0});
5372 return {
5373 first: valueAt(0),
5374 last: valueAt(length - 1),
5375 widest: valueAt(widest),
5376 highest: valueAt(highest),
5377 widths,
5378 heights,
5379 };
5380 }
5381 getLabelForValue(value) {
5382 return value;
5383 }
5384 getPixelForValue(value, index) {
5385 return NaN;
5386 }
5387 getValueForPixel(pixel) {}
5388 getPixelForTick(index) {
5389 const ticks = this.ticks;
5390 if (index < 0 || index > ticks.length - 1) {
5391 return null;
5392 }
5393 return this.getPixelForValue(ticks[index].value);
5394 }
5395 getPixelForDecimal(decimal) {
5396 if (this._reversePixels) {
5397 decimal = 1 - decimal;
5398 }
5399 const pixel = this._startPixel + decimal * this._length;
5400 return _int16Range(this._alignToPixels ? _alignPixel(this.chart, pixel, 0) : pixel);
5401 }
5402 getDecimalForPixel(pixel) {
5403 const decimal = (pixel - this._startPixel) / this._length;
5404 return this._reversePixels ? 1 - decimal : decimal;
5405 }
5406 getBasePixel() {
5407 return this.getPixelForValue(this.getBaseValue());
5408 }
5409 getBaseValue() {
5410 const {min, max} = this;
5411 return min < 0 && max < 0 ? max :
5412 min > 0 && max > 0 ? min :
5413 0;
5414 }
5415 getContext(index) {
5416 const ticks = this.ticks || [];
5417 if (index >= 0 && index < ticks.length) {
5418 const tick = ticks[index];
5419 return tick.$context ||
5420 (tick.$context = createTickContext(this.getContext(), index, tick));
5421 }
5422 return this.$context ||
5423 (this.$context = createScaleContext(this.chart.getContext(), this));
5424 }
5425 _tickSize() {
5426 const optionTicks = this.options.ticks;
5427 const rot = toRadians(this.labelRotation);
5428 const cos = Math.abs(Math.cos(rot));
5429 const sin = Math.abs(Math.sin(rot));
5430 const labelSizes = this._getLabelSizes();
5431 const padding = optionTicks.autoSkipPadding || 0;
5432 const w = labelSizes ? labelSizes.widest.width + padding : 0;
5433 const h = labelSizes ? labelSizes.highest.height + padding : 0;
5434 return this.isHorizontal()
5435 ? h * cos > w * sin ? w / cos : h / sin
5436 : h * sin < w * cos ? h / cos : w / sin;
5437 }
5438 _isVisible() {
5439 const display = this.options.display;
5440 if (display !== 'auto') {
5441 return !!display;
5442 }
5443 return this.getMatchingVisibleMetas().length > 0;
5444 }
5445 _computeGridLineItems(chartArea) {
5446 const axis = this.axis;
5447 const chart = this.chart;
5448 const options = this.options;
5449 const {grid, position} = options;
5450 const offset = grid.offset;
5451 const isHorizontal = this.isHorizontal();
5452 const ticks = this.ticks;
5453 const ticksLength = ticks.length + (offset ? 1 : 0);
5454 const tl = getTickMarkLength(grid);
5455 const items = [];
5456 const borderOpts = grid.setContext(this.getContext());
5457 const axisWidth = borderOpts.drawBorder ? borderOpts.borderWidth : 0;
5458 const axisHalfWidth = axisWidth / 2;
5459 const alignBorderValue = function(pixel) {
5460 return _alignPixel(chart, pixel, axisWidth);
5461 };
5462 let borderValue, i, lineValue, alignedLineValue;
5463 let tx1, ty1, tx2, ty2, x1, y1, x2, y2;
5464 if (position === 'top') {
5465 borderValue = alignBorderValue(this.bottom);
5466 ty1 = this.bottom - tl;
5467 ty2 = borderValue - axisHalfWidth;
5468 y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
5469 y2 = chartArea.bottom;
5470 } else if (position === 'bottom') {
5471 borderValue = alignBorderValue(this.top);
5472 y1 = chartArea.top;
5473 y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
5474 ty1 = borderValue + axisHalfWidth;
5475 ty2 = this.top + tl;
5476 } else if (position === 'left') {
5477 borderValue = alignBorderValue(this.right);
5478 tx1 = this.right - tl;
5479 tx2 = borderValue - axisHalfWidth;
5480 x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
5481 x2 = chartArea.right;
5482 } else if (position === 'right') {
5483 borderValue = alignBorderValue(this.left);
5484 x1 = chartArea.left;
5485 x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
5486 tx1 = borderValue + axisHalfWidth;
5487 tx2 = this.left + tl;
5488 } else if (axis === 'x') {
5489 if (position === 'center') {
5490 borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
5491 } else if (isObject(position)) {
5492 const positionAxisID = Object.keys(position)[0];
5493 const value = position[positionAxisID];
5494 borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
5495 }
5496 y1 = chartArea.top;
5497 y2 = chartArea.bottom;
5498 ty1 = borderValue + axisHalfWidth;
5499 ty2 = ty1 + tl;
5500 } else if (axis === 'y') {
5501 if (position === 'center') {
5502 borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);
5503 } else if (isObject(position)) {
5504 const positionAxisID = Object.keys(position)[0];
5505 const value = position[positionAxisID];
5506 borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
5507 }
5508 tx1 = borderValue - axisHalfWidth;
5509 tx2 = tx1 - tl;
5510 x1 = chartArea.left;
5511 x2 = chartArea.right;
5512 }
5513 const limit = valueOrDefault(options.ticks.maxTicksLimit, ticksLength);
5514 const step = Math.max(1, Math.ceil(ticksLength / limit));
5515 for (i = 0; i < ticksLength; i += step) {
5516 const optsAtIndex = grid.setContext(this.getContext(i));
5517 const lineWidth = optsAtIndex.lineWidth;
5518 const lineColor = optsAtIndex.color;
5519 const borderDash = grid.borderDash || [];
5520 const borderDashOffset = optsAtIndex.borderDashOffset;
5521 const tickWidth = optsAtIndex.tickWidth;
5522 const tickColor = optsAtIndex.tickColor;
5523 const tickBorderDash = optsAtIndex.tickBorderDash || [];
5524 const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;
5525 lineValue = getPixelForGridLine(this, i, offset);
5526 if (lineValue === undefined) {
5527 continue;
5528 }
5529 alignedLineValue = _alignPixel(chart, lineValue, lineWidth);
5530 if (isHorizontal) {
5531 tx1 = tx2 = x1 = x2 = alignedLineValue;
5532 } else {
5533 ty1 = ty2 = y1 = y2 = alignedLineValue;
5534 }
5535 items.push({
5536 tx1,
5537 ty1,
5538 tx2,
5539 ty2,
5540 x1,
5541 y1,
5542 x2,
5543 y2,
5544 width: lineWidth,
5545 color: lineColor,
5546 borderDash,
5547 borderDashOffset,
5548 tickWidth,
5549 tickColor,
5550 tickBorderDash,
5551 tickBorderDashOffset,
5552 });
5553 }
5554 this._ticksLength = ticksLength;
5555 this._borderValue = borderValue;
5556 return items;
5557 }
5558 _computeLabelItems(chartArea) {
5559 const axis = this.axis;
5560 const options = this.options;
5561 const {position, ticks: optionTicks} = options;
5562 const isHorizontal = this.isHorizontal();
5563 const ticks = this.ticks;
5564 const {align, crossAlign, padding, mirror} = optionTicks;
5565 const tl = getTickMarkLength(options.grid);
5566 const tickAndPadding = tl + padding;
5567 const hTickAndPadding = mirror ? -padding : tickAndPadding;
5568 const rotation = -toRadians(this.labelRotation);
5569 const items = [];
5570 let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
5571 let textBaseline = 'middle';
5572 if (position === 'top') {
5573 y = this.bottom - hTickAndPadding;
5574 textAlign = this._getXAxisLabelAlignment();
5575 } else if (position === 'bottom') {
5576 y = this.top + hTickAndPadding;
5577 textAlign = this._getXAxisLabelAlignment();
5578 } else if (position === 'left') {
5579 const ret = this._getYAxisLabelAlignment(tl);
5580 textAlign = ret.textAlign;
5581 x = ret.x;
5582 } else if (position === 'right') {
5583 const ret = this._getYAxisLabelAlignment(tl);
5584 textAlign = ret.textAlign;
5585 x = ret.x;
5586 } else if (axis === 'x') {
5587 if (position === 'center') {
5588 y = ((chartArea.top + chartArea.bottom) / 2) + tickAndPadding;
5589 } else if (isObject(position)) {
5590 const positionAxisID = Object.keys(position)[0];
5591 const value = position[positionAxisID];
5592 y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
5593 }
5594 textAlign = this._getXAxisLabelAlignment();
5595 } else if (axis === 'y') {
5596 if (position === 'center') {
5597 x = ((chartArea.left + chartArea.right) / 2) - tickAndPadding;
5598 } else if (isObject(position)) {
5599 const positionAxisID = Object.keys(position)[0];
5600 const value = position[positionAxisID];
5601 x = this.chart.scales[positionAxisID].getPixelForValue(value);
5602 }
5603 textAlign = this._getYAxisLabelAlignment(tl).textAlign;
5604 }
5605 if (axis === 'y') {
5606 if (align === 'start') {
5607 textBaseline = 'top';
5608 } else if (align === 'end') {
5609 textBaseline = 'bottom';
5610 }
5611 }
5612 const labelSizes = this._getLabelSizes();
5613 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
5614 tick = ticks[i];
5615 label = tick.label;
5616 const optsAtIndex = optionTicks.setContext(this.getContext(i));
5617 pixel = this.getPixelForTick(i) + optionTicks.labelOffset;
5618 font = this._resolveTickFontOptions(i);
5619 lineHeight = font.lineHeight;
5620 lineCount = isArray(label) ? label.length : 1;
5621 const halfCount = lineCount / 2;
5622 const color = optsAtIndex.color;
5623 const strokeColor = optsAtIndex.textStrokeColor;
5624 const strokeWidth = optsAtIndex.textStrokeWidth;
5625 if (isHorizontal) {
5626 x = pixel;
5627 if (position === 'top') {
5628 if (crossAlign === 'near' || rotation !== 0) {
5629 textOffset = -lineCount * lineHeight + lineHeight / 2;
5630 } else if (crossAlign === 'center') {
5631 textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
5632 } else {
5633 textOffset = -labelSizes.highest.height + lineHeight / 2;
5634 }
5635 } else {
5636 if (crossAlign === 'near' || rotation !== 0) {
5637 textOffset = lineHeight / 2;
5638 } else if (crossAlign === 'center') {
5639 textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
5640 } else {
5641 textOffset = labelSizes.highest.height - lineCount * lineHeight;
5642 }
5643 }
5644 if (mirror) {
5645 textOffset *= -1;
5646 }
5647 } else {
5648 y = pixel;
5649 textOffset = (1 - lineCount) * lineHeight / 2;
5650 }
5651 let backdrop;
5652 if (optsAtIndex.showLabelBackdrop) {
5653 const labelPadding = toPadding(optsAtIndex.backdropPadding);
5654 const height = labelSizes.heights[i];
5655 const width = labelSizes.widths[i];
5656 let top = y + textOffset - labelPadding.top;
5657 let left = x - labelPadding.left;
5658 switch (textBaseline) {
5659 case 'middle':
5660 top -= height / 2;
5661 break;
5662 case 'bottom':
5663 top -= height;
5664 break;
5665 }
5666 switch (textAlign) {
5667 case 'center':
5668 left -= width / 2;
5669 break;
5670 case 'right':
5671 left -= width;
5672 break;
5673 }
5674 backdrop = {
5675 left,
5676 top,
5677 width: width + labelPadding.width,
5678 height: height + labelPadding.height,
5679 color: optsAtIndex.backdropColor,
5680 };
5681 }
5682 items.push({
5683 rotation,
5684 label,
5685 font,
5686 color,
5687 strokeColor,
5688 strokeWidth,
5689 textOffset,
5690 textAlign,
5691 textBaseline,
5692 translation: [x, y],
5693 backdrop,
5694 });
5695 }
5696 return items;
5697 }
5698 _getXAxisLabelAlignment() {
5699 const {position, ticks} = this.options;
5700 const rotation = -toRadians(this.labelRotation);
5701 if (rotation) {
5702 return position === 'top' ? 'left' : 'right';
5703 }
5704 let align = 'center';
5705 if (ticks.align === 'start') {
5706 align = 'left';
5707 } else if (ticks.align === 'end') {
5708 align = 'right';
5709 }
5710 return align;
5711 }
5712 _getYAxisLabelAlignment(tl) {
5713 const {position, ticks: {crossAlign, mirror, padding}} = this.options;
5714 const labelSizes = this._getLabelSizes();
5715 const tickAndPadding = tl + padding;
5716 const widest = labelSizes.widest.width;
5717 let textAlign;
5718 let x;
5719 if (position === 'left') {
5720 if (mirror) {
5721 x = this.right + padding;
5722 if (crossAlign === 'near') {
5723 textAlign = 'left';
5724 } else if (crossAlign === 'center') {
5725 textAlign = 'center';
5726 x += (widest / 2);
5727 } else {
5728 textAlign = 'right';
5729 x += widest;
5730 }
5731 } else {
5732 x = this.right - tickAndPadding;
5733 if (crossAlign === 'near') {
5734 textAlign = 'right';
5735 } else if (crossAlign === 'center') {
5736 textAlign = 'center';
5737 x -= (widest / 2);
5738 } else {
5739 textAlign = 'left';
5740 x = this.left;
5741 }
5742 }
5743 } else if (position === 'right') {
5744 if (mirror) {
5745 x = this.left + padding;
5746 if (crossAlign === 'near') {
5747 textAlign = 'right';
5748 } else if (crossAlign === 'center') {
5749 textAlign = 'center';
5750 x -= (widest / 2);
5751 } else {
5752 textAlign = 'left';
5753 x -= widest;
5754 }
5755 } else {
5756 x = this.left + tickAndPadding;
5757 if (crossAlign === 'near') {
5758 textAlign = 'left';
5759 } else if (crossAlign === 'center') {
5760 textAlign = 'center';
5761 x += widest / 2;
5762 } else {
5763 textAlign = 'right';
5764 x = this.right;
5765 }
5766 }
5767 } else {
5768 textAlign = 'right';
5769 }
5770 return {textAlign, x};
5771 }
5772 _computeLabelArea() {
5773 if (this.options.ticks.mirror) {
5774 return;
5775 }
5776 const chart = this.chart;
5777 const position = this.options.position;
5778 if (position === 'left' || position === 'right') {
5779 return {top: 0, left: this.left, bottom: chart.height, right: this.right};
5780 } if (position === 'top' || position === 'bottom') {
5781 return {top: this.top, left: 0, bottom: this.bottom, right: chart.width};
5782 }
5783 }
5784 drawBackground() {
5785 const {ctx, options: {backgroundColor}, left, top, width, height} = this;
5786 if (backgroundColor) {
5787 ctx.save();
5788 ctx.fillStyle = backgroundColor;
5789 ctx.fillRect(left, top, width, height);
5790 ctx.restore();
5791 }
5792 }
5793 getLineWidthForValue(value) {
5794 const grid = this.options.grid;
5795 if (!this._isVisible() || !grid.display) {
5796 return 0;
5797 }
5798 const ticks = this.ticks;
5799 const index = ticks.findIndex(t => t.value === value);
5800 if (index >= 0) {
5801 const opts = grid.setContext(this.getContext(index));
5802 return opts.lineWidth;
5803 }
5804 return 0;
5805 }
5806 drawGrid(chartArea) {
5807 const grid = this.options.grid;
5808 const ctx = this.ctx;
5809 const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea));
5810 let i, ilen;
5811 const drawLine = (p1, p2, style) => {
5812 if (!style.width || !style.color) {
5813 return;
5814 }
5815 ctx.save();
5816 ctx.lineWidth = style.width;
5817 ctx.strokeStyle = style.color;
5818 ctx.setLineDash(style.borderDash || []);
5819 ctx.lineDashOffset = style.borderDashOffset;
5820 ctx.beginPath();
5821 ctx.moveTo(p1.x, p1.y);
5822 ctx.lineTo(p2.x, p2.y);
5823 ctx.stroke();
5824 ctx.restore();
5825 };
5826 if (grid.display) {
5827 for (i = 0, ilen = items.length; i < ilen; ++i) {
5828 const item = items[i];
5829 if (grid.drawOnChartArea) {
5830 drawLine(
5831 {x: item.x1, y: item.y1},
5832 {x: item.x2, y: item.y2},
5833 item
5834 );
5835 }
5836 if (grid.drawTicks) {
5837 drawLine(
5838 {x: item.tx1, y: item.ty1},
5839 {x: item.tx2, y: item.ty2},
5840 {
5841 color: item.tickColor,
5842 width: item.tickWidth,
5843 borderDash: item.tickBorderDash,
5844 borderDashOffset: item.tickBorderDashOffset
5845 }
5846 );
5847 }
5848 }
5849 }
5850 }
5851 drawBorder() {
5852 const {chart, ctx, options: {grid}} = this;
5853 const borderOpts = grid.setContext(this.getContext());
5854 const axisWidth = grid.drawBorder ? borderOpts.borderWidth : 0;
5855 if (!axisWidth) {
5856 return;
5857 }
5858 const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth;
5859 const borderValue = this._borderValue;
5860 let x1, x2, y1, y2;
5861 if (this.isHorizontal()) {
5862 x1 = _alignPixel(chart, this.left, axisWidth) - axisWidth / 2;
5863 x2 = _alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2;
5864 y1 = y2 = borderValue;
5865 } else {
5866 y1 = _alignPixel(chart, this.top, axisWidth) - axisWidth / 2;
5867 y2 = _alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2;
5868 x1 = x2 = borderValue;
5869 }
5870 ctx.save();
5871 ctx.lineWidth = borderOpts.borderWidth;
5872 ctx.strokeStyle = borderOpts.borderColor;
5873 ctx.beginPath();
5874 ctx.moveTo(x1, y1);
5875 ctx.lineTo(x2, y2);
5876 ctx.stroke();
5877 ctx.restore();
5878 }
5879 drawLabels(chartArea) {
5880 const optionTicks = this.options.ticks;
5881 if (!optionTicks.display) {
5882 return;
5883 }
5884 const ctx = this.ctx;
5885 const area = this._computeLabelArea();
5886 if (area) {
5887 clipArea(ctx, area);
5888 }
5889 const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea));
5890 let i, ilen;
5891 for (i = 0, ilen = items.length; i < ilen; ++i) {
5892 const item = items[i];
5893 const tickFont = item.font;
5894 const label = item.label;
5895 if (item.backdrop) {
5896 ctx.fillStyle = item.backdrop.color;
5897 ctx.fillRect(item.backdrop.left, item.backdrop.top, item.backdrop.width, item.backdrop.height);
5898 }
5899 let y = item.textOffset;
5900 renderText(ctx, label, 0, y, tickFont, item);
5901 }
5902 if (area) {
5903 unclipArea(ctx);
5904 }
5905 }
5906 drawTitle() {
5907 const {ctx, options: {position, title, reverse}} = this;
5908 if (!title.display) {
5909 return;
5910 }
5911 const font = toFont(title.font);
5912 const padding = toPadding(title.padding);
5913 const align = title.align;
5914 let offset = font.lineHeight / 2;
5915 if (position === 'bottom' || position === 'center' || isObject(position)) {
5916 offset += padding.bottom;
5917 if (isArray(title.text)) {
5918 offset += font.lineHeight * (title.text.length - 1);
5919 }
5920 } else {
5921 offset += padding.top;
5922 }
5923 const {titleX, titleY, maxWidth, rotation} = titleArgs(this, offset, position, align);
5924 renderText(ctx, title.text, 0, 0, font, {
5925 color: title.color,
5926 maxWidth,
5927 rotation,
5928 textAlign: titleAlign(align, position, reverse),
5929 textBaseline: 'middle',
5930 translation: [titleX, titleY],
5931 });
5932 }
5933 draw(chartArea) {
5934 if (!this._isVisible()) {
5935 return;
5936 }
5937 this.drawBackground();
5938 this.drawGrid(chartArea);
5939 this.drawBorder();
5940 this.drawTitle();
5941 this.drawLabels(chartArea);
5942 }
5943 _layers() {
5944 const opts = this.options;
5945 const tz = opts.ticks && opts.ticks.z || 0;
5946 const gz = valueOrDefault(opts.grid && opts.grid.z, -1);
5947 if (!this._isVisible() || this.draw !== Scale.prototype.draw) {
5948 return [{
5949 z: tz,
5950 draw: (chartArea) => {
5951 this.draw(chartArea);
5952 }
5953 }];
5954 }
5955 return [{
5956 z: gz,
5957 draw: (chartArea) => {
5958 this.drawBackground();
5959 this.drawGrid(chartArea);
5960 this.drawTitle();
5961 }
5962 }, {
5963 z: gz + 1,
5964 draw: () => {
5965 this.drawBorder();
5966 }
5967 }, {
5968 z: tz,
5969 draw: (chartArea) => {
5970 this.drawLabels(chartArea);
5971 }
5972 }];
5973 }
5974 getMatchingVisibleMetas(type) {
5975 const metas = this.chart.getSortedVisibleDatasetMetas();
5976 const axisID = this.axis + 'AxisID';
5977 const result = [];
5978 let i, ilen;
5979 for (i = 0, ilen = metas.length; i < ilen; ++i) {
5980 const meta = metas[i];
5981 if (meta[axisID] === this.id && (!type || meta.type === type)) {
5982 result.push(meta);
5983 }
5984 }
5985 return result;
5986 }
5987 _resolveTickFontOptions(index) {
5988 const opts = this.options.ticks.setContext(this.getContext(index));
5989 return toFont(opts.font);
5990 }
5991 _maxDigits() {
5992 const fontSize = this._resolveTickFontOptions(0).lineHeight;
5993 return (this.isHorizontal() ? this.width : this.height) / fontSize;
5994 }
5995}
5996
5997class TypedRegistry {
5998 constructor(type, scope, override) {
5999 this.type = type;
6000 this.scope = scope;
6001 this.override = override;
6002 this.items = Object.create(null);
6003 }
6004 isForType(type) {
6005 return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);
6006 }
6007 register(item) {
6008 const proto = Object.getPrototypeOf(item);
6009 let parentScope;
6010 if (isIChartComponent(proto)) {
6011 parentScope = this.register(proto);
6012 }
6013 const items = this.items;
6014 const id = item.id;
6015 const scope = this.scope + '.' + id;
6016 if (!id) {
6017 throw new Error('class does not have id: ' + item);
6018 }
6019 if (id in items) {
6020 return scope;
6021 }
6022 items[id] = item;
6023 registerDefaults(item, scope, parentScope);
6024 if (this.override) {
6025 defaults.override(item.id, item.overrides);
6026 }
6027 return scope;
6028 }
6029 get(id) {
6030 return this.items[id];
6031 }
6032 unregister(item) {
6033 const items = this.items;
6034 const id = item.id;
6035 const scope = this.scope;
6036 if (id in items) {
6037 delete items[id];
6038 }
6039 if (scope && id in defaults[scope]) {
6040 delete defaults[scope][id];
6041 if (this.override) {
6042 delete overrides[id];
6043 }
6044 }
6045 }
6046}
6047function registerDefaults(item, scope, parentScope) {
6048 const itemDefaults = merge(Object.create(null), [
6049 parentScope ? defaults.get(parentScope) : {},
6050 defaults.get(scope),
6051 item.defaults
6052 ]);
6053 defaults.set(scope, itemDefaults);
6054 if (item.defaultRoutes) {
6055 routeDefaults(scope, item.defaultRoutes);
6056 }
6057 if (item.descriptors) {
6058 defaults.describe(scope, item.descriptors);
6059 }
6060}
6061function routeDefaults(scope, routes) {
6062 Object.keys(routes).forEach(property => {
6063 const propertyParts = property.split('.');
6064 const sourceName = propertyParts.pop();
6065 const sourceScope = [scope].concat(propertyParts).join('.');
6066 const parts = routes[property].split('.');
6067 const targetName = parts.pop();
6068 const targetScope = parts.join('.');
6069 defaults.route(sourceScope, sourceName, targetScope, targetName);
6070 });
6071}
6072function isIChartComponent(proto) {
6073 return 'id' in proto && 'defaults' in proto;
6074}
6075
6076class Registry {
6077 constructor() {
6078 this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
6079 this.elements = new TypedRegistry(Element, 'elements');
6080 this.plugins = new TypedRegistry(Object, 'plugins');
6081 this.scales = new TypedRegistry(Scale, 'scales');
6082 this._typedRegistries = [this.controllers, this.scales, this.elements];
6083 }
6084 add(...args) {
6085 this._each('register', args);
6086 }
6087 remove(...args) {
6088 this._each('unregister', args);
6089 }
6090 addControllers(...args) {
6091 this._each('register', args, this.controllers);
6092 }
6093 addElements(...args) {
6094 this._each('register', args, this.elements);
6095 }
6096 addPlugins(...args) {
6097 this._each('register', args, this.plugins);
6098 }
6099 addScales(...args) {
6100 this._each('register', args, this.scales);
6101 }
6102 getController(id) {
6103 return this._get(id, this.controllers, 'controller');
6104 }
6105 getElement(id) {
6106 return this._get(id, this.elements, 'element');
6107 }
6108 getPlugin(id) {
6109 return this._get(id, this.plugins, 'plugin');
6110 }
6111 getScale(id) {
6112 return this._get(id, this.scales, 'scale');
6113 }
6114 removeControllers(...args) {
6115 this._each('unregister', args, this.controllers);
6116 }
6117 removeElements(...args) {
6118 this._each('unregister', args, this.elements);
6119 }
6120 removePlugins(...args) {
6121 this._each('unregister', args, this.plugins);
6122 }
6123 removeScales(...args) {
6124 this._each('unregister', args, this.scales);
6125 }
6126 _each(method, args, typedRegistry) {
6127 [...args].forEach(arg => {
6128 const reg = typedRegistry || this._getRegistryForType(arg);
6129 if (typedRegistry || reg.isForType(arg) || (reg === this.plugins && arg.id)) {
6130 this._exec(method, reg, arg);
6131 } else {
6132 each(arg, item => {
6133 const itemReg = typedRegistry || this._getRegistryForType(item);
6134 this._exec(method, itemReg, item);
6135 });
6136 }
6137 });
6138 }
6139 _exec(method, registry, component) {
6140 const camelMethod = _capitalize(method);
6141 callback(component['before' + camelMethod], [], component);
6142 registry[method](component);
6143 callback(component['after' + camelMethod], [], component);
6144 }
6145 _getRegistryForType(type) {
6146 for (let i = 0; i < this._typedRegistries.length; i++) {
6147 const reg = this._typedRegistries[i];
6148 if (reg.isForType(type)) {
6149 return reg;
6150 }
6151 }
6152 return this.plugins;
6153 }
6154 _get(id, typedRegistry, type) {
6155 const item = typedRegistry.get(id);
6156 if (item === undefined) {
6157 throw new Error('"' + id + '" is not a registered ' + type + '.');
6158 }
6159 return item;
6160 }
6161}
6162var registry = new Registry();
6163
6164class PluginService {
6165 constructor() {
6166 this._init = [];
6167 }
6168 notify(chart, hook, args, filter) {
6169 if (hook === 'beforeInit') {
6170 this._init = this._createDescriptors(chart, true);
6171 this._notify(this._init, chart, 'install');
6172 }
6173 const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart);
6174 const result = this._notify(descriptors, chart, hook, args);
6175 if (hook === 'afterDestroy') {
6176 this._notify(descriptors, chart, 'stop');
6177 this._notify(this._init, chart, 'uninstall');
6178 }
6179 return result;
6180 }
6181 _notify(descriptors, chart, hook, args) {
6182 args = args || {};
6183 for (const descriptor of descriptors) {
6184 const plugin = descriptor.plugin;
6185 const method = plugin[hook];
6186 const params = [chart, args, descriptor.options];
6187 if (callback(method, params, plugin) === false && args.cancelable) {
6188 return false;
6189 }
6190 }
6191 return true;
6192 }
6193 invalidate() {
6194 if (!isNullOrUndef(this._cache)) {
6195 this._oldCache = this._cache;
6196 this._cache = undefined;
6197 }
6198 }
6199 _descriptors(chart) {
6200 if (this._cache) {
6201 return this._cache;
6202 }
6203 const descriptors = this._cache = this._createDescriptors(chart);
6204 this._notifyStateChanges(chart);
6205 return descriptors;
6206 }
6207 _createDescriptors(chart, all) {
6208 const config = chart && chart.config;
6209 const options = valueOrDefault(config.options && config.options.plugins, {});
6210 const plugins = allPlugins(config);
6211 return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);
6212 }
6213 _notifyStateChanges(chart) {
6214 const previousDescriptors = this._oldCache || [];
6215 const descriptors = this._cache;
6216 const diff = (a, b) => a.filter(x => !b.some(y => x.plugin.id === y.plugin.id));
6217 this._notify(diff(previousDescriptors, descriptors), chart, 'stop');
6218 this._notify(diff(descriptors, previousDescriptors), chart, 'start');
6219 }
6220}
6221function allPlugins(config) {
6222 const plugins = [];
6223 const keys = Object.keys(registry.plugins.items);
6224 for (let i = 0; i < keys.length; i++) {
6225 plugins.push(registry.getPlugin(keys[i]));
6226 }
6227 const local = config.plugins || [];
6228 for (let i = 0; i < local.length; i++) {
6229 const plugin = local[i];
6230 if (plugins.indexOf(plugin) === -1) {
6231 plugins.push(plugin);
6232 }
6233 }
6234 return plugins;
6235}
6236function getOpts(options, all) {
6237 if (!all && options === false) {
6238 return null;
6239 }
6240 if (options === true) {
6241 return {};
6242 }
6243 return options;
6244}
6245function createDescriptors(chart, plugins, options, all) {
6246 const result = [];
6247 const context = chart.getContext();
6248 for (let i = 0; i < plugins.length; i++) {
6249 const plugin = plugins[i];
6250 const id = plugin.id;
6251 const opts = getOpts(options[id], all);
6252 if (opts === null) {
6253 continue;
6254 }
6255 result.push({
6256 plugin,
6257 options: pluginOpts(chart.config, plugin, opts, context)
6258 });
6259 }
6260 return result;
6261}
6262function pluginOpts(config, plugin, opts, context) {
6263 const keys = config.pluginScopeKeys(plugin);
6264 const scopes = config.getOptionScopes(opts, keys);
6265 return config.createResolver(scopes, context, [''], {scriptable: false, indexable: false, allKeys: true});
6266}
6267
6268function getIndexAxis(type, options) {
6269 const datasetDefaults = defaults.datasets[type] || {};
6270 const datasetOptions = (options.datasets || {})[type] || {};
6271 return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
6272}
6273function getAxisFromDefaultScaleID(id, indexAxis) {
6274 let axis = id;
6275 if (id === '_index_') {
6276 axis = indexAxis;
6277 } else if (id === '_value_') {
6278 axis = indexAxis === 'x' ? 'y' : 'x';
6279 }
6280 return axis;
6281}
6282function getDefaultScaleIDFromAxis(axis, indexAxis) {
6283 return axis === indexAxis ? '_index_' : '_value_';
6284}
6285function axisFromPosition(position) {
6286 if (position === 'top' || position === 'bottom') {
6287 return 'x';
6288 }
6289 if (position === 'left' || position === 'right') {
6290 return 'y';
6291 }
6292}
6293function determineAxis(id, scaleOptions) {
6294 if (id === 'x' || id === 'y') {
6295 return id;
6296 }
6297 return scaleOptions.axis || axisFromPosition(scaleOptions.position) || id.charAt(0).toLowerCase();
6298}
6299function mergeScaleConfig(config, options) {
6300 const chartDefaults = overrides[config.type] || {scales: {}};
6301 const configScales = options.scales || {};
6302 const chartIndexAxis = getIndexAxis(config.type, options);
6303 const firstIDs = Object.create(null);
6304 const scales = Object.create(null);
6305 Object.keys(configScales).forEach(id => {
6306 const scaleConf = configScales[id];
6307 if (!isObject(scaleConf)) {
6308 return console.error(`Invalid scale configuration for scale: ${id}`);
6309 }
6310 if (scaleConf._proxy) {
6311 return console.warn(`Ignoring resolver passed as options for scale: ${id}`);
6312 }
6313 const axis = determineAxis(id, scaleConf);
6314 const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
6315 const defaultScaleOptions = chartDefaults.scales || {};
6316 firstIDs[axis] = firstIDs[axis] || id;
6317 scales[id] = mergeIf(Object.create(null), [{axis}, scaleConf, defaultScaleOptions[axis], defaultScaleOptions[defaultId]]);
6318 });
6319 config.data.datasets.forEach(dataset => {
6320 const type = dataset.type || config.type;
6321 const indexAxis = dataset.indexAxis || getIndexAxis(type, options);
6322 const datasetDefaults = overrides[type] || {};
6323 const defaultScaleOptions = datasetDefaults.scales || {};
6324 Object.keys(defaultScaleOptions).forEach(defaultID => {
6325 const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
6326 const id = dataset[axis + 'AxisID'] || firstIDs[axis] || axis;
6327 scales[id] = scales[id] || Object.create(null);
6328 mergeIf(scales[id], [{axis}, configScales[id], defaultScaleOptions[defaultID]]);
6329 });
6330 });
6331 Object.keys(scales).forEach(key => {
6332 const scale = scales[key];
6333 mergeIf(scale, [defaults.scales[scale.type], defaults.scale]);
6334 });
6335 return scales;
6336}
6337function initOptions(config) {
6338 const options = config.options || (config.options = {});
6339 options.plugins = valueOrDefault(options.plugins, {});
6340 options.scales = mergeScaleConfig(config, options);
6341}
6342function initData(data) {
6343 data = data || {};
6344 data.datasets = data.datasets || [];
6345 data.labels = data.labels || [];
6346 return data;
6347}
6348function initConfig(config) {
6349 config = config || {};
6350 config.data = initData(config.data);
6351 initOptions(config);
6352 return config;
6353}
6354const keyCache = new Map();
6355const keysCached = new Set();
6356function cachedKeys(cacheKey, generate) {
6357 let keys = keyCache.get(cacheKey);
6358 if (!keys) {
6359 keys = generate();
6360 keyCache.set(cacheKey, keys);
6361 keysCached.add(keys);
6362 }
6363 return keys;
6364}
6365const addIfFound = (set, obj, key) => {
6366 const opts = resolveObjectKey(obj, key);
6367 if (opts !== undefined) {
6368 set.add(opts);
6369 }
6370};
6371class Config {
6372 constructor(config) {
6373 this._config = initConfig(config);
6374 this._scopeCache = new Map();
6375 this._resolverCache = new Map();
6376 }
6377 get platform() {
6378 return this._config.platform;
6379 }
6380 get type() {
6381 return this._config.type;
6382 }
6383 set type(type) {
6384 this._config.type = type;
6385 }
6386 get data() {
6387 return this._config.data;
6388 }
6389 set data(data) {
6390 this._config.data = initData(data);
6391 }
6392 get options() {
6393 return this._config.options;
6394 }
6395 set options(options) {
6396 this._config.options = options;
6397 }
6398 get plugins() {
6399 return this._config.plugins;
6400 }
6401 update() {
6402 const config = this._config;
6403 this.clearCache();
6404 initOptions(config);
6405 }
6406 clearCache() {
6407 this._scopeCache.clear();
6408 this._resolverCache.clear();
6409 }
6410 datasetScopeKeys(datasetType) {
6411 return cachedKeys(datasetType,
6412 () => [[
6413 `datasets.${datasetType}`,
6414 ''
6415 ]]);
6416 }
6417 datasetAnimationScopeKeys(datasetType, transition) {
6418 return cachedKeys(`${datasetType}.transition.${transition}`,
6419 () => [
6420 [
6421 `datasets.${datasetType}.transitions.${transition}`,
6422 `transitions.${transition}`,
6423 ],
6424 [
6425 `datasets.${datasetType}`,
6426 ''
6427 ]
6428 ]);
6429 }
6430 datasetElementScopeKeys(datasetType, elementType) {
6431 return cachedKeys(`${datasetType}-${elementType}`,
6432 () => [[
6433 `datasets.${datasetType}.elements.${elementType}`,
6434 `datasets.${datasetType}`,
6435 `elements.${elementType}`,
6436 ''
6437 ]]);
6438 }
6439 pluginScopeKeys(plugin) {
6440 const id = plugin.id;
6441 const type = this.type;
6442 return cachedKeys(`${type}-plugin-${id}`,
6443 () => [[
6444 `plugins.${id}`,
6445 ...plugin.additionalOptionScopes || [],
6446 ]]);
6447 }
6448 _cachedScopes(mainScope, resetCache) {
6449 const _scopeCache = this._scopeCache;
6450 let cache = _scopeCache.get(mainScope);
6451 if (!cache || resetCache) {
6452 cache = new Map();
6453 _scopeCache.set(mainScope, cache);
6454 }
6455 return cache;
6456 }
6457 getOptionScopes(mainScope, keyLists, resetCache) {
6458 const {options, type} = this;
6459 const cache = this._cachedScopes(mainScope, resetCache);
6460 const cached = cache.get(keyLists);
6461 if (cached) {
6462 return cached;
6463 }
6464 const scopes = new Set();
6465 keyLists.forEach(keys => {
6466 if (mainScope) {
6467 scopes.add(mainScope);
6468 keys.forEach(key => addIfFound(scopes, mainScope, key));
6469 }
6470 keys.forEach(key => addIfFound(scopes, options, key));
6471 keys.forEach(key => addIfFound(scopes, overrides[type] || {}, key));
6472 keys.forEach(key => addIfFound(scopes, defaults, key));
6473 keys.forEach(key => addIfFound(scopes, descriptors, key));
6474 });
6475 const array = Array.from(scopes);
6476 if (array.length === 0) {
6477 array.push(Object.create(null));
6478 }
6479 if (keysCached.has(keyLists)) {
6480 cache.set(keyLists, array);
6481 }
6482 return array;
6483 }
6484 chartOptionScopes() {
6485 const {options, type} = this;
6486 return [
6487 options,
6488 overrides[type] || {},
6489 defaults.datasets[type] || {},
6490 {type},
6491 defaults,
6492 descriptors
6493 ];
6494 }
6495 resolveNamedOptions(scopes, names, context, prefixes = ['']) {
6496 const result = {$shared: true};
6497 const {resolver, subPrefixes} = getResolver(this._resolverCache, scopes, prefixes);
6498 let options = resolver;
6499 if (needContext(resolver, names)) {
6500 result.$shared = false;
6501 context = isFunction(context) ? context() : context;
6502 const subResolver = this.createResolver(scopes, context, subPrefixes);
6503 options = _attachContext(resolver, context, subResolver);
6504 }
6505 for (const prop of names) {
6506 result[prop] = options[prop];
6507 }
6508 return result;
6509 }
6510 createResolver(scopes, context, prefixes = [''], descriptorDefaults) {
6511 const {resolver} = getResolver(this._resolverCache, scopes, prefixes);
6512 return isObject(context)
6513 ? _attachContext(resolver, context, undefined, descriptorDefaults)
6514 : resolver;
6515 }
6516}
6517function getResolver(resolverCache, scopes, prefixes) {
6518 let cache = resolverCache.get(scopes);
6519 if (!cache) {
6520 cache = new Map();
6521 resolverCache.set(scopes, cache);
6522 }
6523 const cacheKey = prefixes.join();
6524 let cached = cache.get(cacheKey);
6525 if (!cached) {
6526 const resolver = _createResolver(scopes, prefixes);
6527 cached = {
6528 resolver,
6529 subPrefixes: prefixes.filter(p => !p.toLowerCase().includes('hover'))
6530 };
6531 cache.set(cacheKey, cached);
6532 }
6533 return cached;
6534}
6535const hasFunction = value => isObject(value)
6536 && Object.getOwnPropertyNames(value).reduce((acc, key) => acc || isFunction(value[key]), false);
6537function needContext(proxy, names) {
6538 const {isScriptable, isIndexable} = _descriptors(proxy);
6539 for (const prop of names) {
6540 const scriptable = isScriptable(prop);
6541 const indexable = isIndexable(prop);
6542 const value = (indexable || scriptable) && proxy[prop];
6543 if ((scriptable && (isFunction(value) || hasFunction(value)))
6544 || (indexable && isArray(value))) {
6545 return true;
6546 }
6547 }
6548 return false;
6549}
6550
6551var version = "3.7.1";
6552
6553const KNOWN_POSITIONS = ['top', 'bottom', 'left', 'right', 'chartArea'];
6554function positionIsHorizontal(position, axis) {
6555 return position === 'top' || position === 'bottom' || (KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x');
6556}
6557function compare2Level(l1, l2) {
6558 return function(a, b) {
6559 return a[l1] === b[l1]
6560 ? a[l2] - b[l2]
6561 : a[l1] - b[l1];
6562 };
6563}
6564function onAnimationsComplete(context) {
6565 const chart = context.chart;
6566 const animationOptions = chart.options.animation;
6567 chart.notifyPlugins('afterRender');
6568 callback(animationOptions && animationOptions.onComplete, [context], chart);
6569}
6570function onAnimationProgress(context) {
6571 const chart = context.chart;
6572 const animationOptions = chart.options.animation;
6573 callback(animationOptions && animationOptions.onProgress, [context], chart);
6574}
6575function getCanvas(item) {
6576 if (_isDomSupported() && typeof item === 'string') {
6577 item = document.getElementById(item);
6578 } else if (item && item.length) {
6579 item = item[0];
6580 }
6581 if (item && item.canvas) {
6582 item = item.canvas;
6583 }
6584 return item;
6585}
6586const instances = {};
6587const getChart = (key) => {
6588 const canvas = getCanvas(key);
6589 return Object.values(instances).filter((c) => c.canvas === canvas).pop();
6590};
6591function moveNumericKeys(obj, start, move) {
6592 const keys = Object.keys(obj);
6593 for (const key of keys) {
6594 const intKey = +key;
6595 if (intKey >= start) {
6596 const value = obj[key];
6597 delete obj[key];
6598 if (move > 0 || intKey > start) {
6599 obj[intKey + move] = value;
6600 }
6601 }
6602 }
6603}
6604function determineLastEvent(e, lastEvent, inChartArea, isClick) {
6605 if (!inChartArea || e.type === 'mouseout') {
6606 return null;
6607 }
6608 if (isClick) {
6609 return lastEvent;
6610 }
6611 return e;
6612}
6613class Chart {
6614 constructor(item, userConfig) {
6615 const config = this.config = new Config(userConfig);
6616 const initialCanvas = getCanvas(item);
6617 const existingChart = getChart(initialCanvas);
6618 if (existingChart) {
6619 throw new Error(
6620 'Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' +
6621 ' must be destroyed before the canvas can be reused.'
6622 );
6623 }
6624 const options = config.createResolver(config.chartOptionScopes(), this.getContext());
6625 this.platform = new (config.platform || _detectPlatform(initialCanvas))();
6626 this.platform.updateConfig(config);
6627 const context = this.platform.acquireContext(initialCanvas, options.aspectRatio);
6628 const canvas = context && context.canvas;
6629 const height = canvas && canvas.height;
6630 const width = canvas && canvas.width;
6631 this.id = uid();
6632 this.ctx = context;
6633 this.canvas = canvas;
6634 this.width = width;
6635 this.height = height;
6636 this._options = options;
6637 this._aspectRatio = this.aspectRatio;
6638 this._layers = [];
6639 this._metasets = [];
6640 this._stacks = undefined;
6641 this.boxes = [];
6642 this.currentDevicePixelRatio = undefined;
6643 this.chartArea = undefined;
6644 this._active = [];
6645 this._lastEvent = undefined;
6646 this._listeners = {};
6647 this._responsiveListeners = undefined;
6648 this._sortedMetasets = [];
6649 this.scales = {};
6650 this._plugins = new PluginService();
6651 this.$proxies = {};
6652 this._hiddenIndices = {};
6653 this.attached = false;
6654 this._animationsDisabled = undefined;
6655 this.$context = undefined;
6656 this._doResize = debounce(mode => this.update(mode), options.resizeDelay || 0);
6657 this._dataChanges = [];
6658 instances[this.id] = this;
6659 if (!context || !canvas) {
6660 console.error("Failed to create chart: can't acquire context from the given item");
6661 return;
6662 }
6663 animator.listen(this, 'complete', onAnimationsComplete);
6664 animator.listen(this, 'progress', onAnimationProgress);
6665 this._initialize();
6666 if (this.attached) {
6667 this.update();
6668 }
6669 }
6670 get aspectRatio() {
6671 const {options: {aspectRatio, maintainAspectRatio}, width, height, _aspectRatio} = this;
6672 if (!isNullOrUndef(aspectRatio)) {
6673 return aspectRatio;
6674 }
6675 if (maintainAspectRatio && _aspectRatio) {
6676 return _aspectRatio;
6677 }
6678 return height ? width / height : null;
6679 }
6680 get data() {
6681 return this.config.data;
6682 }
6683 set data(data) {
6684 this.config.data = data;
6685 }
6686 get options() {
6687 return this._options;
6688 }
6689 set options(options) {
6690 this.config.options = options;
6691 }
6692 _initialize() {
6693 this.notifyPlugins('beforeInit');
6694 if (this.options.responsive) {
6695 this.resize();
6696 } else {
6697 retinaScale(this, this.options.devicePixelRatio);
6698 }
6699 this.bindEvents();
6700 this.notifyPlugins('afterInit');
6701 return this;
6702 }
6703 clear() {
6704 clearCanvas(this.canvas, this.ctx);
6705 return this;
6706 }
6707 stop() {
6708 animator.stop(this);
6709 return this;
6710 }
6711 resize(width, height) {
6712 if (!animator.running(this)) {
6713 this._resize(width, height);
6714 } else {
6715 this._resizeBeforeDraw = {width, height};
6716 }
6717 }
6718 _resize(width, height) {
6719 const options = this.options;
6720 const canvas = this.canvas;
6721 const aspectRatio = options.maintainAspectRatio && this.aspectRatio;
6722 const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio);
6723 const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio();
6724 const mode = this.width ? 'resize' : 'attach';
6725 this.width = newSize.width;
6726 this.height = newSize.height;
6727 this._aspectRatio = this.aspectRatio;
6728 if (!retinaScale(this, newRatio, true)) {
6729 return;
6730 }
6731 this.notifyPlugins('resize', {size: newSize});
6732 callback(options.onResize, [this, newSize], this);
6733 if (this.attached) {
6734 if (this._doResize(mode)) {
6735 this.render();
6736 }
6737 }
6738 }
6739 ensureScalesHaveIDs() {
6740 const options = this.options;
6741 const scalesOptions = options.scales || {};
6742 each(scalesOptions, (axisOptions, axisID) => {
6743 axisOptions.id = axisID;
6744 });
6745 }
6746 buildOrUpdateScales() {
6747 const options = this.options;
6748 const scaleOpts = options.scales;
6749 const scales = this.scales;
6750 const updated = Object.keys(scales).reduce((obj, id) => {
6751 obj[id] = false;
6752 return obj;
6753 }, {});
6754 let items = [];
6755 if (scaleOpts) {
6756 items = items.concat(
6757 Object.keys(scaleOpts).map((id) => {
6758 const scaleOptions = scaleOpts[id];
6759 const axis = determineAxis(id, scaleOptions);
6760 const isRadial = axis === 'r';
6761 const isHorizontal = axis === 'x';
6762 return {
6763 options: scaleOptions,
6764 dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',
6765 dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'
6766 };
6767 })
6768 );
6769 }
6770 each(items, (item) => {
6771 const scaleOptions = item.options;
6772 const id = scaleOptions.id;
6773 const axis = determineAxis(id, scaleOptions);
6774 const scaleType = valueOrDefault(scaleOptions.type, item.dtype);
6775 if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {
6776 scaleOptions.position = item.dposition;
6777 }
6778 updated[id] = true;
6779 let scale = null;
6780 if (id in scales && scales[id].type === scaleType) {
6781 scale = scales[id];
6782 } else {
6783 const scaleClass = registry.getScale(scaleType);
6784 scale = new scaleClass({
6785 id,
6786 type: scaleType,
6787 ctx: this.ctx,
6788 chart: this
6789 });
6790 scales[scale.id] = scale;
6791 }
6792 scale.init(scaleOptions, options);
6793 });
6794 each(updated, (hasUpdated, id) => {
6795 if (!hasUpdated) {
6796 delete scales[id];
6797 }
6798 });
6799 each(scales, (scale) => {
6800 layouts.configure(this, scale, scale.options);
6801 layouts.addBox(this, scale);
6802 });
6803 }
6804 _updateMetasets() {
6805 const metasets = this._metasets;
6806 const numData = this.data.datasets.length;
6807 const numMeta = metasets.length;
6808 metasets.sort((a, b) => a.index - b.index);
6809 if (numMeta > numData) {
6810 for (let i = numData; i < numMeta; ++i) {
6811 this._destroyDatasetMeta(i);
6812 }
6813 metasets.splice(numData, numMeta - numData);
6814 }
6815 this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
6816 }
6817 _removeUnreferencedMetasets() {
6818 const {_metasets: metasets, data: {datasets}} = this;
6819 if (metasets.length > datasets.length) {
6820 delete this._stacks;
6821 }
6822 metasets.forEach((meta, index) => {
6823 if (datasets.filter(x => x === meta._dataset).length === 0) {
6824 this._destroyDatasetMeta(index);
6825 }
6826 });
6827 }
6828 buildOrUpdateControllers() {
6829 const newControllers = [];
6830 const datasets = this.data.datasets;
6831 let i, ilen;
6832 this._removeUnreferencedMetasets();
6833 for (i = 0, ilen = datasets.length; i < ilen; i++) {
6834 const dataset = datasets[i];
6835 let meta = this.getDatasetMeta(i);
6836 const type = dataset.type || this.config.type;
6837 if (meta.type && meta.type !== type) {
6838 this._destroyDatasetMeta(i);
6839 meta = this.getDatasetMeta(i);
6840 }
6841 meta.type = type;
6842 meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options);
6843 meta.order = dataset.order || 0;
6844 meta.index = i;
6845 meta.label = '' + dataset.label;
6846 meta.visible = this.isDatasetVisible(i);
6847 if (meta.controller) {
6848 meta.controller.updateIndex(i);
6849 meta.controller.linkScales();
6850 } else {
6851 const ControllerClass = registry.getController(type);
6852 const {datasetElementType, dataElementType} = defaults.datasets[type];
6853 Object.assign(ControllerClass.prototype, {
6854 dataElementType: registry.getElement(dataElementType),
6855 datasetElementType: datasetElementType && registry.getElement(datasetElementType)
6856 });
6857 meta.controller = new ControllerClass(this, i);
6858 newControllers.push(meta.controller);
6859 }
6860 }
6861 this._updateMetasets();
6862 return newControllers;
6863 }
6864 _resetElements() {
6865 each(this.data.datasets, (dataset, datasetIndex) => {
6866 this.getDatasetMeta(datasetIndex).controller.reset();
6867 }, this);
6868 }
6869 reset() {
6870 this._resetElements();
6871 this.notifyPlugins('reset');
6872 }
6873 update(mode) {
6874 const config = this.config;
6875 config.update();
6876 const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext());
6877 const animsDisabled = this._animationsDisabled = !options.animation;
6878 this._updateScales();
6879 this._checkEventBindings();
6880 this._updateHiddenIndices();
6881 this._plugins.invalidate();
6882 if (this.notifyPlugins('beforeUpdate', {mode, cancelable: true}) === false) {
6883 return;
6884 }
6885 const newControllers = this.buildOrUpdateControllers();
6886 this.notifyPlugins('beforeElementsUpdate');
6887 let minPadding = 0;
6888 for (let i = 0, ilen = this.data.datasets.length; i < ilen; i++) {
6889 const {controller} = this.getDatasetMeta(i);
6890 const reset = !animsDisabled && newControllers.indexOf(controller) === -1;
6891 controller.buildOrUpdateElements(reset);
6892 minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
6893 }
6894 minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0;
6895 this._updateLayout(minPadding);
6896 if (!animsDisabled) {
6897 each(newControllers, (controller) => {
6898 controller.reset();
6899 });
6900 }
6901 this._updateDatasets(mode);
6902 this.notifyPlugins('afterUpdate', {mode});
6903 this._layers.sort(compare2Level('z', '_idx'));
6904 const {_active, _lastEvent} = this;
6905 if (_lastEvent) {
6906 this._eventHandler(_lastEvent, true);
6907 } else if (_active.length) {
6908 this._updateHoverStyles(_active, _active, true);
6909 }
6910 this.render();
6911 }
6912 _updateScales() {
6913 each(this.scales, (scale) => {
6914 layouts.removeBox(this, scale);
6915 });
6916 this.ensureScalesHaveIDs();
6917 this.buildOrUpdateScales();
6918 }
6919 _checkEventBindings() {
6920 const options = this.options;
6921 const existingEvents = new Set(Object.keys(this._listeners));
6922 const newEvents = new Set(options.events);
6923 if (!setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) {
6924 this.unbindEvents();
6925 this.bindEvents();
6926 }
6927 }
6928 _updateHiddenIndices() {
6929 const {_hiddenIndices} = this;
6930 const changes = this._getUniformDataChanges() || [];
6931 for (const {method, start, count} of changes) {
6932 const move = method === '_removeElements' ? -count : count;
6933 moveNumericKeys(_hiddenIndices, start, move);
6934 }
6935 }
6936 _getUniformDataChanges() {
6937 const _dataChanges = this._dataChanges;
6938 if (!_dataChanges || !_dataChanges.length) {
6939 return;
6940 }
6941 this._dataChanges = [];
6942 const datasetCount = this.data.datasets.length;
6943 const makeSet = (idx) => new Set(
6944 _dataChanges
6945 .filter(c => c[0] === idx)
6946 .map((c, i) => i + ',' + c.splice(1).join(','))
6947 );
6948 const changeSet = makeSet(0);
6949 for (let i = 1; i < datasetCount; i++) {
6950 if (!setsEqual(changeSet, makeSet(i))) {
6951 return;
6952 }
6953 }
6954 return Array.from(changeSet)
6955 .map(c => c.split(','))
6956 .map(a => ({method: a[1], start: +a[2], count: +a[3]}));
6957 }
6958 _updateLayout(minPadding) {
6959 if (this.notifyPlugins('beforeLayout', {cancelable: true}) === false) {
6960 return;
6961 }
6962 layouts.update(this, this.width, this.height, minPadding);
6963 const area = this.chartArea;
6964 const noArea = area.width <= 0 || area.height <= 0;
6965 this._layers = [];
6966 each(this.boxes, (box) => {
6967 if (noArea && box.position === 'chartArea') {
6968 return;
6969 }
6970 if (box.configure) {
6971 box.configure();
6972 }
6973 this._layers.push(...box._layers());
6974 }, this);
6975 this._layers.forEach((item, index) => {
6976 item._idx = index;
6977 });
6978 this.notifyPlugins('afterLayout');
6979 }
6980 _updateDatasets(mode) {
6981 if (this.notifyPlugins('beforeDatasetsUpdate', {mode, cancelable: true}) === false) {
6982 return;
6983 }
6984 for (let i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
6985 this.getDatasetMeta(i).controller.configure();
6986 }
6987 for (let i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
6988 this._updateDataset(i, isFunction(mode) ? mode({datasetIndex: i}) : mode);
6989 }
6990 this.notifyPlugins('afterDatasetsUpdate', {mode});
6991 }
6992 _updateDataset(index, mode) {
6993 const meta = this.getDatasetMeta(index);
6994 const args = {meta, index, mode, cancelable: true};
6995 if (this.notifyPlugins('beforeDatasetUpdate', args) === false) {
6996 return;
6997 }
6998 meta.controller._update(mode);
6999 args.cancelable = false;
7000 this.notifyPlugins('afterDatasetUpdate', args);
7001 }
7002 render() {
7003 if (this.notifyPlugins('beforeRender', {cancelable: true}) === false) {
7004 return;
7005 }
7006 if (animator.has(this)) {
7007 if (this.attached && !animator.running(this)) {
7008 animator.start(this);
7009 }
7010 } else {
7011 this.draw();
7012 onAnimationsComplete({chart: this});
7013 }
7014 }
7015 draw() {
7016 let i;
7017 if (this._resizeBeforeDraw) {
7018 const {width, height} = this._resizeBeforeDraw;
7019 this._resize(width, height);
7020 this._resizeBeforeDraw = null;
7021 }
7022 this.clear();
7023 if (this.width <= 0 || this.height <= 0) {
7024 return;
7025 }
7026 if (this.notifyPlugins('beforeDraw', {cancelable: true}) === false) {
7027 return;
7028 }
7029 const layers = this._layers;
7030 for (i = 0; i < layers.length && layers[i].z <= 0; ++i) {
7031 layers[i].draw(this.chartArea);
7032 }
7033 this._drawDatasets();
7034 for (; i < layers.length; ++i) {
7035 layers[i].draw(this.chartArea);
7036 }
7037 this.notifyPlugins('afterDraw');
7038 }
7039 _getSortedDatasetMetas(filterVisible) {
7040 const metasets = this._sortedMetasets;
7041 const result = [];
7042 let i, ilen;
7043 for (i = 0, ilen = metasets.length; i < ilen; ++i) {
7044 const meta = metasets[i];
7045 if (!filterVisible || meta.visible) {
7046 result.push(meta);
7047 }
7048 }
7049 return result;
7050 }
7051 getSortedVisibleDatasetMetas() {
7052 return this._getSortedDatasetMetas(true);
7053 }
7054 _drawDatasets() {
7055 if (this.notifyPlugins('beforeDatasetsDraw', {cancelable: true}) === false) {
7056 return;
7057 }
7058 const metasets = this.getSortedVisibleDatasetMetas();
7059 for (let i = metasets.length - 1; i >= 0; --i) {
7060 this._drawDataset(metasets[i]);
7061 }
7062 this.notifyPlugins('afterDatasetsDraw');
7063 }
7064 _drawDataset(meta) {
7065 const ctx = this.ctx;
7066 const clip = meta._clip;
7067 const useClip = !clip.disabled;
7068 const area = this.chartArea;
7069 const args = {
7070 meta,
7071 index: meta.index,
7072 cancelable: true
7073 };
7074 if (this.notifyPlugins('beforeDatasetDraw', args) === false) {
7075 return;
7076 }
7077 if (useClip) {
7078 clipArea(ctx, {
7079 left: clip.left === false ? 0 : area.left - clip.left,
7080 right: clip.right === false ? this.width : area.right + clip.right,
7081 top: clip.top === false ? 0 : area.top - clip.top,
7082 bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom
7083 });
7084 }
7085 meta.controller.draw();
7086 if (useClip) {
7087 unclipArea(ctx);
7088 }
7089 args.cancelable = false;
7090 this.notifyPlugins('afterDatasetDraw', args);
7091 }
7092 getElementsAtEventForMode(e, mode, options, useFinalPosition) {
7093 const method = Interaction.modes[mode];
7094 if (typeof method === 'function') {
7095 return method(this, e, options, useFinalPosition);
7096 }
7097 return [];
7098 }
7099 getDatasetMeta(datasetIndex) {
7100 const dataset = this.data.datasets[datasetIndex];
7101 const metasets = this._metasets;
7102 let meta = metasets.filter(x => x && x._dataset === dataset).pop();
7103 if (!meta) {
7104 meta = {
7105 type: null,
7106 data: [],
7107 dataset: null,
7108 controller: null,
7109 hidden: null,
7110 xAxisID: null,
7111 yAxisID: null,
7112 order: dataset && dataset.order || 0,
7113 index: datasetIndex,
7114 _dataset: dataset,
7115 _parsed: [],
7116 _sorted: false
7117 };
7118 metasets.push(meta);
7119 }
7120 return meta;
7121 }
7122 getContext() {
7123 return this.$context || (this.$context = createContext(null, {chart: this, type: 'chart'}));
7124 }
7125 getVisibleDatasetCount() {
7126 return this.getSortedVisibleDatasetMetas().length;
7127 }
7128 isDatasetVisible(datasetIndex) {
7129 const dataset = this.data.datasets[datasetIndex];
7130 if (!dataset) {
7131 return false;
7132 }
7133 const meta = this.getDatasetMeta(datasetIndex);
7134 return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;
7135 }
7136 setDatasetVisibility(datasetIndex, visible) {
7137 const meta = this.getDatasetMeta(datasetIndex);
7138 meta.hidden = !visible;
7139 }
7140 toggleDataVisibility(index) {
7141 this._hiddenIndices[index] = !this._hiddenIndices[index];
7142 }
7143 getDataVisibility(index) {
7144 return !this._hiddenIndices[index];
7145 }
7146 _updateVisibility(datasetIndex, dataIndex, visible) {
7147 const mode = visible ? 'show' : 'hide';
7148 const meta = this.getDatasetMeta(datasetIndex);
7149 const anims = meta.controller._resolveAnimations(undefined, mode);
7150 if (defined(dataIndex)) {
7151 meta.data[dataIndex].hidden = !visible;
7152 this.update();
7153 } else {
7154 this.setDatasetVisibility(datasetIndex, visible);
7155 anims.update(meta, {visible});
7156 this.update((ctx) => ctx.datasetIndex === datasetIndex ? mode : undefined);
7157 }
7158 }
7159 hide(datasetIndex, dataIndex) {
7160 this._updateVisibility(datasetIndex, dataIndex, false);
7161 }
7162 show(datasetIndex, dataIndex) {
7163 this._updateVisibility(datasetIndex, dataIndex, true);
7164 }
7165 _destroyDatasetMeta(datasetIndex) {
7166 const meta = this._metasets[datasetIndex];
7167 if (meta && meta.controller) {
7168 meta.controller._destroy();
7169 }
7170 delete this._metasets[datasetIndex];
7171 }
7172 _stop() {
7173 let i, ilen;
7174 this.stop();
7175 animator.remove(this);
7176 for (i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
7177 this._destroyDatasetMeta(i);
7178 }
7179 }
7180 destroy() {
7181 this.notifyPlugins('beforeDestroy');
7182 const {canvas, ctx} = this;
7183 this._stop();
7184 this.config.clearCache();
7185 if (canvas) {
7186 this.unbindEvents();
7187 clearCanvas(canvas, ctx);
7188 this.platform.releaseContext(ctx);
7189 this.canvas = null;
7190 this.ctx = null;
7191 }
7192 this.notifyPlugins('destroy');
7193 delete instances[this.id];
7194 this.notifyPlugins('afterDestroy');
7195 }
7196 toBase64Image(...args) {
7197 return this.canvas.toDataURL(...args);
7198 }
7199 bindEvents() {
7200 this.bindUserEvents();
7201 if (this.options.responsive) {
7202 this.bindResponsiveEvents();
7203 } else {
7204 this.attached = true;
7205 }
7206 }
7207 bindUserEvents() {
7208 const listeners = this._listeners;
7209 const platform = this.platform;
7210 const _add = (type, listener) => {
7211 platform.addEventListener(this, type, listener);
7212 listeners[type] = listener;
7213 };
7214 const listener = (e, x, y) => {
7215 e.offsetX = x;
7216 e.offsetY = y;
7217 this._eventHandler(e);
7218 };
7219 each(this.options.events, (type) => _add(type, listener));
7220 }
7221 bindResponsiveEvents() {
7222 if (!this._responsiveListeners) {
7223 this._responsiveListeners = {};
7224 }
7225 const listeners = this._responsiveListeners;
7226 const platform = this.platform;
7227 const _add = (type, listener) => {
7228 platform.addEventListener(this, type, listener);
7229 listeners[type] = listener;
7230 };
7231 const _remove = (type, listener) => {
7232 if (listeners[type]) {
7233 platform.removeEventListener(this, type, listener);
7234 delete listeners[type];
7235 }
7236 };
7237 const listener = (width, height) => {
7238 if (this.canvas) {
7239 this.resize(width, height);
7240 }
7241 };
7242 let detached;
7243 const attached = () => {
7244 _remove('attach', attached);
7245 this.attached = true;
7246 this.resize();
7247 _add('resize', listener);
7248 _add('detach', detached);
7249 };
7250 detached = () => {
7251 this.attached = false;
7252 _remove('resize', listener);
7253 this._stop();
7254 this._resize(0, 0);
7255 _add('attach', attached);
7256 };
7257 if (platform.isAttached(this.canvas)) {
7258 attached();
7259 } else {
7260 detached();
7261 }
7262 }
7263 unbindEvents() {
7264 each(this._listeners, (listener, type) => {
7265 this.platform.removeEventListener(this, type, listener);
7266 });
7267 this._listeners = {};
7268 each(this._responsiveListeners, (listener, type) => {
7269 this.platform.removeEventListener(this, type, listener);
7270 });
7271 this._responsiveListeners = undefined;
7272 }
7273 updateHoverStyle(items, mode, enabled) {
7274 const prefix = enabled ? 'set' : 'remove';
7275 let meta, item, i, ilen;
7276 if (mode === 'dataset') {
7277 meta = this.getDatasetMeta(items[0].datasetIndex);
7278 meta.controller['_' + prefix + 'DatasetHoverStyle']();
7279 }
7280 for (i = 0, ilen = items.length; i < ilen; ++i) {
7281 item = items[i];
7282 const controller = item && this.getDatasetMeta(item.datasetIndex).controller;
7283 if (controller) {
7284 controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);
7285 }
7286 }
7287 }
7288 getActiveElements() {
7289 return this._active || [];
7290 }
7291 setActiveElements(activeElements) {
7292 const lastActive = this._active || [];
7293 const active = activeElements.map(({datasetIndex, index}) => {
7294 const meta = this.getDatasetMeta(datasetIndex);
7295 if (!meta) {
7296 throw new Error('No dataset found at index ' + datasetIndex);
7297 }
7298 return {
7299 datasetIndex,
7300 element: meta.data[index],
7301 index,
7302 };
7303 });
7304 const changed = !_elementsEqual(active, lastActive);
7305 if (changed) {
7306 this._active = active;
7307 this._lastEvent = null;
7308 this._updateHoverStyles(active, lastActive);
7309 }
7310 }
7311 notifyPlugins(hook, args, filter) {
7312 return this._plugins.notify(this, hook, args, filter);
7313 }
7314 _updateHoverStyles(active, lastActive, replay) {
7315 const hoverOptions = this.options.hover;
7316 const diff = (a, b) => a.filter(x => !b.some(y => x.datasetIndex === y.datasetIndex && x.index === y.index));
7317 const deactivated = diff(lastActive, active);
7318 const activated = replay ? active : diff(active, lastActive);
7319 if (deactivated.length) {
7320 this.updateHoverStyle(deactivated, hoverOptions.mode, false);
7321 }
7322 if (activated.length && hoverOptions.mode) {
7323 this.updateHoverStyle(activated, hoverOptions.mode, true);
7324 }
7325 }
7326 _eventHandler(e, replay) {
7327 const args = {
7328 event: e,
7329 replay,
7330 cancelable: true,
7331 inChartArea: _isPointInArea(e, this.chartArea, this._minPadding)
7332 };
7333 const eventFilter = (plugin) => (plugin.options.events || this.options.events).includes(e.native.type);
7334 if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) {
7335 return;
7336 }
7337 const changed = this._handleEvent(e, replay, args.inChartArea);
7338 args.cancelable = false;
7339 this.notifyPlugins('afterEvent', args, eventFilter);
7340 if (changed || args.changed) {
7341 this.render();
7342 }
7343 return this;
7344 }
7345 _handleEvent(e, replay, inChartArea) {
7346 const {_active: lastActive = [], options} = this;
7347 const useFinalPosition = replay;
7348 const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition);
7349 const isClick = _isClickEvent(e);
7350 const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick);
7351 if (inChartArea) {
7352 this._lastEvent = null;
7353 callback(options.onHover, [e, active, this], this);
7354 if (isClick) {
7355 callback(options.onClick, [e, active, this], this);
7356 }
7357 }
7358 const changed = !_elementsEqual(active, lastActive);
7359 if (changed || replay) {
7360 this._active = active;
7361 this._updateHoverStyles(active, lastActive, replay);
7362 }
7363 this._lastEvent = lastEvent;
7364 return changed;
7365 }
7366 _getActiveElements(e, lastActive, inChartArea, useFinalPosition) {
7367 if (e.type === 'mouseout') {
7368 return [];
7369 }
7370 if (!inChartArea) {
7371 return lastActive;
7372 }
7373 const hoverOptions = this.options.hover;
7374 return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
7375 }
7376}
7377const invalidatePlugins = () => each(Chart.instances, (chart) => chart._plugins.invalidate());
7378const enumerable = true;
7379Object.defineProperties(Chart, {
7380 defaults: {
7381 enumerable,
7382 value: defaults
7383 },
7384 instances: {
7385 enumerable,
7386 value: instances
7387 },
7388 overrides: {
7389 enumerable,
7390 value: overrides
7391 },
7392 registry: {
7393 enumerable,
7394 value: registry
7395 },
7396 version: {
7397 enumerable,
7398 value: version
7399 },
7400 getChart: {
7401 enumerable,
7402 value: getChart
7403 },
7404 register: {
7405 enumerable,
7406 value: (...items) => {
7407 registry.add(...items);
7408 invalidatePlugins();
7409 }
7410 },
7411 unregister: {
7412 enumerable,
7413 value: (...items) => {
7414 registry.remove(...items);
7415 invalidatePlugins();
7416 }
7417 }
7418});
7419
7420function abstract() {
7421 throw new Error('This method is not implemented: Check that a complete date adapter is provided.');
7422}
7423class DateAdapter {
7424 constructor(options) {
7425 this.options = options || {};
7426 }
7427 formats() {
7428 return abstract();
7429 }
7430 parse(value, format) {
7431 return abstract();
7432 }
7433 format(timestamp, format) {
7434 return abstract();
7435 }
7436 add(timestamp, amount, unit) {
7437 return abstract();
7438 }
7439 diff(a, b, unit) {
7440 return abstract();
7441 }
7442 startOf(timestamp, unit, weekday) {
7443 return abstract();
7444 }
7445 endOf(timestamp, unit) {
7446 return abstract();
7447 }
7448}
7449DateAdapter.override = function(members) {
7450 Object.assign(DateAdapter.prototype, members);
7451};
7452var _adapters = {
7453 _date: DateAdapter
7454};
7455
7456function getAllScaleValues(scale, type) {
7457 if (!scale._cache.$bar) {
7458 const visibleMetas = scale.getMatchingVisibleMetas(type);
7459 let values = [];
7460 for (let i = 0, ilen = visibleMetas.length; i < ilen; i++) {
7461 values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale));
7462 }
7463 scale._cache.$bar = _arrayUnique(values.sort((a, b) => a - b));
7464 }
7465 return scale._cache.$bar;
7466}
7467function computeMinSampleSize(meta) {
7468 const scale = meta.iScale;
7469 const values = getAllScaleValues(scale, meta.type);
7470 let min = scale._length;
7471 let i, ilen, curr, prev;
7472 const updateMinAndPrev = () => {
7473 if (curr === 32767 || curr === -32768) {
7474 return;
7475 }
7476 if (defined(prev)) {
7477 min = Math.min(min, Math.abs(curr - prev) || min);
7478 }
7479 prev = curr;
7480 };
7481 for (i = 0, ilen = values.length; i < ilen; ++i) {
7482 curr = scale.getPixelForValue(values[i]);
7483 updateMinAndPrev();
7484 }
7485 prev = undefined;
7486 for (i = 0, ilen = scale.ticks.length; i < ilen; ++i) {
7487 curr = scale.getPixelForTick(i);
7488 updateMinAndPrev();
7489 }
7490 return min;
7491}
7492function computeFitCategoryTraits(index, ruler, options, stackCount) {
7493 const thickness = options.barThickness;
7494 let size, ratio;
7495 if (isNullOrUndef(thickness)) {
7496 size = ruler.min * options.categoryPercentage;
7497 ratio = options.barPercentage;
7498 } else {
7499 size = thickness * stackCount;
7500 ratio = 1;
7501 }
7502 return {
7503 chunk: size / stackCount,
7504 ratio,
7505 start: ruler.pixels[index] - (size / 2)
7506 };
7507}
7508function computeFlexCategoryTraits(index, ruler, options, stackCount) {
7509 const pixels = ruler.pixels;
7510 const curr = pixels[index];
7511 let prev = index > 0 ? pixels[index - 1] : null;
7512 let next = index < pixels.length - 1 ? pixels[index + 1] : null;
7513 const percent = options.categoryPercentage;
7514 if (prev === null) {
7515 prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
7516 }
7517 if (next === null) {
7518 next = curr + curr - prev;
7519 }
7520 const start = curr - (curr - Math.min(prev, next)) / 2 * percent;
7521 const size = Math.abs(next - prev) / 2 * percent;
7522 return {
7523 chunk: size / stackCount,
7524 ratio: options.barPercentage,
7525 start
7526 };
7527}
7528function parseFloatBar(entry, item, vScale, i) {
7529 const startValue = vScale.parse(entry[0], i);
7530 const endValue = vScale.parse(entry[1], i);
7531 const min = Math.min(startValue, endValue);
7532 const max = Math.max(startValue, endValue);
7533 let barStart = min;
7534 let barEnd = max;
7535 if (Math.abs(min) > Math.abs(max)) {
7536 barStart = max;
7537 barEnd = min;
7538 }
7539 item[vScale.axis] = barEnd;
7540 item._custom = {
7541 barStart,
7542 barEnd,
7543 start: startValue,
7544 end: endValue,
7545 min,
7546 max
7547 };
7548}
7549function parseValue(entry, item, vScale, i) {
7550 if (isArray(entry)) {
7551 parseFloatBar(entry, item, vScale, i);
7552 } else {
7553 item[vScale.axis] = vScale.parse(entry, i);
7554 }
7555 return item;
7556}
7557function parseArrayOrPrimitive(meta, data, start, count) {
7558 const iScale = meta.iScale;
7559 const vScale = meta.vScale;
7560 const labels = iScale.getLabels();
7561 const singleScale = iScale === vScale;
7562 const parsed = [];
7563 let i, ilen, item, entry;
7564 for (i = start, ilen = start + count; i < ilen; ++i) {
7565 entry = data[i];
7566 item = {};
7567 item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
7568 parsed.push(parseValue(entry, item, vScale, i));
7569 }
7570 return parsed;
7571}
7572function isFloatBar(custom) {
7573 return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
7574}
7575function barSign(size, vScale, actualBase) {
7576 if (size !== 0) {
7577 return sign(size);
7578 }
7579 return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1);
7580}
7581function borderProps(properties) {
7582 let reverse, start, end, top, bottom;
7583 if (properties.horizontal) {
7584 reverse = properties.base > properties.x;
7585 start = 'left';
7586 end = 'right';
7587 } else {
7588 reverse = properties.base < properties.y;
7589 start = 'bottom';
7590 end = 'top';
7591 }
7592 if (reverse) {
7593 top = 'end';
7594 bottom = 'start';
7595 } else {
7596 top = 'start';
7597 bottom = 'end';
7598 }
7599 return {start, end, reverse, top, bottom};
7600}
7601function setBorderSkipped(properties, options, stack, index) {
7602 let edge = options.borderSkipped;
7603 const res = {};
7604 if (!edge) {
7605 properties.borderSkipped = res;
7606 return;
7607 }
7608 const {start, end, reverse, top, bottom} = borderProps(properties);
7609 if (edge === 'middle' && stack) {
7610 properties.enableBorderRadius = true;
7611 if ((stack._top || 0) === index) {
7612 edge = top;
7613 } else if ((stack._bottom || 0) === index) {
7614 edge = bottom;
7615 } else {
7616 res[parseEdge(bottom, start, end, reverse)] = true;
7617 edge = top;
7618 }
7619 }
7620 res[parseEdge(edge, start, end, reverse)] = true;
7621 properties.borderSkipped = res;
7622}
7623function parseEdge(edge, a, b, reverse) {
7624 if (reverse) {
7625 edge = swap(edge, a, b);
7626 edge = startEnd(edge, b, a);
7627 } else {
7628 edge = startEnd(edge, a, b);
7629 }
7630 return edge;
7631}
7632function swap(orig, v1, v2) {
7633 return orig === v1 ? v2 : orig === v2 ? v1 : orig;
7634}
7635function startEnd(v, start, end) {
7636 return v === 'start' ? start : v === 'end' ? end : v;
7637}
7638function setInflateAmount(properties, {inflateAmount}, ratio) {
7639 properties.inflateAmount = inflateAmount === 'auto'
7640 ? ratio === 1 ? 0.33 : 0
7641 : inflateAmount;
7642}
7643class BarController extends DatasetController {
7644 parsePrimitiveData(meta, data, start, count) {
7645 return parseArrayOrPrimitive(meta, data, start, count);
7646 }
7647 parseArrayData(meta, data, start, count) {
7648 return parseArrayOrPrimitive(meta, data, start, count);
7649 }
7650 parseObjectData(meta, data, start, count) {
7651 const {iScale, vScale} = meta;
7652 const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
7653 const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
7654 const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
7655 const parsed = [];
7656 let i, ilen, item, obj;
7657 for (i = start, ilen = start + count; i < ilen; ++i) {
7658 obj = data[i];
7659 item = {};
7660 item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i);
7661 parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i));
7662 }
7663 return parsed;
7664 }
7665 updateRangeFromParsed(range, scale, parsed, stack) {
7666 super.updateRangeFromParsed(range, scale, parsed, stack);
7667 const custom = parsed._custom;
7668 if (custom && scale === this._cachedMeta.vScale) {
7669 range.min = Math.min(range.min, custom.min);
7670 range.max = Math.max(range.max, custom.max);
7671 }
7672 }
7673 getMaxOverflow() {
7674 return 0;
7675 }
7676 getLabelAndValue(index) {
7677 const meta = this._cachedMeta;
7678 const {iScale, vScale} = meta;
7679 const parsed = this.getParsed(index);
7680 const custom = parsed._custom;
7681 const value = isFloatBar(custom)
7682 ? '[' + custom.start + ', ' + custom.end + ']'
7683 : '' + vScale.getLabelForValue(parsed[vScale.axis]);
7684 return {
7685 label: '' + iScale.getLabelForValue(parsed[iScale.axis]),
7686 value
7687 };
7688 }
7689 initialize() {
7690 this.enableOptionSharing = true;
7691 super.initialize();
7692 const meta = this._cachedMeta;
7693 meta.stack = this.getDataset().stack;
7694 }
7695 update(mode) {
7696 const meta = this._cachedMeta;
7697 this.updateElements(meta.data, 0, meta.data.length, mode);
7698 }
7699 updateElements(bars, start, count, mode) {
7700 const reset = mode === 'reset';
7701 const {index, _cachedMeta: {vScale}} = this;
7702 const base = vScale.getBasePixel();
7703 const horizontal = vScale.isHorizontal();
7704 const ruler = this._getRuler();
7705 const firstOpts = this.resolveDataElementOptions(start, mode);
7706 const sharedOptions = this.getSharedOptions(firstOpts);
7707 const includeOptions = this.includeOptions(mode, sharedOptions);
7708 this.updateSharedOptions(sharedOptions, mode, firstOpts);
7709 for (let i = start; i < start + count; i++) {
7710 const parsed = this.getParsed(i);
7711 const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {base, head: base} : this._calculateBarValuePixels(i);
7712 const ipixels = this._calculateBarIndexPixels(i, ruler);
7713 const stack = (parsed._stacks || {})[vScale.axis];
7714 const properties = {
7715 horizontal,
7716 base: vpixels.base,
7717 enableBorderRadius: !stack || isFloatBar(parsed._custom) || (index === stack._top || index === stack._bottom),
7718 x: horizontal ? vpixels.head : ipixels.center,
7719 y: horizontal ? ipixels.center : vpixels.head,
7720 height: horizontal ? ipixels.size : Math.abs(vpixels.size),
7721 width: horizontal ? Math.abs(vpixels.size) : ipixels.size
7722 };
7723 if (includeOptions) {
7724 properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode);
7725 }
7726 const options = properties.options || bars[i].options;
7727 setBorderSkipped(properties, options, stack, index);
7728 setInflateAmount(properties, options, ruler.ratio);
7729 this.updateElement(bars[i], i, properties, mode);
7730 }
7731 }
7732 _getStacks(last, dataIndex) {
7733 const meta = this._cachedMeta;
7734 const iScale = meta.iScale;
7735 const metasets = iScale.getMatchingVisibleMetas(this._type);
7736 const stacked = iScale.options.stacked;
7737 const ilen = metasets.length;
7738 const stacks = [];
7739 let i, item;
7740 for (i = 0; i < ilen; ++i) {
7741 item = metasets[i];
7742 if (!item.controller.options.grouped) {
7743 continue;
7744 }
7745 if (typeof dataIndex !== 'undefined') {
7746 const val = item.controller.getParsed(dataIndex)[
7747 item.controller._cachedMeta.vScale.axis
7748 ];
7749 if (isNullOrUndef(val) || isNaN(val)) {
7750 continue;
7751 }
7752 }
7753 if (stacked === false || stacks.indexOf(item.stack) === -1 ||
7754 (stacked === undefined && item.stack === undefined)) {
7755 stacks.push(item.stack);
7756 }
7757 if (item.index === last) {
7758 break;
7759 }
7760 }
7761 if (!stacks.length) {
7762 stacks.push(undefined);
7763 }
7764 return stacks;
7765 }
7766 _getStackCount(index) {
7767 return this._getStacks(undefined, index).length;
7768 }
7769 _getStackIndex(datasetIndex, name, dataIndex) {
7770 const stacks = this._getStacks(datasetIndex, dataIndex);
7771 const index = (name !== undefined)
7772 ? stacks.indexOf(name)
7773 : -1;
7774 return (index === -1)
7775 ? stacks.length - 1
7776 : index;
7777 }
7778 _getRuler() {
7779 const opts = this.options;
7780 const meta = this._cachedMeta;
7781 const iScale = meta.iScale;
7782 const pixels = [];
7783 let i, ilen;
7784 for (i = 0, ilen = meta.data.length; i < ilen; ++i) {
7785 pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i));
7786 }
7787 const barThickness = opts.barThickness;
7788 const min = barThickness || computeMinSampleSize(meta);
7789 return {
7790 min,
7791 pixels,
7792 start: iScale._startPixel,
7793 end: iScale._endPixel,
7794 stackCount: this._getStackCount(),
7795 scale: iScale,
7796 grouped: opts.grouped,
7797 ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage
7798 };
7799 }
7800 _calculateBarValuePixels(index) {
7801 const {_cachedMeta: {vScale, _stacked}, options: {base: baseValue, minBarLength}} = this;
7802 const actualBase = baseValue || 0;
7803 const parsed = this.getParsed(index);
7804 const custom = parsed._custom;
7805 const floating = isFloatBar(custom);
7806 let value = parsed[vScale.axis];
7807 let start = 0;
7808 let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value;
7809 let head, size;
7810 if (length !== value) {
7811 start = length - value;
7812 length = value;
7813 }
7814 if (floating) {
7815 value = custom.barStart;
7816 length = custom.barEnd - custom.barStart;
7817 if (value !== 0 && sign(value) !== sign(custom.barEnd)) {
7818 start = 0;
7819 }
7820 start += value;
7821 }
7822 const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start;
7823 let base = vScale.getPixelForValue(startValue);
7824 if (this.chart.getDataVisibility(index)) {
7825 head = vScale.getPixelForValue(start + length);
7826 } else {
7827 head = base;
7828 }
7829 size = head - base;
7830 if (Math.abs(size) < minBarLength) {
7831 size = barSign(size, vScale, actualBase) * minBarLength;
7832 if (value === actualBase) {
7833 base -= size / 2;
7834 }
7835 head = base + size;
7836 }
7837 if (base === vScale.getPixelForValue(actualBase)) {
7838 const halfGrid = sign(size) * vScale.getLineWidthForValue(actualBase) / 2;
7839 base += halfGrid;
7840 size -= halfGrid;
7841 }
7842 return {
7843 size,
7844 base,
7845 head,
7846 center: head + size / 2
7847 };
7848 }
7849 _calculateBarIndexPixels(index, ruler) {
7850 const scale = ruler.scale;
7851 const options = this.options;
7852 const skipNull = options.skipNull;
7853 const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity);
7854 let center, size;
7855 if (ruler.grouped) {
7856 const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount;
7857 const range = options.barThickness === 'flex'
7858 ? computeFlexCategoryTraits(index, ruler, options, stackCount)
7859 : computeFitCategoryTraits(index, ruler, options, stackCount);
7860 const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined);
7861 center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
7862 size = Math.min(maxBarThickness, range.chunk * range.ratio);
7863 } else {
7864 center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index);
7865 size = Math.min(maxBarThickness, ruler.min * ruler.ratio);
7866 }
7867 return {
7868 base: center - size / 2,
7869 head: center + size / 2,
7870 center,
7871 size
7872 };
7873 }
7874 draw() {
7875 const meta = this._cachedMeta;
7876 const vScale = meta.vScale;
7877 const rects = meta.data;
7878 const ilen = rects.length;
7879 let i = 0;
7880 for (; i < ilen; ++i) {
7881 if (this.getParsed(i)[vScale.axis] !== null) {
7882 rects[i].draw(this._ctx);
7883 }
7884 }
7885 }
7886}
7887BarController.id = 'bar';
7888BarController.defaults = {
7889 datasetElementType: false,
7890 dataElementType: 'bar',
7891 categoryPercentage: 0.8,
7892 barPercentage: 0.9,
7893 grouped: true,
7894 animations: {
7895 numbers: {
7896 type: 'number',
7897 properties: ['x', 'y', 'base', 'width', 'height']
7898 }
7899 }
7900};
7901BarController.overrides = {
7902 scales: {
7903 _index_: {
7904 type: 'category',
7905 offset: true,
7906 grid: {
7907 offset: true
7908 }
7909 },
7910 _value_: {
7911 type: 'linear',
7912 beginAtZero: true,
7913 }
7914 }
7915};
7916
7917class BubbleController extends DatasetController {
7918 initialize() {
7919 this.enableOptionSharing = true;
7920 super.initialize();
7921 }
7922 parsePrimitiveData(meta, data, start, count) {
7923 const parsed = super.parsePrimitiveData(meta, data, start, count);
7924 for (let i = 0; i < parsed.length; i++) {
7925 parsed[i]._custom = this.resolveDataElementOptions(i + start).radius;
7926 }
7927 return parsed;
7928 }
7929 parseArrayData(meta, data, start, count) {
7930 const parsed = super.parseArrayData(meta, data, start, count);
7931 for (let i = 0; i < parsed.length; i++) {
7932 const item = data[start + i];
7933 parsed[i]._custom = valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius);
7934 }
7935 return parsed;
7936 }
7937 parseObjectData(meta, data, start, count) {
7938 const parsed = super.parseObjectData(meta, data, start, count);
7939 for (let i = 0; i < parsed.length; i++) {
7940 const item = data[start + i];
7941 parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius);
7942 }
7943 return parsed;
7944 }
7945 getMaxOverflow() {
7946 const data = this._cachedMeta.data;
7947 let max = 0;
7948 for (let i = data.length - 1; i >= 0; --i) {
7949 max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
7950 }
7951 return max > 0 && max;
7952 }
7953 getLabelAndValue(index) {
7954 const meta = this._cachedMeta;
7955 const {xScale, yScale} = meta;
7956 const parsed = this.getParsed(index);
7957 const x = xScale.getLabelForValue(parsed.x);
7958 const y = yScale.getLabelForValue(parsed.y);
7959 const r = parsed._custom;
7960 return {
7961 label: meta.label,
7962 value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'
7963 };
7964 }
7965 update(mode) {
7966 const points = this._cachedMeta.data;
7967 this.updateElements(points, 0, points.length, mode);
7968 }
7969 updateElements(points, start, count, mode) {
7970 const reset = mode === 'reset';
7971 const {iScale, vScale} = this._cachedMeta;
7972 const firstOpts = this.resolveDataElementOptions(start, mode);
7973 const sharedOptions = this.getSharedOptions(firstOpts);
7974 const includeOptions = this.includeOptions(mode, sharedOptions);
7975 const iAxis = iScale.axis;
7976 const vAxis = vScale.axis;
7977 for (let i = start; i < start + count; i++) {
7978 const point = points[i];
7979 const parsed = !reset && this.getParsed(i);
7980 const properties = {};
7981 const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]);
7982 const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]);
7983 properties.skip = isNaN(iPixel) || isNaN(vPixel);
7984 if (includeOptions) {
7985 properties.options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
7986 if (reset) {
7987 properties.options.radius = 0;
7988 }
7989 }
7990 this.updateElement(point, i, properties, mode);
7991 }
7992 this.updateSharedOptions(sharedOptions, mode, firstOpts);
7993 }
7994 resolveDataElementOptions(index, mode) {
7995 const parsed = this.getParsed(index);
7996 let values = super.resolveDataElementOptions(index, mode);
7997 if (values.$shared) {
7998 values = Object.assign({}, values, {$shared: false});
7999 }
8000 const radius = values.radius;
8001 if (mode !== 'active') {
8002 values.radius = 0;
8003 }
8004 values.radius += valueOrDefault(parsed && parsed._custom, radius);
8005 return values;
8006 }
8007}
8008BubbleController.id = 'bubble';
8009BubbleController.defaults = {
8010 datasetElementType: false,
8011 dataElementType: 'point',
8012 animations: {
8013 numbers: {
8014 type: 'number',
8015 properties: ['x', 'y', 'borderWidth', 'radius']
8016 }
8017 }
8018};
8019BubbleController.overrides = {
8020 scales: {
8021 x: {
8022 type: 'linear'
8023 },
8024 y: {
8025 type: 'linear'
8026 }
8027 },
8028 plugins: {
8029 tooltip: {
8030 callbacks: {
8031 title() {
8032 return '';
8033 }
8034 }
8035 }
8036 }
8037};
8038
8039function getRatioAndOffset(rotation, circumference, cutout) {
8040 let ratioX = 1;
8041 let ratioY = 1;
8042 let offsetX = 0;
8043 let offsetY = 0;
8044 if (circumference < TAU) {
8045 const startAngle = rotation;
8046 const endAngle = startAngle + circumference;
8047 const startX = Math.cos(startAngle);
8048 const startY = Math.sin(startAngle);
8049 const endX = Math.cos(endAngle);
8050 const endY = Math.sin(endAngle);
8051 const calcMax = (angle, a, b) => _angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout);
8052 const calcMin = (angle, a, b) => _angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout);
8053 const maxX = calcMax(0, startX, endX);
8054 const maxY = calcMax(HALF_PI, startY, endY);
8055 const minX = calcMin(PI, startX, endX);
8056 const minY = calcMin(PI + HALF_PI, startY, endY);
8057 ratioX = (maxX - minX) / 2;
8058 ratioY = (maxY - minY) / 2;
8059 offsetX = -(maxX + minX) / 2;
8060 offsetY = -(maxY + minY) / 2;
8061 }
8062 return {ratioX, ratioY, offsetX, offsetY};
8063}
8064class DoughnutController extends DatasetController {
8065 constructor(chart, datasetIndex) {
8066 super(chart, datasetIndex);
8067 this.enableOptionSharing = true;
8068 this.innerRadius = undefined;
8069 this.outerRadius = undefined;
8070 this.offsetX = undefined;
8071 this.offsetY = undefined;
8072 }
8073 linkScales() {}
8074 parse(start, count) {
8075 const data = this.getDataset().data;
8076 const meta = this._cachedMeta;
8077 if (this._parsing === false) {
8078 meta._parsed = data;
8079 } else {
8080 let getter = (i) => +data[i];
8081 if (isObject(data[start])) {
8082 const {key = 'value'} = this._parsing;
8083 getter = (i) => +resolveObjectKey(data[i], key);
8084 }
8085 let i, ilen;
8086 for (i = start, ilen = start + count; i < ilen; ++i) {
8087 meta._parsed[i] = getter(i);
8088 }
8089 }
8090 }
8091 _getRotation() {
8092 return toRadians(this.options.rotation - 90);
8093 }
8094 _getCircumference() {
8095 return toRadians(this.options.circumference);
8096 }
8097 _getRotationExtents() {
8098 let min = TAU;
8099 let max = -TAU;
8100 for (let i = 0; i < this.chart.data.datasets.length; ++i) {
8101 if (this.chart.isDatasetVisible(i)) {
8102 const controller = this.chart.getDatasetMeta(i).controller;
8103 const rotation = controller._getRotation();
8104 const circumference = controller._getCircumference();
8105 min = Math.min(min, rotation);
8106 max = Math.max(max, rotation + circumference);
8107 }
8108 }
8109 return {
8110 rotation: min,
8111 circumference: max - min,
8112 };
8113 }
8114 update(mode) {
8115 const chart = this.chart;
8116 const {chartArea} = chart;
8117 const meta = this._cachedMeta;
8118 const arcs = meta.data;
8119 const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing;
8120 const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
8121 const cutout = Math.min(toPercentage(this.options.cutout, maxSize), 1);
8122 const chartWeight = this._getRingWeight(this.index);
8123 const {circumference, rotation} = this._getRotationExtents();
8124 const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(rotation, circumference, cutout);
8125 const maxWidth = (chartArea.width - spacing) / ratioX;
8126 const maxHeight = (chartArea.height - spacing) / ratioY;
8127 const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
8128 const outerRadius = toDimension(this.options.radius, maxRadius);
8129 const innerRadius = Math.max(outerRadius * cutout, 0);
8130 const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal();
8131 this.offsetX = offsetX * outerRadius;
8132 this.offsetY = offsetY * outerRadius;
8133 meta.total = this.calculateTotal();
8134 this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);
8135 this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);
8136 this.updateElements(arcs, 0, arcs.length, mode);
8137 }
8138 _circumference(i, reset) {
8139 const opts = this.options;
8140 const meta = this._cachedMeta;
8141 const circumference = this._getCircumference();
8142 if ((reset && opts.animation.animateRotate) || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) {
8143 return 0;
8144 }
8145 return this.calculateCircumference(meta._parsed[i] * circumference / TAU);
8146 }
8147 updateElements(arcs, start, count, mode) {
8148 const reset = mode === 'reset';
8149 const chart = this.chart;
8150 const chartArea = chart.chartArea;
8151 const opts = chart.options;
8152 const animationOpts = opts.animation;
8153 const centerX = (chartArea.left + chartArea.right) / 2;
8154 const centerY = (chartArea.top + chartArea.bottom) / 2;
8155 const animateScale = reset && animationOpts.animateScale;
8156 const innerRadius = animateScale ? 0 : this.innerRadius;
8157 const outerRadius = animateScale ? 0 : this.outerRadius;
8158 const firstOpts = this.resolveDataElementOptions(start, mode);
8159 const sharedOptions = this.getSharedOptions(firstOpts);
8160 const includeOptions = this.includeOptions(mode, sharedOptions);
8161 let startAngle = this._getRotation();
8162 let i;
8163 for (i = 0; i < start; ++i) {
8164 startAngle += this._circumference(i, reset);
8165 }
8166 for (i = start; i < start + count; ++i) {
8167 const circumference = this._circumference(i, reset);
8168 const arc = arcs[i];
8169 const properties = {
8170 x: centerX + this.offsetX,
8171 y: centerY + this.offsetY,
8172 startAngle,
8173 endAngle: startAngle + circumference,
8174 circumference,
8175 outerRadius,
8176 innerRadius
8177 };
8178 if (includeOptions) {
8179 properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode);
8180 }
8181 startAngle += circumference;
8182 this.updateElement(arc, i, properties, mode);
8183 }
8184 this.updateSharedOptions(sharedOptions, mode, firstOpts);
8185 }
8186 calculateTotal() {
8187 const meta = this._cachedMeta;
8188 const metaData = meta.data;
8189 let total = 0;
8190 let i;
8191 for (i = 0; i < metaData.length; i++) {
8192 const value = meta._parsed[i];
8193 if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) {
8194 total += Math.abs(value);
8195 }
8196 }
8197 return total;
8198 }
8199 calculateCircumference(value) {
8200 const total = this._cachedMeta.total;
8201 if (total > 0 && !isNaN(value)) {
8202 return TAU * (Math.abs(value) / total);
8203 }
8204 return 0;
8205 }
8206 getLabelAndValue(index) {
8207 const meta = this._cachedMeta;
8208 const chart = this.chart;
8209 const labels = chart.data.labels || [];
8210 const value = formatNumber(meta._parsed[index], chart.options.locale);
8211 return {
8212 label: labels[index] || '',
8213 value,
8214 };
8215 }
8216 getMaxBorderWidth(arcs) {
8217 let max = 0;
8218 const chart = this.chart;
8219 let i, ilen, meta, controller, options;
8220 if (!arcs) {
8221 for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
8222 if (chart.isDatasetVisible(i)) {
8223 meta = chart.getDatasetMeta(i);
8224 arcs = meta.data;
8225 controller = meta.controller;
8226 break;
8227 }
8228 }
8229 }
8230 if (!arcs) {
8231 return 0;
8232 }
8233 for (i = 0, ilen = arcs.length; i < ilen; ++i) {
8234 options = controller.resolveDataElementOptions(i);
8235 if (options.borderAlign !== 'inner') {
8236 max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
8237 }
8238 }
8239 return max;
8240 }
8241 getMaxOffset(arcs) {
8242 let max = 0;
8243 for (let i = 0, ilen = arcs.length; i < ilen; ++i) {
8244 const options = this.resolveDataElementOptions(i);
8245 max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
8246 }
8247 return max;
8248 }
8249 _getRingWeightOffset(datasetIndex) {
8250 let ringWeightOffset = 0;
8251 for (let i = 0; i < datasetIndex; ++i) {
8252 if (this.chart.isDatasetVisible(i)) {
8253 ringWeightOffset += this._getRingWeight(i);
8254 }
8255 }
8256 return ringWeightOffset;
8257 }
8258 _getRingWeight(datasetIndex) {
8259 return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
8260 }
8261 _getVisibleDatasetWeightTotal() {
8262 return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
8263 }
8264}
8265DoughnutController.id = 'doughnut';
8266DoughnutController.defaults = {
8267 datasetElementType: false,
8268 dataElementType: 'arc',
8269 animation: {
8270 animateRotate: true,
8271 animateScale: false
8272 },
8273 animations: {
8274 numbers: {
8275 type: 'number',
8276 properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth', 'spacing']
8277 },
8278 },
8279 cutout: '50%',
8280 rotation: 0,
8281 circumference: 360,
8282 radius: '100%',
8283 spacing: 0,
8284 indexAxis: 'r',
8285};
8286DoughnutController.descriptors = {
8287 _scriptable: (name) => name !== 'spacing',
8288 _indexable: (name) => name !== 'spacing',
8289};
8290DoughnutController.overrides = {
8291 aspectRatio: 1,
8292 plugins: {
8293 legend: {
8294 labels: {
8295 generateLabels(chart) {
8296 const data = chart.data;
8297 if (data.labels.length && data.datasets.length) {
8298 const {labels: {pointStyle}} = chart.legend.options;
8299 return data.labels.map((label, i) => {
8300 const meta = chart.getDatasetMeta(0);
8301 const style = meta.controller.getStyle(i);
8302 return {
8303 text: label,
8304 fillStyle: style.backgroundColor,
8305 strokeStyle: style.borderColor,
8306 lineWidth: style.borderWidth,
8307 pointStyle: pointStyle,
8308 hidden: !chart.getDataVisibility(i),
8309 index: i
8310 };
8311 });
8312 }
8313 return [];
8314 }
8315 },
8316 onClick(e, legendItem, legend) {
8317 legend.chart.toggleDataVisibility(legendItem.index);
8318 legend.chart.update();
8319 }
8320 },
8321 tooltip: {
8322 callbacks: {
8323 title() {
8324 return '';
8325 },
8326 label(tooltipItem) {
8327 let dataLabel = tooltipItem.label;
8328 const value = ': ' + tooltipItem.formattedValue;
8329 if (isArray(dataLabel)) {
8330 dataLabel = dataLabel.slice();
8331 dataLabel[0] += value;
8332 } else {
8333 dataLabel += value;
8334 }
8335 return dataLabel;
8336 }
8337 }
8338 }
8339 }
8340};
8341
8342class LineController extends DatasetController {
8343 initialize() {
8344 this.enableOptionSharing = true;
8345 super.initialize();
8346 }
8347 update(mode) {
8348 const meta = this._cachedMeta;
8349 const {dataset: line, data: points = [], _dataset} = meta;
8350 const animationsDisabled = this.chart._animationsDisabled;
8351 let {start, count} = getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
8352 this._drawStart = start;
8353 this._drawCount = count;
8354 if (scaleRangesChanged(meta)) {
8355 start = 0;
8356 count = points.length;
8357 }
8358 line._chart = this.chart;
8359 line._datasetIndex = this.index;
8360 line._decimated = !!_dataset._decimated;
8361 line.points = points;
8362 const options = this.resolveDatasetElementOptions(mode);
8363 if (!this.options.showLine) {
8364 options.borderWidth = 0;
8365 }
8366 options.segment = this.options.segment;
8367 this.updateElement(line, undefined, {
8368 animated: !animationsDisabled,
8369 options
8370 }, mode);
8371 this.updateElements(points, start, count, mode);
8372 }
8373 updateElements(points, start, count, mode) {
8374 const reset = mode === 'reset';
8375 const {iScale, vScale, _stacked, _dataset} = this._cachedMeta;
8376 const firstOpts = this.resolveDataElementOptions(start, mode);
8377 const sharedOptions = this.getSharedOptions(firstOpts);
8378 const includeOptions = this.includeOptions(mode, sharedOptions);
8379 const iAxis = iScale.axis;
8380 const vAxis = vScale.axis;
8381 const {spanGaps, segment} = this.options;
8382 const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
8383 const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
8384 let prevParsed = start > 0 && this.getParsed(start - 1);
8385 for (let i = start; i < start + count; ++i) {
8386 const point = points[i];
8387 const parsed = this.getParsed(i);
8388 const properties = directUpdate ? point : {};
8389 const nullData = isNullOrUndef(parsed[vAxis]);
8390 const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
8391 const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
8392 properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
8393 properties.stop = i > 0 && (parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
8394 if (segment) {
8395 properties.parsed = parsed;
8396 properties.raw = _dataset.data[i];
8397 }
8398 if (includeOptions) {
8399 properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
8400 }
8401 if (!directUpdate) {
8402 this.updateElement(point, i, properties, mode);
8403 }
8404 prevParsed = parsed;
8405 }
8406 this.updateSharedOptions(sharedOptions, mode, firstOpts);
8407 }
8408 getMaxOverflow() {
8409 const meta = this._cachedMeta;
8410 const dataset = meta.dataset;
8411 const border = dataset.options && dataset.options.borderWidth || 0;
8412 const data = meta.data || [];
8413 if (!data.length) {
8414 return border;
8415 }
8416 const firstPoint = data[0].size(this.resolveDataElementOptions(0));
8417 const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
8418 return Math.max(border, firstPoint, lastPoint) / 2;
8419 }
8420 draw() {
8421 const meta = this._cachedMeta;
8422 meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
8423 super.draw();
8424 }
8425}
8426LineController.id = 'line';
8427LineController.defaults = {
8428 datasetElementType: 'line',
8429 dataElementType: 'point',
8430 showLine: true,
8431 spanGaps: false,
8432};
8433LineController.overrides = {
8434 scales: {
8435 _index_: {
8436 type: 'category',
8437 },
8438 _value_: {
8439 type: 'linear',
8440 },
8441 }
8442};
8443function getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) {
8444 const pointCount = points.length;
8445 let start = 0;
8446 let count = pointCount;
8447 if (meta._sorted) {
8448 const {iScale, _parsed} = meta;
8449 const axis = iScale.axis;
8450 const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
8451 if (minDefined) {
8452 start = _limitValue(Math.min(
8453 _lookupByKey(_parsed, iScale.axis, min).lo,
8454 animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo),
8455 0, pointCount - 1);
8456 }
8457 if (maxDefined) {
8458 count = _limitValue(Math.max(
8459 _lookupByKey(_parsed, iScale.axis, max).hi + 1,
8460 animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max)).hi + 1),
8461 start, pointCount) - start;
8462 } else {
8463 count = pointCount - start;
8464 }
8465 }
8466 return {start, count};
8467}
8468function scaleRangesChanged(meta) {
8469 const {xScale, yScale, _scaleRanges} = meta;
8470 const newRanges = {
8471 xmin: xScale.min,
8472 xmax: xScale.max,
8473 ymin: yScale.min,
8474 ymax: yScale.max
8475 };
8476 if (!_scaleRanges) {
8477 meta._scaleRanges = newRanges;
8478 return true;
8479 }
8480 const changed = _scaleRanges.xmin !== xScale.min
8481 || _scaleRanges.xmax !== xScale.max
8482 || _scaleRanges.ymin !== yScale.min
8483 || _scaleRanges.ymax !== yScale.max;
8484 Object.assign(_scaleRanges, newRanges);
8485 return changed;
8486}
8487
8488class PolarAreaController extends DatasetController {
8489 constructor(chart, datasetIndex) {
8490 super(chart, datasetIndex);
8491 this.innerRadius = undefined;
8492 this.outerRadius = undefined;
8493 }
8494 getLabelAndValue(index) {
8495 const meta = this._cachedMeta;
8496 const chart = this.chart;
8497 const labels = chart.data.labels || [];
8498 const value = formatNumber(meta._parsed[index].r, chart.options.locale);
8499 return {
8500 label: labels[index] || '',
8501 value,
8502 };
8503 }
8504 update(mode) {
8505 const arcs = this._cachedMeta.data;
8506 this._updateRadius();
8507 this.updateElements(arcs, 0, arcs.length, mode);
8508 }
8509 _updateRadius() {
8510 const chart = this.chart;
8511 const chartArea = chart.chartArea;
8512 const opts = chart.options;
8513 const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
8514 const outerRadius = Math.max(minSize / 2, 0);
8515 const innerRadius = Math.max(opts.cutoutPercentage ? (outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
8516 const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
8517 this.outerRadius = outerRadius - (radiusLength * this.index);
8518 this.innerRadius = this.outerRadius - radiusLength;
8519 }
8520 updateElements(arcs, start, count, mode) {
8521 const reset = mode === 'reset';
8522 const chart = this.chart;
8523 const dataset = this.getDataset();
8524 const opts = chart.options;
8525 const animationOpts = opts.animation;
8526 const scale = this._cachedMeta.rScale;
8527 const centerX = scale.xCenter;
8528 const centerY = scale.yCenter;
8529 const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI;
8530 let angle = datasetStartAngle;
8531 let i;
8532 const defaultAngle = 360 / this.countVisibleElements();
8533 for (i = 0; i < start; ++i) {
8534 angle += this._computeAngle(i, mode, defaultAngle);
8535 }
8536 for (i = start; i < start + count; i++) {
8537 const arc = arcs[i];
8538 let startAngle = angle;
8539 let endAngle = angle + this._computeAngle(i, mode, defaultAngle);
8540 let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(dataset.data[i]) : 0;
8541 angle = endAngle;
8542 if (reset) {
8543 if (animationOpts.animateScale) {
8544 outerRadius = 0;
8545 }
8546 if (animationOpts.animateRotate) {
8547 startAngle = endAngle = datasetStartAngle;
8548 }
8549 }
8550 const properties = {
8551 x: centerX,
8552 y: centerY,
8553 innerRadius: 0,
8554 outerRadius,
8555 startAngle,
8556 endAngle,
8557 options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode)
8558 };
8559 this.updateElement(arc, i, properties, mode);
8560 }
8561 }
8562 countVisibleElements() {
8563 const dataset = this.getDataset();
8564 const meta = this._cachedMeta;
8565 let count = 0;
8566 meta.data.forEach((element, index) => {
8567 if (!isNaN(dataset.data[index]) && this.chart.getDataVisibility(index)) {
8568 count++;
8569 }
8570 });
8571 return count;
8572 }
8573 _computeAngle(index, mode, defaultAngle) {
8574 return this.chart.getDataVisibility(index)
8575 ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle)
8576 : 0;
8577 }
8578}
8579PolarAreaController.id = 'polarArea';
8580PolarAreaController.defaults = {
8581 dataElementType: 'arc',
8582 animation: {
8583 animateRotate: true,
8584 animateScale: true
8585 },
8586 animations: {
8587 numbers: {
8588 type: 'number',
8589 properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
8590 },
8591 },
8592 indexAxis: 'r',
8593 startAngle: 0,
8594};
8595PolarAreaController.overrides = {
8596 aspectRatio: 1,
8597 plugins: {
8598 legend: {
8599 labels: {
8600 generateLabels(chart) {
8601 const data = chart.data;
8602 if (data.labels.length && data.datasets.length) {
8603 const {labels: {pointStyle}} = chart.legend.options;
8604 return data.labels.map((label, i) => {
8605 const meta = chart.getDatasetMeta(0);
8606 const style = meta.controller.getStyle(i);
8607 return {
8608 text: label,
8609 fillStyle: style.backgroundColor,
8610 strokeStyle: style.borderColor,
8611 lineWidth: style.borderWidth,
8612 pointStyle: pointStyle,
8613 hidden: !chart.getDataVisibility(i),
8614 index: i
8615 };
8616 });
8617 }
8618 return [];
8619 }
8620 },
8621 onClick(e, legendItem, legend) {
8622 legend.chart.toggleDataVisibility(legendItem.index);
8623 legend.chart.update();
8624 }
8625 },
8626 tooltip: {
8627 callbacks: {
8628 title() {
8629 return '';
8630 },
8631 label(context) {
8632 return context.chart.data.labels[context.dataIndex] + ': ' + context.formattedValue;
8633 }
8634 }
8635 }
8636 },
8637 scales: {
8638 r: {
8639 type: 'radialLinear',
8640 angleLines: {
8641 display: false
8642 },
8643 beginAtZero: true,
8644 grid: {
8645 circular: true
8646 },
8647 pointLabels: {
8648 display: false
8649 },
8650 startAngle: 0
8651 }
8652 }
8653};
8654
8655class PieController extends DoughnutController {
8656}
8657PieController.id = 'pie';
8658PieController.defaults = {
8659 cutout: 0,
8660 rotation: 0,
8661 circumference: 360,
8662 radius: '100%'
8663};
8664
8665class RadarController extends DatasetController {
8666 getLabelAndValue(index) {
8667 const vScale = this._cachedMeta.vScale;
8668 const parsed = this.getParsed(index);
8669 return {
8670 label: vScale.getLabels()[index],
8671 value: '' + vScale.getLabelForValue(parsed[vScale.axis])
8672 };
8673 }
8674 update(mode) {
8675 const meta = this._cachedMeta;
8676 const line = meta.dataset;
8677 const points = meta.data || [];
8678 const labels = meta.iScale.getLabels();
8679 line.points = points;
8680 if (mode !== 'resize') {
8681 const options = this.resolveDatasetElementOptions(mode);
8682 if (!this.options.showLine) {
8683 options.borderWidth = 0;
8684 }
8685 const properties = {
8686 _loop: true,
8687 _fullLoop: labels.length === points.length,
8688 options
8689 };
8690 this.updateElement(line, undefined, properties, mode);
8691 }
8692 this.updateElements(points, 0, points.length, mode);
8693 }
8694 updateElements(points, start, count, mode) {
8695 const dataset = this.getDataset();
8696 const scale = this._cachedMeta.rScale;
8697 const reset = mode === 'reset';
8698 for (let i = start; i < start + count; i++) {
8699 const point = points[i];
8700 const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
8701 const pointPosition = scale.getPointPositionForValue(i, dataset.data[i]);
8702 const x = reset ? scale.xCenter : pointPosition.x;
8703 const y = reset ? scale.yCenter : pointPosition.y;
8704 const properties = {
8705 x,
8706 y,
8707 angle: pointPosition.angle,
8708 skip: isNaN(x) || isNaN(y),
8709 options
8710 };
8711 this.updateElement(point, i, properties, mode);
8712 }
8713 }
8714}
8715RadarController.id = 'radar';
8716RadarController.defaults = {
8717 datasetElementType: 'line',
8718 dataElementType: 'point',
8719 indexAxis: 'r',
8720 showLine: true,
8721 elements: {
8722 line: {
8723 fill: 'start'
8724 }
8725 },
8726};
8727RadarController.overrides = {
8728 aspectRatio: 1,
8729 scales: {
8730 r: {
8731 type: 'radialLinear',
8732 }
8733 }
8734};
8735
8736class ScatterController extends LineController {
8737}
8738ScatterController.id = 'scatter';
8739ScatterController.defaults = {
8740 showLine: false,
8741 fill: false
8742};
8743ScatterController.overrides = {
8744 interaction: {
8745 mode: 'point'
8746 },
8747 plugins: {
8748 tooltip: {
8749 callbacks: {
8750 title() {
8751 return '';
8752 },
8753 label(item) {
8754 return '(' + item.label + ', ' + item.formattedValue + ')';
8755 }
8756 }
8757 }
8758 },
8759 scales: {
8760 x: {
8761 type: 'linear'
8762 },
8763 y: {
8764 type: 'linear'
8765 }
8766 }
8767};
8768
8769var controllers = /*#__PURE__*/Object.freeze({
8770__proto__: null,
8771BarController: BarController,
8772BubbleController: BubbleController,
8773DoughnutController: DoughnutController,
8774LineController: LineController,
8775PolarAreaController: PolarAreaController,
8776PieController: PieController,
8777RadarController: RadarController,
8778ScatterController: ScatterController
8779});
8780
8781function clipArc(ctx, element, endAngle) {
8782 const {startAngle, pixelMargin, x, y, outerRadius, innerRadius} = element;
8783 let angleMargin = pixelMargin / outerRadius;
8784 ctx.beginPath();
8785 ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
8786 if (innerRadius > pixelMargin) {
8787 angleMargin = pixelMargin / innerRadius;
8788 ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
8789 } else {
8790 ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI);
8791 }
8792 ctx.closePath();
8793 ctx.clip();
8794}
8795function toRadiusCorners(value) {
8796 return _readValueToProps(value, ['outerStart', 'outerEnd', 'innerStart', 'innerEnd']);
8797}
8798function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) {
8799 const o = toRadiusCorners(arc.options.borderRadius);
8800 const halfThickness = (outerRadius - innerRadius) / 2;
8801 const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);
8802 const computeOuterLimit = (val) => {
8803 const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;
8804 return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit));
8805 };
8806 return {
8807 outerStart: computeOuterLimit(o.outerStart),
8808 outerEnd: computeOuterLimit(o.outerEnd),
8809 innerStart: _limitValue(o.innerStart, 0, innerLimit),
8810 innerEnd: _limitValue(o.innerEnd, 0, innerLimit),
8811 };
8812}
8813function rThetaToXY(r, theta, x, y) {
8814 return {
8815 x: x + r * Math.cos(theta),
8816 y: y + r * Math.sin(theta),
8817 };
8818}
8819function pathArc(ctx, element, offset, spacing, end) {
8820 const {x, y, startAngle: start, pixelMargin, innerRadius: innerR} = element;
8821 const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);
8822 const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0;
8823 let spacingOffset = 0;
8824 const alpha = end - start;
8825 if (spacing) {
8826 const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0;
8827 const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0;
8828 const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2;
8829 const adjustedAngle = avNogSpacingRadius !== 0 ? (alpha * avNogSpacingRadius) / (avNogSpacingRadius + spacing) : alpha;
8830 spacingOffset = (alpha - adjustedAngle) / 2;
8831 }
8832 const beta = Math.max(0.001, alpha * outerRadius - offset / PI) / outerRadius;
8833 const angleOffset = (alpha - beta) / 2;
8834 const startAngle = start + angleOffset + spacingOffset;
8835 const endAngle = end - angleOffset - spacingOffset;
8836 const {outerStart, outerEnd, innerStart, innerEnd} = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle);
8837 const outerStartAdjustedRadius = outerRadius - outerStart;
8838 const outerEndAdjustedRadius = outerRadius - outerEnd;
8839 const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;
8840 const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;
8841 const innerStartAdjustedRadius = innerRadius + innerStart;
8842 const innerEndAdjustedRadius = innerRadius + innerEnd;
8843 const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;
8844 const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;
8845 ctx.beginPath();
8846 ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerEndAdjustedAngle);
8847 if (outerEnd > 0) {
8848 const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
8849 ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);
8850 }
8851 const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
8852 ctx.lineTo(p4.x, p4.y);
8853 if (innerEnd > 0) {
8854 const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
8855 ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);
8856 }
8857 ctx.arc(x, y, innerRadius, endAngle - (innerEnd / innerRadius), startAngle + (innerStart / innerRadius), true);
8858 if (innerStart > 0) {
8859 const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
8860 ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);
8861 }
8862 const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
8863 ctx.lineTo(p8.x, p8.y);
8864 if (outerStart > 0) {
8865 const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
8866 ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);
8867 }
8868 ctx.closePath();
8869}
8870function drawArc(ctx, element, offset, spacing) {
8871 const {fullCircles, startAngle, circumference} = element;
8872 let endAngle = element.endAngle;
8873 if (fullCircles) {
8874 pathArc(ctx, element, offset, spacing, startAngle + TAU);
8875 for (let i = 0; i < fullCircles; ++i) {
8876 ctx.fill();
8877 }
8878 if (!isNaN(circumference)) {
8879 endAngle = startAngle + circumference % TAU;
8880 if (circumference % TAU === 0) {
8881 endAngle += TAU;
8882 }
8883 }
8884 }
8885 pathArc(ctx, element, offset, spacing, endAngle);
8886 ctx.fill();
8887 return endAngle;
8888}
8889function drawFullCircleBorders(ctx, element, inner) {
8890 const {x, y, startAngle, pixelMargin, fullCircles} = element;
8891 const outerRadius = Math.max(element.outerRadius - pixelMargin, 0);
8892 const innerRadius = element.innerRadius + pixelMargin;
8893 let i;
8894 if (inner) {
8895 clipArc(ctx, element, startAngle + TAU);
8896 }
8897 ctx.beginPath();
8898 ctx.arc(x, y, innerRadius, startAngle + TAU, startAngle, true);
8899 for (i = 0; i < fullCircles; ++i) {
8900 ctx.stroke();
8901 }
8902 ctx.beginPath();
8903 ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU);
8904 for (i = 0; i < fullCircles; ++i) {
8905 ctx.stroke();
8906 }
8907}
8908function drawBorder(ctx, element, offset, spacing, endAngle) {
8909 const {options} = element;
8910 const {borderWidth, borderJoinStyle} = options;
8911 const inner = options.borderAlign === 'inner';
8912 if (!borderWidth) {
8913 return;
8914 }
8915 if (inner) {
8916 ctx.lineWidth = borderWidth * 2;
8917 ctx.lineJoin = borderJoinStyle || 'round';
8918 } else {
8919 ctx.lineWidth = borderWidth;
8920 ctx.lineJoin = borderJoinStyle || 'bevel';
8921 }
8922 if (element.fullCircles) {
8923 drawFullCircleBorders(ctx, element, inner);
8924 }
8925 if (inner) {
8926 clipArc(ctx, element, endAngle);
8927 }
8928 pathArc(ctx, element, offset, spacing, endAngle);
8929 ctx.stroke();
8930}
8931class ArcElement extends Element {
8932 constructor(cfg) {
8933 super();
8934 this.options = undefined;
8935 this.circumference = undefined;
8936 this.startAngle = undefined;
8937 this.endAngle = undefined;
8938 this.innerRadius = undefined;
8939 this.outerRadius = undefined;
8940 this.pixelMargin = 0;
8941 this.fullCircles = 0;
8942 if (cfg) {
8943 Object.assign(this, cfg);
8944 }
8945 }
8946 inRange(chartX, chartY, useFinalPosition) {
8947 const point = this.getProps(['x', 'y'], useFinalPosition);
8948 const {angle, distance} = getAngleFromPoint(point, {x: chartX, y: chartY});
8949 const {startAngle, endAngle, innerRadius, outerRadius, circumference} = this.getProps([
8950 'startAngle',
8951 'endAngle',
8952 'innerRadius',
8953 'outerRadius',
8954 'circumference'
8955 ], useFinalPosition);
8956 const rAdjust = this.options.spacing / 2;
8957 const _circumference = valueOrDefault(circumference, endAngle - startAngle);
8958 const betweenAngles = _circumference >= TAU || _angleBetween(angle, startAngle, endAngle);
8959 const withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);
8960 return (betweenAngles && withinRadius);
8961 }
8962 getCenterPoint(useFinalPosition) {
8963 const {x, y, startAngle, endAngle, innerRadius, outerRadius} = this.getProps([
8964 'x',
8965 'y',
8966 'startAngle',
8967 'endAngle',
8968 'innerRadius',
8969 'outerRadius',
8970 'circumference',
8971 ], useFinalPosition);
8972 const {offset, spacing} = this.options;
8973 const halfAngle = (startAngle + endAngle) / 2;
8974 const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2;
8975 return {
8976 x: x + Math.cos(halfAngle) * halfRadius,
8977 y: y + Math.sin(halfAngle) * halfRadius
8978 };
8979 }
8980 tooltipPosition(useFinalPosition) {
8981 return this.getCenterPoint(useFinalPosition);
8982 }
8983 draw(ctx) {
8984 const {options, circumference} = this;
8985 const offset = (options.offset || 0) / 2;
8986 const spacing = (options.spacing || 0) / 2;
8987 this.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0;
8988 this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0;
8989 if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) {
8990 return;
8991 }
8992 ctx.save();
8993 let radiusOffset = 0;
8994 if (offset) {
8995 radiusOffset = offset / 2;
8996 const halfAngle = (this.startAngle + this.endAngle) / 2;
8997 ctx.translate(Math.cos(halfAngle) * radiusOffset, Math.sin(halfAngle) * radiusOffset);
8998 if (this.circumference >= PI) {
8999 radiusOffset = offset;
9000 }
9001 }
9002 ctx.fillStyle = options.backgroundColor;
9003 ctx.strokeStyle = options.borderColor;
9004 const endAngle = drawArc(ctx, this, radiusOffset, spacing);
9005 drawBorder(ctx, this, radiusOffset, spacing, endAngle);
9006 ctx.restore();
9007 }
9008}
9009ArcElement.id = 'arc';
9010ArcElement.defaults = {
9011 borderAlign: 'center',
9012 borderColor: '#fff',
9013 borderJoinStyle: undefined,
9014 borderRadius: 0,
9015 borderWidth: 2,
9016 offset: 0,
9017 spacing: 0,
9018 angle: undefined,
9019};
9020ArcElement.defaultRoutes = {
9021 backgroundColor: 'backgroundColor'
9022};
9023
9024function setStyle(ctx, options, style = options) {
9025 ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle);
9026 ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash));
9027 ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset);
9028 ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);
9029 ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth);
9030 ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor);
9031}
9032function lineTo(ctx, previous, target) {
9033 ctx.lineTo(target.x, target.y);
9034}
9035function getLineMethod(options) {
9036 if (options.stepped) {
9037 return _steppedLineTo;
9038 }
9039 if (options.tension || options.cubicInterpolationMode === 'monotone') {
9040 return _bezierCurveTo;
9041 }
9042 return lineTo;
9043}
9044function pathVars(points, segment, params = {}) {
9045 const count = points.length;
9046 const {start: paramsStart = 0, end: paramsEnd = count - 1} = params;
9047 const {start: segmentStart, end: segmentEnd} = segment;
9048 const start = Math.max(paramsStart, segmentStart);
9049 const end = Math.min(paramsEnd, segmentEnd);
9050 const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;
9051 return {
9052 count,
9053 start,
9054 loop: segment.loop,
9055 ilen: end < start && !outside ? count + end - start : end - start
9056 };
9057}
9058function pathSegment(ctx, line, segment, params) {
9059 const {points, options} = line;
9060 const {count, start, loop, ilen} = pathVars(points, segment, params);
9061 const lineMethod = getLineMethod(options);
9062 let {move = true, reverse} = params || {};
9063 let i, point, prev;
9064 for (i = 0; i <= ilen; ++i) {
9065 point = points[(start + (reverse ? ilen - i : i)) % count];
9066 if (point.skip) {
9067 continue;
9068 } else if (move) {
9069 ctx.moveTo(point.x, point.y);
9070 move = false;
9071 } else {
9072 lineMethod(ctx, prev, point, reverse, options.stepped);
9073 }
9074 prev = point;
9075 }
9076 if (loop) {
9077 point = points[(start + (reverse ? ilen : 0)) % count];
9078 lineMethod(ctx, prev, point, reverse, options.stepped);
9079 }
9080 return !!loop;
9081}
9082function fastPathSegment(ctx, line, segment, params) {
9083 const points = line.points;
9084 const {count, start, ilen} = pathVars(points, segment, params);
9085 const {move = true, reverse} = params || {};
9086 let avgX = 0;
9087 let countX = 0;
9088 let i, point, prevX, minY, maxY, lastY;
9089 const pointIndex = (index) => (start + (reverse ? ilen - index : index)) % count;
9090 const drawX = () => {
9091 if (minY !== maxY) {
9092 ctx.lineTo(avgX, maxY);
9093 ctx.lineTo(avgX, minY);
9094 ctx.lineTo(avgX, lastY);
9095 }
9096 };
9097 if (move) {
9098 point = points[pointIndex(0)];
9099 ctx.moveTo(point.x, point.y);
9100 }
9101 for (i = 0; i <= ilen; ++i) {
9102 point = points[pointIndex(i)];
9103 if (point.skip) {
9104 continue;
9105 }
9106 const x = point.x;
9107 const y = point.y;
9108 const truncX = x | 0;
9109 if (truncX === prevX) {
9110 if (y < minY) {
9111 minY = y;
9112 } else if (y > maxY) {
9113 maxY = y;
9114 }
9115 avgX = (countX * avgX + x) / ++countX;
9116 } else {
9117 drawX();
9118 ctx.lineTo(x, y);
9119 prevX = truncX;
9120 countX = 0;
9121 minY = maxY = y;
9122 }
9123 lastY = y;
9124 }
9125 drawX();
9126}
9127function _getSegmentMethod(line) {
9128 const opts = line.options;
9129 const borderDash = opts.borderDash && opts.borderDash.length;
9130 const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash;
9131 return useFastPath ? fastPathSegment : pathSegment;
9132}
9133function _getInterpolationMethod(options) {
9134 if (options.stepped) {
9135 return _steppedInterpolation;
9136 }
9137 if (options.tension || options.cubicInterpolationMode === 'monotone') {
9138 return _bezierInterpolation;
9139 }
9140 return _pointInLine;
9141}
9142function strokePathWithCache(ctx, line, start, count) {
9143 let path = line._path;
9144 if (!path) {
9145 path = line._path = new Path2D();
9146 if (line.path(path, start, count)) {
9147 path.closePath();
9148 }
9149 }
9150 setStyle(ctx, line.options);
9151 ctx.stroke(path);
9152}
9153function strokePathDirect(ctx, line, start, count) {
9154 const {segments, options} = line;
9155 const segmentMethod = _getSegmentMethod(line);
9156 for (const segment of segments) {
9157 setStyle(ctx, options, segment.style);
9158 ctx.beginPath();
9159 if (segmentMethod(ctx, line, segment, {start, end: start + count - 1})) {
9160 ctx.closePath();
9161 }
9162 ctx.stroke();
9163 }
9164}
9165const usePath2D = typeof Path2D === 'function';
9166function draw(ctx, line, start, count) {
9167 if (usePath2D && !line.options.segment) {
9168 strokePathWithCache(ctx, line, start, count);
9169 } else {
9170 strokePathDirect(ctx, line, start, count);
9171 }
9172}
9173class LineElement extends Element {
9174 constructor(cfg) {
9175 super();
9176 this.animated = true;
9177 this.options = undefined;
9178 this._chart = undefined;
9179 this._loop = undefined;
9180 this._fullLoop = undefined;
9181 this._path = undefined;
9182 this._points = undefined;
9183 this._segments = undefined;
9184 this._decimated = false;
9185 this._pointsUpdated = false;
9186 this._datasetIndex = undefined;
9187 if (cfg) {
9188 Object.assign(this, cfg);
9189 }
9190 }
9191 updateControlPoints(chartArea, indexAxis) {
9192 const options = this.options;
9193 if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) {
9194 const loop = options.spanGaps ? this._loop : this._fullLoop;
9195 _updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis);
9196 this._pointsUpdated = true;
9197 }
9198 }
9199 set points(points) {
9200 this._points = points;
9201 delete this._segments;
9202 delete this._path;
9203 this._pointsUpdated = false;
9204 }
9205 get points() {
9206 return this._points;
9207 }
9208 get segments() {
9209 return this._segments || (this._segments = _computeSegments(this, this.options.segment));
9210 }
9211 first() {
9212 const segments = this.segments;
9213 const points = this.points;
9214 return segments.length && points[segments[0].start];
9215 }
9216 last() {
9217 const segments = this.segments;
9218 const points = this.points;
9219 const count = segments.length;
9220 return count && points[segments[count - 1].end];
9221 }
9222 interpolate(point, property) {
9223 const options = this.options;
9224 const value = point[property];
9225 const points = this.points;
9226 const segments = _boundSegments(this, {property, start: value, end: value});
9227 if (!segments.length) {
9228 return;
9229 }
9230 const result = [];
9231 const _interpolate = _getInterpolationMethod(options);
9232 let i, ilen;
9233 for (i = 0, ilen = segments.length; i < ilen; ++i) {
9234 const {start, end} = segments[i];
9235 const p1 = points[start];
9236 const p2 = points[end];
9237 if (p1 === p2) {
9238 result.push(p1);
9239 continue;
9240 }
9241 const t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));
9242 const interpolated = _interpolate(p1, p2, t, options.stepped);
9243 interpolated[property] = point[property];
9244 result.push(interpolated);
9245 }
9246 return result.length === 1 ? result[0] : result;
9247 }
9248 pathSegment(ctx, segment, params) {
9249 const segmentMethod = _getSegmentMethod(this);
9250 return segmentMethod(ctx, this, segment, params);
9251 }
9252 path(ctx, start, count) {
9253 const segments = this.segments;
9254 const segmentMethod = _getSegmentMethod(this);
9255 let loop = this._loop;
9256 start = start || 0;
9257 count = count || (this.points.length - start);
9258 for (const segment of segments) {
9259 loop &= segmentMethod(ctx, this, segment, {start, end: start + count - 1});
9260 }
9261 return !!loop;
9262 }
9263 draw(ctx, chartArea, start, count) {
9264 const options = this.options || {};
9265 const points = this.points || [];
9266 if (points.length && options.borderWidth) {
9267 ctx.save();
9268 draw(ctx, this, start, count);
9269 ctx.restore();
9270 }
9271 if (this.animated) {
9272 this._pointsUpdated = false;
9273 this._path = undefined;
9274 }
9275 }
9276}
9277LineElement.id = 'line';
9278LineElement.defaults = {
9279 borderCapStyle: 'butt',
9280 borderDash: [],
9281 borderDashOffset: 0,
9282 borderJoinStyle: 'miter',
9283 borderWidth: 3,
9284 capBezierPoints: true,
9285 cubicInterpolationMode: 'default',
9286 fill: false,
9287 spanGaps: false,
9288 stepped: false,
9289 tension: 0,
9290};
9291LineElement.defaultRoutes = {
9292 backgroundColor: 'backgroundColor',
9293 borderColor: 'borderColor'
9294};
9295LineElement.descriptors = {
9296 _scriptable: true,
9297 _indexable: (name) => name !== 'borderDash' && name !== 'fill',
9298};
9299
9300function inRange$1(el, pos, axis, useFinalPosition) {
9301 const options = el.options;
9302 const {[axis]: value} = el.getProps([axis], useFinalPosition);
9303 return (Math.abs(pos - value) < options.radius + options.hitRadius);
9304}
9305class PointElement extends Element {
9306 constructor(cfg) {
9307 super();
9308 this.options = undefined;
9309 this.parsed = undefined;
9310 this.skip = undefined;
9311 this.stop = undefined;
9312 if (cfg) {
9313 Object.assign(this, cfg);
9314 }
9315 }
9316 inRange(mouseX, mouseY, useFinalPosition) {
9317 const options = this.options;
9318 const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
9319 return ((Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2)) < Math.pow(options.hitRadius + options.radius, 2));
9320 }
9321 inXRange(mouseX, useFinalPosition) {
9322 return inRange$1(this, mouseX, 'x', useFinalPosition);
9323 }
9324 inYRange(mouseY, useFinalPosition) {
9325 return inRange$1(this, mouseY, 'y', useFinalPosition);
9326 }
9327 getCenterPoint(useFinalPosition) {
9328 const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
9329 return {x, y};
9330 }
9331 size(options) {
9332 options = options || this.options || {};
9333 let radius = options.radius || 0;
9334 radius = Math.max(radius, radius && options.hoverRadius || 0);
9335 const borderWidth = radius && options.borderWidth || 0;
9336 return (radius + borderWidth) * 2;
9337 }
9338 draw(ctx, area) {
9339 const options = this.options;
9340 if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) {
9341 return;
9342 }
9343 ctx.strokeStyle = options.borderColor;
9344 ctx.lineWidth = options.borderWidth;
9345 ctx.fillStyle = options.backgroundColor;
9346 drawPoint(ctx, options, this.x, this.y);
9347 }
9348 getRange() {
9349 const options = this.options || {};
9350 return options.radius + options.hitRadius;
9351 }
9352}
9353PointElement.id = 'point';
9354PointElement.defaults = {
9355 borderWidth: 1,
9356 hitRadius: 1,
9357 hoverBorderWidth: 1,
9358 hoverRadius: 4,
9359 pointStyle: 'circle',
9360 radius: 3,
9361 rotation: 0
9362};
9363PointElement.defaultRoutes = {
9364 backgroundColor: 'backgroundColor',
9365 borderColor: 'borderColor'
9366};
9367
9368function getBarBounds(bar, useFinalPosition) {
9369 const {x, y, base, width, height} = bar.getProps(['x', 'y', 'base', 'width', 'height'], useFinalPosition);
9370 let left, right, top, bottom, half;
9371 if (bar.horizontal) {
9372 half = height / 2;
9373 left = Math.min(x, base);
9374 right = Math.max(x, base);
9375 top = y - half;
9376 bottom = y + half;
9377 } else {
9378 half = width / 2;
9379 left = x - half;
9380 right = x + half;
9381 top = Math.min(y, base);
9382 bottom = Math.max(y, base);
9383 }
9384 return {left, top, right, bottom};
9385}
9386function skipOrLimit(skip, value, min, max) {
9387 return skip ? 0 : _limitValue(value, min, max);
9388}
9389function parseBorderWidth(bar, maxW, maxH) {
9390 const value = bar.options.borderWidth;
9391 const skip = bar.borderSkipped;
9392 const o = toTRBL(value);
9393 return {
9394 t: skipOrLimit(skip.top, o.top, 0, maxH),
9395 r: skipOrLimit(skip.right, o.right, 0, maxW),
9396 b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
9397 l: skipOrLimit(skip.left, o.left, 0, maxW)
9398 };
9399}
9400function parseBorderRadius(bar, maxW, maxH) {
9401 const {enableBorderRadius} = bar.getProps(['enableBorderRadius']);
9402 const value = bar.options.borderRadius;
9403 const o = toTRBLCorners(value);
9404 const maxR = Math.min(maxW, maxH);
9405 const skip = bar.borderSkipped;
9406 const enableBorder = enableBorderRadius || isObject(value);
9407 return {
9408 topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
9409 topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
9410 bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
9411 bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
9412 };
9413}
9414function boundingRects(bar) {
9415 const bounds = getBarBounds(bar);
9416 const width = bounds.right - bounds.left;
9417 const height = bounds.bottom - bounds.top;
9418 const border = parseBorderWidth(bar, width / 2, height / 2);
9419 const radius = parseBorderRadius(bar, width / 2, height / 2);
9420 return {
9421 outer: {
9422 x: bounds.left,
9423 y: bounds.top,
9424 w: width,
9425 h: height,
9426 radius
9427 },
9428 inner: {
9429 x: bounds.left + border.l,
9430 y: bounds.top + border.t,
9431 w: width - border.l - border.r,
9432 h: height - border.t - border.b,
9433 radius: {
9434 topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
9435 topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
9436 bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
9437 bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
9438 }
9439 }
9440 };
9441}
9442function inRange(bar, x, y, useFinalPosition) {
9443 const skipX = x === null;
9444 const skipY = y === null;
9445 const skipBoth = skipX && skipY;
9446 const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
9447 return bounds
9448 && (skipX || _isBetween(x, bounds.left, bounds.right))
9449 && (skipY || _isBetween(y, bounds.top, bounds.bottom));
9450}
9451function hasRadius(radius) {
9452 return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
9453}
9454function addNormalRectPath(ctx, rect) {
9455 ctx.rect(rect.x, rect.y, rect.w, rect.h);
9456}
9457function inflateRect(rect, amount, refRect = {}) {
9458 const x = rect.x !== refRect.x ? -amount : 0;
9459 const y = rect.y !== refRect.y ? -amount : 0;
9460 const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;
9461 const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;
9462 return {
9463 x: rect.x + x,
9464 y: rect.y + y,
9465 w: rect.w + w,
9466 h: rect.h + h,
9467 radius: rect.radius
9468 };
9469}
9470class BarElement extends Element {
9471 constructor(cfg) {
9472 super();
9473 this.options = undefined;
9474 this.horizontal = undefined;
9475 this.base = undefined;
9476 this.width = undefined;
9477 this.height = undefined;
9478 this.inflateAmount = undefined;
9479 if (cfg) {
9480 Object.assign(this, cfg);
9481 }
9482 }
9483 draw(ctx) {
9484 const {inflateAmount, options: {borderColor, backgroundColor}} = this;
9485 const {inner, outer} = boundingRects(this);
9486 const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
9487 ctx.save();
9488 if (outer.w !== inner.w || outer.h !== inner.h) {
9489 ctx.beginPath();
9490 addRectPath(ctx, inflateRect(outer, inflateAmount, inner));
9491 ctx.clip();
9492 addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));
9493 ctx.fillStyle = borderColor;
9494 ctx.fill('evenodd');
9495 }
9496 ctx.beginPath();
9497 addRectPath(ctx, inflateRect(inner, inflateAmount));
9498 ctx.fillStyle = backgroundColor;
9499 ctx.fill();
9500 ctx.restore();
9501 }
9502 inRange(mouseX, mouseY, useFinalPosition) {
9503 return inRange(this, mouseX, mouseY, useFinalPosition);
9504 }
9505 inXRange(mouseX, useFinalPosition) {
9506 return inRange(this, mouseX, null, useFinalPosition);
9507 }
9508 inYRange(mouseY, useFinalPosition) {
9509 return inRange(this, null, mouseY, useFinalPosition);
9510 }
9511 getCenterPoint(useFinalPosition) {
9512 const {x, y, base, horizontal} = this.getProps(['x', 'y', 'base', 'horizontal'], useFinalPosition);
9513 return {
9514 x: horizontal ? (x + base) / 2 : x,
9515 y: horizontal ? y : (y + base) / 2
9516 };
9517 }
9518 getRange(axis) {
9519 return axis === 'x' ? this.width / 2 : this.height / 2;
9520 }
9521}
9522BarElement.id = 'bar';
9523BarElement.defaults = {
9524 borderSkipped: 'start',
9525 borderWidth: 0,
9526 borderRadius: 0,
9527 inflateAmount: 'auto',
9528 pointStyle: undefined
9529};
9530BarElement.defaultRoutes = {
9531 backgroundColor: 'backgroundColor',
9532 borderColor: 'borderColor'
9533};
9534
9535var elements = /*#__PURE__*/Object.freeze({
9536__proto__: null,
9537ArcElement: ArcElement,
9538LineElement: LineElement,
9539PointElement: PointElement,
9540BarElement: BarElement
9541});
9542
9543function lttbDecimation(data, start, count, availableWidth, options) {
9544 const samples = options.samples || availableWidth;
9545 if (samples >= count) {
9546 return data.slice(start, start + count);
9547 }
9548 const decimated = [];
9549 const bucketWidth = (count - 2) / (samples - 2);
9550 let sampledIndex = 0;
9551 const endIndex = start + count - 1;
9552 let a = start;
9553 let i, maxAreaPoint, maxArea, area, nextA;
9554 decimated[sampledIndex++] = data[a];
9555 for (i = 0; i < samples - 2; i++) {
9556 let avgX = 0;
9557 let avgY = 0;
9558 let j;
9559 const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
9560 const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
9561 const avgRangeLength = avgRangeEnd - avgRangeStart;
9562 for (j = avgRangeStart; j < avgRangeEnd; j++) {
9563 avgX += data[j].x;
9564 avgY += data[j].y;
9565 }
9566 avgX /= avgRangeLength;
9567 avgY /= avgRangeLength;
9568 const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
9569 const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start;
9570 const {x: pointAx, y: pointAy} = data[a];
9571 maxArea = area = -1;
9572 for (j = rangeOffs; j < rangeTo; j++) {
9573 area = 0.5 * Math.abs(
9574 (pointAx - avgX) * (data[j].y - pointAy) -
9575 (pointAx - data[j].x) * (avgY - pointAy)
9576 );
9577 if (area > maxArea) {
9578 maxArea = area;
9579 maxAreaPoint = data[j];
9580 nextA = j;
9581 }
9582 }
9583 decimated[sampledIndex++] = maxAreaPoint;
9584 a = nextA;
9585 }
9586 decimated[sampledIndex++] = data[endIndex];
9587 return decimated;
9588}
9589function minMaxDecimation(data, start, count, availableWidth) {
9590 let avgX = 0;
9591 let countX = 0;
9592 let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
9593 const decimated = [];
9594 const endIndex = start + count - 1;
9595 const xMin = data[start].x;
9596 const xMax = data[endIndex].x;
9597 const dx = xMax - xMin;
9598 for (i = start; i < start + count; ++i) {
9599 point = data[i];
9600 x = (point.x - xMin) / dx * availableWidth;
9601 y = point.y;
9602 const truncX = x | 0;
9603 if (truncX === prevX) {
9604 if (y < minY) {
9605 minY = y;
9606 minIndex = i;
9607 } else if (y > maxY) {
9608 maxY = y;
9609 maxIndex = i;
9610 }
9611 avgX = (countX * avgX + point.x) / ++countX;
9612 } else {
9613 const lastIndex = i - 1;
9614 if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) {
9615 const intermediateIndex1 = Math.min(minIndex, maxIndex);
9616 const intermediateIndex2 = Math.max(minIndex, maxIndex);
9617 if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {
9618 decimated.push({
9619 ...data[intermediateIndex1],
9620 x: avgX,
9621 });
9622 }
9623 if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {
9624 decimated.push({
9625 ...data[intermediateIndex2],
9626 x: avgX
9627 });
9628 }
9629 }
9630 if (i > 0 && lastIndex !== startIndex) {
9631 decimated.push(data[lastIndex]);
9632 }
9633 decimated.push(point);
9634 prevX = truncX;
9635 countX = 0;
9636 minY = maxY = y;
9637 minIndex = maxIndex = startIndex = i;
9638 }
9639 }
9640 return decimated;
9641}
9642function cleanDecimatedDataset(dataset) {
9643 if (dataset._decimated) {
9644 const data = dataset._data;
9645 delete dataset._decimated;
9646 delete dataset._data;
9647 Object.defineProperty(dataset, 'data', {value: data});
9648 }
9649}
9650function cleanDecimatedData(chart) {
9651 chart.data.datasets.forEach((dataset) => {
9652 cleanDecimatedDataset(dataset);
9653 });
9654}
9655function getStartAndCountOfVisiblePointsSimplified(meta, points) {
9656 const pointCount = points.length;
9657 let start = 0;
9658 let count;
9659 const {iScale} = meta;
9660 const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
9661 if (minDefined) {
9662 start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
9663 }
9664 if (maxDefined) {
9665 count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
9666 } else {
9667 count = pointCount - start;
9668 }
9669 return {start, count};
9670}
9671var plugin_decimation = {
9672 id: 'decimation',
9673 defaults: {
9674 algorithm: 'min-max',
9675 enabled: false,
9676 },
9677 beforeElementsUpdate: (chart, args, options) => {
9678 if (!options.enabled) {
9679 cleanDecimatedData(chart);
9680 return;
9681 }
9682 const availableWidth = chart.width;
9683 chart.data.datasets.forEach((dataset, datasetIndex) => {
9684 const {_data, indexAxis} = dataset;
9685 const meta = chart.getDatasetMeta(datasetIndex);
9686 const data = _data || dataset.data;
9687 if (resolve([indexAxis, chart.options.indexAxis]) === 'y') {
9688 return;
9689 }
9690 if (meta.type !== 'line') {
9691 return;
9692 }
9693 const xAxis = chart.scales[meta.xAxisID];
9694 if (xAxis.type !== 'linear' && xAxis.type !== 'time') {
9695 return;
9696 }
9697 if (chart.options.parsing) {
9698 return;
9699 }
9700 let {start, count} = getStartAndCountOfVisiblePointsSimplified(meta, data);
9701 const threshold = options.threshold || 4 * availableWidth;
9702 if (count <= threshold) {
9703 cleanDecimatedDataset(dataset);
9704 return;
9705 }
9706 if (isNullOrUndef(_data)) {
9707 dataset._data = data;
9708 delete dataset.data;
9709 Object.defineProperty(dataset, 'data', {
9710 configurable: true,
9711 enumerable: true,
9712 get: function() {
9713 return this._decimated;
9714 },
9715 set: function(d) {
9716 this._data = d;
9717 }
9718 });
9719 }
9720 let decimated;
9721 switch (options.algorithm) {
9722 case 'lttb':
9723 decimated = lttbDecimation(data, start, count, availableWidth, options);
9724 break;
9725 case 'min-max':
9726 decimated = minMaxDecimation(data, start, count, availableWidth);
9727 break;
9728 default:
9729 throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);
9730 }
9731 dataset._decimated = decimated;
9732 });
9733 },
9734 destroy(chart) {
9735 cleanDecimatedData(chart);
9736 }
9737};
9738
9739function getLineByIndex(chart, index) {
9740 const meta = chart.getDatasetMeta(index);
9741 const visible = meta && chart.isDatasetVisible(index);
9742 return visible ? meta.dataset : null;
9743}
9744function parseFillOption(line) {
9745 const options = line.options;
9746 const fillOption = options.fill;
9747 let fill = valueOrDefault(fillOption && fillOption.target, fillOption);
9748 if (fill === undefined) {
9749 fill = !!options.backgroundColor;
9750 }
9751 if (fill === false || fill === null) {
9752 return false;
9753 }
9754 if (fill === true) {
9755 return 'origin';
9756 }
9757 return fill;
9758}
9759function decodeFill(line, index, count) {
9760 const fill = parseFillOption(line);
9761 if (isObject(fill)) {
9762 return isNaN(fill.value) ? false : fill;
9763 }
9764 let target = parseFloat(fill);
9765 if (isNumberFinite(target) && Math.floor(target) === target) {
9766 if (fill[0] === '-' || fill[0] === '+') {
9767 target = index + target;
9768 }
9769 if (target === index || target < 0 || target >= count) {
9770 return false;
9771 }
9772 return target;
9773 }
9774 return ['origin', 'start', 'end', 'stack', 'shape'].indexOf(fill) >= 0 && fill;
9775}
9776function computeLinearBoundary(source) {
9777 const {scale = {}, fill} = source;
9778 let target = null;
9779 let horizontal;
9780 if (fill === 'start') {
9781 target = scale.bottom;
9782 } else if (fill === 'end') {
9783 target = scale.top;
9784 } else if (isObject(fill)) {
9785 target = scale.getPixelForValue(fill.value);
9786 } else if (scale.getBasePixel) {
9787 target = scale.getBasePixel();
9788 }
9789 if (isNumberFinite(target)) {
9790 horizontal = scale.isHorizontal();
9791 return {
9792 x: horizontal ? target : null,
9793 y: horizontal ? null : target
9794 };
9795 }
9796 return null;
9797}
9798class simpleArc {
9799 constructor(opts) {
9800 this.x = opts.x;
9801 this.y = opts.y;
9802 this.radius = opts.radius;
9803 }
9804 pathSegment(ctx, bounds, opts) {
9805 const {x, y, radius} = this;
9806 bounds = bounds || {start: 0, end: TAU};
9807 ctx.arc(x, y, radius, bounds.end, bounds.start, true);
9808 return !opts.bounds;
9809 }
9810 interpolate(point) {
9811 const {x, y, radius} = this;
9812 const angle = point.angle;
9813 return {
9814 x: x + Math.cos(angle) * radius,
9815 y: y + Math.sin(angle) * radius,
9816 angle
9817 };
9818 }
9819}
9820function computeCircularBoundary(source) {
9821 const {scale, fill} = source;
9822 const options = scale.options;
9823 const length = scale.getLabels().length;
9824 const target = [];
9825 const start = options.reverse ? scale.max : scale.min;
9826 const end = options.reverse ? scale.min : scale.max;
9827 let i, center, value;
9828 if (fill === 'start') {
9829 value = start;
9830 } else if (fill === 'end') {
9831 value = end;
9832 } else if (isObject(fill)) {
9833 value = fill.value;
9834 } else {
9835 value = scale.getBaseValue();
9836 }
9837 if (options.grid.circular) {
9838 center = scale.getPointPositionForValue(0, start);
9839 return new simpleArc({
9840 x: center.x,
9841 y: center.y,
9842 radius: scale.getDistanceFromCenterForValue(value)
9843 });
9844 }
9845 for (i = 0; i < length; ++i) {
9846 target.push(scale.getPointPositionForValue(i, value));
9847 }
9848 return target;
9849}
9850function computeBoundary(source) {
9851 const scale = source.scale || {};
9852 if (scale.getPointPositionForValue) {
9853 return computeCircularBoundary(source);
9854 }
9855 return computeLinearBoundary(source);
9856}
9857function findSegmentEnd(start, end, points) {
9858 for (;end > start; end--) {
9859 const point = points[end];
9860 if (!isNaN(point.x) && !isNaN(point.y)) {
9861 break;
9862 }
9863 }
9864 return end;
9865}
9866function pointsFromSegments(boundary, line) {
9867 const {x = null, y = null} = boundary || {};
9868 const linePoints = line.points;
9869 const points = [];
9870 line.segments.forEach(({start, end}) => {
9871 end = findSegmentEnd(start, end, linePoints);
9872 const first = linePoints[start];
9873 const last = linePoints[end];
9874 if (y !== null) {
9875 points.push({x: first.x, y});
9876 points.push({x: last.x, y});
9877 } else if (x !== null) {
9878 points.push({x, y: first.y});
9879 points.push({x, y: last.y});
9880 }
9881 });
9882 return points;
9883}
9884function buildStackLine(source) {
9885 const {scale, index, line} = source;
9886 const points = [];
9887 const segments = line.segments;
9888 const sourcePoints = line.points;
9889 const linesBelow = getLinesBelow(scale, index);
9890 linesBelow.push(createBoundaryLine({x: null, y: scale.bottom}, line));
9891 for (let i = 0; i < segments.length; i++) {
9892 const segment = segments[i];
9893 for (let j = segment.start; j <= segment.end; j++) {
9894 addPointsBelow(points, sourcePoints[j], linesBelow);
9895 }
9896 }
9897 return new LineElement({points, options: {}});
9898}
9899function getLinesBelow(scale, index) {
9900 const below = [];
9901 const metas = scale.getMatchingVisibleMetas('line');
9902 for (let i = 0; i < metas.length; i++) {
9903 const meta = metas[i];
9904 if (meta.index === index) {
9905 break;
9906 }
9907 if (!meta.hidden) {
9908 below.unshift(meta.dataset);
9909 }
9910 }
9911 return below;
9912}
9913function addPointsBelow(points, sourcePoint, linesBelow) {
9914 const postponed = [];
9915 for (let j = 0; j < linesBelow.length; j++) {
9916 const line = linesBelow[j];
9917 const {first, last, point} = findPoint(line, sourcePoint, 'x');
9918 if (!point || (first && last)) {
9919 continue;
9920 }
9921 if (first) {
9922 postponed.unshift(point);
9923 } else {
9924 points.push(point);
9925 if (!last) {
9926 break;
9927 }
9928 }
9929 }
9930 points.push(...postponed);
9931}
9932function findPoint(line, sourcePoint, property) {
9933 const point = line.interpolate(sourcePoint, property);
9934 if (!point) {
9935 return {};
9936 }
9937 const pointValue = point[property];
9938 const segments = line.segments;
9939 const linePoints = line.points;
9940 let first = false;
9941 let last = false;
9942 for (let i = 0; i < segments.length; i++) {
9943 const segment = segments[i];
9944 const firstValue = linePoints[segment.start][property];
9945 const lastValue = linePoints[segment.end][property];
9946 if (_isBetween(pointValue, firstValue, lastValue)) {
9947 first = pointValue === firstValue;
9948 last = pointValue === lastValue;
9949 break;
9950 }
9951 }
9952 return {first, last, point};
9953}
9954function getTarget(source) {
9955 const {chart, fill, line} = source;
9956 if (isNumberFinite(fill)) {
9957 return getLineByIndex(chart, fill);
9958 }
9959 if (fill === 'stack') {
9960 return buildStackLine(source);
9961 }
9962 if (fill === 'shape') {
9963 return true;
9964 }
9965 const boundary = computeBoundary(source);
9966 if (boundary instanceof simpleArc) {
9967 return boundary;
9968 }
9969 return createBoundaryLine(boundary, line);
9970}
9971function createBoundaryLine(boundary, line) {
9972 let points = [];
9973 let _loop = false;
9974 if (isArray(boundary)) {
9975 _loop = true;
9976 points = boundary;
9977 } else {
9978 points = pointsFromSegments(boundary, line);
9979 }
9980 return points.length ? new LineElement({
9981 points,
9982 options: {tension: 0},
9983 _loop,
9984 _fullLoop: _loop
9985 }) : null;
9986}
9987function resolveTarget(sources, index, propagate) {
9988 const source = sources[index];
9989 let fill = source.fill;
9990 const visited = [index];
9991 let target;
9992 if (!propagate) {
9993 return fill;
9994 }
9995 while (fill !== false && visited.indexOf(fill) === -1) {
9996 if (!isNumberFinite(fill)) {
9997 return fill;
9998 }
9999 target = sources[fill];
10000 if (!target) {
10001 return false;
10002 }
10003 if (target.visible) {
10004 return fill;
10005 }
10006 visited.push(fill);
10007 fill = target.fill;
10008 }
10009 return false;
10010}
10011function _clip(ctx, target, clipY) {
10012 const {segments, points} = target;
10013 let first = true;
10014 let lineLoop = false;
10015 ctx.beginPath();
10016 for (const segment of segments) {
10017 const {start, end} = segment;
10018 const firstPoint = points[start];
10019 const lastPoint = points[findSegmentEnd(start, end, points)];
10020 if (first) {
10021 ctx.moveTo(firstPoint.x, firstPoint.y);
10022 first = false;
10023 } else {
10024 ctx.lineTo(firstPoint.x, clipY);
10025 ctx.lineTo(firstPoint.x, firstPoint.y);
10026 }
10027 lineLoop = !!target.pathSegment(ctx, segment, {move: lineLoop});
10028 if (lineLoop) {
10029 ctx.closePath();
10030 } else {
10031 ctx.lineTo(lastPoint.x, clipY);
10032 }
10033 }
10034 ctx.lineTo(target.first().x, clipY);
10035 ctx.closePath();
10036 ctx.clip();
10037}
10038function getBounds(property, first, last, loop) {
10039 if (loop) {
10040 return;
10041 }
10042 let start = first[property];
10043 let end = last[property];
10044 if (property === 'angle') {
10045 start = _normalizeAngle(start);
10046 end = _normalizeAngle(end);
10047 }
10048 return {property, start, end};
10049}
10050function _getEdge(a, b, prop, fn) {
10051 if (a && b) {
10052 return fn(a[prop], b[prop]);
10053 }
10054 return a ? a[prop] : b ? b[prop] : 0;
10055}
10056function _segments(line, target, property) {
10057 const segments = line.segments;
10058 const points = line.points;
10059 const tpoints = target.points;
10060 const parts = [];
10061 for (const segment of segments) {
10062 let {start, end} = segment;
10063 end = findSegmentEnd(start, end, points);
10064 const bounds = getBounds(property, points[start], points[end], segment.loop);
10065 if (!target.segments) {
10066 parts.push({
10067 source: segment,
10068 target: bounds,
10069 start: points[start],
10070 end: points[end]
10071 });
10072 continue;
10073 }
10074 const targetSegments = _boundSegments(target, bounds);
10075 for (const tgt of targetSegments) {
10076 const subBounds = getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
10077 const fillSources = _boundSegment(segment, points, subBounds);
10078 for (const fillSource of fillSources) {
10079 parts.push({
10080 source: fillSource,
10081 target: tgt,
10082 start: {
10083 [property]: _getEdge(bounds, subBounds, 'start', Math.max)
10084 },
10085 end: {
10086 [property]: _getEdge(bounds, subBounds, 'end', Math.min)
10087 }
10088 });
10089 }
10090 }
10091 }
10092 return parts;
10093}
10094function clipBounds(ctx, scale, bounds) {
10095 const {top, bottom} = scale.chart.chartArea;
10096 const {property, start, end} = bounds || {};
10097 if (property === 'x') {
10098 ctx.beginPath();
10099 ctx.rect(start, top, end - start, bottom - top);
10100 ctx.clip();
10101 }
10102}
10103function interpolatedLineTo(ctx, target, point, property) {
10104 const interpolatedPoint = target.interpolate(point, property);
10105 if (interpolatedPoint) {
10106 ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
10107 }
10108}
10109function _fill(ctx, cfg) {
10110 const {line, target, property, color, scale} = cfg;
10111 const segments = _segments(line, target, property);
10112 for (const {source: src, target: tgt, start, end} of segments) {
10113 const {style: {backgroundColor = color} = {}} = src;
10114 const notShape = target !== true;
10115 ctx.save();
10116 ctx.fillStyle = backgroundColor;
10117 clipBounds(ctx, scale, notShape && getBounds(property, start, end));
10118 ctx.beginPath();
10119 const lineLoop = !!line.pathSegment(ctx, src);
10120 let loop;
10121 if (notShape) {
10122 if (lineLoop) {
10123 ctx.closePath();
10124 } else {
10125 interpolatedLineTo(ctx, target, end, property);
10126 }
10127 const targetLoop = !!target.pathSegment(ctx, tgt, {move: lineLoop, reverse: true});
10128 loop = lineLoop && targetLoop;
10129 if (!loop) {
10130 interpolatedLineTo(ctx, target, start, property);
10131 }
10132 }
10133 ctx.closePath();
10134 ctx.fill(loop ? 'evenodd' : 'nonzero');
10135 ctx.restore();
10136 }
10137}
10138function doFill(ctx, cfg) {
10139 const {line, target, above, below, area, scale} = cfg;
10140 const property = line._loop ? 'angle' : cfg.axis;
10141 ctx.save();
10142 if (property === 'x' && below !== above) {
10143 _clip(ctx, target, area.top);
10144 _fill(ctx, {line, target, color: above, scale, property});
10145 ctx.restore();
10146 ctx.save();
10147 _clip(ctx, target, area.bottom);
10148 }
10149 _fill(ctx, {line, target, color: below, scale, property});
10150 ctx.restore();
10151}
10152function drawfill(ctx, source, area) {
10153 const target = getTarget(source);
10154 const {line, scale, axis} = source;
10155 const lineOpts = line.options;
10156 const fillOption = lineOpts.fill;
10157 const color = lineOpts.backgroundColor;
10158 const {above = color, below = color} = fillOption || {};
10159 if (target && line.points.length) {
10160 clipArea(ctx, area);
10161 doFill(ctx, {line, target, above, below, area, scale, axis});
10162 unclipArea(ctx);
10163 }
10164}
10165var plugin_filler = {
10166 id: 'filler',
10167 afterDatasetsUpdate(chart, _args, options) {
10168 const count = (chart.data.datasets || []).length;
10169 const sources = [];
10170 let meta, i, line, source;
10171 for (i = 0; i < count; ++i) {
10172 meta = chart.getDatasetMeta(i);
10173 line = meta.dataset;
10174 source = null;
10175 if (line && line.options && line instanceof LineElement) {
10176 source = {
10177 visible: chart.isDatasetVisible(i),
10178 index: i,
10179 fill: decodeFill(line, i, count),
10180 chart,
10181 axis: meta.controller.options.indexAxis,
10182 scale: meta.vScale,
10183 line,
10184 };
10185 }
10186 meta.$filler = source;
10187 sources.push(source);
10188 }
10189 for (i = 0; i < count; ++i) {
10190 source = sources[i];
10191 if (!source || source.fill === false) {
10192 continue;
10193 }
10194 source.fill = resolveTarget(sources, i, options.propagate);
10195 }
10196 },
10197 beforeDraw(chart, _args, options) {
10198 const draw = options.drawTime === 'beforeDraw';
10199 const metasets = chart.getSortedVisibleDatasetMetas();
10200 const area = chart.chartArea;
10201 for (let i = metasets.length - 1; i >= 0; --i) {
10202 const source = metasets[i].$filler;
10203 if (!source) {
10204 continue;
10205 }
10206 source.line.updateControlPoints(area, source.axis);
10207 if (draw) {
10208 drawfill(chart.ctx, source, area);
10209 }
10210 }
10211 },
10212 beforeDatasetsDraw(chart, _args, options) {
10213 if (options.drawTime !== 'beforeDatasetsDraw') {
10214 return;
10215 }
10216 const metasets = chart.getSortedVisibleDatasetMetas();
10217 for (let i = metasets.length - 1; i >= 0; --i) {
10218 const source = metasets[i].$filler;
10219 if (source) {
10220 drawfill(chart.ctx, source, chart.chartArea);
10221 }
10222 }
10223 },
10224 beforeDatasetDraw(chart, args, options) {
10225 const source = args.meta.$filler;
10226 if (!source || source.fill === false || options.drawTime !== 'beforeDatasetDraw') {
10227 return;
10228 }
10229 drawfill(chart.ctx, source, chart.chartArea);
10230 },
10231 defaults: {
10232 propagate: true,
10233 drawTime: 'beforeDatasetDraw'
10234 }
10235};
10236
10237const getBoxSize = (labelOpts, fontSize) => {
10238 let {boxHeight = fontSize, boxWidth = fontSize} = labelOpts;
10239 if (labelOpts.usePointStyle) {
10240 boxHeight = Math.min(boxHeight, fontSize);
10241 boxWidth = Math.min(boxWidth, fontSize);
10242 }
10243 return {
10244 boxWidth,
10245 boxHeight,
10246 itemHeight: Math.max(fontSize, boxHeight)
10247 };
10248};
10249const itemsEqual = (a, b) => a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
10250class Legend extends Element {
10251 constructor(config) {
10252 super();
10253 this._added = false;
10254 this.legendHitBoxes = [];
10255 this._hoveredItem = null;
10256 this.doughnutMode = false;
10257 this.chart = config.chart;
10258 this.options = config.options;
10259 this.ctx = config.ctx;
10260 this.legendItems = undefined;
10261 this.columnSizes = undefined;
10262 this.lineWidths = undefined;
10263 this.maxHeight = undefined;
10264 this.maxWidth = undefined;
10265 this.top = undefined;
10266 this.bottom = undefined;
10267 this.left = undefined;
10268 this.right = undefined;
10269 this.height = undefined;
10270 this.width = undefined;
10271 this._margins = undefined;
10272 this.position = undefined;
10273 this.weight = undefined;
10274 this.fullSize = undefined;
10275 }
10276 update(maxWidth, maxHeight, margins) {
10277 this.maxWidth = maxWidth;
10278 this.maxHeight = maxHeight;
10279 this._margins = margins;
10280 this.setDimensions();
10281 this.buildLabels();
10282 this.fit();
10283 }
10284 setDimensions() {
10285 if (this.isHorizontal()) {
10286 this.width = this.maxWidth;
10287 this.left = this._margins.left;
10288 this.right = this.width;
10289 } else {
10290 this.height = this.maxHeight;
10291 this.top = this._margins.top;
10292 this.bottom = this.height;
10293 }
10294 }
10295 buildLabels() {
10296 const labelOpts = this.options.labels || {};
10297 let legendItems = callback(labelOpts.generateLabels, [this.chart], this) || [];
10298 if (labelOpts.filter) {
10299 legendItems = legendItems.filter((item) => labelOpts.filter(item, this.chart.data));
10300 }
10301 if (labelOpts.sort) {
10302 legendItems = legendItems.sort((a, b) => labelOpts.sort(a, b, this.chart.data));
10303 }
10304 if (this.options.reverse) {
10305 legendItems.reverse();
10306 }
10307 this.legendItems = legendItems;
10308 }
10309 fit() {
10310 const {options, ctx} = this;
10311 if (!options.display) {
10312 this.width = this.height = 0;
10313 return;
10314 }
10315 const labelOpts = options.labels;
10316 const labelFont = toFont(labelOpts.font);
10317 const fontSize = labelFont.size;
10318 const titleHeight = this._computeTitleHeight();
10319 const {boxWidth, itemHeight} = getBoxSize(labelOpts, fontSize);
10320 let width, height;
10321 ctx.font = labelFont.string;
10322 if (this.isHorizontal()) {
10323 width = this.maxWidth;
10324 height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
10325 } else {
10326 height = this.maxHeight;
10327 width = this._fitCols(titleHeight, fontSize, boxWidth, itemHeight) + 10;
10328 }
10329 this.width = Math.min(width, options.maxWidth || this.maxWidth);
10330 this.height = Math.min(height, options.maxHeight || this.maxHeight);
10331 }
10332 _fitRows(titleHeight, fontSize, boxWidth, itemHeight) {
10333 const {ctx, maxWidth, options: {labels: {padding}}} = this;
10334 const hitboxes = this.legendHitBoxes = [];
10335 const lineWidths = this.lineWidths = [0];
10336 const lineHeight = itemHeight + padding;
10337 let totalHeight = titleHeight;
10338 ctx.textAlign = 'left';
10339 ctx.textBaseline = 'middle';
10340 let row = -1;
10341 let top = -lineHeight;
10342 this.legendItems.forEach((legendItem, i) => {
10343 const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
10344 if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {
10345 totalHeight += lineHeight;
10346 lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
10347 top += lineHeight;
10348 row++;
10349 }
10350 hitboxes[i] = {left: 0, top, row, width: itemWidth, height: itemHeight};
10351 lineWidths[lineWidths.length - 1] += itemWidth + padding;
10352 });
10353 return totalHeight;
10354 }
10355 _fitCols(titleHeight, fontSize, boxWidth, itemHeight) {
10356 const {ctx, maxHeight, options: {labels: {padding}}} = this;
10357 const hitboxes = this.legendHitBoxes = [];
10358 const columnSizes = this.columnSizes = [];
10359 const heightLimit = maxHeight - titleHeight;
10360 let totalWidth = padding;
10361 let currentColWidth = 0;
10362 let currentColHeight = 0;
10363 let left = 0;
10364 let col = 0;
10365 this.legendItems.forEach((legendItem, i) => {
10366 const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
10367 if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {
10368 totalWidth += currentColWidth + padding;
10369 columnSizes.push({width: currentColWidth, height: currentColHeight});
10370 left += currentColWidth + padding;
10371 col++;
10372 currentColWidth = currentColHeight = 0;
10373 }
10374 hitboxes[i] = {left, top: currentColHeight, col, width: itemWidth, height: itemHeight};
10375 currentColWidth = Math.max(currentColWidth, itemWidth);
10376 currentColHeight += itemHeight + padding;
10377 });
10378 totalWidth += currentColWidth;
10379 columnSizes.push({width: currentColWidth, height: currentColHeight});
10380 return totalWidth;
10381 }
10382 adjustHitBoxes() {
10383 if (!this.options.display) {
10384 return;
10385 }
10386 const titleHeight = this._computeTitleHeight();
10387 const {legendHitBoxes: hitboxes, options: {align, labels: {padding}, rtl}} = this;
10388 const rtlHelper = getRtlAdapter(rtl, this.left, this.width);
10389 if (this.isHorizontal()) {
10390 let row = 0;
10391 let left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
10392 for (const hitbox of hitboxes) {
10393 if (row !== hitbox.row) {
10394 row = hitbox.row;
10395 left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
10396 }
10397 hitbox.top += this.top + titleHeight + padding;
10398 hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width);
10399 left += hitbox.width + padding;
10400 }
10401 } else {
10402 let col = 0;
10403 let top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
10404 for (const hitbox of hitboxes) {
10405 if (hitbox.col !== col) {
10406 col = hitbox.col;
10407 top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
10408 }
10409 hitbox.top = top;
10410 hitbox.left += this.left + padding;
10411 hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width);
10412 top += hitbox.height + padding;
10413 }
10414 }
10415 }
10416 isHorizontal() {
10417 return this.options.position === 'top' || this.options.position === 'bottom';
10418 }
10419 draw() {
10420 if (this.options.display) {
10421 const ctx = this.ctx;
10422 clipArea(ctx, this);
10423 this._draw();
10424 unclipArea(ctx);
10425 }
10426 }
10427 _draw() {
10428 const {options: opts, columnSizes, lineWidths, ctx} = this;
10429 const {align, labels: labelOpts} = opts;
10430 const defaultColor = defaults.color;
10431 const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
10432 const labelFont = toFont(labelOpts.font);
10433 const {color: fontColor, padding} = labelOpts;
10434 const fontSize = labelFont.size;
10435 const halfFontSize = fontSize / 2;
10436 let cursor;
10437 this.drawTitle();
10438 ctx.textAlign = rtlHelper.textAlign('left');
10439 ctx.textBaseline = 'middle';
10440 ctx.lineWidth = 0.5;
10441 ctx.font = labelFont.string;
10442 const {boxWidth, boxHeight, itemHeight} = getBoxSize(labelOpts, fontSize);
10443 const drawLegendBox = function(x, y, legendItem) {
10444 if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {
10445 return;
10446 }
10447 ctx.save();
10448 const lineWidth = valueOrDefault(legendItem.lineWidth, 1);
10449 ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor);
10450 ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt');
10451 ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0);
10452 ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter');
10453 ctx.lineWidth = lineWidth;
10454 ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor);
10455 ctx.setLineDash(valueOrDefault(legendItem.lineDash, []));
10456 if (labelOpts.usePointStyle) {
10457 const drawOptions = {
10458 radius: boxWidth * Math.SQRT2 / 2,
10459 pointStyle: legendItem.pointStyle,
10460 rotation: legendItem.rotation,
10461 borderWidth: lineWidth
10462 };
10463 const centerX = rtlHelper.xPlus(x, boxWidth / 2);
10464 const centerY = y + halfFontSize;
10465 drawPoint(ctx, drawOptions, centerX, centerY);
10466 } else {
10467 const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);
10468 const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);
10469 const borderRadius = toTRBLCorners(legendItem.borderRadius);
10470 ctx.beginPath();
10471 if (Object.values(borderRadius).some(v => v !== 0)) {
10472 addRoundedRectPath(ctx, {
10473 x: xBoxLeft,
10474 y: yBoxTop,
10475 w: boxWidth,
10476 h: boxHeight,
10477 radius: borderRadius,
10478 });
10479 } else {
10480 ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);
10481 }
10482 ctx.fill();
10483 if (lineWidth !== 0) {
10484 ctx.stroke();
10485 }
10486 }
10487 ctx.restore();
10488 };
10489 const fillText = function(x, y, legendItem) {
10490 renderText(ctx, legendItem.text, x, y + (itemHeight / 2), labelFont, {
10491 strikethrough: legendItem.hidden,
10492 textAlign: rtlHelper.textAlign(legendItem.textAlign)
10493 });
10494 };
10495 const isHorizontal = this.isHorizontal();
10496 const titleHeight = this._computeTitleHeight();
10497 if (isHorizontal) {
10498 cursor = {
10499 x: _alignStartEnd(align, this.left + padding, this.right - lineWidths[0]),
10500 y: this.top + padding + titleHeight,
10501 line: 0
10502 };
10503 } else {
10504 cursor = {
10505 x: this.left + padding,
10506 y: _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height),
10507 line: 0
10508 };
10509 }
10510 overrideTextDirection(this.ctx, opts.textDirection);
10511 const lineHeight = itemHeight + padding;
10512 this.legendItems.forEach((legendItem, i) => {
10513 ctx.strokeStyle = legendItem.fontColor || fontColor;
10514 ctx.fillStyle = legendItem.fontColor || fontColor;
10515 const textWidth = ctx.measureText(legendItem.text).width;
10516 const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
10517 const width = boxWidth + halfFontSize + textWidth;
10518 let x = cursor.x;
10519 let y = cursor.y;
10520 rtlHelper.setWidth(this.width);
10521 if (isHorizontal) {
10522 if (i > 0 && x + width + padding > this.right) {
10523 y = cursor.y += lineHeight;
10524 cursor.line++;
10525 x = cursor.x = _alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]);
10526 }
10527 } else if (i > 0 && y + lineHeight > this.bottom) {
10528 x = cursor.x = x + columnSizes[cursor.line].width + padding;
10529 cursor.line++;
10530 y = cursor.y = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height);
10531 }
10532 const realX = rtlHelper.x(x);
10533 drawLegendBox(realX, y, legendItem);
10534 x = _textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl);
10535 fillText(rtlHelper.x(x), y, legendItem);
10536 if (isHorizontal) {
10537 cursor.x += width + padding;
10538 } else {
10539 cursor.y += lineHeight;
10540 }
10541 });
10542 restoreTextDirection(this.ctx, opts.textDirection);
10543 }
10544 drawTitle() {
10545 const opts = this.options;
10546 const titleOpts = opts.title;
10547 const titleFont = toFont(titleOpts.font);
10548 const titlePadding = toPadding(titleOpts.padding);
10549 if (!titleOpts.display) {
10550 return;
10551 }
10552 const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
10553 const ctx = this.ctx;
10554 const position = titleOpts.position;
10555 const halfFontSize = titleFont.size / 2;
10556 const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;
10557 let y;
10558 let left = this.left;
10559 let maxWidth = this.width;
10560 if (this.isHorizontal()) {
10561 maxWidth = Math.max(...this.lineWidths);
10562 y = this.top + topPaddingPlusHalfFontSize;
10563 left = _alignStartEnd(opts.align, left, this.right - maxWidth);
10564 } else {
10565 const maxHeight = this.columnSizes.reduce((acc, size) => Math.max(acc, size.height), 0);
10566 y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight());
10567 }
10568 const x = _alignStartEnd(position, left, left + maxWidth);
10569 ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position));
10570 ctx.textBaseline = 'middle';
10571 ctx.strokeStyle = titleOpts.color;
10572 ctx.fillStyle = titleOpts.color;
10573 ctx.font = titleFont.string;
10574 renderText(ctx, titleOpts.text, x, y, titleFont);
10575 }
10576 _computeTitleHeight() {
10577 const titleOpts = this.options.title;
10578 const titleFont = toFont(titleOpts.font);
10579 const titlePadding = toPadding(titleOpts.padding);
10580 return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;
10581 }
10582 _getLegendItemAt(x, y) {
10583 let i, hitBox, lh;
10584 if (_isBetween(x, this.left, this.right)
10585 && _isBetween(y, this.top, this.bottom)) {
10586 lh = this.legendHitBoxes;
10587 for (i = 0; i < lh.length; ++i) {
10588 hitBox = lh[i];
10589 if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width)
10590 && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {
10591 return this.legendItems[i];
10592 }
10593 }
10594 }
10595 return null;
10596 }
10597 handleEvent(e) {
10598 const opts = this.options;
10599 if (!isListened(e.type, opts)) {
10600 return;
10601 }
10602 const hoveredItem = this._getLegendItemAt(e.x, e.y);
10603 if (e.type === 'mousemove') {
10604 const previous = this._hoveredItem;
10605 const sameItem = itemsEqual(previous, hoveredItem);
10606 if (previous && !sameItem) {
10607 callback(opts.onLeave, [e, previous, this], this);
10608 }
10609 this._hoveredItem = hoveredItem;
10610 if (hoveredItem && !sameItem) {
10611 callback(opts.onHover, [e, hoveredItem, this], this);
10612 }
10613 } else if (hoveredItem) {
10614 callback(opts.onClick, [e, hoveredItem, this], this);
10615 }
10616 }
10617}
10618function isListened(type, opts) {
10619 if (type === 'mousemove' && (opts.onHover || opts.onLeave)) {
10620 return true;
10621 }
10622 if (opts.onClick && (type === 'click' || type === 'mouseup')) {
10623 return true;
10624 }
10625 return false;
10626}
10627var plugin_legend = {
10628 id: 'legend',
10629 _element: Legend,
10630 start(chart, _args, options) {
10631 const legend = chart.legend = new Legend({ctx: chart.ctx, options, chart});
10632 layouts.configure(chart, legend, options);
10633 layouts.addBox(chart, legend);
10634 },
10635 stop(chart) {
10636 layouts.removeBox(chart, chart.legend);
10637 delete chart.legend;
10638 },
10639 beforeUpdate(chart, _args, options) {
10640 const legend = chart.legend;
10641 layouts.configure(chart, legend, options);
10642 legend.options = options;
10643 },
10644 afterUpdate(chart) {
10645 const legend = chart.legend;
10646 legend.buildLabels();
10647 legend.adjustHitBoxes();
10648 },
10649 afterEvent(chart, args) {
10650 if (!args.replay) {
10651 chart.legend.handleEvent(args.event);
10652 }
10653 },
10654 defaults: {
10655 display: true,
10656 position: 'top',
10657 align: 'center',
10658 fullSize: true,
10659 reverse: false,
10660 weight: 1000,
10661 onClick(e, legendItem, legend) {
10662 const index = legendItem.datasetIndex;
10663 const ci = legend.chart;
10664 if (ci.isDatasetVisible(index)) {
10665 ci.hide(index);
10666 legendItem.hidden = true;
10667 } else {
10668 ci.show(index);
10669 legendItem.hidden = false;
10670 }
10671 },
10672 onHover: null,
10673 onLeave: null,
10674 labels: {
10675 color: (ctx) => ctx.chart.options.color,
10676 boxWidth: 40,
10677 padding: 10,
10678 generateLabels(chart) {
10679 const datasets = chart.data.datasets;
10680 const {labels: {usePointStyle, pointStyle, textAlign, color}} = chart.legend.options;
10681 return chart._getSortedDatasetMetas().map((meta) => {
10682 const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
10683 const borderWidth = toPadding(style.borderWidth);
10684 return {
10685 text: datasets[meta.index].label,
10686 fillStyle: style.backgroundColor,
10687 fontColor: color,
10688 hidden: !meta.visible,
10689 lineCap: style.borderCapStyle,
10690 lineDash: style.borderDash,
10691 lineDashOffset: style.borderDashOffset,
10692 lineJoin: style.borderJoinStyle,
10693 lineWidth: (borderWidth.width + borderWidth.height) / 4,
10694 strokeStyle: style.borderColor,
10695 pointStyle: pointStyle || style.pointStyle,
10696 rotation: style.rotation,
10697 textAlign: textAlign || style.textAlign,
10698 borderRadius: 0,
10699 datasetIndex: meta.index
10700 };
10701 }, this);
10702 }
10703 },
10704 title: {
10705 color: (ctx) => ctx.chart.options.color,
10706 display: false,
10707 position: 'center',
10708 text: '',
10709 }
10710 },
10711 descriptors: {
10712 _scriptable: (name) => !name.startsWith('on'),
10713 labels: {
10714 _scriptable: (name) => !['generateLabels', 'filter', 'sort'].includes(name),
10715 }
10716 },
10717};
10718
10719class Title extends Element {
10720 constructor(config) {
10721 super();
10722 this.chart = config.chart;
10723 this.options = config.options;
10724 this.ctx = config.ctx;
10725 this._padding = undefined;
10726 this.top = undefined;
10727 this.bottom = undefined;
10728 this.left = undefined;
10729 this.right = undefined;
10730 this.width = undefined;
10731 this.height = undefined;
10732 this.position = undefined;
10733 this.weight = undefined;
10734 this.fullSize = undefined;
10735 }
10736 update(maxWidth, maxHeight) {
10737 const opts = this.options;
10738 this.left = 0;
10739 this.top = 0;
10740 if (!opts.display) {
10741 this.width = this.height = this.right = this.bottom = 0;
10742 return;
10743 }
10744 this.width = this.right = maxWidth;
10745 this.height = this.bottom = maxHeight;
10746 const lineCount = isArray(opts.text) ? opts.text.length : 1;
10747 this._padding = toPadding(opts.padding);
10748 const textSize = lineCount * toFont(opts.font).lineHeight + this._padding.height;
10749 if (this.isHorizontal()) {
10750 this.height = textSize;
10751 } else {
10752 this.width = textSize;
10753 }
10754 }
10755 isHorizontal() {
10756 const pos = this.options.position;
10757 return pos === 'top' || pos === 'bottom';
10758 }
10759 _drawArgs(offset) {
10760 const {top, left, bottom, right, options} = this;
10761 const align = options.align;
10762 let rotation = 0;
10763 let maxWidth, titleX, titleY;
10764 if (this.isHorizontal()) {
10765 titleX = _alignStartEnd(align, left, right);
10766 titleY = top + offset;
10767 maxWidth = right - left;
10768 } else {
10769 if (options.position === 'left') {
10770 titleX = left + offset;
10771 titleY = _alignStartEnd(align, bottom, top);
10772 rotation = PI * -0.5;
10773 } else {
10774 titleX = right - offset;
10775 titleY = _alignStartEnd(align, top, bottom);
10776 rotation = PI * 0.5;
10777 }
10778 maxWidth = bottom - top;
10779 }
10780 return {titleX, titleY, maxWidth, rotation};
10781 }
10782 draw() {
10783 const ctx = this.ctx;
10784 const opts = this.options;
10785 if (!opts.display) {
10786 return;
10787 }
10788 const fontOpts = toFont(opts.font);
10789 const lineHeight = fontOpts.lineHeight;
10790 const offset = lineHeight / 2 + this._padding.top;
10791 const {titleX, titleY, maxWidth, rotation} = this._drawArgs(offset);
10792 renderText(ctx, opts.text, 0, 0, fontOpts, {
10793 color: opts.color,
10794 maxWidth,
10795 rotation,
10796 textAlign: _toLeftRightCenter(opts.align),
10797 textBaseline: 'middle',
10798 translation: [titleX, titleY],
10799 });
10800 }
10801}
10802function createTitle(chart, titleOpts) {
10803 const title = new Title({
10804 ctx: chart.ctx,
10805 options: titleOpts,
10806 chart
10807 });
10808 layouts.configure(chart, title, titleOpts);
10809 layouts.addBox(chart, title);
10810 chart.titleBlock = title;
10811}
10812var plugin_title = {
10813 id: 'title',
10814 _element: Title,
10815 start(chart, _args, options) {
10816 createTitle(chart, options);
10817 },
10818 stop(chart) {
10819 const titleBlock = chart.titleBlock;
10820 layouts.removeBox(chart, titleBlock);
10821 delete chart.titleBlock;
10822 },
10823 beforeUpdate(chart, _args, options) {
10824 const title = chart.titleBlock;
10825 layouts.configure(chart, title, options);
10826 title.options = options;
10827 },
10828 defaults: {
10829 align: 'center',
10830 display: false,
10831 font: {
10832 weight: 'bold',
10833 },
10834 fullSize: true,
10835 padding: 10,
10836 position: 'top',
10837 text: '',
10838 weight: 2000
10839 },
10840 defaultRoutes: {
10841 color: 'color'
10842 },
10843 descriptors: {
10844 _scriptable: true,
10845 _indexable: false,
10846 },
10847};
10848
10849const map = new WeakMap();
10850var plugin_subtitle = {
10851 id: 'subtitle',
10852 start(chart, _args, options) {
10853 const title = new Title({
10854 ctx: chart.ctx,
10855 options,
10856 chart
10857 });
10858 layouts.configure(chart, title, options);
10859 layouts.addBox(chart, title);
10860 map.set(chart, title);
10861 },
10862 stop(chart) {
10863 layouts.removeBox(chart, map.get(chart));
10864 map.delete(chart);
10865 },
10866 beforeUpdate(chart, _args, options) {
10867 const title = map.get(chart);
10868 layouts.configure(chart, title, options);
10869 title.options = options;
10870 },
10871 defaults: {
10872 align: 'center',
10873 display: false,
10874 font: {
10875 weight: 'normal',
10876 },
10877 fullSize: true,
10878 padding: 0,
10879 position: 'top',
10880 text: '',
10881 weight: 1500
10882 },
10883 defaultRoutes: {
10884 color: 'color'
10885 },
10886 descriptors: {
10887 _scriptable: true,
10888 _indexable: false,
10889 },
10890};
10891
10892const positioners = {
10893 average(items) {
10894 if (!items.length) {
10895 return false;
10896 }
10897 let i, len;
10898 let x = 0;
10899 let y = 0;
10900 let count = 0;
10901 for (i = 0, len = items.length; i < len; ++i) {
10902 const el = items[i].element;
10903 if (el && el.hasValue()) {
10904 const pos = el.tooltipPosition();
10905 x += pos.x;
10906 y += pos.y;
10907 ++count;
10908 }
10909 }
10910 return {
10911 x: x / count,
10912 y: y / count
10913 };
10914 },
10915 nearest(items, eventPosition) {
10916 if (!items.length) {
10917 return false;
10918 }
10919 let x = eventPosition.x;
10920 let y = eventPosition.y;
10921 let minDistance = Number.POSITIVE_INFINITY;
10922 let i, len, nearestElement;
10923 for (i = 0, len = items.length; i < len; ++i) {
10924 const el = items[i].element;
10925 if (el && el.hasValue()) {
10926 const center = el.getCenterPoint();
10927 const d = distanceBetweenPoints(eventPosition, center);
10928 if (d < minDistance) {
10929 minDistance = d;
10930 nearestElement = el;
10931 }
10932 }
10933 }
10934 if (nearestElement) {
10935 const tp = nearestElement.tooltipPosition();
10936 x = tp.x;
10937 y = tp.y;
10938 }
10939 return {
10940 x,
10941 y
10942 };
10943 }
10944};
10945function pushOrConcat(base, toPush) {
10946 if (toPush) {
10947 if (isArray(toPush)) {
10948 Array.prototype.push.apply(base, toPush);
10949 } else {
10950 base.push(toPush);
10951 }
10952 }
10953 return base;
10954}
10955function splitNewlines(str) {
10956 if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
10957 return str.split('\n');
10958 }
10959 return str;
10960}
10961function createTooltipItem(chart, item) {
10962 const {element, datasetIndex, index} = item;
10963 const controller = chart.getDatasetMeta(datasetIndex).controller;
10964 const {label, value} = controller.getLabelAndValue(index);
10965 return {
10966 chart,
10967 label,
10968 parsed: controller.getParsed(index),
10969 raw: chart.data.datasets[datasetIndex].data[index],
10970 formattedValue: value,
10971 dataset: controller.getDataset(),
10972 dataIndex: index,
10973 datasetIndex,
10974 element
10975 };
10976}
10977function getTooltipSize(tooltip, options) {
10978 const ctx = tooltip.chart.ctx;
10979 const {body, footer, title} = tooltip;
10980 const {boxWidth, boxHeight} = options;
10981 const bodyFont = toFont(options.bodyFont);
10982 const titleFont = toFont(options.titleFont);
10983 const footerFont = toFont(options.footerFont);
10984 const titleLineCount = title.length;
10985 const footerLineCount = footer.length;
10986 const bodyLineItemCount = body.length;
10987 const padding = toPadding(options.padding);
10988 let height = padding.height;
10989 let width = 0;
10990 let combinedBodyLength = body.reduce((count, bodyItem) => count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0);
10991 combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
10992 if (titleLineCount) {
10993 height += titleLineCount * titleFont.lineHeight
10994 + (titleLineCount - 1) * options.titleSpacing
10995 + options.titleMarginBottom;
10996 }
10997 if (combinedBodyLength) {
10998 const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;
10999 height += bodyLineItemCount * bodyLineHeight
11000 + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight
11001 + (combinedBodyLength - 1) * options.bodySpacing;
11002 }
11003 if (footerLineCount) {
11004 height += options.footerMarginTop
11005 + footerLineCount * footerFont.lineHeight
11006 + (footerLineCount - 1) * options.footerSpacing;
11007 }
11008 let widthPadding = 0;
11009 const maxLineWidth = function(line) {
11010 width = Math.max(width, ctx.measureText(line).width + widthPadding);
11011 };
11012 ctx.save();
11013 ctx.font = titleFont.string;
11014 each(tooltip.title, maxLineWidth);
11015 ctx.font = bodyFont.string;
11016 each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
11017 widthPadding = options.displayColors ? (boxWidth + 2 + options.boxPadding) : 0;
11018 each(body, (bodyItem) => {
11019 each(bodyItem.before, maxLineWidth);
11020 each(bodyItem.lines, maxLineWidth);
11021 each(bodyItem.after, maxLineWidth);
11022 });
11023 widthPadding = 0;
11024 ctx.font = footerFont.string;
11025 each(tooltip.footer, maxLineWidth);
11026 ctx.restore();
11027 width += padding.width;
11028 return {width, height};
11029}
11030function determineYAlign(chart, size) {
11031 const {y, height} = size;
11032 if (y < height / 2) {
11033 return 'top';
11034 } else if (y > (chart.height - height / 2)) {
11035 return 'bottom';
11036 }
11037 return 'center';
11038}
11039function doesNotFitWithAlign(xAlign, chart, options, size) {
11040 const {x, width} = size;
11041 const caret = options.caretSize + options.caretPadding;
11042 if (xAlign === 'left' && x + width + caret > chart.width) {
11043 return true;
11044 }
11045 if (xAlign === 'right' && x - width - caret < 0) {
11046 return true;
11047 }
11048}
11049function determineXAlign(chart, options, size, yAlign) {
11050 const {x, width} = size;
11051 const {width: chartWidth, chartArea: {left, right}} = chart;
11052 let xAlign = 'center';
11053 if (yAlign === 'center') {
11054 xAlign = x <= (left + right) / 2 ? 'left' : 'right';
11055 } else if (x <= width / 2) {
11056 xAlign = 'left';
11057 } else if (x >= chartWidth - width / 2) {
11058 xAlign = 'right';
11059 }
11060 if (doesNotFitWithAlign(xAlign, chart, options, size)) {
11061 xAlign = 'center';
11062 }
11063 return xAlign;
11064}
11065function determineAlignment(chart, options, size) {
11066 const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size);
11067 return {
11068 xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign),
11069 yAlign
11070 };
11071}
11072function alignX(size, xAlign) {
11073 let {x, width} = size;
11074 if (xAlign === 'right') {
11075 x -= width;
11076 } else if (xAlign === 'center') {
11077 x -= (width / 2);
11078 }
11079 return x;
11080}
11081function alignY(size, yAlign, paddingAndSize) {
11082 let {y, height} = size;
11083 if (yAlign === 'top') {
11084 y += paddingAndSize;
11085 } else if (yAlign === 'bottom') {
11086 y -= height + paddingAndSize;
11087 } else {
11088 y -= (height / 2);
11089 }
11090 return y;
11091}
11092function getBackgroundPoint(options, size, alignment, chart) {
11093 const {caretSize, caretPadding, cornerRadius} = options;
11094 const {xAlign, yAlign} = alignment;
11095 const paddingAndSize = caretSize + caretPadding;
11096 const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(cornerRadius);
11097 let x = alignX(size, xAlign);
11098 const y = alignY(size, yAlign, paddingAndSize);
11099 if (yAlign === 'center') {
11100 if (xAlign === 'left') {
11101 x += paddingAndSize;
11102 } else if (xAlign === 'right') {
11103 x -= paddingAndSize;
11104 }
11105 } else if (xAlign === 'left') {
11106 x -= Math.max(topLeft, bottomLeft) + caretSize;
11107 } else if (xAlign === 'right') {
11108 x += Math.max(topRight, bottomRight) + caretSize;
11109 }
11110 return {
11111 x: _limitValue(x, 0, chart.width - size.width),
11112 y: _limitValue(y, 0, chart.height - size.height)
11113 };
11114}
11115function getAlignedX(tooltip, align, options) {
11116 const padding = toPadding(options.padding);
11117 return align === 'center'
11118 ? tooltip.x + tooltip.width / 2
11119 : align === 'right'
11120 ? tooltip.x + tooltip.width - padding.right
11121 : tooltip.x + padding.left;
11122}
11123function getBeforeAfterBodyLines(callback) {
11124 return pushOrConcat([], splitNewlines(callback));
11125}
11126function createTooltipContext(parent, tooltip, tooltipItems) {
11127 return createContext(parent, {
11128 tooltip,
11129 tooltipItems,
11130 type: 'tooltip'
11131 });
11132}
11133function overrideCallbacks(callbacks, context) {
11134 const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
11135 return override ? callbacks.override(override) : callbacks;
11136}
11137class Tooltip extends Element {
11138 constructor(config) {
11139 super();
11140 this.opacity = 0;
11141 this._active = [];
11142 this._eventPosition = undefined;
11143 this._size = undefined;
11144 this._cachedAnimations = undefined;
11145 this._tooltipItems = [];
11146 this.$animations = undefined;
11147 this.$context = undefined;
11148 this.chart = config.chart || config._chart;
11149 this._chart = this.chart;
11150 this.options = config.options;
11151 this.dataPoints = undefined;
11152 this.title = undefined;
11153 this.beforeBody = undefined;
11154 this.body = undefined;
11155 this.afterBody = undefined;
11156 this.footer = undefined;
11157 this.xAlign = undefined;
11158 this.yAlign = undefined;
11159 this.x = undefined;
11160 this.y = undefined;
11161 this.height = undefined;
11162 this.width = undefined;
11163 this.caretX = undefined;
11164 this.caretY = undefined;
11165 this.labelColors = undefined;
11166 this.labelPointStyles = undefined;
11167 this.labelTextColors = undefined;
11168 }
11169 initialize(options) {
11170 this.options = options;
11171 this._cachedAnimations = undefined;
11172 this.$context = undefined;
11173 }
11174 _resolveAnimations() {
11175 const cached = this._cachedAnimations;
11176 if (cached) {
11177 return cached;
11178 }
11179 const chart = this.chart;
11180 const options = this.options.setContext(this.getContext());
11181 const opts = options.enabled && chart.options.animation && options.animations;
11182 const animations = new Animations(this.chart, opts);
11183 if (opts._cacheable) {
11184 this._cachedAnimations = Object.freeze(animations);
11185 }
11186 return animations;
11187 }
11188 getContext() {
11189 return this.$context ||
11190 (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems));
11191 }
11192 getTitle(context, options) {
11193 const {callbacks} = options;
11194 const beforeTitle = callbacks.beforeTitle.apply(this, [context]);
11195 const title = callbacks.title.apply(this, [context]);
11196 const afterTitle = callbacks.afterTitle.apply(this, [context]);
11197 let lines = [];
11198 lines = pushOrConcat(lines, splitNewlines(beforeTitle));
11199 lines = pushOrConcat(lines, splitNewlines(title));
11200 lines = pushOrConcat(lines, splitNewlines(afterTitle));
11201 return lines;
11202 }
11203 getBeforeBody(tooltipItems, options) {
11204 return getBeforeAfterBodyLines(options.callbacks.beforeBody.apply(this, [tooltipItems]));
11205 }
11206 getBody(tooltipItems, options) {
11207 const {callbacks} = options;
11208 const bodyItems = [];
11209 each(tooltipItems, (context) => {
11210 const bodyItem = {
11211 before: [],
11212 lines: [],
11213 after: []
11214 };
11215 const scoped = overrideCallbacks(callbacks, context);
11216 pushOrConcat(bodyItem.before, splitNewlines(scoped.beforeLabel.call(this, context)));
11217 pushOrConcat(bodyItem.lines, scoped.label.call(this, context));
11218 pushOrConcat(bodyItem.after, splitNewlines(scoped.afterLabel.call(this, context)));
11219 bodyItems.push(bodyItem);
11220 });
11221 return bodyItems;
11222 }
11223 getAfterBody(tooltipItems, options) {
11224 return getBeforeAfterBodyLines(options.callbacks.afterBody.apply(this, [tooltipItems]));
11225 }
11226 getFooter(tooltipItems, options) {
11227 const {callbacks} = options;
11228 const beforeFooter = callbacks.beforeFooter.apply(this, [tooltipItems]);
11229 const footer = callbacks.footer.apply(this, [tooltipItems]);
11230 const afterFooter = callbacks.afterFooter.apply(this, [tooltipItems]);
11231 let lines = [];
11232 lines = pushOrConcat(lines, splitNewlines(beforeFooter));
11233 lines = pushOrConcat(lines, splitNewlines(footer));
11234 lines = pushOrConcat(lines, splitNewlines(afterFooter));
11235 return lines;
11236 }
11237 _createItems(options) {
11238 const active = this._active;
11239 const data = this.chart.data;
11240 const labelColors = [];
11241 const labelPointStyles = [];
11242 const labelTextColors = [];
11243 let tooltipItems = [];
11244 let i, len;
11245 for (i = 0, len = active.length; i < len; ++i) {
11246 tooltipItems.push(createTooltipItem(this.chart, active[i]));
11247 }
11248 if (options.filter) {
11249 tooltipItems = tooltipItems.filter((element, index, array) => options.filter(element, index, array, data));
11250 }
11251 if (options.itemSort) {
11252 tooltipItems = tooltipItems.sort((a, b) => options.itemSort(a, b, data));
11253 }
11254 each(tooltipItems, (context) => {
11255 const scoped = overrideCallbacks(options.callbacks, context);
11256 labelColors.push(scoped.labelColor.call(this, context));
11257 labelPointStyles.push(scoped.labelPointStyle.call(this, context));
11258 labelTextColors.push(scoped.labelTextColor.call(this, context));
11259 });
11260 this.labelColors = labelColors;
11261 this.labelPointStyles = labelPointStyles;
11262 this.labelTextColors = labelTextColors;
11263 this.dataPoints = tooltipItems;
11264 return tooltipItems;
11265 }
11266 update(changed, replay) {
11267 const options = this.options.setContext(this.getContext());
11268 const active = this._active;
11269 let properties;
11270 let tooltipItems = [];
11271 if (!active.length) {
11272 if (this.opacity !== 0) {
11273 properties = {
11274 opacity: 0
11275 };
11276 }
11277 } else {
11278 const position = positioners[options.position].call(this, active, this._eventPosition);
11279 tooltipItems = this._createItems(options);
11280 this.title = this.getTitle(tooltipItems, options);
11281 this.beforeBody = this.getBeforeBody(tooltipItems, options);
11282 this.body = this.getBody(tooltipItems, options);
11283 this.afterBody = this.getAfterBody(tooltipItems, options);
11284 this.footer = this.getFooter(tooltipItems, options);
11285 const size = this._size = getTooltipSize(this, options);
11286 const positionAndSize = Object.assign({}, position, size);
11287 const alignment = determineAlignment(this.chart, options, positionAndSize);
11288 const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart);
11289 this.xAlign = alignment.xAlign;
11290 this.yAlign = alignment.yAlign;
11291 properties = {
11292 opacity: 1,
11293 x: backgroundPoint.x,
11294 y: backgroundPoint.y,
11295 width: size.width,
11296 height: size.height,
11297 caretX: position.x,
11298 caretY: position.y
11299 };
11300 }
11301 this._tooltipItems = tooltipItems;
11302 this.$context = undefined;
11303 if (properties) {
11304 this._resolveAnimations().update(this, properties);
11305 }
11306 if (changed && options.external) {
11307 options.external.call(this, {chart: this.chart, tooltip: this, replay});
11308 }
11309 }
11310 drawCaret(tooltipPoint, ctx, size, options) {
11311 const caretPosition = this.getCaretPosition(tooltipPoint, size, options);
11312 ctx.lineTo(caretPosition.x1, caretPosition.y1);
11313 ctx.lineTo(caretPosition.x2, caretPosition.y2);
11314 ctx.lineTo(caretPosition.x3, caretPosition.y3);
11315 }
11316 getCaretPosition(tooltipPoint, size, options) {
11317 const {xAlign, yAlign} = this;
11318 const {caretSize, cornerRadius} = options;
11319 const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(cornerRadius);
11320 const {x: ptX, y: ptY} = tooltipPoint;
11321 const {width, height} = size;
11322 let x1, x2, x3, y1, y2, y3;
11323 if (yAlign === 'center') {
11324 y2 = ptY + (height / 2);
11325 if (xAlign === 'left') {
11326 x1 = ptX;
11327 x2 = x1 - caretSize;
11328 y1 = y2 + caretSize;
11329 y3 = y2 - caretSize;
11330 } else {
11331 x1 = ptX + width;
11332 x2 = x1 + caretSize;
11333 y1 = y2 - caretSize;
11334 y3 = y2 + caretSize;
11335 }
11336 x3 = x1;
11337 } else {
11338 if (xAlign === 'left') {
11339 x2 = ptX + Math.max(topLeft, bottomLeft) + (caretSize);
11340 } else if (xAlign === 'right') {
11341 x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize;
11342 } else {
11343 x2 = this.caretX;
11344 }
11345 if (yAlign === 'top') {
11346 y1 = ptY;
11347 y2 = y1 - caretSize;
11348 x1 = x2 - caretSize;
11349 x3 = x2 + caretSize;
11350 } else {
11351 y1 = ptY + height;
11352 y2 = y1 + caretSize;
11353 x1 = x2 + caretSize;
11354 x3 = x2 - caretSize;
11355 }
11356 y3 = y1;
11357 }
11358 return {x1, x2, x3, y1, y2, y3};
11359 }
11360 drawTitle(pt, ctx, options) {
11361 const title = this.title;
11362 const length = title.length;
11363 let titleFont, titleSpacing, i;
11364 if (length) {
11365 const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
11366 pt.x = getAlignedX(this, options.titleAlign, options);
11367 ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
11368 ctx.textBaseline = 'middle';
11369 titleFont = toFont(options.titleFont);
11370 titleSpacing = options.titleSpacing;
11371 ctx.fillStyle = options.titleColor;
11372 ctx.font = titleFont.string;
11373 for (i = 0; i < length; ++i) {
11374 ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);
11375 pt.y += titleFont.lineHeight + titleSpacing;
11376 if (i + 1 === length) {
11377 pt.y += options.titleMarginBottom - titleSpacing;
11378 }
11379 }
11380 }
11381 }
11382 _drawColorBox(ctx, pt, i, rtlHelper, options) {
11383 const labelColors = this.labelColors[i];
11384 const labelPointStyle = this.labelPointStyles[i];
11385 const {boxHeight, boxWidth, boxPadding} = options;
11386 const bodyFont = toFont(options.bodyFont);
11387 const colorX = getAlignedX(this, 'left', options);
11388 const rtlColorX = rtlHelper.x(colorX);
11389 const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;
11390 const colorY = pt.y + yOffSet;
11391 if (options.usePointStyle) {
11392 const drawOptions = {
11393 radius: Math.min(boxWidth, boxHeight) / 2,
11394 pointStyle: labelPointStyle.pointStyle,
11395 rotation: labelPointStyle.rotation,
11396 borderWidth: 1
11397 };
11398 const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;
11399 const centerY = colorY + boxHeight / 2;
11400 ctx.strokeStyle = options.multiKeyBackground;
11401 ctx.fillStyle = options.multiKeyBackground;
11402 drawPoint(ctx, drawOptions, centerX, centerY);
11403 ctx.strokeStyle = labelColors.borderColor;
11404 ctx.fillStyle = labelColors.backgroundColor;
11405 drawPoint(ctx, drawOptions, centerX, centerY);
11406 } else {
11407 ctx.lineWidth = labelColors.borderWidth || 1;
11408 ctx.strokeStyle = labelColors.borderColor;
11409 ctx.setLineDash(labelColors.borderDash || []);
11410 ctx.lineDashOffset = labelColors.borderDashOffset || 0;
11411 const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth - boxPadding);
11412 const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - boxPadding - 2);
11413 const borderRadius = toTRBLCorners(labelColors.borderRadius);
11414 if (Object.values(borderRadius).some(v => v !== 0)) {
11415 ctx.beginPath();
11416 ctx.fillStyle = options.multiKeyBackground;
11417 addRoundedRectPath(ctx, {
11418 x: outerX,
11419 y: colorY,
11420 w: boxWidth,
11421 h: boxHeight,
11422 radius: borderRadius,
11423 });
11424 ctx.fill();
11425 ctx.stroke();
11426 ctx.fillStyle = labelColors.backgroundColor;
11427 ctx.beginPath();
11428 addRoundedRectPath(ctx, {
11429 x: innerX,
11430 y: colorY + 1,
11431 w: boxWidth - 2,
11432 h: boxHeight - 2,
11433 radius: borderRadius,
11434 });
11435 ctx.fill();
11436 } else {
11437 ctx.fillStyle = options.multiKeyBackground;
11438 ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
11439 ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
11440 ctx.fillStyle = labelColors.backgroundColor;
11441 ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
11442 }
11443 }
11444 ctx.fillStyle = this.labelTextColors[i];
11445 }
11446 drawBody(pt, ctx, options) {
11447 const {body} = this;
11448 const {bodySpacing, bodyAlign, displayColors, boxHeight, boxWidth, boxPadding} = options;
11449 const bodyFont = toFont(options.bodyFont);
11450 let bodyLineHeight = bodyFont.lineHeight;
11451 let xLinePadding = 0;
11452 const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
11453 const fillLineOfText = function(line) {
11454 ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);
11455 pt.y += bodyLineHeight + bodySpacing;
11456 };
11457 const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
11458 let bodyItem, textColor, lines, i, j, ilen, jlen;
11459 ctx.textAlign = bodyAlign;
11460 ctx.textBaseline = 'middle';
11461 ctx.font = bodyFont.string;
11462 pt.x = getAlignedX(this, bodyAlignForCalculation, options);
11463 ctx.fillStyle = options.bodyColor;
11464 each(this.beforeBody, fillLineOfText);
11465 xLinePadding = displayColors && bodyAlignForCalculation !== 'right'
11466 ? bodyAlign === 'center' ? (boxWidth / 2 + boxPadding) : (boxWidth + 2 + boxPadding)
11467 : 0;
11468 for (i = 0, ilen = body.length; i < ilen; ++i) {
11469 bodyItem = body[i];
11470 textColor = this.labelTextColors[i];
11471 ctx.fillStyle = textColor;
11472 each(bodyItem.before, fillLineOfText);
11473 lines = bodyItem.lines;
11474 if (displayColors && lines.length) {
11475 this._drawColorBox(ctx, pt, i, rtlHelper, options);
11476 bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);
11477 }
11478 for (j = 0, jlen = lines.length; j < jlen; ++j) {
11479 fillLineOfText(lines[j]);
11480 bodyLineHeight = bodyFont.lineHeight;
11481 }
11482 each(bodyItem.after, fillLineOfText);
11483 }
11484 xLinePadding = 0;
11485 bodyLineHeight = bodyFont.lineHeight;
11486 each(this.afterBody, fillLineOfText);
11487 pt.y -= bodySpacing;
11488 }
11489 drawFooter(pt, ctx, options) {
11490 const footer = this.footer;
11491 const length = footer.length;
11492 let footerFont, i;
11493 if (length) {
11494 const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
11495 pt.x = getAlignedX(this, options.footerAlign, options);
11496 pt.y += options.footerMarginTop;
11497 ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
11498 ctx.textBaseline = 'middle';
11499 footerFont = toFont(options.footerFont);
11500 ctx.fillStyle = options.footerColor;
11501 ctx.font = footerFont.string;
11502 for (i = 0; i < length; ++i) {
11503 ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);
11504 pt.y += footerFont.lineHeight + options.footerSpacing;
11505 }
11506 }
11507 }
11508 drawBackground(pt, ctx, tooltipSize, options) {
11509 const {xAlign, yAlign} = this;
11510 const {x, y} = pt;
11511 const {width, height} = tooltipSize;
11512 const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(options.cornerRadius);
11513 ctx.fillStyle = options.backgroundColor;
11514 ctx.strokeStyle = options.borderColor;
11515 ctx.lineWidth = options.borderWidth;
11516 ctx.beginPath();
11517 ctx.moveTo(x + topLeft, y);
11518 if (yAlign === 'top') {
11519 this.drawCaret(pt, ctx, tooltipSize, options);
11520 }
11521 ctx.lineTo(x + width - topRight, y);
11522 ctx.quadraticCurveTo(x + width, y, x + width, y + topRight);
11523 if (yAlign === 'center' && xAlign === 'right') {
11524 this.drawCaret(pt, ctx, tooltipSize, options);
11525 }
11526 ctx.lineTo(x + width, y + height - bottomRight);
11527 ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height);
11528 if (yAlign === 'bottom') {
11529 this.drawCaret(pt, ctx, tooltipSize, options);
11530 }
11531 ctx.lineTo(x + bottomLeft, y + height);
11532 ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft);
11533 if (yAlign === 'center' && xAlign === 'left') {
11534 this.drawCaret(pt, ctx, tooltipSize, options);
11535 }
11536 ctx.lineTo(x, y + topLeft);
11537 ctx.quadraticCurveTo(x, y, x + topLeft, y);
11538 ctx.closePath();
11539 ctx.fill();
11540 if (options.borderWidth > 0) {
11541 ctx.stroke();
11542 }
11543 }
11544 _updateAnimationTarget(options) {
11545 const chart = this.chart;
11546 const anims = this.$animations;
11547 const animX = anims && anims.x;
11548 const animY = anims && anims.y;
11549 if (animX || animY) {
11550 const position = positioners[options.position].call(this, this._active, this._eventPosition);
11551 if (!position) {
11552 return;
11553 }
11554 const size = this._size = getTooltipSize(this, options);
11555 const positionAndSize = Object.assign({}, position, this._size);
11556 const alignment = determineAlignment(chart, options, positionAndSize);
11557 const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
11558 if (animX._to !== point.x || animY._to !== point.y) {
11559 this.xAlign = alignment.xAlign;
11560 this.yAlign = alignment.yAlign;
11561 this.width = size.width;
11562 this.height = size.height;
11563 this.caretX = position.x;
11564 this.caretY = position.y;
11565 this._resolveAnimations().update(this, point);
11566 }
11567 }
11568 }
11569 draw(ctx) {
11570 const options = this.options.setContext(this.getContext());
11571 let opacity = this.opacity;
11572 if (!opacity) {
11573 return;
11574 }
11575 this._updateAnimationTarget(options);
11576 const tooltipSize = {
11577 width: this.width,
11578 height: this.height
11579 };
11580 const pt = {
11581 x: this.x,
11582 y: this.y
11583 };
11584 opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;
11585 const padding = toPadding(options.padding);
11586 const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length;
11587 if (options.enabled && hasTooltipContent) {
11588 ctx.save();
11589 ctx.globalAlpha = opacity;
11590 this.drawBackground(pt, ctx, tooltipSize, options);
11591 overrideTextDirection(ctx, options.textDirection);
11592 pt.y += padding.top;
11593 this.drawTitle(pt, ctx, options);
11594 this.drawBody(pt, ctx, options);
11595 this.drawFooter(pt, ctx, options);
11596 restoreTextDirection(ctx, options.textDirection);
11597 ctx.restore();
11598 }
11599 }
11600 getActiveElements() {
11601 return this._active || [];
11602 }
11603 setActiveElements(activeElements, eventPosition) {
11604 const lastActive = this._active;
11605 const active = activeElements.map(({datasetIndex, index}) => {
11606 const meta = this.chart.getDatasetMeta(datasetIndex);
11607 if (!meta) {
11608 throw new Error('Cannot find a dataset at index ' + datasetIndex);
11609 }
11610 return {
11611 datasetIndex,
11612 element: meta.data[index],
11613 index,
11614 };
11615 });
11616 const changed = !_elementsEqual(lastActive, active);
11617 const positionChanged = this._positionChanged(active, eventPosition);
11618 if (changed || positionChanged) {
11619 this._active = active;
11620 this._eventPosition = eventPosition;
11621 this._ignoreReplayEvents = true;
11622 this.update(true);
11623 }
11624 }
11625 handleEvent(e, replay, inChartArea = true) {
11626 if (replay && this._ignoreReplayEvents) {
11627 return false;
11628 }
11629 this._ignoreReplayEvents = false;
11630 const options = this.options;
11631 const lastActive = this._active || [];
11632 const active = this._getActiveElements(e, lastActive, replay, inChartArea);
11633 const positionChanged = this._positionChanged(active, e);
11634 const changed = replay || !_elementsEqual(active, lastActive) || positionChanged;
11635 if (changed) {
11636 this._active = active;
11637 if (options.enabled || options.external) {
11638 this._eventPosition = {
11639 x: e.x,
11640 y: e.y
11641 };
11642 this.update(true, replay);
11643 }
11644 }
11645 return changed;
11646 }
11647 _getActiveElements(e, lastActive, replay, inChartArea) {
11648 const options = this.options;
11649 if (e.type === 'mouseout') {
11650 return [];
11651 }
11652 if (!inChartArea) {
11653 return lastActive;
11654 }
11655 const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay);
11656 if (options.reverse) {
11657 active.reverse();
11658 }
11659 return active;
11660 }
11661 _positionChanged(active, e) {
11662 const {caretX, caretY, options} = this;
11663 const position = positioners[options.position].call(this, active, e);
11664 return position !== false && (caretX !== position.x || caretY !== position.y);
11665 }
11666}
11667Tooltip.positioners = positioners;
11668var plugin_tooltip = {
11669 id: 'tooltip',
11670 _element: Tooltip,
11671 positioners,
11672 afterInit(chart, _args, options) {
11673 if (options) {
11674 chart.tooltip = new Tooltip({chart, options});
11675 }
11676 },
11677 beforeUpdate(chart, _args, options) {
11678 if (chart.tooltip) {
11679 chart.tooltip.initialize(options);
11680 }
11681 },
11682 reset(chart, _args, options) {
11683 if (chart.tooltip) {
11684 chart.tooltip.initialize(options);
11685 }
11686 },
11687 afterDraw(chart) {
11688 const tooltip = chart.tooltip;
11689 const args = {
11690 tooltip
11691 };
11692 if (chart.notifyPlugins('beforeTooltipDraw', args) === false) {
11693 return;
11694 }
11695 if (tooltip) {
11696 tooltip.draw(chart.ctx);
11697 }
11698 chart.notifyPlugins('afterTooltipDraw', args);
11699 },
11700 afterEvent(chart, args) {
11701 if (chart.tooltip) {
11702 const useFinalPosition = args.replay;
11703 if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) {
11704 args.changed = true;
11705 }
11706 }
11707 },
11708 defaults: {
11709 enabled: true,
11710 external: null,
11711 position: 'average',
11712 backgroundColor: 'rgba(0,0,0,0.8)',
11713 titleColor: '#fff',
11714 titleFont: {
11715 weight: 'bold',
11716 },
11717 titleSpacing: 2,
11718 titleMarginBottom: 6,
11719 titleAlign: 'left',
11720 bodyColor: '#fff',
11721 bodySpacing: 2,
11722 bodyFont: {
11723 },
11724 bodyAlign: 'left',
11725 footerColor: '#fff',
11726 footerSpacing: 2,
11727 footerMarginTop: 6,
11728 footerFont: {
11729 weight: 'bold',
11730 },
11731 footerAlign: 'left',
11732 padding: 6,
11733 caretPadding: 2,
11734 caretSize: 5,
11735 cornerRadius: 6,
11736 boxHeight: (ctx, opts) => opts.bodyFont.size,
11737 boxWidth: (ctx, opts) => opts.bodyFont.size,
11738 multiKeyBackground: '#fff',
11739 displayColors: true,
11740 boxPadding: 0,
11741 borderColor: 'rgba(0,0,0,0)',
11742 borderWidth: 0,
11743 animation: {
11744 duration: 400,
11745 easing: 'easeOutQuart',
11746 },
11747 animations: {
11748 numbers: {
11749 type: 'number',
11750 properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'],
11751 },
11752 opacity: {
11753 easing: 'linear',
11754 duration: 200
11755 }
11756 },
11757 callbacks: {
11758 beforeTitle: noop,
11759 title(tooltipItems) {
11760 if (tooltipItems.length > 0) {
11761 const item = tooltipItems[0];
11762 const labels = item.chart.data.labels;
11763 const labelCount = labels ? labels.length : 0;
11764 if (this && this.options && this.options.mode === 'dataset') {
11765 return item.dataset.label || '';
11766 } else if (item.label) {
11767 return item.label;
11768 } else if (labelCount > 0 && item.dataIndex < labelCount) {
11769 return labels[item.dataIndex];
11770 }
11771 }
11772 return '';
11773 },
11774 afterTitle: noop,
11775 beforeBody: noop,
11776 beforeLabel: noop,
11777 label(tooltipItem) {
11778 if (this && this.options && this.options.mode === 'dataset') {
11779 return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;
11780 }
11781 let label = tooltipItem.dataset.label || '';
11782 if (label) {
11783 label += ': ';
11784 }
11785 const value = tooltipItem.formattedValue;
11786 if (!isNullOrUndef(value)) {
11787 label += value;
11788 }
11789 return label;
11790 },
11791 labelColor(tooltipItem) {
11792 const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
11793 const options = meta.controller.getStyle(tooltipItem.dataIndex);
11794 return {
11795 borderColor: options.borderColor,
11796 backgroundColor: options.backgroundColor,
11797 borderWidth: options.borderWidth,
11798 borderDash: options.borderDash,
11799 borderDashOffset: options.borderDashOffset,
11800 borderRadius: 0,
11801 };
11802 },
11803 labelTextColor() {
11804 return this.options.bodyColor;
11805 },
11806 labelPointStyle(tooltipItem) {
11807 const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
11808 const options = meta.controller.getStyle(tooltipItem.dataIndex);
11809 return {
11810 pointStyle: options.pointStyle,
11811 rotation: options.rotation,
11812 };
11813 },
11814 afterLabel: noop,
11815 afterBody: noop,
11816 beforeFooter: noop,
11817 footer: noop,
11818 afterFooter: noop
11819 }
11820 },
11821 defaultRoutes: {
11822 bodyFont: 'font',
11823 footerFont: 'font',
11824 titleFont: 'font'
11825 },
11826 descriptors: {
11827 _scriptable: (name) => name !== 'filter' && name !== 'itemSort' && name !== 'external',
11828 _indexable: false,
11829 callbacks: {
11830 _scriptable: false,
11831 _indexable: false,
11832 },
11833 animation: {
11834 _fallback: false
11835 },
11836 animations: {
11837 _fallback: 'animation'
11838 }
11839 },
11840 additionalOptionScopes: ['interaction']
11841};
11842
11843var plugins = /*#__PURE__*/Object.freeze({
11844__proto__: null,
11845Decimation: plugin_decimation,
11846Filler: plugin_filler,
11847Legend: plugin_legend,
11848SubTitle: plugin_subtitle,
11849Title: plugin_title,
11850Tooltip: plugin_tooltip
11851});
11852
11853const addIfString = (labels, raw, index, addedLabels) => {
11854 if (typeof raw === 'string') {
11855 index = labels.push(raw) - 1;
11856 addedLabels.unshift({index, label: raw});
11857 } else if (isNaN(raw)) {
11858 index = null;
11859 }
11860 return index;
11861};
11862function findOrAddLabel(labels, raw, index, addedLabels) {
11863 const first = labels.indexOf(raw);
11864 if (first === -1) {
11865 return addIfString(labels, raw, index, addedLabels);
11866 }
11867 const last = labels.lastIndexOf(raw);
11868 return first !== last ? index : first;
11869}
11870const validIndex = (index, max) => index === null ? null : _limitValue(Math.round(index), 0, max);
11871class CategoryScale extends Scale {
11872 constructor(cfg) {
11873 super(cfg);
11874 this._startValue = undefined;
11875 this._valueRange = 0;
11876 this._addedLabels = [];
11877 }
11878 init(scaleOptions) {
11879 const added = this._addedLabels;
11880 if (added.length) {
11881 const labels = this.getLabels();
11882 for (const {index, label} of added) {
11883 if (labels[index] === label) {
11884 labels.splice(index, 1);
11885 }
11886 }
11887 this._addedLabels = [];
11888 }
11889 super.init(scaleOptions);
11890 }
11891 parse(raw, index) {
11892 if (isNullOrUndef(raw)) {
11893 return null;
11894 }
11895 const labels = this.getLabels();
11896 index = isFinite(index) && labels[index] === raw ? index
11897 : findOrAddLabel(labels, raw, valueOrDefault(index, raw), this._addedLabels);
11898 return validIndex(index, labels.length - 1);
11899 }
11900 determineDataLimits() {
11901 const {minDefined, maxDefined} = this.getUserBounds();
11902 let {min, max} = this.getMinMax(true);
11903 if (this.options.bounds === 'ticks') {
11904 if (!minDefined) {
11905 min = 0;
11906 }
11907 if (!maxDefined) {
11908 max = this.getLabels().length - 1;
11909 }
11910 }
11911 this.min = min;
11912 this.max = max;
11913 }
11914 buildTicks() {
11915 const min = this.min;
11916 const max = this.max;
11917 const offset = this.options.offset;
11918 const ticks = [];
11919 let labels = this.getLabels();
11920 labels = (min === 0 && max === labels.length - 1) ? labels : labels.slice(min, max + 1);
11921 this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);
11922 this._startValue = this.min - (offset ? 0.5 : 0);
11923 for (let value = min; value <= max; value++) {
11924 ticks.push({value});
11925 }
11926 return ticks;
11927 }
11928 getLabelForValue(value) {
11929 const labels = this.getLabels();
11930 if (value >= 0 && value < labels.length) {
11931 return labels[value];
11932 }
11933 return value;
11934 }
11935 configure() {
11936 super.configure();
11937 if (!this.isHorizontal()) {
11938 this._reversePixels = !this._reversePixels;
11939 }
11940 }
11941 getPixelForValue(value) {
11942 if (typeof value !== 'number') {
11943 value = this.parse(value);
11944 }
11945 return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
11946 }
11947 getPixelForTick(index) {
11948 const ticks = this.ticks;
11949 if (index < 0 || index > ticks.length - 1) {
11950 return null;
11951 }
11952 return this.getPixelForValue(ticks[index].value);
11953 }
11954 getValueForPixel(pixel) {
11955 return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange);
11956 }
11957 getBasePixel() {
11958 return this.bottom;
11959 }
11960}
11961CategoryScale.id = 'category';
11962CategoryScale.defaults = {
11963 ticks: {
11964 callback: CategoryScale.prototype.getLabelForValue
11965 }
11966};
11967
11968function generateTicks$1(generationOptions, dataRange) {
11969 const ticks = [];
11970 const MIN_SPACING = 1e-14;
11971 const {bounds, step, min, max, precision, count, maxTicks, maxDigits, includeBounds} = generationOptions;
11972 const unit = step || 1;
11973 const maxSpaces = maxTicks - 1;
11974 const {min: rmin, max: rmax} = dataRange;
11975 const minDefined = !isNullOrUndef(min);
11976 const maxDefined = !isNullOrUndef(max);
11977 const countDefined = !isNullOrUndef(count);
11978 const minSpacing = (rmax - rmin) / (maxDigits + 1);
11979 let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit;
11980 let factor, niceMin, niceMax, numSpaces;
11981 if (spacing < MIN_SPACING && !minDefined && !maxDefined) {
11982 return [{value: rmin}, {value: rmax}];
11983 }
11984 numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
11985 if (numSpaces > maxSpaces) {
11986 spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
11987 }
11988 if (!isNullOrUndef(precision)) {
11989 factor = Math.pow(10, precision);
11990 spacing = Math.ceil(spacing * factor) / factor;
11991 }
11992 if (bounds === 'ticks') {
11993 niceMin = Math.floor(rmin / spacing) * spacing;
11994 niceMax = Math.ceil(rmax / spacing) * spacing;
11995 } else {
11996 niceMin = rmin;
11997 niceMax = rmax;
11998 }
11999 if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) {
12000 numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks));
12001 spacing = (max - min) / numSpaces;
12002 niceMin = min;
12003 niceMax = max;
12004 } else if (countDefined) {
12005 niceMin = minDefined ? min : niceMin;
12006 niceMax = maxDefined ? max : niceMax;
12007 numSpaces = count - 1;
12008 spacing = (niceMax - niceMin) / numSpaces;
12009 } else {
12010 numSpaces = (niceMax - niceMin) / spacing;
12011 if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
12012 numSpaces = Math.round(numSpaces);
12013 } else {
12014 numSpaces = Math.ceil(numSpaces);
12015 }
12016 }
12017 const decimalPlaces = Math.max(
12018 _decimalPlaces(spacing),
12019 _decimalPlaces(niceMin)
12020 );
12021 factor = Math.pow(10, isNullOrUndef(precision) ? decimalPlaces : precision);
12022 niceMin = Math.round(niceMin * factor) / factor;
12023 niceMax = Math.round(niceMax * factor) / factor;
12024 let j = 0;
12025 if (minDefined) {
12026 if (includeBounds && niceMin !== min) {
12027 ticks.push({value: min});
12028 if (niceMin < min) {
12029 j++;
12030 }
12031 if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {
12032 j++;
12033 }
12034 } else if (niceMin < min) {
12035 j++;
12036 }
12037 }
12038 for (; j < numSpaces; ++j) {
12039 ticks.push({value: Math.round((niceMin + j * spacing) * factor) / factor});
12040 }
12041 if (maxDefined && includeBounds && niceMax !== max) {
12042 if (ticks.length && almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {
12043 ticks[ticks.length - 1].value = max;
12044 } else {
12045 ticks.push({value: max});
12046 }
12047 } else if (!maxDefined || niceMax === max) {
12048 ticks.push({value: niceMax});
12049 }
12050 return ticks;
12051}
12052function relativeLabelSize(value, minSpacing, {horizontal, minRotation}) {
12053 const rad = toRadians(minRotation);
12054 const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001;
12055 const length = 0.75 * minSpacing * ('' + value).length;
12056 return Math.min(minSpacing / ratio, length);
12057}
12058class LinearScaleBase extends Scale {
12059 constructor(cfg) {
12060 super(cfg);
12061 this.start = undefined;
12062 this.end = undefined;
12063 this._startValue = undefined;
12064 this._endValue = undefined;
12065 this._valueRange = 0;
12066 }
12067 parse(raw, index) {
12068 if (isNullOrUndef(raw)) {
12069 return null;
12070 }
12071 if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
12072 return null;
12073 }
12074 return +raw;
12075 }
12076 handleTickRangeOptions() {
12077 const {beginAtZero} = this.options;
12078 const {minDefined, maxDefined} = this.getUserBounds();
12079 let {min, max} = this;
12080 const setMin = v => (min = minDefined ? min : v);
12081 const setMax = v => (max = maxDefined ? max : v);
12082 if (beginAtZero) {
12083 const minSign = sign(min);
12084 const maxSign = sign(max);
12085 if (minSign < 0 && maxSign < 0) {
12086 setMax(0);
12087 } else if (minSign > 0 && maxSign > 0) {
12088 setMin(0);
12089 }
12090 }
12091 if (min === max) {
12092 let offset = 1;
12093 if (max >= Number.MAX_SAFE_INTEGER || min <= Number.MIN_SAFE_INTEGER) {
12094 offset = Math.abs(max * 0.05);
12095 }
12096 setMax(max + offset);
12097 if (!beginAtZero) {
12098 setMin(min - offset);
12099 }
12100 }
12101 this.min = min;
12102 this.max = max;
12103 }
12104 getTickLimit() {
12105 const tickOpts = this.options.ticks;
12106 let {maxTicksLimit, stepSize} = tickOpts;
12107 let maxTicks;
12108 if (stepSize) {
12109 maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1;
12110 if (maxTicks > 1000) {
12111 console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`);
12112 maxTicks = 1000;
12113 }
12114 } else {
12115 maxTicks = this.computeTickLimit();
12116 maxTicksLimit = maxTicksLimit || 11;
12117 }
12118 if (maxTicksLimit) {
12119 maxTicks = Math.min(maxTicksLimit, maxTicks);
12120 }
12121 return maxTicks;
12122 }
12123 computeTickLimit() {
12124 return Number.POSITIVE_INFINITY;
12125 }
12126 buildTicks() {
12127 const opts = this.options;
12128 const tickOpts = opts.ticks;
12129 let maxTicks = this.getTickLimit();
12130 maxTicks = Math.max(2, maxTicks);
12131 const numericGeneratorOptions = {
12132 maxTicks,
12133 bounds: opts.bounds,
12134 min: opts.min,
12135 max: opts.max,
12136 precision: tickOpts.precision,
12137 step: tickOpts.stepSize,
12138 count: tickOpts.count,
12139 maxDigits: this._maxDigits(),
12140 horizontal: this.isHorizontal(),
12141 minRotation: tickOpts.minRotation || 0,
12142 includeBounds: tickOpts.includeBounds !== false
12143 };
12144 const dataRange = this._range || this;
12145 const ticks = generateTicks$1(numericGeneratorOptions, dataRange);
12146 if (opts.bounds === 'ticks') {
12147 _setMinAndMaxByKey(ticks, this, 'value');
12148 }
12149 if (opts.reverse) {
12150 ticks.reverse();
12151 this.start = this.max;
12152 this.end = this.min;
12153 } else {
12154 this.start = this.min;
12155 this.end = this.max;
12156 }
12157 return ticks;
12158 }
12159 configure() {
12160 const ticks = this.ticks;
12161 let start = this.min;
12162 let end = this.max;
12163 super.configure();
12164 if (this.options.offset && ticks.length) {
12165 const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
12166 start -= offset;
12167 end += offset;
12168 }
12169 this._startValue = start;
12170 this._endValue = end;
12171 this._valueRange = end - start;
12172 }
12173 getLabelForValue(value) {
12174 return formatNumber(value, this.chart.options.locale, this.options.ticks.format);
12175 }
12176}
12177
12178class LinearScale extends LinearScaleBase {
12179 determineDataLimits() {
12180 const {min, max} = this.getMinMax(true);
12181 this.min = isNumberFinite(min) ? min : 0;
12182 this.max = isNumberFinite(max) ? max : 1;
12183 this.handleTickRangeOptions();
12184 }
12185 computeTickLimit() {
12186 const horizontal = this.isHorizontal();
12187 const length = horizontal ? this.width : this.height;
12188 const minRotation = toRadians(this.options.ticks.minRotation);
12189 const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001;
12190 const tickFont = this._resolveTickFontOptions(0);
12191 return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio));
12192 }
12193 getPixelForValue(value) {
12194 return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
12195 }
12196 getValueForPixel(pixel) {
12197 return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
12198 }
12199}
12200LinearScale.id = 'linear';
12201LinearScale.defaults = {
12202 ticks: {
12203 callback: Ticks.formatters.numeric
12204 }
12205};
12206
12207function isMajor(tickVal) {
12208 const remain = tickVal / (Math.pow(10, Math.floor(log10(tickVal))));
12209 return remain === 1;
12210}
12211function generateTicks(generationOptions, dataRange) {
12212 const endExp = Math.floor(log10(dataRange.max));
12213 const endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
12214 const ticks = [];
12215 let tickVal = finiteOrDefault(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min))));
12216 let exp = Math.floor(log10(tickVal));
12217 let significand = Math.floor(tickVal / Math.pow(10, exp));
12218 let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
12219 do {
12220 ticks.push({value: tickVal, major: isMajor(tickVal)});
12221 ++significand;
12222 if (significand === 10) {
12223 significand = 1;
12224 ++exp;
12225 precision = exp >= 0 ? 1 : precision;
12226 }
12227 tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
12228 } while (exp < endExp || (exp === endExp && significand < endSignificand));
12229 const lastTick = finiteOrDefault(generationOptions.max, tickVal);
12230 ticks.push({value: lastTick, major: isMajor(tickVal)});
12231 return ticks;
12232}
12233class LogarithmicScale extends Scale {
12234 constructor(cfg) {
12235 super(cfg);
12236 this.start = undefined;
12237 this.end = undefined;
12238 this._startValue = undefined;
12239 this._valueRange = 0;
12240 }
12241 parse(raw, index) {
12242 const value = LinearScaleBase.prototype.parse.apply(this, [raw, index]);
12243 if (value === 0) {
12244 this._zero = true;
12245 return undefined;
12246 }
12247 return isNumberFinite(value) && value > 0 ? value : null;
12248 }
12249 determineDataLimits() {
12250 const {min, max} = this.getMinMax(true);
12251 this.min = isNumberFinite(min) ? Math.max(0, min) : null;
12252 this.max = isNumberFinite(max) ? Math.max(0, max) : null;
12253 if (this.options.beginAtZero) {
12254 this._zero = true;
12255 }
12256 this.handleTickRangeOptions();
12257 }
12258 handleTickRangeOptions() {
12259 const {minDefined, maxDefined} = this.getUserBounds();
12260 let min = this.min;
12261 let max = this.max;
12262 const setMin = v => (min = minDefined ? min : v);
12263 const setMax = v => (max = maxDefined ? max : v);
12264 const exp = (v, m) => Math.pow(10, Math.floor(log10(v)) + m);
12265 if (min === max) {
12266 if (min <= 0) {
12267 setMin(1);
12268 setMax(10);
12269 } else {
12270 setMin(exp(min, -1));
12271 setMax(exp(max, +1));
12272 }
12273 }
12274 if (min <= 0) {
12275 setMin(exp(max, -1));
12276 }
12277 if (max <= 0) {
12278 setMax(exp(min, +1));
12279 }
12280 if (this._zero && this.min !== this._suggestedMin && min === exp(this.min, 0)) {
12281 setMin(exp(min, -1));
12282 }
12283 this.min = min;
12284 this.max = max;
12285 }
12286 buildTicks() {
12287 const opts = this.options;
12288 const generationOptions = {
12289 min: this._userMin,
12290 max: this._userMax
12291 };
12292 const ticks = generateTicks(generationOptions, this);
12293 if (opts.bounds === 'ticks') {
12294 _setMinAndMaxByKey(ticks, this, 'value');
12295 }
12296 if (opts.reverse) {
12297 ticks.reverse();
12298 this.start = this.max;
12299 this.end = this.min;
12300 } else {
12301 this.start = this.min;
12302 this.end = this.max;
12303 }
12304 return ticks;
12305 }
12306 getLabelForValue(value) {
12307 return value === undefined
12308 ? '0'
12309 : formatNumber(value, this.chart.options.locale, this.options.ticks.format);
12310 }
12311 configure() {
12312 const start = this.min;
12313 super.configure();
12314 this._startValue = log10(start);
12315 this._valueRange = log10(this.max) - log10(start);
12316 }
12317 getPixelForValue(value) {
12318 if (value === undefined || value === 0) {
12319 value = this.min;
12320 }
12321 if (value === null || isNaN(value)) {
12322 return NaN;
12323 }
12324 return this.getPixelForDecimal(value === this.min
12325 ? 0
12326 : (log10(value) - this._startValue) / this._valueRange);
12327 }
12328 getValueForPixel(pixel) {
12329 const decimal = this.getDecimalForPixel(pixel);
12330 return Math.pow(10, this._startValue + decimal * this._valueRange);
12331 }
12332}
12333LogarithmicScale.id = 'logarithmic';
12334LogarithmicScale.defaults = {
12335 ticks: {
12336 callback: Ticks.formatters.logarithmic,
12337 major: {
12338 enabled: true
12339 }
12340 }
12341};
12342
12343function getTickBackdropHeight(opts) {
12344 const tickOpts = opts.ticks;
12345 if (tickOpts.display && opts.display) {
12346 const padding = toPadding(tickOpts.backdropPadding);
12347 return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height;
12348 }
12349 return 0;
12350}
12351function measureLabelSize(ctx, font, label) {
12352 label = isArray(label) ? label : [label];
12353 return {
12354 w: _longestText(ctx, font.string, label),
12355 h: label.length * font.lineHeight
12356 };
12357}
12358function determineLimits(angle, pos, size, min, max) {
12359 if (angle === min || angle === max) {
12360 return {
12361 start: pos - (size / 2),
12362 end: pos + (size / 2)
12363 };
12364 } else if (angle < min || angle > max) {
12365 return {
12366 start: pos - size,
12367 end: pos
12368 };
12369 }
12370 return {
12371 start: pos,
12372 end: pos + size
12373 };
12374}
12375function fitWithPointLabels(scale) {
12376 const orig = {
12377 l: scale.left + scale._padding.left,
12378 r: scale.right - scale._padding.right,
12379 t: scale.top + scale._padding.top,
12380 b: scale.bottom - scale._padding.bottom
12381 };
12382 const limits = Object.assign({}, orig);
12383 const labelSizes = [];
12384 const padding = [];
12385 const valueCount = scale._pointLabels.length;
12386 const pointLabelOpts = scale.options.pointLabels;
12387 const additionalAngle = pointLabelOpts.centerPointLabels ? PI / valueCount : 0;
12388 for (let i = 0; i < valueCount; i++) {
12389 const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i));
12390 padding[i] = opts.padding;
12391 const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle);
12392 const plFont = toFont(opts.font);
12393 const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);
12394 labelSizes[i] = textSize;
12395 const angleRadians = _normalizeAngle(scale.getIndexAngle(i) + additionalAngle);
12396 const angle = Math.round(toDegrees(angleRadians));
12397 const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
12398 const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
12399 updateLimits(limits, orig, angleRadians, hLimits, vLimits);
12400 }
12401 scale.setCenterPoint(
12402 orig.l - limits.l,
12403 limits.r - orig.r,
12404 orig.t - limits.t,
12405 limits.b - orig.b
12406 );
12407 scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);
12408}
12409function updateLimits(limits, orig, angle, hLimits, vLimits) {
12410 const sin = Math.abs(Math.sin(angle));
12411 const cos = Math.abs(Math.cos(angle));
12412 let x = 0;
12413 let y = 0;
12414 if (hLimits.start < orig.l) {
12415 x = (orig.l - hLimits.start) / sin;
12416 limits.l = Math.min(limits.l, orig.l - x);
12417 } else if (hLimits.end > orig.r) {
12418 x = (hLimits.end - orig.r) / sin;
12419 limits.r = Math.max(limits.r, orig.r + x);
12420 }
12421 if (vLimits.start < orig.t) {
12422 y = (orig.t - vLimits.start) / cos;
12423 limits.t = Math.min(limits.t, orig.t - y);
12424 } else if (vLimits.end > orig.b) {
12425 y = (vLimits.end - orig.b) / cos;
12426 limits.b = Math.max(limits.b, orig.b + y);
12427 }
12428}
12429function buildPointLabelItems(scale, labelSizes, padding) {
12430 const items = [];
12431 const valueCount = scale._pointLabels.length;
12432 const opts = scale.options;
12433 const extra = getTickBackdropHeight(opts) / 2;
12434 const outerDistance = scale.drawingArea;
12435 const additionalAngle = opts.pointLabels.centerPointLabels ? PI / valueCount : 0;
12436 for (let i = 0; i < valueCount; i++) {
12437 const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + padding[i], additionalAngle);
12438 const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI)));
12439 const size = labelSizes[i];
12440 const y = yForAngle(pointLabelPosition.y, size.h, angle);
12441 const textAlign = getTextAlignForAngle(angle);
12442 const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);
12443 items.push({
12444 x: pointLabelPosition.x,
12445 y,
12446 textAlign,
12447 left,
12448 top: y,
12449 right: left + size.w,
12450 bottom: y + size.h
12451 });
12452 }
12453 return items;
12454}
12455function getTextAlignForAngle(angle) {
12456 if (angle === 0 || angle === 180) {
12457 return 'center';
12458 } else if (angle < 180) {
12459 return 'left';
12460 }
12461 return 'right';
12462}
12463function leftForTextAlign(x, w, align) {
12464 if (align === 'right') {
12465 x -= w;
12466 } else if (align === 'center') {
12467 x -= (w / 2);
12468 }
12469 return x;
12470}
12471function yForAngle(y, h, angle) {
12472 if (angle === 90 || angle === 270) {
12473 y -= (h / 2);
12474 } else if (angle > 270 || angle < 90) {
12475 y -= h;
12476 }
12477 return y;
12478}
12479function drawPointLabels(scale, labelCount) {
12480 const {ctx, options: {pointLabels}} = scale;
12481 for (let i = labelCount - 1; i >= 0; i--) {
12482 const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
12483 const plFont = toFont(optsAtIndex.font);
12484 const {x, y, textAlign, left, top, right, bottom} = scale._pointLabelItems[i];
12485 const {backdropColor} = optsAtIndex;
12486 if (!isNullOrUndef(backdropColor)) {
12487 const padding = toPadding(optsAtIndex.backdropPadding);
12488 ctx.fillStyle = backdropColor;
12489 ctx.fillRect(left - padding.left, top - padding.top, right - left + padding.width, bottom - top + padding.height);
12490 }
12491 renderText(
12492 ctx,
12493 scale._pointLabels[i],
12494 x,
12495 y + (plFont.lineHeight / 2),
12496 plFont,
12497 {
12498 color: optsAtIndex.color,
12499 textAlign: textAlign,
12500 textBaseline: 'middle'
12501 }
12502 );
12503 }
12504}
12505function pathRadiusLine(scale, radius, circular, labelCount) {
12506 const {ctx} = scale;
12507 if (circular) {
12508 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU);
12509 } else {
12510 let pointPosition = scale.getPointPosition(0, radius);
12511 ctx.moveTo(pointPosition.x, pointPosition.y);
12512 for (let i = 1; i < labelCount; i++) {
12513 pointPosition = scale.getPointPosition(i, radius);
12514 ctx.lineTo(pointPosition.x, pointPosition.y);
12515 }
12516 }
12517}
12518function drawRadiusLine(scale, gridLineOpts, radius, labelCount) {
12519 const ctx = scale.ctx;
12520 const circular = gridLineOpts.circular;
12521 const {color, lineWidth} = gridLineOpts;
12522 if ((!circular && !labelCount) || !color || !lineWidth || radius < 0) {
12523 return;
12524 }
12525 ctx.save();
12526 ctx.strokeStyle = color;
12527 ctx.lineWidth = lineWidth;
12528 ctx.setLineDash(gridLineOpts.borderDash);
12529 ctx.lineDashOffset = gridLineOpts.borderDashOffset;
12530 ctx.beginPath();
12531 pathRadiusLine(scale, radius, circular, labelCount);
12532 ctx.closePath();
12533 ctx.stroke();
12534 ctx.restore();
12535}
12536function createPointLabelContext(parent, index, label) {
12537 return createContext(parent, {
12538 label,
12539 index,
12540 type: 'pointLabel'
12541 });
12542}
12543class RadialLinearScale extends LinearScaleBase {
12544 constructor(cfg) {
12545 super(cfg);
12546 this.xCenter = undefined;
12547 this.yCenter = undefined;
12548 this.drawingArea = undefined;
12549 this._pointLabels = [];
12550 this._pointLabelItems = [];
12551 }
12552 setDimensions() {
12553 const padding = this._padding = toPadding(getTickBackdropHeight(this.options) / 2);
12554 const w = this.width = this.maxWidth - padding.width;
12555 const h = this.height = this.maxHeight - padding.height;
12556 this.xCenter = Math.floor(this.left + w / 2 + padding.left);
12557 this.yCenter = Math.floor(this.top + h / 2 + padding.top);
12558 this.drawingArea = Math.floor(Math.min(w, h) / 2);
12559 }
12560 determineDataLimits() {
12561 const {min, max} = this.getMinMax(false);
12562 this.min = isNumberFinite(min) && !isNaN(min) ? min : 0;
12563 this.max = isNumberFinite(max) && !isNaN(max) ? max : 0;
12564 this.handleTickRangeOptions();
12565 }
12566 computeTickLimit() {
12567 return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
12568 }
12569 generateTickLabels(ticks) {
12570 LinearScaleBase.prototype.generateTickLabels.call(this, ticks);
12571 this._pointLabels = this.getLabels()
12572 .map((value, index) => {
12573 const label = callback(this.options.pointLabels.callback, [value, index], this);
12574 return label || label === 0 ? label : '';
12575 })
12576 .filter((v, i) => this.chart.getDataVisibility(i));
12577 }
12578 fit() {
12579 const opts = this.options;
12580 if (opts.display && opts.pointLabels.display) {
12581 fitWithPointLabels(this);
12582 } else {
12583 this.setCenterPoint(0, 0, 0, 0);
12584 }
12585 }
12586 setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {
12587 this.xCenter += Math.floor((leftMovement - rightMovement) / 2);
12588 this.yCenter += Math.floor((topMovement - bottomMovement) / 2);
12589 this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement));
12590 }
12591 getIndexAngle(index) {
12592 const angleMultiplier = TAU / (this._pointLabels.length || 1);
12593 const startAngle = this.options.startAngle || 0;
12594 return _normalizeAngle(index * angleMultiplier + toRadians(startAngle));
12595 }
12596 getDistanceFromCenterForValue(value) {
12597 if (isNullOrUndef(value)) {
12598 return NaN;
12599 }
12600 const scalingFactor = this.drawingArea / (this.max - this.min);
12601 if (this.options.reverse) {
12602 return (this.max - value) * scalingFactor;
12603 }
12604 return (value - this.min) * scalingFactor;
12605 }
12606 getValueForDistanceFromCenter(distance) {
12607 if (isNullOrUndef(distance)) {
12608 return NaN;
12609 }
12610 const scaledDistance = distance / (this.drawingArea / (this.max - this.min));
12611 return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance;
12612 }
12613 getPointLabelContext(index) {
12614 const pointLabels = this._pointLabels || [];
12615 if (index >= 0 && index < pointLabels.length) {
12616 const pointLabel = pointLabels[index];
12617 return createPointLabelContext(this.getContext(), index, pointLabel);
12618 }
12619 }
12620 getPointPosition(index, distanceFromCenter, additionalAngle = 0) {
12621 const angle = this.getIndexAngle(index) - HALF_PI + additionalAngle;
12622 return {
12623 x: Math.cos(angle) * distanceFromCenter + this.xCenter,
12624 y: Math.sin(angle) * distanceFromCenter + this.yCenter,
12625 angle
12626 };
12627 }
12628 getPointPositionForValue(index, value) {
12629 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
12630 }
12631 getBasePosition(index) {
12632 return this.getPointPositionForValue(index || 0, this.getBaseValue());
12633 }
12634 getPointLabelPosition(index) {
12635 const {left, top, right, bottom} = this._pointLabelItems[index];
12636 return {
12637 left,
12638 top,
12639 right,
12640 bottom,
12641 };
12642 }
12643 drawBackground() {
12644 const {backgroundColor, grid: {circular}} = this.options;
12645 if (backgroundColor) {
12646 const ctx = this.ctx;
12647 ctx.save();
12648 ctx.beginPath();
12649 pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length);
12650 ctx.closePath();
12651 ctx.fillStyle = backgroundColor;
12652 ctx.fill();
12653 ctx.restore();
12654 }
12655 }
12656 drawGrid() {
12657 const ctx = this.ctx;
12658 const opts = this.options;
12659 const {angleLines, grid} = opts;
12660 const labelCount = this._pointLabels.length;
12661 let i, offset, position;
12662 if (opts.pointLabels.display) {
12663 drawPointLabels(this, labelCount);
12664 }
12665 if (grid.display) {
12666 this.ticks.forEach((tick, index) => {
12667 if (index !== 0) {
12668 offset = this.getDistanceFromCenterForValue(tick.value);
12669 const optsAtIndex = grid.setContext(this.getContext(index - 1));
12670 drawRadiusLine(this, optsAtIndex, offset, labelCount);
12671 }
12672 });
12673 }
12674 if (angleLines.display) {
12675 ctx.save();
12676 for (i = labelCount - 1; i >= 0; i--) {
12677 const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i));
12678 const {color, lineWidth} = optsAtIndex;
12679 if (!lineWidth || !color) {
12680 continue;
12681 }
12682 ctx.lineWidth = lineWidth;
12683 ctx.strokeStyle = color;
12684 ctx.setLineDash(optsAtIndex.borderDash);
12685 ctx.lineDashOffset = optsAtIndex.borderDashOffset;
12686 offset = this.getDistanceFromCenterForValue(opts.ticks.reverse ? this.min : this.max);
12687 position = this.getPointPosition(i, offset);
12688 ctx.beginPath();
12689 ctx.moveTo(this.xCenter, this.yCenter);
12690 ctx.lineTo(position.x, position.y);
12691 ctx.stroke();
12692 }
12693 ctx.restore();
12694 }
12695 }
12696 drawBorder() {}
12697 drawLabels() {
12698 const ctx = this.ctx;
12699 const opts = this.options;
12700 const tickOpts = opts.ticks;
12701 if (!tickOpts.display) {
12702 return;
12703 }
12704 const startAngle = this.getIndexAngle(0);
12705 let offset, width;
12706 ctx.save();
12707 ctx.translate(this.xCenter, this.yCenter);
12708 ctx.rotate(startAngle);
12709 ctx.textAlign = 'center';
12710 ctx.textBaseline = 'middle';
12711 this.ticks.forEach((tick, index) => {
12712 if (index === 0 && !opts.reverse) {
12713 return;
12714 }
12715 const optsAtIndex = tickOpts.setContext(this.getContext(index));
12716 const tickFont = toFont(optsAtIndex.font);
12717 offset = this.getDistanceFromCenterForValue(this.ticks[index].value);
12718 if (optsAtIndex.showLabelBackdrop) {
12719 ctx.font = tickFont.string;
12720 width = ctx.measureText(tick.label).width;
12721 ctx.fillStyle = optsAtIndex.backdropColor;
12722 const padding = toPadding(optsAtIndex.backdropPadding);
12723 ctx.fillRect(
12724 -width / 2 - padding.left,
12725 -offset - tickFont.size / 2 - padding.top,
12726 width + padding.width,
12727 tickFont.size + padding.height
12728 );
12729 }
12730 renderText(ctx, tick.label, 0, -offset, tickFont, {
12731 color: optsAtIndex.color,
12732 });
12733 });
12734 ctx.restore();
12735 }
12736 drawTitle() {}
12737}
12738RadialLinearScale.id = 'radialLinear';
12739RadialLinearScale.defaults = {
12740 display: true,
12741 animate: true,
12742 position: 'chartArea',
12743 angleLines: {
12744 display: true,
12745 lineWidth: 1,
12746 borderDash: [],
12747 borderDashOffset: 0.0
12748 },
12749 grid: {
12750 circular: false
12751 },
12752 startAngle: 0,
12753 ticks: {
12754 showLabelBackdrop: true,
12755 callback: Ticks.formatters.numeric
12756 },
12757 pointLabels: {
12758 backdropColor: undefined,
12759 backdropPadding: 2,
12760 display: true,
12761 font: {
12762 size: 10
12763 },
12764 callback(label) {
12765 return label;
12766 },
12767 padding: 5,
12768 centerPointLabels: false
12769 }
12770};
12771RadialLinearScale.defaultRoutes = {
12772 'angleLines.color': 'borderColor',
12773 'pointLabels.color': 'color',
12774 'ticks.color': 'color'
12775};
12776RadialLinearScale.descriptors = {
12777 angleLines: {
12778 _fallback: 'grid'
12779 }
12780};
12781
12782const INTERVALS = {
12783 millisecond: {common: true, size: 1, steps: 1000},
12784 second: {common: true, size: 1000, steps: 60},
12785 minute: {common: true, size: 60000, steps: 60},
12786 hour: {common: true, size: 3600000, steps: 24},
12787 day: {common: true, size: 86400000, steps: 30},
12788 week: {common: false, size: 604800000, steps: 4},
12789 month: {common: true, size: 2.628e9, steps: 12},
12790 quarter: {common: false, size: 7.884e9, steps: 4},
12791 year: {common: true, size: 3.154e10}
12792};
12793const UNITS = (Object.keys(INTERVALS));
12794function sorter(a, b) {
12795 return a - b;
12796}
12797function parse(scale, input) {
12798 if (isNullOrUndef(input)) {
12799 return null;
12800 }
12801 const adapter = scale._adapter;
12802 const {parser, round, isoWeekday} = scale._parseOpts;
12803 let value = input;
12804 if (typeof parser === 'function') {
12805 value = parser(value);
12806 }
12807 if (!isNumberFinite(value)) {
12808 value = typeof parser === 'string'
12809 ? adapter.parse(value, parser)
12810 : adapter.parse(value);
12811 }
12812 if (value === null) {
12813 return null;
12814 }
12815 if (round) {
12816 value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true)
12817 ? adapter.startOf(value, 'isoWeek', isoWeekday)
12818 : adapter.startOf(value, round);
12819 }
12820 return +value;
12821}
12822function determineUnitForAutoTicks(minUnit, min, max, capacity) {
12823 const ilen = UNITS.length;
12824 for (let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
12825 const interval = INTERVALS[UNITS[i]];
12826 const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;
12827 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
12828 return UNITS[i];
12829 }
12830 }
12831 return UNITS[ilen - 1];
12832}
12833function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
12834 for (let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) {
12835 const unit = UNITS[i];
12836 if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
12837 return unit;
12838 }
12839 }
12840 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
12841}
12842function determineMajorUnit(unit) {
12843 for (let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
12844 if (INTERVALS[UNITS[i]].common) {
12845 return UNITS[i];
12846 }
12847 }
12848}
12849function addTick(ticks, time, timestamps) {
12850 if (!timestamps) {
12851 ticks[time] = true;
12852 } else if (timestamps.length) {
12853 const {lo, hi} = _lookup(timestamps, time);
12854 const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
12855 ticks[timestamp] = true;
12856 }
12857}
12858function setMajorTicks(scale, ticks, map, majorUnit) {
12859 const adapter = scale._adapter;
12860 const first = +adapter.startOf(ticks[0].value, majorUnit);
12861 const last = ticks[ticks.length - 1].value;
12862 let major, index;
12863 for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) {
12864 index = map[major];
12865 if (index >= 0) {
12866 ticks[index].major = true;
12867 }
12868 }
12869 return ticks;
12870}
12871function ticksFromTimestamps(scale, values, majorUnit) {
12872 const ticks = [];
12873 const map = {};
12874 const ilen = values.length;
12875 let i, value;
12876 for (i = 0; i < ilen; ++i) {
12877 value = values[i];
12878 map[value] = i;
12879 ticks.push({
12880 value,
12881 major: false
12882 });
12883 }
12884 return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
12885}
12886class TimeScale extends Scale {
12887 constructor(props) {
12888 super(props);
12889 this._cache = {
12890 data: [],
12891 labels: [],
12892 all: []
12893 };
12894 this._unit = 'day';
12895 this._majorUnit = undefined;
12896 this._offsets = {};
12897 this._normalized = false;
12898 this._parseOpts = undefined;
12899 }
12900 init(scaleOpts, opts) {
12901 const time = scaleOpts.time || (scaleOpts.time = {});
12902 const adapter = this._adapter = new _adapters._date(scaleOpts.adapters.date);
12903 mergeIf(time.displayFormats, adapter.formats());
12904 this._parseOpts = {
12905 parser: time.parser,
12906 round: time.round,
12907 isoWeekday: time.isoWeekday
12908 };
12909 super.init(scaleOpts);
12910 this._normalized = opts.normalized;
12911 }
12912 parse(raw, index) {
12913 if (raw === undefined) {
12914 return null;
12915 }
12916 return parse(this, raw);
12917 }
12918 beforeLayout() {
12919 super.beforeLayout();
12920 this._cache = {
12921 data: [],
12922 labels: [],
12923 all: []
12924 };
12925 }
12926 determineDataLimits() {
12927 const options = this.options;
12928 const adapter = this._adapter;
12929 const unit = options.time.unit || 'day';
12930 let {min, max, minDefined, maxDefined} = this.getUserBounds();
12931 function _applyBounds(bounds) {
12932 if (!minDefined && !isNaN(bounds.min)) {
12933 min = Math.min(min, bounds.min);
12934 }
12935 if (!maxDefined && !isNaN(bounds.max)) {
12936 max = Math.max(max, bounds.max);
12937 }
12938 }
12939 if (!minDefined || !maxDefined) {
12940 _applyBounds(this._getLabelBounds());
12941 if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {
12942 _applyBounds(this.getMinMax(false));
12943 }
12944 }
12945 min = isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
12946 max = isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
12947 this.min = Math.min(min, max - 1);
12948 this.max = Math.max(min + 1, max);
12949 }
12950 _getLabelBounds() {
12951 const arr = this.getLabelTimestamps();
12952 let min = Number.POSITIVE_INFINITY;
12953 let max = Number.NEGATIVE_INFINITY;
12954 if (arr.length) {
12955 min = arr[0];
12956 max = arr[arr.length - 1];
12957 }
12958 return {min, max};
12959 }
12960 buildTicks() {
12961 const options = this.options;
12962 const timeOpts = options.time;
12963 const tickOpts = options.ticks;
12964 const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate();
12965 if (options.bounds === 'ticks' && timestamps.length) {
12966 this.min = this._userMin || timestamps[0];
12967 this.max = this._userMax || timestamps[timestamps.length - 1];
12968 }
12969 const min = this.min;
12970 const max = this.max;
12971 const ticks = _filterBetween(timestamps, min, max);
12972 this._unit = timeOpts.unit || (tickOpts.autoSkip
12973 ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min))
12974 : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max));
12975 this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined
12976 : determineMajorUnit(this._unit);
12977 this.initOffsets(timestamps);
12978 if (options.reverse) {
12979 ticks.reverse();
12980 }
12981 return ticksFromTimestamps(this, ticks, this._majorUnit);
12982 }
12983 initOffsets(timestamps) {
12984 let start = 0;
12985 let end = 0;
12986 let first, last;
12987 if (this.options.offset && timestamps.length) {
12988 first = this.getDecimalForValue(timestamps[0]);
12989 if (timestamps.length === 1) {
12990 start = 1 - first;
12991 } else {
12992 start = (this.getDecimalForValue(timestamps[1]) - first) / 2;
12993 }
12994 last = this.getDecimalForValue(timestamps[timestamps.length - 1]);
12995 if (timestamps.length === 1) {
12996 end = last;
12997 } else {
12998 end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
12999 }
13000 }
13001 const limit = timestamps.length < 3 ? 0.5 : 0.25;
13002 start = _limitValue(start, 0, limit);
13003 end = _limitValue(end, 0, limit);
13004 this._offsets = {start, end, factor: 1 / (start + 1 + end)};
13005 }
13006 _generate() {
13007 const adapter = this._adapter;
13008 const min = this.min;
13009 const max = this.max;
13010 const options = this.options;
13011 const timeOpts = options.time;
13012 const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min));
13013 const stepSize = valueOrDefault(timeOpts.stepSize, 1);
13014 const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
13015 const hasWeekday = isNumber(weekday) || weekday === true;
13016 const ticks = {};
13017 let first = min;
13018 let time, count;
13019 if (hasWeekday) {
13020 first = +adapter.startOf(first, 'isoWeek', weekday);
13021 }
13022 first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
13023 if (adapter.diff(max, min, minor) > 100000 * stepSize) {
13024 throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
13025 }
13026 const timestamps = options.ticks.source === 'data' && this.getDataTimestamps();
13027 for (time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++) {
13028 addTick(ticks, time, timestamps);
13029 }
13030 if (time === max || options.bounds === 'ticks' || count === 1) {
13031 addTick(ticks, time, timestamps);
13032 }
13033 return Object.keys(ticks).sort((a, b) => a - b).map(x => +x);
13034 }
13035 getLabelForValue(value) {
13036 const adapter = this._adapter;
13037 const timeOpts = this.options.time;
13038 if (timeOpts.tooltipFormat) {
13039 return adapter.format(value, timeOpts.tooltipFormat);
13040 }
13041 return adapter.format(value, timeOpts.displayFormats.datetime);
13042 }
13043 _tickFormatFunction(time, index, ticks, format) {
13044 const options = this.options;
13045 const formats = options.time.displayFormats;
13046 const unit = this._unit;
13047 const majorUnit = this._majorUnit;
13048 const minorFormat = unit && formats[unit];
13049 const majorFormat = majorUnit && formats[majorUnit];
13050 const tick = ticks[index];
13051 const major = majorUnit && majorFormat && tick && tick.major;
13052 const label = this._adapter.format(time, format || (major ? majorFormat : minorFormat));
13053 const formatter = options.ticks.callback;
13054 return formatter ? callback(formatter, [label, index, ticks], this) : label;
13055 }
13056 generateTickLabels(ticks) {
13057 let i, ilen, tick;
13058 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
13059 tick = ticks[i];
13060 tick.label = this._tickFormatFunction(tick.value, i, ticks);
13061 }
13062 }
13063 getDecimalForValue(value) {
13064 return value === null ? NaN : (value - this.min) / (this.max - this.min);
13065 }
13066 getPixelForValue(value) {
13067 const offsets = this._offsets;
13068 const pos = this.getDecimalForValue(value);
13069 return this.getPixelForDecimal((offsets.start + pos) * offsets.factor);
13070 }
13071 getValueForPixel(pixel) {
13072 const offsets = this._offsets;
13073 const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
13074 return this.min + pos * (this.max - this.min);
13075 }
13076 _getLabelSize(label) {
13077 const ticksOpts = this.options.ticks;
13078 const tickLabelWidth = this.ctx.measureText(label).width;
13079 const angle = toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
13080 const cosRotation = Math.cos(angle);
13081 const sinRotation = Math.sin(angle);
13082 const tickFontSize = this._resolveTickFontOptions(0).size;
13083 return {
13084 w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation),
13085 h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation)
13086 };
13087 }
13088 _getLabelCapacity(exampleTime) {
13089 const timeOpts = this.options.time;
13090 const displayFormats = timeOpts.displayFormats;
13091 const format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
13092 const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [exampleTime], this._majorUnit), format);
13093 const size = this._getLabelSize(exampleLabel);
13094 const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1;
13095 return capacity > 0 ? capacity : 1;
13096 }
13097 getDataTimestamps() {
13098 let timestamps = this._cache.data || [];
13099 let i, ilen;
13100 if (timestamps.length) {
13101 return timestamps;
13102 }
13103 const metas = this.getMatchingVisibleMetas();
13104 if (this._normalized && metas.length) {
13105 return (this._cache.data = metas[0].controller.getAllParsedValues(this));
13106 }
13107 for (i = 0, ilen = metas.length; i < ilen; ++i) {
13108 timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this));
13109 }
13110 return (this._cache.data = this.normalize(timestamps));
13111 }
13112 getLabelTimestamps() {
13113 const timestamps = this._cache.labels || [];
13114 let i, ilen;
13115 if (timestamps.length) {
13116 return timestamps;
13117 }
13118 const labels = this.getLabels();
13119 for (i = 0, ilen = labels.length; i < ilen; ++i) {
13120 timestamps.push(parse(this, labels[i]));
13121 }
13122 return (this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps));
13123 }
13124 normalize(values) {
13125 return _arrayUnique(values.sort(sorter));
13126 }
13127}
13128TimeScale.id = 'time';
13129TimeScale.defaults = {
13130 bounds: 'data',
13131 adapters: {},
13132 time: {
13133 parser: false,
13134 unit: false,
13135 round: false,
13136 isoWeekday: false,
13137 minUnit: 'millisecond',
13138 displayFormats: {}
13139 },
13140 ticks: {
13141 source: 'auto',
13142 major: {
13143 enabled: false
13144 }
13145 }
13146};
13147
13148function interpolate(table, val, reverse) {
13149 let lo = 0;
13150 let hi = table.length - 1;
13151 let prevSource, nextSource, prevTarget, nextTarget;
13152 if (reverse) {
13153 if (val >= table[lo].pos && val <= table[hi].pos) {
13154 ({lo, hi} = _lookupByKey(table, 'pos', val));
13155 }
13156 ({pos: prevSource, time: prevTarget} = table[lo]);
13157 ({pos: nextSource, time: nextTarget} = table[hi]);
13158 } else {
13159 if (val >= table[lo].time && val <= table[hi].time) {
13160 ({lo, hi} = _lookupByKey(table, 'time', val));
13161 }
13162 ({time: prevSource, pos: prevTarget} = table[lo]);
13163 ({time: nextSource, pos: nextTarget} = table[hi]);
13164 }
13165 const span = nextSource - prevSource;
13166 return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;
13167}
13168class TimeSeriesScale extends TimeScale {
13169 constructor(props) {
13170 super(props);
13171 this._table = [];
13172 this._minPos = undefined;
13173 this._tableRange = undefined;
13174 }
13175 initOffsets() {
13176 const timestamps = this._getTimestampsForTable();
13177 const table = this._table = this.buildLookupTable(timestamps);
13178 this._minPos = interpolate(table, this.min);
13179 this._tableRange = interpolate(table, this.max) - this._minPos;
13180 super.initOffsets(timestamps);
13181 }
13182 buildLookupTable(timestamps) {
13183 const {min, max} = this;
13184 const items = [];
13185 const table = [];
13186 let i, ilen, prev, curr, next;
13187 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
13188 curr = timestamps[i];
13189 if (curr >= min && curr <= max) {
13190 items.push(curr);
13191 }
13192 }
13193 if (items.length < 2) {
13194 return [
13195 {time: min, pos: 0},
13196 {time: max, pos: 1}
13197 ];
13198 }
13199 for (i = 0, ilen = items.length; i < ilen; ++i) {
13200 next = items[i + 1];
13201 prev = items[i - 1];
13202 curr = items[i];
13203 if (Math.round((next + prev) / 2) !== curr) {
13204 table.push({time: curr, pos: i / (ilen - 1)});
13205 }
13206 }
13207 return table;
13208 }
13209 _getTimestampsForTable() {
13210 let timestamps = this._cache.all || [];
13211 if (timestamps.length) {
13212 return timestamps;
13213 }
13214 const data = this.getDataTimestamps();
13215 const label = this.getLabelTimestamps();
13216 if (data.length && label.length) {
13217 timestamps = this.normalize(data.concat(label));
13218 } else {
13219 timestamps = data.length ? data : label;
13220 }
13221 timestamps = this._cache.all = timestamps;
13222 return timestamps;
13223 }
13224 getDecimalForValue(value) {
13225 return (interpolate(this._table, value) - this._minPos) / this._tableRange;
13226 }
13227 getValueForPixel(pixel) {
13228 const offsets = this._offsets;
13229 const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
13230 return interpolate(this._table, decimal * this._tableRange + this._minPos, true);
13231 }
13232}
13233TimeSeriesScale.id = 'timeseries';
13234TimeSeriesScale.defaults = TimeScale.defaults;
13235
13236var scales = /*#__PURE__*/Object.freeze({
13237__proto__: null,
13238CategoryScale: CategoryScale,
13239LinearScale: LinearScale,
13240LogarithmicScale: LogarithmicScale,
13241RadialLinearScale: RadialLinearScale,
13242TimeScale: TimeScale,
13243TimeSeriesScale: TimeSeriesScale
13244});
13245
13246Chart.register(controllers, scales, elements, plugins);
13247Chart.helpers = {...helpers};
13248Chart._adapters = _adapters;
13249Chart.Animation = Animation;
13250Chart.Animations = Animations;
13251Chart.animator = animator;
13252Chart.controllers = registry.controllers.items;
13253Chart.DatasetController = DatasetController;
13254Chart.Element = Element;
13255Chart.elements = elements;
13256Chart.Interaction = Interaction;
13257Chart.layouts = layouts;
13258Chart.platforms = platforms;
13259Chart.Scale = Scale;
13260Chart.Ticks = Ticks;
13261Object.assign(Chart, controllers, scales, elements, plugins, platforms);
13262Chart.Chart = Chart;
13263if (typeof window !== 'undefined') {
13264 window.Chart = Chart;
13265}
13266
13267return Chart;
13268
13269}));