tempestas

A REST API for processing sensor.community data
git clone https://git.bracken.jp/tempestas.git
Log | Files | Refs | README | LICENSE

chart.js (408236B)


      1 /*!
      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) {
      8 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
      9 typeof define === 'function' && define.amd ? define(factory) :
     10 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chart = factory());
     11 })(this, (function () { 'use strict';
     12 
     13 function fontString(pixelSize, fontStyle, fontFamily) {
     14   return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
     15 }
     16 const requestAnimFrame = (function() {
     17   if (typeof window === 'undefined') {
     18     return function(callback) {
     19       return callback();
     20     };
     21   }
     22   return window.requestAnimationFrame;
     23 }());
     24 function 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 }
     39 function 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 }
     51 const _toLeftRightCenter = (align) => align === 'start' ? 'left' : align === 'end' ? 'right' : 'center';
     52 const _alignStartEnd = (align, start, end) => align === 'start' ? start : align === 'end' ? end : (start + end) / 2;
     53 const _textX = (align, left, right, rtl) => {
     54   const check = rtl ? 'left' : 'right';
     55   return align === check ? right : align === 'center' ? (left + right) / 2 : left;
     56 };
     57 
     58 class 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 }
    193 var 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  */
    201 const 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};
    202 const hex = '0123456789ABCDEF';
    203 const h1 = (b) => hex[b & 0xF];
    204 const h2 = (b) => hex[(b & 0xF0) >> 4] + hex[b & 0xF];
    205 const eq = (b) => (((b & 0xF0) >> 4) === (b & 0xF));
    206 function isShort(v) {
    207 	return eq(v.r) && eq(v.g) && eq(v.b) && eq(v.a);
    208 }
    209 function 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 }
    231 function 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 }
    237 function round(v) {
    238 	return v + 0.5 | 0;
    239 }
    240 const lim = (v, l, h) => Math.max(Math.min(v, h), l);
    241 function p2b(v) {
    242 	return lim(round(v * 2.55), 0, 255);
    243 }
    244 function n2b(v) {
    245 	return lim(round(v * 255), 0, 255);
    246 }
    247 function b2n(v) {
    248 	return lim(round(v / 2.55) / 100, 0, 1);
    249 }
    250 function n2p(v) {
    251 	return lim(round(v * 100), 0, 100);
    252 }
    253 const RGB_RE = /^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;
    254 function 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 }
    278 function 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 }
    285 const HUE_RE = /^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;
    286 function 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 }
    291 function 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 }
    295 function 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 }
    309 function 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 }
    330 function 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 }
    337 function hsl2rgb(h, s, l) {
    338 	return calln(hsl2rgbn, h, s, l);
    339 }
    340 function hwb2rgb(h, w, b) {
    341 	return calln(hwb2rgbn, h, w, b);
    342 }
    343 function hsv2rgb(h, s, v) {
    344 	return calln(hsv2rgbn, h, s, v);
    345 }
    346 function hue(h) {
    347 	return (h % 360 + 360) % 360;
    348 }
    349 function 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 }
    376 function 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 }
    384 function 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 }
    396 const 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 };
    425 const 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 };
    575 function 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 }
    591 let names$1;
    592 function 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 }
    605 function 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 }
    615 function clone$1(v, proto) {
    616 	return v ? Object.assign(proto || {}, v) : v;
    617 }
    618 function 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 }
    633 function functionParse(str) {
    634 	if (str.charAt(0) === 'r') {
    635 		return rgbParse(str);
    636 	}
    637 	return hueParse(str);
    638 }
    639 class 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 }
    746 function index_esm(input) {
    747 	return new Color(input);
    748 }
    749 
    750 const isPatternOrGradient = (value) => value instanceof CanvasGradient || value instanceof CanvasPattern;
    751 function color(value) {
    752   return isPatternOrGradient(value) ? value : index_esm(value);
    753 }
    754 function getHoverColor(value) {
    755   return isPatternOrGradient(value)
    756     ? value
    757     : index_esm(value).saturate(0.5).darken(0.1).hexString();
    758 }
    759 
    760 function noop() {}
    761 const uid = (function() {
    762   let id = 0;
    763   return function() {
    764     return id++;
    765   };
    766 }());
    767 function isNullOrUndef(value) {
    768   return value === null || typeof value === 'undefined';
    769 }
    770 function 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 }
    780 function isObject(value) {
    781   return value !== null && Object.prototype.toString.call(value) === '[object Object]';
    782 }
    783 const isNumberFinite = (value) => (typeof value === 'number' || value instanceof Number) && isFinite(+value);
    784 function finiteOrDefault(value, defaultValue) {
    785   return isNumberFinite(value) ? value : defaultValue;
    786 }
    787 function valueOrDefault(value, defaultValue) {
    788   return typeof value === 'undefined' ? defaultValue : value;
    789 }
    790 const toPercentage = (value, dimension) =>
    791   typeof value === 'string' && value.endsWith('%') ?
    792     parseFloat(value) / 100
    793     : value / dimension;
    794 const toDimension = (value, dimension) =>
    795   typeof value === 'string' && value.endsWith('%') ?
    796     parseFloat(value) / 100 * dimension
    797     : +value;
    798 function callback(fn, args, thisArg) {
    799   if (fn && typeof fn.call === 'function') {
    800     return fn.apply(thisArg, args);
    801   }
    802 }
    803 function 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 }
    824 function _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 }
    838 function 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 }
    854 function isValidKey(key) {
    855   return ['__proto__', 'prototype', 'constructor'].indexOf(key) === -1;
    856 }
    857 function _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 }
    869 function 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 }
    889 function mergeIf(target, source) {
    890   return merge(target, source, {merger: _mergerIf});
    891 }
    892 function _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 }
    904 function _deprecated(scope, value, previous, current) {
    905   if (value !== undefined) {
    906     console.warn(scope + ': "' + previous +
    907 			'" is deprecated. Please use "' + current + '" instead');
    908   }
    909 }
    910 const emptyString = '';
    911 const dot = '.';
    912 function indexOfDotOrLength(key, start) {
    913   const idx = key.indexOf(dot, start);
    914   return idx === -1 ? key.length : idx;
    915 }
    916 function 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 }
    929 function _capitalize(str) {
    930   return str.charAt(0).toUpperCase() + str.slice(1);
    931 }
    932 const defined = (value) => typeof value !== 'undefined';
    933 const isFunction = (value) => typeof value === 'function';
    934 const 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 };
    945 function _isClickEvent(e) {
    946   return e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu';
    947 }
    948 
    949 const overrides = Object.create(null);
    950 const descriptors = Object.create(null);
    951 function 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 }
    962 function 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 }
    968 class 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 }
   1050 var 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 
   1062 const PI = Math.PI;
   1063 const TAU = 2 * PI;
   1064 const PITAU = TAU + PI;
   1065 const INFINITY = Number.POSITIVE_INFINITY;
   1066 const RAD_PER_DEG = PI / 180;
   1067 const HALF_PI = PI / 2;
   1068 const QUARTER_PI = PI / 4;
   1069 const TWO_THIRDS_PI = PI * 2 / 3;
   1070 const log10 = Math.log10;
   1071 const sign = Math.sign;
   1072 function 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 }
   1080 function _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 }
   1096 function isNumber(n) {
   1097   return !isNaN(parseFloat(n)) && isFinite(n);
   1098 }
   1099 function almostEquals(x, y, epsilon) {
   1100   return Math.abs(x - y) < epsilon;
   1101 }
   1102 function almostWhole(x, epsilon) {
   1103   const rounded = Math.round(x);
   1104   return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x);
   1105 }
   1106 function _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 }
   1116 function toRadians(degrees) {
   1117   return degrees * (PI / 180);
   1118 }
   1119 function toDegrees(radians) {
   1120   return radians * (180 / PI);
   1121 }
   1122 function _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 }
   1134 function 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 }
   1147 function distanceBetweenPoints(pt1, pt2) {
   1148   return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
   1149 }
   1150 function _angleDiff(a, b) {
   1151   return (a - b + PITAU) % TAU - PI;
   1152 }
   1153 function _normalizeAngle(a) {
   1154   return (a % TAU + TAU) % TAU;
   1155 }
   1156 function _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 }
   1167 function _limitValue(value, min, max) {
   1168   return Math.max(min, Math.min(max, value));
   1169 }
   1170 function _int16Range(value) {
   1171   return _limitValue(value, -32768, 32767);
   1172 }
   1173 function _isBetween(value, start, end, epsilon = 1e-6) {
   1174   return value >= Math.min(start, end) - epsilon && value <= Math.max(start, end) + epsilon;
   1175 }
   1176 
   1177 function 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 }
   1186 function _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 }
   1197 function _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 }
   1234 function _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 }
   1239 function 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 }
   1246 function 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 }
   1348 function _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 }
   1353 function 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 }
   1359 function unclipArea(ctx) {
   1360   ctx.restore();
   1361 }
   1362 function _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 }
   1377 function _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 }
   1389 function 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 }
   1413 function 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 }
   1430 function 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 }
   1446 function 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 
   1458 function _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 }
   1473 const _lookupByKey = (table, key, value) =>
   1474   _lookup(table, value, index => table[index][key] < value);
   1475 const _rlookupByKey = (table, key, value) =>
   1476   _lookup(table, value, index => table[index][key] >= value);
   1477 function _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 }
   1490 const arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
   1491 function 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 }
   1521 function 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 }
   1539 function _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 
   1551 function _isDomSupported() {
   1552   return typeof window !== 'undefined' && typeof document !== 'undefined';
   1553 }
   1554 function _getParentNode(domNode) {
   1555   let parent = domNode.parentNode;
   1556   if (parent && parent.toString() === '[object ShadowRoot]') {
   1557     parent = parent.host;
   1558   }
   1559   return parent;
   1560 }
   1561 function 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 }
   1573 const getComputedStyle = (element) => window.getComputedStyle(element, null);
   1574 function getStyle(el, property) {
   1575   return getComputedStyle(el).getPropertyValue(property);
   1576 }
   1577 const positions = ['top', 'right', 'bottom', 'left'];
   1578 function 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 }
   1589 const useOffsetPos = (x, y, target) => (x > 0 || y > 0) && (!target || !target.shadowRoot);
   1590 function 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 }
   1608 function 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 }
   1627 function 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 }
   1652 const round1 = v => Math.round(v * 10) / 10;
   1653 function 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 }
   1678 function 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 }
   1700 const 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 }());
   1715 function 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 
   1721 function 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 }
   1730 function 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 }
   1743 function 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 }
   1762 function 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 }
   1776 function 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 }
   1785 function 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 }
   1798 function 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 }
   1810 function 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 }
   1835 function 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 }
   1843 function 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 }
   1862 var 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 
   1918 const LINE_HEIGHT = new RegExp(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
   1919 const FONT_STYLE = new RegExp(/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/);
   1920 function 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 }
   1935 const numberOrZero = v => +v || 0;
   1936 function _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 }
   1950 function toTRBL(value) {
   1951   return _readValueToProps(value, {top: 'y', right: 'x', bottom: 'y', left: 'x'});
   1952 }
   1953 function toTRBLCorners(value) {
   1954   return _readValueToProps(value, ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']);
   1955 }
   1956 function 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 }
   1962 function 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 }
   1985 function 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 }
   2009 function _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 }
   2018 function createContext(parentContext, context) {
   2019   return Object.assign(Object.create(parentContext), context);
   2020 }
   2021 
   2022 const STATIC_POSITIONS = ['left', 'top', 'right', 'bottom'];
   2023 function filterByPosition(array, position) {
   2024   return array.filter(v => v.pos === position);
   2025 }
   2026 function filterDynamicPositionByAxis(array, axis) {
   2027   return array.filter(v => STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);
   2028 }
   2029 function 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 }
   2038 function 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 }
   2056 function 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 }
   2069 function 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 }
   2088 function 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 }
   2106 function getCombinedMax(maxPadding, chartArea, a, b) {
   2107   return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
   2108 }
   2109 function 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 }
   2115 function 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 }
   2140 function 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 }
   2152 function 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 }
   2165 function 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 }
   2185 function 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 }
   2193 function 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 }
   2233 defaults.set('layout', {
   2234   autoPadding: true,
   2235   padding: {
   2236     top: 0,
   2237     right: 0,
   2238     bottom: 0,
   2239     left: 0
   2240   }
   2241 });
   2242 var 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 
   2333 function _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 }
   2377 function _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 }
   2419 function _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 }
   2429 const readKey = (prefix, name) => prefix ? prefix + _capitalize(name) : name;
   2430 const needsSubResolver = (prop, value) => isObject(value) && prop !== 'adapters' &&
   2431   (Object.getPrototypeOf(value) === null || value.constructor === Object);
   2432 function _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 }
   2440 function _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 }
   2454 function _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 }
   2467 function _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 }
   2482 function resolveFallback(fallback, prop, value) {
   2483   return isFunction(fallback) ? fallback(prop, value) : fallback;
   2484 }
   2485 const getScope = (key, parent) => key === true ? parent
   2486   : typeof key === 'string' ? resolveObjectKey(parent, key) : undefined;
   2487 function 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 }
   2502 function 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 }
   2521 function addScopesFromKey(set, allScopes, key, fallback, item) {
   2522   while (key) {
   2523     key = addScopes(set, allScopes, key, fallback, item);
   2524   }
   2525   return key;
   2526 }
   2527 function 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 }
   2538 function _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 }
   2549 function _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 }
   2560 function getKeysFromAllScopes(target) {
   2561   let keys = target._keys;
   2562   if (!keys) {
   2563     keys = target._keys = resolveKeysFromAllScopes(target._scopes);
   2564   }
   2565   return keys;
   2566 }
   2567 function 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 
   2577 const EPSILON = Number.EPSILON || 1e-14;
   2578 const getPoint = (points, i) => i < points.length && !points[i].skip && points[i];
   2579 const getValueAxis = (indexAxis) => indexAxis === 'x' ? 'y' : 'x';
   2580 function 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 }
   2603 function 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 }
   2628 function 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 }
   2654 function 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 }
   2680 function capControlPoint(pt, min, max) {
   2681   return Math.max(Math.min(pt, max), min);
   2682 }
   2683 function 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 }
   2704 function _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 
   2733 const atEdge = (t) => t === 0 || t === 1;
   2734 const elasticIn = (t, s, p) => -(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p));
   2735 const elasticOut = (t, s, p) => Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1;
   2736 const 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 
   2816 function _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 }
   2822 function _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 }
   2830 function _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 
   2841 const intlCache = new Map();
   2842 function 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 }
   2852 function formatNumber(num, locale, options) {
   2853   return getNumberFormat(locale, options).format(num);
   2854 }
   2855 
   2856 const 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 };
   2878 const 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 };
   2896 function getRtlAdapter(rtl, rectX, width) {
   2897   return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter();
   2898 }
   2899 function 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 }
   2911 function restoreTextDirection(ctx, original) {
   2912   if (original !== undefined) {
   2913     delete ctx.prevTextDirection;
   2914     ctx.canvas.style.setProperty('direction', original[0], original[1]);
   2915   }
   2916 }
   2917 
   2918 function 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 }
   2932 function 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 }
   2940 function 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 }
   2964 function _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 }
   3005 function _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 }
   3016 function 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 }
   3037 function 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 }
   3064 function _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 }
   3080 function splitByStyles(line, segments, points, segmentOptions) {
   3081   if (!segmentOptions || !segmentOptions.setContext || !points) {
   3082     return segments;
   3083   }
   3084   return doSplitByStyles(line, segments, points, segmentOptions);
   3085 }
   3086 function 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 }
   3139 function 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 }
   3150 function styleChanged(style, prevStyle) {
   3151   return prevStyle && JSON.stringify(style) !== JSON.stringify(prevStyle);
   3152 }
   3153 
   3154 var helpers = /*#__PURE__*/Object.freeze({
   3155 __proto__: null,
   3156 easingEffects: effects,
   3157 color: color,
   3158 getHoverColor: getHoverColor,
   3159 noop: noop,
   3160 uid: uid,
   3161 isNullOrUndef: isNullOrUndef,
   3162 isArray: isArray,
   3163 isObject: isObject,
   3164 isFinite: isNumberFinite,
   3165 finiteOrDefault: finiteOrDefault,
   3166 valueOrDefault: valueOrDefault,
   3167 toPercentage: toPercentage,
   3168 toDimension: toDimension,
   3169 callback: callback,
   3170 each: each,
   3171 _elementsEqual: _elementsEqual,
   3172 clone: clone,
   3173 _merger: _merger,
   3174 merge: merge,
   3175 mergeIf: mergeIf,
   3176 _mergerIf: _mergerIf,
   3177 _deprecated: _deprecated,
   3178 resolveObjectKey: resolveObjectKey,
   3179 _capitalize: _capitalize,
   3180 defined: defined,
   3181 isFunction: isFunction,
   3182 setsEqual: setsEqual,
   3183 _isClickEvent: _isClickEvent,
   3184 toFontString: toFontString,
   3185 _measureText: _measureText,
   3186 _longestText: _longestText,
   3187 _alignPixel: _alignPixel,
   3188 clearCanvas: clearCanvas,
   3189 drawPoint: drawPoint,
   3190 _isPointInArea: _isPointInArea,
   3191 clipArea: clipArea,
   3192 unclipArea: unclipArea,
   3193 _steppedLineTo: _steppedLineTo,
   3194 _bezierCurveTo: _bezierCurveTo,
   3195 renderText: renderText,
   3196 addRoundedRectPath: addRoundedRectPath,
   3197 _lookup: _lookup,
   3198 _lookupByKey: _lookupByKey,
   3199 _rlookupByKey: _rlookupByKey,
   3200 _filterBetween: _filterBetween,
   3201 listenArrayEvents: listenArrayEvents,
   3202 unlistenArrayEvents: unlistenArrayEvents,
   3203 _arrayUnique: _arrayUnique,
   3204 _createResolver: _createResolver,
   3205 _attachContext: _attachContext,
   3206 _descriptors: _descriptors,
   3207 splineCurve: splineCurve,
   3208 splineCurveMonotone: splineCurveMonotone,
   3209 _updateBezierControlPoints: _updateBezierControlPoints,
   3210 _isDomSupported: _isDomSupported,
   3211 _getParentNode: _getParentNode,
   3212 getStyle: getStyle,
   3213 getRelativePosition: getRelativePosition$1,
   3214 getMaximumSize: getMaximumSize,
   3215 retinaScale: retinaScale,
   3216 supportsEventListenerOptions: supportsEventListenerOptions,
   3217 readUsedSize: readUsedSize,
   3218 fontString: fontString,
   3219 requestAnimFrame: requestAnimFrame,
   3220 throttled: throttled,
   3221 debounce: debounce,
   3222 _toLeftRightCenter: _toLeftRightCenter,
   3223 _alignStartEnd: _alignStartEnd,
   3224 _textX: _textX,
   3225 _pointInLine: _pointInLine,
   3226 _steppedInterpolation: _steppedInterpolation,
   3227 _bezierInterpolation: _bezierInterpolation,
   3228 formatNumber: formatNumber,
   3229 toLineHeight: toLineHeight,
   3230 _readValueToProps: _readValueToProps,
   3231 toTRBL: toTRBL,
   3232 toTRBLCorners: toTRBLCorners,
   3233 toPadding: toPadding,
   3234 toFont: toFont,
   3235 resolve: resolve,
   3236 _addGrace: _addGrace,
   3237 createContext: createContext,
   3238 PI: PI,
   3239 TAU: TAU,
   3240 PITAU: PITAU,
   3241 INFINITY: INFINITY,
   3242 RAD_PER_DEG: RAD_PER_DEG,
   3243 HALF_PI: HALF_PI,
   3244 QUARTER_PI: QUARTER_PI,
   3245 TWO_THIRDS_PI: TWO_THIRDS_PI,
   3246 log10: log10,
   3247 sign: sign,
   3248 niceNum: niceNum,
   3249 _factorize: _factorize,
   3250 isNumber: isNumber,
   3251 almostEquals: almostEquals,
   3252 almostWhole: almostWhole,
   3253 _setMinAndMaxByKey: _setMinAndMaxByKey,
   3254 toRadians: toRadians,
   3255 toDegrees: toDegrees,
   3256 _decimalPlaces: _decimalPlaces,
   3257 getAngleFromPoint: getAngleFromPoint,
   3258 distanceBetweenPoints: distanceBetweenPoints,
   3259 _angleDiff: _angleDiff,
   3260 _normalizeAngle: _normalizeAngle,
   3261 _angleBetween: _angleBetween,
   3262 _limitValue: _limitValue,
   3263 _int16Range: _int16Range,
   3264 _isBetween: _isBetween,
   3265 getRtlAdapter: getRtlAdapter,
   3266 overrideTextDirection: overrideTextDirection,
   3267 restoreTextDirection: restoreTextDirection,
   3268 _boundSegment: _boundSegment,
   3269 _boundSegments: _boundSegments,
   3270 _computeSegments: _computeSegments
   3271 });
   3272 
   3273 class 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 
   3298 class 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 
   3307 const EXPANDO_KEY = '$chartjs';
   3308 const 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 };
   3319 const isNullOrEmpty = value => value === null || value === '';
   3320 function 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 }
   3355 const eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
   3356 function addListener(node, type, listener) {
   3357   node.addEventListener(type, listener, eventListenerOptions);
   3358 }
   3359 function removeListener(chart, type, listener) {
   3360   chart.canvas.removeEventListener(type, listener, eventListenerOptions);
   3361 }
   3362 function 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 }
   3373 function nodeListContains(nodeList, canvas) {
   3374   for (const node of nodeList) {
   3375     if (node === canvas || node.contains(canvas)) {
   3376       return true;
   3377     }
   3378   }
   3379 }
   3380 function 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 }
   3395 function 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 }
   3410 const drpListeningCharts = new Map();
   3411 let oldDevicePixelRatio = 0;
   3412 function 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 }
   3424 function listenDevicePixelRatioChanges(chart, resize) {
   3425   if (!drpListeningCharts.size) {
   3426     window.addEventListener('resize', onWindowResize);
   3427   }
   3428   drpListeningCharts.set(chart, resize);
   3429 }
   3430 function unlistenDevicePixelRatioChanges(chart) {
   3431   drpListeningCharts.delete(chart);
   3432   if (!drpListeningCharts.size) {
   3433     window.removeEventListener('resize', onWindowResize);
   3434   }
   3435 }
   3436 function 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 }
   3462 function releaseObserver(chart, type, observer) {
   3463   if (observer) {
   3464     observer.disconnect();
   3465   }
   3466   if (type === 'resize') {
   3467     unlistenDevicePixelRatioChanges(chart);
   3468   }
   3469 }
   3470 function 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 }
   3483 class 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 
   3552 function _detectPlatform(canvas) {
   3553   if (!_isDomSupported() || (typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)) {
   3554     return BasicPlatform;
   3555   }
   3556   return DomPlatform;
   3557 }
   3558 
   3559 var platforms = /*#__PURE__*/Object.freeze({
   3560 __proto__: null,
   3561 _detectPlatform: _detectPlatform,
   3562 BasePlatform: BasePlatform,
   3563 BasicPlatform: BasicPlatform,
   3564 DomPlatform: DomPlatform
   3565 });
   3566 
   3567 const transparent = 'transparent';
   3568 const 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 };
   3583 class 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 
   3662 const numbers = ['x', 'y', 'borderWidth', 'radius', 'tension'];
   3663 const colors = ['color', 'borderColor', 'backgroundColor'];
   3664 defaults.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 });
   3674 const animationOptions = Object.keys(defaults.animation);
   3675 defaults.describe('animation', {
   3676   _fallback: false,
   3677   _indexable: false,
   3678   _scriptable: (name) => name !== 'onProgress' && name !== 'onComplete' && name !== 'fn',
   3679 });
   3680 defaults.set('animations', {
   3681   colors: {
   3682     type: 'color',
   3683     properties: colors
   3684   },
   3685   numbers: {
   3686     type: 'number',
   3687     properties: numbers
   3688   },
   3689 });
   3690 defaults.describe('animations', {
   3691   _fallback: 'animation',
   3692 });
   3693 defaults.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 });
   3728 class 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 }
   3818 function 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 }
   3829 function 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 
   3844 function 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 }
   3854 function 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 }
   3867 function 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 }
   3885 function 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 }
   3894 function 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 }
   3916 function 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 }
   3929 function isStacked(scale, meta) {
   3930   const stacked = scale && scale.options.stacked;
   3931   return stacked || (stacked === undefined && meta.stack !== undefined);
   3932 }
   3933 function getStackKey(indexScale, valueScale, meta) {
   3934   return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;
   3935 }
   3936 function 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 }
   3943 function getOrCreateStack(stacks, stackKey, indexValue) {
   3944   const subStack = stacks[stackKey] || (stacks[stackKey] = {});
   3945   return subStack[indexValue] || (subStack[indexValue] = {});
   3946 }
   3947 function 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 }
   3956 function 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 }
   3975 function getFirstScaleId(chart, axis) {
   3976   const scales = chart.scales;
   3977   return Object.keys(scales).filter(key => scales[key].axis === axis).shift();
   3978 }
   3979 function 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 }
   3991 function 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 }
   4003 function 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 }
   4018 const isDirectUpdateMode = (mode) => mode === 'reset' || mode === 'none';
   4019 const cloneIfNotShared = (cached, shared) => shared ? cached : Object.assign({}, cached);
   4020 const createStack = (canStack, meta, chart) => canStack && !meta.hidden && meta._stacked
   4021   && {keys: getSortedDatasetIndices(chart, true), values: null};
   4022 class 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 }
   4560 DatasetController.defaults = {};
   4561 DatasetController.prototype.datasetElementType = null;
   4562 DatasetController.prototype.dataElementType = null;
   4563 
   4564 class 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 }
   4591 Element.defaults = {};
   4592 Element.defaultRoutes = undefined;
   4593 
   4594 const 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 };
   4629 function 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 }
   4636 var Ticks = {formatters};
   4637 
   4638 defaults.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 });
   4688 defaults.route('scale.ticks', 'color', '', 'color');
   4689 defaults.route('scale.grid', 'color', '', 'borderColor');
   4690 defaults.route('scale.grid', 'borderColor', '', 'borderColor');
   4691 defaults.route('scale.title', 'color', '', 'color');
   4692 defaults.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 });
   4697 defaults.describe('scales', {
   4698   _fallback: 'scale',
   4699 });
   4700 defaults.describe('scale.ticks', {
   4701   _scriptable: (name) => name !== 'backdropPadding' && name !== 'callback',
   4702   _indexable: (name) => name !== 'backdropPadding',
   4703 });
   4704 
   4705 function 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 }
   4731 function 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 }
   4738 function 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 }
   4753 function 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 }
   4763 function 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 }
   4776 function 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 }
   4799 function 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 
   4813 const reverseAlign = (align) => align === 'left' ? 'right' : align === 'right' ? 'left' : align;
   4814 const offsetFromEdge = (scale, edge, offset) => edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
   4815 function 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 }
   4825 function 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 }
   4848 function 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 }
   4861 function getTickMarkLength(options) {
   4862   return options.drawTicks ? options.tickLength : 0;
   4863 }
   4864 function 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 }
   4873 function createScaleContext(parent, scale) {
   4874   return createContext(parent, {
   4875     scale,
   4876     type: 'scale'
   4877   });
   4878 }
   4879 function createTickContext(parent, index, tick) {
   4880   return createContext(parent, {
   4881     tick,
   4882     index,
   4883     type: 'tick'
   4884   });
   4885 }
   4886 function 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 }
   4893 function 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 }
   4927 class 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 
   5997 class 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 }
   6047 function 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 }
   6061 function 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 }
   6072 function isIChartComponent(proto) {
   6073   return 'id' in proto && 'defaults' in proto;
   6074 }
   6075 
   6076 class 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 }
   6162 var registry = new Registry();
   6163 
   6164 class 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 }
   6221 function 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 }
   6236 function getOpts(options, all) {
   6237   if (!all && options === false) {
   6238     return null;
   6239   }
   6240   if (options === true) {
   6241     return {};
   6242   }
   6243   return options;
   6244 }
   6245 function 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 }
   6262 function 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 
   6268 function 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 }
   6273 function 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 }
   6282 function getDefaultScaleIDFromAxis(axis, indexAxis) {
   6283   return axis === indexAxis ? '_index_' : '_value_';
   6284 }
   6285 function axisFromPosition(position) {
   6286   if (position === 'top' || position === 'bottom') {
   6287     return 'x';
   6288   }
   6289   if (position === 'left' || position === 'right') {
   6290     return 'y';
   6291   }
   6292 }
   6293 function 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 }
   6299 function 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 }
   6337 function initOptions(config) {
   6338   const options = config.options || (config.options = {});
   6339   options.plugins = valueOrDefault(options.plugins, {});
   6340   options.scales = mergeScaleConfig(config, options);
   6341 }
   6342 function initData(data) {
   6343   data = data || {};
   6344   data.datasets = data.datasets || [];
   6345   data.labels = data.labels || [];
   6346   return data;
   6347 }
   6348 function initConfig(config) {
   6349   config = config || {};
   6350   config.data = initData(config.data);
   6351   initOptions(config);
   6352   return config;
   6353 }
   6354 const keyCache = new Map();
   6355 const keysCached = new Set();
   6356 function 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 }
   6365 const addIfFound = (set, obj, key) => {
   6366   const opts = resolveObjectKey(obj, key);
   6367   if (opts !== undefined) {
   6368     set.add(opts);
   6369   }
   6370 };
   6371 class 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 }
   6517 function 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 }
   6535 const hasFunction = value => isObject(value)
   6536   && Object.getOwnPropertyNames(value).reduce((acc, key) => acc || isFunction(value[key]), false);
   6537 function 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 
   6551 var version = "3.7.1";
   6552 
   6553 const KNOWN_POSITIONS = ['top', 'bottom', 'left', 'right', 'chartArea'];
   6554 function positionIsHorizontal(position, axis) {
   6555   return position === 'top' || position === 'bottom' || (KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x');
   6556 }
   6557 function 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 }
   6564 function 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 }
   6570 function onAnimationProgress(context) {
   6571   const chart = context.chart;
   6572   const animationOptions = chart.options.animation;
   6573   callback(animationOptions && animationOptions.onProgress, [context], chart);
   6574 }
   6575 function 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 }
   6586 const instances = {};
   6587 const getChart = (key) => {
   6588   const canvas = getCanvas(key);
   6589   return Object.values(instances).filter((c) => c.canvas === canvas).pop();
   6590 };
   6591 function 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 }
   6604 function 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 }
   6613 class 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 }
   7377 const invalidatePlugins = () => each(Chart.instances, (chart) => chart._plugins.invalidate());
   7378 const enumerable = true;
   7379 Object.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 
   7420 function abstract() {
   7421   throw new Error('This method is not implemented: Check that a complete date adapter is provided.');
   7422 }
   7423 class 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 }
   7449 DateAdapter.override = function(members) {
   7450   Object.assign(DateAdapter.prototype, members);
   7451 };
   7452 var _adapters = {
   7453   _date: DateAdapter
   7454 };
   7455 
   7456 function 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 }
   7467 function 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 }
   7492 function 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 }
   7508 function 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 }
   7528 function 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 }
   7549 function 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 }
   7557 function 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 }
   7572 function isFloatBar(custom) {
   7573   return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
   7574 }
   7575 function 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 }
   7581 function 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 }
   7601 function 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 }
   7623 function 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 }
   7632 function swap(orig, v1, v2) {
   7633   return orig === v1 ? v2 : orig === v2 ? v1 : orig;
   7634 }
   7635 function startEnd(v, start, end) {
   7636   return v === 'start' ? start : v === 'end' ? end : v;
   7637 }
   7638 function setInflateAmount(properties, {inflateAmount}, ratio) {
   7639   properties.inflateAmount = inflateAmount === 'auto'
   7640     ? ratio === 1 ? 0.33 : 0
   7641     : inflateAmount;
   7642 }
   7643 class 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 }
   7887 BarController.id = 'bar';
   7888 BarController.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 };
   7901 BarController.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 
   7917 class 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 }
   8008 BubbleController.id = 'bubble';
   8009 BubbleController.defaults = {
   8010   datasetElementType: false,
   8011   dataElementType: 'point',
   8012   animations: {
   8013     numbers: {
   8014       type: 'number',
   8015       properties: ['x', 'y', 'borderWidth', 'radius']
   8016     }
   8017   }
   8018 };
   8019 BubbleController.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 
   8039 function 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 }
   8064 class 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 }
   8265 DoughnutController.id = 'doughnut';
   8266 DoughnutController.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 };
   8286 DoughnutController.descriptors = {
   8287   _scriptable: (name) => name !== 'spacing',
   8288   _indexable: (name) => name !== 'spacing',
   8289 };
   8290 DoughnutController.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 
   8342 class 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 }
   8426 LineController.id = 'line';
   8427 LineController.defaults = {
   8428   datasetElementType: 'line',
   8429   dataElementType: 'point',
   8430   showLine: true,
   8431   spanGaps: false,
   8432 };
   8433 LineController.overrides = {
   8434   scales: {
   8435     _index_: {
   8436       type: 'category',
   8437     },
   8438     _value_: {
   8439       type: 'linear',
   8440     },
   8441   }
   8442 };
   8443 function 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 }
   8468 function 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 
   8488 class 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 }
   8579 PolarAreaController.id = 'polarArea';
   8580 PolarAreaController.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 };
   8595 PolarAreaController.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 
   8655 class PieController extends DoughnutController {
   8656 }
   8657 PieController.id = 'pie';
   8658 PieController.defaults = {
   8659   cutout: 0,
   8660   rotation: 0,
   8661   circumference: 360,
   8662   radius: '100%'
   8663 };
   8664 
   8665 class 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 }
   8715 RadarController.id = 'radar';
   8716 RadarController.defaults = {
   8717   datasetElementType: 'line',
   8718   dataElementType: 'point',
   8719   indexAxis: 'r',
   8720   showLine: true,
   8721   elements: {
   8722     line: {
   8723       fill: 'start'
   8724     }
   8725   },
   8726 };
   8727 RadarController.overrides = {
   8728   aspectRatio: 1,
   8729   scales: {
   8730     r: {
   8731       type: 'radialLinear',
   8732     }
   8733   }
   8734 };
   8735 
   8736 class ScatterController extends LineController {
   8737 }
   8738 ScatterController.id = 'scatter';
   8739 ScatterController.defaults = {
   8740   showLine: false,
   8741   fill: false
   8742 };
   8743 ScatterController.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 
   8769 var controllers = /*#__PURE__*/Object.freeze({
   8770 __proto__: null,
   8771 BarController: BarController,
   8772 BubbleController: BubbleController,
   8773 DoughnutController: DoughnutController,
   8774 LineController: LineController,
   8775 PolarAreaController: PolarAreaController,
   8776 PieController: PieController,
   8777 RadarController: RadarController,
   8778 ScatterController: ScatterController
   8779 });
   8780 
   8781 function 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 }
   8795 function toRadiusCorners(value) {
   8796   return _readValueToProps(value, ['outerStart', 'outerEnd', 'innerStart', 'innerEnd']);
   8797 }
   8798 function 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 }
   8813 function rThetaToXY(r, theta, x, y) {
   8814   return {
   8815     x: x + r * Math.cos(theta),
   8816     y: y + r * Math.sin(theta),
   8817   };
   8818 }
   8819 function 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 }
   8870 function 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 }
   8889 function 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 }
   8908 function 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 }
   8931 class 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 }
   9009 ArcElement.id = 'arc';
   9010 ArcElement.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 };
   9020 ArcElement.defaultRoutes = {
   9021   backgroundColor: 'backgroundColor'
   9022 };
   9023 
   9024 function 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 }
   9032 function lineTo(ctx, previous, target) {
   9033   ctx.lineTo(target.x, target.y);
   9034 }
   9035 function 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 }
   9044 function 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 }
   9058 function 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 }
   9082 function 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 }
   9127 function _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 }
   9133 function _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 }
   9142 function 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 }
   9153 function 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 }
   9165 const usePath2D = typeof Path2D === 'function';
   9166 function 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 }
   9173 class 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 }
   9277 LineElement.id = 'line';
   9278 LineElement.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 };
   9291 LineElement.defaultRoutes = {
   9292   backgroundColor: 'backgroundColor',
   9293   borderColor: 'borderColor'
   9294 };
   9295 LineElement.descriptors = {
   9296   _scriptable: true,
   9297   _indexable: (name) => name !== 'borderDash' && name !== 'fill',
   9298 };
   9299 
   9300 function 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 }
   9305 class 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 }
   9353 PointElement.id = 'point';
   9354 PointElement.defaults = {
   9355   borderWidth: 1,
   9356   hitRadius: 1,
   9357   hoverBorderWidth: 1,
   9358   hoverRadius: 4,
   9359   pointStyle: 'circle',
   9360   radius: 3,
   9361   rotation: 0
   9362 };
   9363 PointElement.defaultRoutes = {
   9364   backgroundColor: 'backgroundColor',
   9365   borderColor: 'borderColor'
   9366 };
   9367 
   9368 function 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 }
   9386 function skipOrLimit(skip, value, min, max) {
   9387   return skip ? 0 : _limitValue(value, min, max);
   9388 }
   9389 function 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 }
   9400 function 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 }
   9414 function 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 }
   9442 function 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 }
   9451 function hasRadius(radius) {
   9452   return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
   9453 }
   9454 function addNormalRectPath(ctx, rect) {
   9455   ctx.rect(rect.x, rect.y, rect.w, rect.h);
   9456 }
   9457 function 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 }
   9470 class 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 }
   9522 BarElement.id = 'bar';
   9523 BarElement.defaults = {
   9524   borderSkipped: 'start',
   9525   borderWidth: 0,
   9526   borderRadius: 0,
   9527   inflateAmount: 'auto',
   9528   pointStyle: undefined
   9529 };
   9530 BarElement.defaultRoutes = {
   9531   backgroundColor: 'backgroundColor',
   9532   borderColor: 'borderColor'
   9533 };
   9534 
   9535 var elements = /*#__PURE__*/Object.freeze({
   9536 __proto__: null,
   9537 ArcElement: ArcElement,
   9538 LineElement: LineElement,
   9539 PointElement: PointElement,
   9540 BarElement: BarElement
   9541 });
   9542 
   9543 function 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 }
   9589 function 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 }
   9642 function 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 }
   9650 function cleanDecimatedData(chart) {
   9651   chart.data.datasets.forEach((dataset) => {
   9652     cleanDecimatedDataset(dataset);
   9653   });
   9654 }
   9655 function 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 }
   9671 var 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 
   9739 function getLineByIndex(chart, index) {
   9740   const meta = chart.getDatasetMeta(index);
   9741   const visible = meta && chart.isDatasetVisible(index);
   9742   return visible ? meta.dataset : null;
   9743 }
   9744 function 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 }
   9759 function 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 }
   9776 function 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 }
   9798 class 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 }
   9820 function 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 }
   9850 function computeBoundary(source) {
   9851   const scale = source.scale || {};
   9852   if (scale.getPointPositionForValue) {
   9853     return computeCircularBoundary(source);
   9854   }
   9855   return computeLinearBoundary(source);
   9856 }
   9857 function 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 }
   9866 function 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 }
   9884 function 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 }
   9899 function 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 }
   9913 function 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 }
   9932 function 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 }
   9954 function 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 }
   9971 function 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 }
   9987 function 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 }
  10011 function _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 }
  10038 function 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 }
  10050 function _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 }
  10056 function _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 }
  10094 function 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 }
  10103 function interpolatedLineTo(ctx, target, point, property) {
  10104   const interpolatedPoint = target.interpolate(point, property);
  10105   if (interpolatedPoint) {
  10106     ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
  10107   }
  10108 }
  10109 function _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 }
  10138 function 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 }
  10152 function 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 }
  10165 var 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 
  10237 const 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 };
  10249 const itemsEqual = (a, b) => a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
  10250 class 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 }
  10618 function 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 }
  10627 var 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 
  10719 class 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 }
  10802 function 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 }
  10812 var 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 
  10849 const map = new WeakMap();
  10850 var 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 
  10892 const 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 };
  10945 function 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 }
  10955 function splitNewlines(str) {
  10956   if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
  10957     return str.split('\n');
  10958   }
  10959   return str;
  10960 }
  10961 function 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 }
  10977 function 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 }
  11030 function 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 }
  11039 function 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 }
  11049 function 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 }
  11065 function 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 }
  11072 function 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 }
  11081 function 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 }
  11092 function 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 }
  11115 function 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 }
  11123 function getBeforeAfterBodyLines(callback) {
  11124   return pushOrConcat([], splitNewlines(callback));
  11125 }
  11126 function createTooltipContext(parent, tooltip, tooltipItems) {
  11127   return createContext(parent, {
  11128     tooltip,
  11129     tooltipItems,
  11130     type: 'tooltip'
  11131   });
  11132 }
  11133 function overrideCallbacks(callbacks, context) {
  11134   const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
  11135   return override ? callbacks.override(override) : callbacks;
  11136 }
  11137 class 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 }
  11667 Tooltip.positioners = positioners;
  11668 var 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 
  11843 var plugins = /*#__PURE__*/Object.freeze({
  11844 __proto__: null,
  11845 Decimation: plugin_decimation,
  11846 Filler: plugin_filler,
  11847 Legend: plugin_legend,
  11848 SubTitle: plugin_subtitle,
  11849 Title: plugin_title,
  11850 Tooltip: plugin_tooltip
  11851 });
  11852 
  11853 const 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 };
  11862 function 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 }
  11870 const validIndex = (index, max) => index === null ? null : _limitValue(Math.round(index), 0, max);
  11871 class 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 }
  11961 CategoryScale.id = 'category';
  11962 CategoryScale.defaults = {
  11963   ticks: {
  11964     callback: CategoryScale.prototype.getLabelForValue
  11965   }
  11966 };
  11967 
  11968 function 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 }
  12052 function 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 }
  12058 class 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 
  12178 class 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 }
  12200 LinearScale.id = 'linear';
  12201 LinearScale.defaults = {
  12202   ticks: {
  12203     callback: Ticks.formatters.numeric
  12204   }
  12205 };
  12206 
  12207 function isMajor(tickVal) {
  12208   const remain = tickVal / (Math.pow(10, Math.floor(log10(tickVal))));
  12209   return remain === 1;
  12210 }
  12211 function 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 }
  12233 class 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 }
  12333 LogarithmicScale.id = 'logarithmic';
  12334 LogarithmicScale.defaults = {
  12335   ticks: {
  12336     callback: Ticks.formatters.logarithmic,
  12337     major: {
  12338       enabled: true
  12339     }
  12340   }
  12341 };
  12342 
  12343 function 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 }
  12351 function 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 }
  12358 function 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 }
  12375 function 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 }
  12409 function 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 }
  12429 function 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 }
  12455 function getTextAlignForAngle(angle) {
  12456   if (angle === 0 || angle === 180) {
  12457     return 'center';
  12458   } else if (angle < 180) {
  12459     return 'left';
  12460   }
  12461   return 'right';
  12462 }
  12463 function 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 }
  12471 function 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 }
  12479 function 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 }
  12505 function 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 }
  12518 function 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 }
  12536 function createPointLabelContext(parent, index, label) {
  12537   return createContext(parent, {
  12538     label,
  12539     index,
  12540     type: 'pointLabel'
  12541   });
  12542 }
  12543 class 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 }
  12738 RadialLinearScale.id = 'radialLinear';
  12739 RadialLinearScale.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 };
  12771 RadialLinearScale.defaultRoutes = {
  12772   'angleLines.color': 'borderColor',
  12773   'pointLabels.color': 'color',
  12774   'ticks.color': 'color'
  12775 };
  12776 RadialLinearScale.descriptors = {
  12777   angleLines: {
  12778     _fallback: 'grid'
  12779   }
  12780 };
  12781 
  12782 const 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 };
  12793 const UNITS = (Object.keys(INTERVALS));
  12794 function sorter(a, b) {
  12795   return a - b;
  12796 }
  12797 function 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 }
  12822 function 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 }
  12833 function 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 }
  12842 function 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 }
  12849 function 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 }
  12858 function 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 }
  12871 function 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 }
  12886 class 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 }
  13128 TimeScale.id = 'time';
  13129 TimeScale.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 
  13148 function 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 }
  13168 class 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 }
  13233 TimeSeriesScale.id = 'timeseries';
  13234 TimeSeriesScale.defaults = TimeScale.defaults;
  13235 
  13236 var scales = /*#__PURE__*/Object.freeze({
  13237 __proto__: null,
  13238 CategoryScale: CategoryScale,
  13239 LinearScale: LinearScale,
  13240 LogarithmicScale: LogarithmicScale,
  13241 RadialLinearScale: RadialLinearScale,
  13242 TimeScale: TimeScale,
  13243 TimeSeriesScale: TimeSeriesScale
  13244 });
  13245 
  13246 Chart.register(controllers, scales, elements, plugins);
  13247 Chart.helpers = {...helpers};
  13248 Chart._adapters = _adapters;
  13249 Chart.Animation = Animation;
  13250 Chart.Animations = Animations;
  13251 Chart.animator = animator;
  13252 Chart.controllers = registry.controllers.items;
  13253 Chart.DatasetController = DatasetController;
  13254 Chart.Element = Element;
  13255 Chart.elements = elements;
  13256 Chart.Interaction = Interaction;
  13257 Chart.layouts = layouts;
  13258 Chart.platforms = platforms;
  13259 Chart.Scale = Scale;
  13260 Chart.Ticks = Ticks;
  13261 Object.assign(Chart, controllers, scales, elements, plugins, platforms);
  13262 Chart.Chart = Chart;
  13263 if (typeof window !== 'undefined') {
  13264   window.Chart = Chart;
  13265 }
  13266 
  13267 return Chart;
  13268 
  13269 }));