3 * https://www.chartjs.org
4 * (c) 2023 Chart.js Contributors
5 * Released under the MIT License
9 * Description of import into Moodle:
11 * - Download Chartjs source code (zip) file from https://github.com/chartjs/Chart.js/releases/latest.
12 * - You must build Chart.js to generate the dist files (https://www.chartjs.org/docs/latest/developers/contributing.html#building-and-testing).
13 * Chart.js will generate a new file dist/chart.umd.js with minified format, in order to avoid the minification
14 * we need to modify rollup.config.js, find below code in the file:
17 * 2. // dist/chart.umd.js
19 * 4. input: 'src/index.umd.ts',
20 * 5. plugins: plugins(true),
32 * Save the file and run build script again.
34 * - Copy /dist/chart.umd.js content to lib/amd/src/chartjs-lazy.js.
35 * - Remove below line in the lib/amd/src/chartjs-lazy.js:
37 * //# sourceMappingURL=chart.umd.js.map
39 * - Convert line endings to LF-Unix format.
40 * - Change the version number and the copyright year at the file header block.
41 * - Keep these instructions in the file.
42 * - Visit lib/tests/other/chartjstestpage.php to see if the library still works after the update.
46 (function (global, factory) {
47 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
48 typeof define === 'function' && define.amd ? define(factory) :
49 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chart = factory());
50 })(this, (function () { 'use strict';
52 var plugins = /*#__PURE__*/Object.freeze({
54 get Colors () { return plugin_colors; },
55 get Decimation () { return plugin_decimation; },
56 get Filler () { return index; },
57 get Legend () { return plugin_legend; },
58 get SubTitle () { return plugin_subtitle; },
59 get Title () { return plugin_title; },
60 get Tooltip () { return plugin_tooltip; }
64 * @namespace Chart.helpers
66 * An empty function that can be used, for example, for optional callback.
70 * Returns a unique id, sequentially generated from a global variable.
76 * Returns true if `value` is neither null nor undefined, else returns false.
77 * @param value - The value to test.
79 */ function isNullOrUndef(value) {
80 return value === null || typeof value === 'undefined';
83 * Returns true if `value` is an array (including typed arrays), else returns false.
84 * @param value - The value to test.
86 */ function isArray(value) {
87 if (Array.isArray && Array.isArray(value)) {
90 const type = Object.prototype.toString.call(value);
91 if (type.slice(0, 7) === '[object' && type.slice(-6) === 'Array]') {
97 * Returns true if `value` is an object (excluding null), else returns false.
98 * @param value - The value to test.
100 */ function isObject(value) {
101 return value !== null && Object.prototype.toString.call(value) === '[object Object]';
104 * Returns true if `value` is a finite number, else returns false
105 * @param value - The value to test.
106 */ function isNumberFinite(value) {
107 return (typeof value === 'number' || value instanceof Number) && isFinite(+value);
110 * Returns `value` if finite, else returns `defaultValue`.
111 * @param value - The value to return if defined.
112 * @param defaultValue - The value to return if `value` is not finite.
113 */ function finiteOrDefault(value, defaultValue) {
114 return isNumberFinite(value) ? value : defaultValue;
117 * Returns `value` if defined, else returns `defaultValue`.
118 * @param value - The value to return if defined.
119 * @param defaultValue - The value to return if `value` is undefined.
120 */ function valueOrDefault(value, defaultValue) {
121 return typeof value === 'undefined' ? defaultValue : value;
123 const toPercentage = (value, dimension)=>typeof value === 'string' && value.endsWith('%') ? parseFloat(value) / 100 : +value / dimension;
124 const toDimension = (value, dimension)=>typeof value === 'string' && value.endsWith('%') ? parseFloat(value) / 100 * dimension : +value;
126 * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
127 * value returned by `fn`. If `fn` is not a function, this method returns undefined.
128 * @param fn - The function to call.
129 * @param args - The arguments with which `fn` should be called.
130 * @param [thisArg] - The value of `this` provided for the call to `fn`.
131 */ function callback(fn, args, thisArg) {
132 if (fn && typeof fn.call === 'function') {
133 return fn.apply(thisArg, args);
136 function each(loopable, fn, thisArg, reverse) {
138 if (isArray(loopable)) {
139 len = loopable.length;
141 for(i = len - 1; i >= 0; i--){
142 fn.call(thisArg, loopable[i], i);
145 for(i = 0; i < len; i++){
146 fn.call(thisArg, loopable[i], i);
149 } else if (isObject(loopable)) {
150 keys = Object.keys(loopable);
152 for(i = 0; i < len; i++){
153 fn.call(thisArg, loopable[keys[i]], keys[i]);
158 * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
159 * @param a0 - The array to compare
160 * @param a1 - The array to compare
162 */ function _elementsEqual(a0, a1) {
164 if (!a0 || !a1 || a0.length !== a1.length) {
167 for(i = 0, ilen = a0.length; i < ilen; ++i){
170 if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) {
177 * Returns a deep copy of `source` without keeping references on objects and arrays.
178 * @param source - The value to clone.
179 */ function clone$1(source) {
180 if (isArray(source)) {
181 return source.map(clone$1);
183 if (isObject(source)) {
184 const target = Object.create(null);
185 const keys = Object.keys(source);
186 const klen = keys.length;
188 for(; k < klen; ++k){
189 target[keys[k]] = clone$1(source[keys[k]]);
195 function isValidKey(key) {
200 ].indexOf(key) === -1;
203 * The default merger when Chart.helpers.merge is called without merger option.
204 * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback.
206 */ function _merger(key, target, source, options) {
207 if (!isValidKey(key)) {
210 const tval = target[key];
211 const sval = source[key];
212 if (isObject(tval) && isObject(sval)) {
213 // eslint-disable-next-line @typescript-eslint/no-use-before-define
214 merge(tval, sval, options);
216 target[key] = clone$1(sval);
219 function merge(target, source, options) {
220 const sources = isArray(source) ? source : [
223 const ilen = sources.length;
224 if (!isObject(target)) {
227 options = options || {};
228 const merger = options.merger || _merger;
230 for(let i = 0; i < ilen; ++i){
231 current = sources[i];
232 if (!isObject(current)) {
235 const keys = Object.keys(current);
236 for(let k = 0, klen = keys.length; k < klen; ++k){
237 merger(keys[k], target, current, options);
242 function mergeIf(target, source) {
243 // eslint-disable-next-line @typescript-eslint/no-use-before-define
244 return merge(target, source, {
249 * Merges source[key] in target[key] only if target[key] is undefined.
251 */ function _mergerIf(key, target, source) {
252 if (!isValidKey(key)) {
255 const tval = target[key];
256 const sval = source[key];
257 if (isObject(tval) && isObject(sval)) {
259 } else if (!Object.prototype.hasOwnProperty.call(target, key)) {
260 target[key] = clone$1(sval);
265 */ function _deprecated(scope, value, previous, current) {
266 if (value !== undefined) {
267 console.warn(scope + ': "' + previous + '" is deprecated. Please use "' + current + '" instead');
270 // resolveObjectKey resolver cache
271 const keyResolvers = {
272 // Chart.helpers.core resolveObjectKey should resolve empty key to root object
280 */ function _splitKey(key) {
281 const parts = key.split('.');
284 for (const part of parts){
286 if (tmp.endsWith('\\')) {
287 tmp = tmp.slice(0, -1) + '.';
295 function _getKeyResolver(key) {
296 const keys = _splitKey(key);
298 for (const k of keys){
307 function resolveObjectKey(obj, key) {
308 const resolver = keyResolvers[key] || (keyResolvers[key] = _getKeyResolver(key));
309 return resolver(obj);
313 */ function _capitalize(str) {
314 return str.charAt(0).toUpperCase() + str.slice(1);
316 const defined = (value)=>typeof value !== 'undefined';
317 const isFunction = (value)=>typeof value === 'function';
318 // Adapted from https://stackoverflow.com/questions/31128855/comparing-ecma6-sets-for-equality#31129384
319 const setsEqual = (a, b)=>{
320 if (a.size !== b.size) {
323 for (const item of a){
331 * @param e - The event
333 */ function _isClickEvent(e) {
334 return e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu';
338 * @alias Chart.helpers.math
340 */ const PI = Math.PI;
342 const PITAU = TAU + PI;
343 const INFINITY = Number.POSITIVE_INFINITY;
344 const RAD_PER_DEG = PI / 180;
345 const HALF_PI = PI / 2;
346 const QUARTER_PI = PI / 4;
347 const TWO_THIRDS_PI = PI * 2 / 3;
348 const log10 = Math.log10;
349 const sign = Math.sign;
350 function almostEquals(x, y, epsilon) {
351 return Math.abs(x - y) < epsilon;
354 * Implementation of the nice number algorithm used in determining where axis labels will go
355 */ function niceNum(range) {
356 const roundedRange = Math.round(range);
357 range = almostEquals(range, roundedRange, range / 1000) ? roundedRange : range;
358 const niceRange = Math.pow(10, Math.floor(log10(range)));
359 const fraction = range / niceRange;
360 const niceFraction = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 5 ? 5 : 10;
361 return niceFraction * niceRange;
364 * Returns an array of factors sorted from 1 to sqrt(value)
366 */ function _factorize(value) {
368 const sqrt = Math.sqrt(value);
370 for(i = 1; i < sqrt; i++){
371 if (value % i === 0) {
373 result.push(value / i);
376 if (sqrt === (sqrt | 0)) {
379 result.sort((a, b)=>a - b).pop();
382 function isNumber(n) {
383 return !isNaN(parseFloat(n)) && isFinite(n);
385 function almostWhole(x, epsilon) {
386 const rounded = Math.round(x);
387 return rounded - epsilon <= x && rounded + epsilon >= x;
391 */ function _setMinAndMaxByKey(array, target, property) {
393 for(i = 0, ilen = array.length; i < ilen; i++){
394 value = array[i][property];
396 target.min = Math.min(target.min, value);
397 target.max = Math.max(target.max, value);
401 function toRadians(degrees) {
402 return degrees * (PI / 180);
404 function toDegrees(radians) {
405 return radians * (180 / PI);
408 * Returns the number of decimal places
409 * i.e. the number of digits after the decimal point, of the value of this Number.
410 * @param x - A number.
411 * @returns The number of decimal places.
413 */ function _decimalPlaces(x) {
414 if (!isNumberFinite(x)) {
419 while(Math.round(x * e) / e !== x){
425 // Gets the angle from vertical upright to the point about a centre.
426 function getAngleFromPoint(centrePoint, anglePoint) {
427 const distanceFromXCenter = anglePoint.x - centrePoint.x;
428 const distanceFromYCenter = anglePoint.y - centrePoint.y;
429 const radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
430 let angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
431 if (angle < -0.5 * PI) {
432 angle += TAU; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
436 distance: radialDistanceFromCenter
439 function distanceBetweenPoints(pt1, pt2) {
440 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
443 * Shortest distance between angles, in either direction.
445 */ function _angleDiff(a, b) {
446 return (a - b + PITAU) % TAU - PI;
449 * Normalize angle to be between 0 and 2*PI
451 */ function _normalizeAngle(a) {
452 return (a % TAU + TAU) % TAU;
456 */ function _angleBetween(angle, start, end, sameAngleIsFullCircle) {
457 const a = _normalizeAngle(angle);
458 const s = _normalizeAngle(start);
459 const e = _normalizeAngle(end);
460 const angleToStart = _normalizeAngle(s - a);
461 const angleToEnd = _normalizeAngle(e - a);
462 const startToAngle = _normalizeAngle(a - s);
463 const endToAngle = _normalizeAngle(a - e);
464 return a === s || a === e || sameAngleIsFullCircle && s === e || angleToStart > angleToEnd && startToAngle < endToAngle;
467 * Limit `value` between `min` and `max`
472 */ function _limitValue(value, min, max) {
473 return Math.max(min, Math.min(max, value));
476 * @param {number} value
478 */ function _int16Range(value) {
479 return _limitValue(value, -32768, 32767);
487 */ function _isBetween(value, start, end, epsilon = 1e-6) {
488 return value >= Math.min(start, end) - epsilon && value <= Math.max(start, end) + epsilon;
491 function _lookup(table, value, cmp) {
492 cmp = cmp || ((index)=>table[index] < value);
493 let hi = table.length - 1;
511 * @param table - the table search. must be sorted!
512 * @param key - property name for the value in each entry
513 * @param value - value to find
514 * @param last - lookup last index
516 */ const _lookupByKey = (table, key, value, last)=>_lookup(table, value, last ? (index)=>{
517 const ti = table[index][key];
518 return ti < value || ti === value && table[index + 1][key] === value;
519 } : (index)=>table[index][key] < value);
521 * Reverse binary search
522 * @param table - the table search. must be sorted!
523 * @param key - property name for the value in each entry
524 * @param value - value to find
526 */ const _rlookupByKey = (table, key, value)=>_lookup(table, value, (index)=>table[index][key] >= value);
528 * Return subset of `values` between `min` and `max` inclusive.
529 * Values are assumed to be in sorted order.
530 * @param values - sorted array of values
531 * @param min - min value
532 * @param max - max value
533 */ function _filterBetween(values, min, max) {
535 let end = values.length;
536 while(start < end && values[start] < min){
539 while(end > start && values[end - 1] > max){
542 return start > 0 || end < values.length ? values.slice(start, end) : values;
544 const arrayEvents = [
551 function listenArrayEvents(array, listener) {
552 if (array._chartjs) {
553 array._chartjs.listeners.push(listener);
556 Object.defineProperty(array, '_chartjs', {
565 arrayEvents.forEach((key)=>{
566 const method = '_onData' + _capitalize(key);
567 const base = array[key];
568 Object.defineProperty(array, key, {
572 const res = base.apply(this, args);
573 array._chartjs.listeners.forEach((object)=>{
574 if (typeof object[method] === 'function') {
575 object[method](...args);
583 function unlistenArrayEvents(array, listener) {
584 const stub = array._chartjs;
588 const listeners = stub.listeners;
589 const index = listeners.indexOf(listener);
591 listeners.splice(index, 1);
593 if (listeners.length > 0) {
596 arrayEvents.forEach((key)=>{
599 delete array._chartjs;
603 */ function _arrayUnique(items) {
604 const set = new Set(items);
605 if (set.size === items.length) {
608 return Array.from(set);
611 function fontString(pixelSize, fontStyle, fontFamily) {
612 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
615 * Request animation polyfill
616 */ const requestAnimFrame = function() {
617 if (typeof window === 'undefined') {
618 return function(callback) {
622 return window.requestAnimationFrame;
625 * Throttles calling `fn` once per animation frame
626 * Latest arguments are used on the actual call
627 */ function throttled(fn, thisArg) {
630 return function(...args) {
631 // Save the args for use later
635 requestAnimFrame.call(window, ()=>{
637 fn.apply(thisArg, argsToUse);
643 * Debounces calling `fn` for `delay` ms
644 */ function debounce(fn, delay) {
646 return function(...args) {
648 clearTimeout(timeout);
649 timeout = setTimeout(fn, delay, args);
651 fn.apply(this, args);
657 * Converts 'start' to 'left', 'end' to 'right' and others to 'center'
659 */ const _toLeftRightCenter = (align)=>align === 'start' ? 'left' : align === 'end' ? 'right' : 'center';
661 * Returns `start`, `end` or `(start + end) / 2` depending on `align`. Defaults to `center`
663 */ const _alignStartEnd = (align, start, end)=>align === 'start' ? start : align === 'end' ? end : (start + end) / 2;
665 * Returns `left`, `right` or `(left + right) / 2` depending on `align`. Defaults to `left`
667 */ const _textX = (align, left, right, rtl)=>{
668 const check = rtl ? 'left' : 'right';
669 return align === check ? right : align === 'center' ? (left + right) / 2 : left;
672 * Return start and count of visible points.
674 */ function _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) {
675 const pointCount = points.length;
677 let count = pointCount;
679 const { iScale , _parsed } = meta;
680 const axis = iScale.axis;
681 const { min , max , minDefined , maxDefined } = iScale.getUserBounds();
683 start = _limitValue(Math.min(// @ts-expect-error Need to type _parsed
684 _lookupByKey(_parsed, axis, min).lo, // @ts-expect-error Need to fix types on _lookupByKey
685 animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo), 0, pointCount - 1);
688 count = _limitValue(Math.max(// @ts-expect-error Need to type _parsed
689 _lookupByKey(_parsed, iScale.axis, max, true).hi + 1, // @ts-expect-error Need to fix types on _lookupByKey
690 animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max), true).hi + 1), start, pointCount) - start;
692 count = pointCount - start;
701 * Checks if the scale ranges have changed.
702 * @param {object} meta - dataset meta.
705 */ function _scaleRangesChanged(meta) {
706 const { xScale , yScale , _scaleRanges } = meta;
714 meta._scaleRanges = newRanges;
717 const changed = _scaleRanges.xmin !== xScale.min || _scaleRanges.xmax !== xScale.max || _scaleRanges.ymin !== yScale.min || _scaleRanges.ymax !== yScale.max;
718 Object.assign(_scaleRanges, newRanges);
724 this._request = null;
725 this._charts = new Map();
726 this._running = false;
727 this._lastDate = undefined;
729 _notify(chart, anims, date, type) {
730 const callbacks = anims.listeners[type];
731 const numSteps = anims.duration;
732 callbacks.forEach((fn)=>fn({
734 initial: anims.initial,
736 currentStep: Math.min(date - anims.start, numSteps)
743 this._running = true;
744 this._request = requestAnimFrame.call(window, ()=>{
746 this._request = null;
752 _update(date = Date.now()) {
754 this._charts.forEach((anims, chart)=>{
755 if (!anims.running || !anims.items.length) {
758 const items = anims.items;
759 let i = items.length - 1;
765 if (item._total > anims.duration) {
766 anims.duration = item._total;
771 items[i] = items[items.length - 1];
777 this._notify(chart, anims, date, 'progress');
780 anims.running = false;
781 this._notify(chart, anims, date, 'complete');
782 anims.initial = false;
784 remaining += items.length;
786 this._lastDate = date;
787 if (remaining === 0) {
788 this._running = false;
792 const charts = this._charts;
793 let anims = charts.get(chart);
804 charts.set(chart, anims);
808 listen(chart, event, cb) {
809 this._getAnims(chart).listeners[event].push(cb);
812 if (!items || !items.length) {
815 this._getAnims(chart).items.push(...items);
818 return this._getAnims(chart).items.length > 0;
821 const anims = this._charts.get(chart);
825 anims.running = true;
826 anims.start = Date.now();
827 anims.duration = anims.items.reduce((acc, cur)=>Math.max(acc, cur._duration), 0);
831 if (!this._running) {
834 const anims = this._charts.get(chart);
835 if (!anims || !anims.running || !anims.items.length) {
841 const anims = this._charts.get(chart);
842 if (!anims || !anims.items.length) {
845 const items = anims.items;
846 let i = items.length - 1;
851 this._notify(chart, anims, Date.now(), 'complete');
854 return this._charts.delete(chart);
857 var animator = /* #__PURE__ */ new Animator();
860 * @kurkle/color v0.3.2
861 * https://github.com/kurkle/color#readme
862 * (c) 2023 Jukka Kurkela
863 * Released under the MIT License
868 const lim = (v, l, h) => Math.max(Math.min(v, h), l);
870 return lim(round(v * 2.55), 0, 255);
873 return lim(round(v * 255), 0, 255);
876 return lim(round(v / 2.55) / 100, 0, 1);
879 return lim(round(v * 100), 0, 100);
881 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};
882 const hex = [...'0123456789ABCDEF'];
883 const h1 = b => hex[b & 0xF];
884 const h2 = b => hex[(b & 0xF0) >> 4] + hex[b & 0xF];
885 const eq = b => ((b & 0xF0) >> 4) === (b & 0xF);
886 const isShort = v => eq(v.r) && eq(v.g) && eq(v.b) && eq(v.a);
887 function hexParse(str) {
888 var len = str.length;
890 if (str[0] === '#') {
891 if (len === 4 || len === 5) {
893 r: 255 & map$1[str[1]] * 17,
894 g: 255 & map$1[str[2]] * 17,
895 b: 255 & map$1[str[3]] * 17,
896 a: len === 5 ? map$1[str[4]] * 17 : 255
898 } else if (len === 7 || len === 9) {
900 r: map$1[str[1]] << 4 | map$1[str[2]],
901 g: map$1[str[3]] << 4 | map$1[str[4]],
902 b: map$1[str[5]] << 4 | map$1[str[6]],
903 a: len === 9 ? (map$1[str[7]] << 4 | map$1[str[8]]) : 255
909 const alpha = (a, f) => a < 255 ? f(a) : '';
910 function hexString(v) {
911 var f = isShort(v) ? h1 : h2;
913 ? '#' + f(v.r) + f(v.g) + f(v.b) + alpha(v.a, f)
916 const HUE_RE = /^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;
917 function hsl2rgbn(h, s, l) {
918 const a = s * Math.min(l, 1 - l);
919 const f = (n, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
920 return [f(0), f(8), f(4)];
922 function hsv2rgbn(h, s, v) {
923 const f = (n, k = (n + h / 60) % 6) => v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
924 return [f(5), f(3), f(1)];
926 function hwb2rgbn(h, w, b) {
927 const rgb = hsl2rgbn(h, 1, 0.5);
934 for (i = 0; i < 3; i++) {
940 function hueValue(r, g, b, d, max) {
942 return ((g - b) / d) + (g < b ? 6 : 0);
945 return (b - r) / d + 2;
947 return (r - g) / d + 4;
949 function rgb2hsl(v) {
951 const r = v.r / range;
952 const g = v.g / range;
953 const b = v.b / range;
954 const max = Math.max(r, g, b);
955 const min = Math.min(r, g, b);
956 const l = (max + min) / 2;
960 s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
961 h = hueValue(r, g, b, d, max);
964 return [h | 0, s || 0, l];
966 function calln(f, a, b, c) {
969 ? f(a[0], a[1], a[2])
973 function hsl2rgb(h, s, l) {
974 return calln(hsl2rgbn, h, s, l);
976 function hwb2rgb(h, w, b) {
977 return calln(hwb2rgbn, h, w, b);
979 function hsv2rgb(h, s, v) {
980 return calln(hsv2rgbn, h, s, v);
983 return (h % 360 + 360) % 360;
985 function hueParse(str) {
986 const m = HUE_RE.exec(str);
993 a = m[6] ? p2b(+m[5]) : n2b(+m[5]);
995 const h = hue(+m[2]);
996 const p1 = +m[3] / 100;
997 const p2 = +m[4] / 100;
998 if (m[1] === 'hwb') {
999 v = hwb2rgb(h, p1, p2);
1000 } else if (m[1] === 'hsv') {
1001 v = hsv2rgb(h, p1, p2);
1003 v = hsl2rgb(h, p1, p2);
1012 function rotate(v, deg) {
1014 h[0] = hue(h[0] + deg);
1020 function hslString(v) {
1024 const a = rgb2hsl(v);
1026 const s = n2p(a[1]);
1027 const l = n2p(a[2]);
1029 ? `hsla(${h}, ${s}%, ${l}%, ${b2n(v.a)})`
1030 : `hsl(${h}, ${s}%, ${l}%)`;
1063 antiquewEte: 'faebd7',
1065 aquamarRe: '7fffd4',
1070 blanKedOmond: 'ffebcd',
1074 burlywood: 'deb887',
1079 cSnflowerXe: '6495ed',
1112 ghostwEte: 'f8f8ff',
1126 lavFMrXsh: 'fff0f5',
1128 NmoncEffon: 'fffacd',
1132 ZgTMnPdLw: 'fafad2',
1149 VaquamarRe: '66cdaa',
1158 midnightXe: '191970',
1162 navajowEte: 'ffdead',
1170 pOegTMnPd: 'eee8aa',
1173 pOeviTetYd: 'db7093',
1174 papayawEp: 'ffefd5',
1181 YbeccapurpN: '663399',
1185 saddNbPwn: '8b4513',
1187 sandybPwn: 'f4a460',
1207 wEtesmoke: 'f5f5f5',
1212 const unpacked = {};
1213 const keys = Object.keys(names$1);
1214 const tkeys = Object.keys(map$2);
1215 let i, j, k, ok, nk;
1216 for (i = 0; i < keys.length; i++) {
1218 for (j = 0; j < tkeys.length; j++) {
1220 nk = nk.replace(k, map$2[k]);
1222 k = parseInt(names$1[ok], 16);
1223 unpacked[nk] = [k >> 16 & 0xFF, k >> 8 & 0xFF, k & 0xFF];
1228 function nameParse(str) {
1231 names.transparent = [0, 0, 0, 0];
1233 const a = names[str.toLowerCase()];
1238 a: a.length === 4 ? a[3] : 255
1241 const RGB_RE = /^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;
1242 function rgbParse(str) {
1243 const m = RGB_RE.exec(str);
1251 a = m[8] ? p2b(v) : lim(v * 255, 0, 255);
1256 r = 255 & (m[2] ? p2b(r) : lim(r, 0, 255));
1257 g = 255 & (m[4] ? p2b(g) : lim(g, 0, 255));
1258 b = 255 & (m[6] ? p2b(b) : lim(b, 0, 255));
1266 function rgbString(v) {
1269 ? `rgba(${v.r}, ${v.g}, ${v.b}, ${b2n(v.a)})`
1270 : `rgb(${v.r}, ${v.g}, ${v.b})`
1273 const to = v => v <= 0.0031308 ? v * 12.92 : Math.pow(v, 1.0 / 2.4) * 1.055 - 0.055;
1274 const from = v => v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
1275 function interpolate$1(rgb1, rgb2, t) {
1276 const r = from(b2n(rgb1.r));
1277 const g = from(b2n(rgb1.g));
1278 const b = from(b2n(rgb1.b));
1280 r: n2b(to(r + t * (from(b2n(rgb2.r)) - r))),
1281 g: n2b(to(g + t * (from(b2n(rgb2.g)) - g))),
1282 b: n2b(to(b + t * (from(b2n(rgb2.b)) - b))),
1283 a: rgb1.a + t * (rgb2.a - rgb1.a)
1286 function modHSL(v, i, ratio) {
1288 let tmp = rgb2hsl(v);
1289 tmp[i] = Math.max(0, Math.min(tmp[i] + tmp[i] * ratio, i === 0 ? 360 : 1));
1296 function clone(v, proto) {
1297 return v ? Object.assign(proto || {}, v) : v;
1299 function fromObject(input) {
1300 var v = {r: 0, g: 0, b: 0, a: 255};
1301 if (Array.isArray(input)) {
1302 if (input.length >= 3) {
1303 v = {r: input[0], g: input[1], b: input[2], a: 255};
1304 if (input.length > 3) {
1305 v.a = n2b(input[3]);
1309 v = clone(input, {r: 0, g: 0, b: 0, a: 1});
1314 function functionParse(str) {
1315 if (str.charAt(0) === 'r') {
1316 return rgbParse(str);
1318 return hueParse(str);
1321 constructor(input) {
1322 if (input instanceof Color) {
1325 const type = typeof input;
1327 if (type === 'object') {
1328 v = fromObject(input);
1329 } else if (type === 'string') {
1330 v = hexParse(input) || nameParse(input) || functionParse(input);
1339 var v = clone(this._rgb);
1346 this._rgb = fromObject(obj);
1349 return this._valid ? rgbString(this._rgb) : undefined;
1352 return this._valid ? hexString(this._rgb) : undefined;
1355 return this._valid ? hslString(this._rgb) : undefined;
1357 mix(color, weight) {
1359 const c1 = this.rgb;
1360 const c2 = color.rgb;
1362 const p = weight === w2 ? 0.5 : weight;
1363 const w = 2 * p - 1;
1364 const a = c1.a - c2.a;
1365 const w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
1367 c1.r = 0xFF & w1 * c1.r + w2 * c2.r + 0.5;
1368 c1.g = 0xFF & w1 * c1.g + w2 * c2.g + 0.5;
1369 c1.b = 0xFF & w1 * c1.b + w2 * c2.b + 0.5;
1370 c1.a = p * c1.a + (1 - p) * c2.a;
1375 interpolate(color, t) {
1377 this._rgb = interpolate$1(this._rgb, color._rgb, t);
1382 return new Color(this.rgb);
1385 this._rgb.a = n2b(a);
1389 const rgb = this._rgb;
1394 const rgb = this._rgb;
1395 const val = round(rgb.r * 0.3 + rgb.g * 0.59 + rgb.b * 0.11);
1396 rgb.r = rgb.g = rgb.b = val;
1400 const rgb = this._rgb;
1405 const v = this._rgb;
1412 modHSL(this._rgb, 2, ratio);
1416 modHSL(this._rgb, 2, -ratio);
1420 modHSL(this._rgb, 1, ratio);
1424 modHSL(this._rgb, 1, -ratio);
1428 rotate(this._rgb, deg);
1433 function isPatternOrGradient(value) {
1434 if (value && typeof value === 'object') {
1435 const type = value.toString();
1436 return type === '[object CanvasPattern]' || type === '[object CanvasGradient]';
1440 function color(value) {
1441 return isPatternOrGradient(value) ? value : new Color(value);
1443 function getHoverColor(value) {
1444 return isPatternOrGradient(value) ? value : new Color(value).saturate(0.5).darken(0.1).hexString();
1459 function applyAnimationsDefaults(defaults) {
1460 defaults.set('animation', {
1463 easing: 'easeOutQuart',
1470 defaults.describe('animation', {
1473 _scriptable: (name)=>name !== 'onProgress' && name !== 'onComplete' && name !== 'fn'
1475 defaults.set('animations', {
1485 defaults.describe('animations', {
1486 _fallback: 'animation'
1488 defaults.set('transitions', {
1525 function applyLayoutsDefaults(defaults) {
1526 defaults.set('layout', {
1537 const intlCache = new Map();
1538 function getNumberFormat(locale, options) {
1539 options = options || {};
1540 const cacheKey = locale + JSON.stringify(options);
1541 let formatter = intlCache.get(cacheKey);
1543 formatter = new Intl.NumberFormat(locale, options);
1544 intlCache.set(cacheKey, formatter);
1548 function formatNumber(num, locale, options) {
1549 return getNumberFormat(locale, options).format(num);
1552 const formatters = {
1554 return isArray(value) ? value : '' + value;
1556 numeric (tickValue, index, ticks) {
1557 if (tickValue === 0) {
1560 const locale = this.chart.options.locale;
1562 let delta = tickValue;
1563 if (ticks.length > 1) {
1564 const maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value));
1565 if (maxTick < 1e-4 || maxTick > 1e+15) {
1566 notation = 'scientific';
1568 delta = calculateDelta(tickValue, ticks);
1570 const logDelta = log10(Math.abs(delta));
1571 const numDecimal = isNaN(logDelta) ? 1 : Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0);
1574 minimumFractionDigits: numDecimal,
1575 maximumFractionDigits: numDecimal
1577 Object.assign(options, this.options.ticks.format);
1578 return formatNumber(tickValue, locale, options);
1580 logarithmic (tickValue, index, ticks) {
1581 if (tickValue === 0) {
1584 const remain = ticks[index].significand || tickValue / Math.pow(10, Math.floor(log10(tickValue)));
1592 ].includes(remain) || index > 0.8 * ticks.length) {
1593 return formatters.numeric.call(this, tickValue, index, ticks);
1598 function calculateDelta(tickValue, ticks) {
1599 let delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value;
1600 if (Math.abs(delta) >= 1 && tickValue !== Math.floor(tickValue)) {
1601 delta = tickValue - Math.floor(tickValue);
1609 function applyScaleDefaults(defaults) {
1610 defaults.set('scale', {
1621 drawOnChartArea: true,
1624 tickWidth: (_ctx, options)=>options.lineWidth,
1625 tickColor: (_ctx, options)=>options.color,
1647 textStrokeColor: '',
1653 callback: Ticks.formatters.values,
1658 showLabelBackdrop: false,
1659 backdropColor: 'rgba(255, 255, 255, 0.75)',
1663 defaults.route('scale.ticks', 'color', '', 'color');
1664 defaults.route('scale.grid', 'color', '', 'borderColor');
1665 defaults.route('scale.border', 'color', '', 'borderColor');
1666 defaults.route('scale.title', 'color', '', 'color');
1667 defaults.describe('scale', {
1669 _scriptable: (name)=>!name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser',
1670 _indexable: (name)=>name !== 'borderDash' && name !== 'tickBorderDash' && name !== 'dash'
1672 defaults.describe('scales', {
1675 defaults.describe('scale.ticks', {
1676 _scriptable: (name)=>name !== 'backdropPadding' && name !== 'callback',
1677 _indexable: (name)=>name !== 'backdropPadding'
1681 const overrides = Object.create(null);
1682 const descriptors = Object.create(null);
1683 function getScope$1(node, key) {
1687 const keys = key.split('.');
1688 for(let i = 0, n = keys.length; i < n; ++i){
1690 node = node[k] || (node[k] = Object.create(null));
1694 function set(root, scope, values) {
1695 if (typeof scope === 'string') {
1696 return merge(getScope$1(root, scope), values);
1698 return merge(getScope$1(root, ''), scope);
1701 constructor(_descriptors, _appliers){
1702 this.animation = undefined;
1703 this.backgroundColor = 'rgba(0,0,0,0.1)';
1704 this.borderColor = 'rgba(0,0,0,0.1)';
1705 this.color = '#666';
1707 this.devicePixelRatio = (context)=>context.chart.platform.getDevicePixelRatio();
1717 family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
1724 this.hoverBackgroundColor = (ctx, options)=>getHoverColor(options.backgroundColor);
1725 this.hoverBorderColor = (ctx, options)=>getHoverColor(options.borderColor);
1726 this.hoverColor = (ctx, options)=>getHoverColor(options.color);
1727 this.indexAxis = 'x';
1728 this.interaction = {
1731 includeInvisible: false
1733 this.maintainAspectRatio = true;
1734 this.onHover = null;
1735 this.onClick = null;
1736 this.parsing = true;
1738 this.responsive = true;
1739 this.scale = undefined;
1741 this.showLine = true;
1742 this.drawActiveElementsOnTop = true;
1743 this.describe(_descriptors);
1744 this.apply(_appliers);
1746 set(scope, values) {
1747 return set(this, scope, values);
1750 return getScope$1(this, scope);
1752 describe(scope, values) {
1753 return set(descriptors, scope, values);
1755 override(scope, values) {
1756 return set(overrides, scope, values);
1758 route(scope, name, targetScope, targetName) {
1759 const scopeObject = getScope$1(this, scope);
1760 const targetScopeObject = getScope$1(this, targetScope);
1761 const privateName = '_' + name;
1762 Object.defineProperties(scopeObject, {
1764 value: scopeObject[name],
1770 const local = this[privateName];
1771 const target = targetScopeObject[targetName];
1772 if (isObject(local)) {
1773 return Object.assign({}, target, local);
1775 return valueOrDefault(local, target);
1778 this[privateName] = value;
1784 appliers.forEach((apply)=>apply(this));
1787 var defaults = /* #__PURE__ */ new Defaults({
1788 _scriptable: (name)=>!name.startsWith('on'),
1789 _indexable: (name)=>name !== 'events',
1791 _fallback: 'interaction'
1798 applyAnimationsDefaults,
1799 applyLayoutsDefaults,
1804 * Note: typedefs are auto-exported, so use a made-up `dom` namespace where
1805 * necessary to avoid duplicates with `export * from './helpers`; see
1806 * https://github.com/microsoft/TypeScript/issues/46011
1807 * @typedef { import('../core/core.controller.js').default } dom.Chart
1808 * @typedef { import('../../types').ChartEvent } ChartEvent
1811 */ function _isDomSupported() {
1812 return typeof window !== 'undefined' && typeof document !== 'undefined';
1816 */ function _getParentNode(domNode) {
1817 let parent = domNode.parentNode;
1818 if (parent && parent.toString() === '[object ShadowRoot]') {
1819 parent = parent.host;
1824 * convert max-width/max-height values that may be percentages into a number
1826 */ function parseMaxStyle(styleValue, node, parentProperty) {
1828 if (typeof styleValue === 'string') {
1829 valueInPixels = parseInt(styleValue, 10);
1830 if (styleValue.indexOf('%') !== -1) {
1831 // percentage * size in dimension
1832 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
1835 valueInPixels = styleValue;
1837 return valueInPixels;
1839 const getComputedStyle = (element)=>element.ownerDocument.defaultView.getComputedStyle(element, null);
1840 function getStyle(el, property) {
1841 return getComputedStyle(el).getPropertyValue(property);
1849 function getPositionedStyle(styles, style, suffix) {
1851 suffix = suffix ? '-' + suffix : '';
1852 for(let i = 0; i < 4; i++){
1853 const pos = positions[i];
1854 result[pos] = parseFloat(styles[style + '-' + pos + suffix]) || 0;
1856 result.width = result.left + result.right;
1857 result.height = result.top + result.bottom;
1860 const useOffsetPos = (x, y, target)=>(x > 0 || y > 0) && (!target || !target.shadowRoot);
1864 * @returns Canvas position
1865 */ function getCanvasPosition(e, canvas) {
1866 const touches = e.touches;
1867 const source = touches && touches.length ? touches[0] : e;
1868 const { offsetX , offsetY } = source;
1871 if (useOffsetPos(offsetX, offsetY, e.target)) {
1875 const rect = canvas.getBoundingClientRect();
1876 x = source.clientX - rect.left;
1877 y = source.clientY - rect.top;
1887 * Gets an event's x, y coordinates, relative to the chart area
1890 * @returns x and y coordinates of the event
1891 */ function getRelativePosition(event, chart) {
1892 if ('native' in event) {
1895 const { canvas , currentDevicePixelRatio } = chart;
1896 const style = getComputedStyle(canvas);
1897 const borderBox = style.boxSizing === 'border-box';
1898 const paddings = getPositionedStyle(style, 'padding');
1899 const borders = getPositionedStyle(style, 'border', 'width');
1900 const { x , y , box } = getCanvasPosition(event, canvas);
1901 const xOffset = paddings.left + (box && borders.left);
1902 const yOffset = paddings.top + (box && borders.top);
1903 let { width , height } = chart;
1905 width -= paddings.width + borders.width;
1906 height -= paddings.height + borders.height;
1909 x: Math.round((x - xOffset) / width * canvas.width / currentDevicePixelRatio),
1910 y: Math.round((y - yOffset) / height * canvas.height / currentDevicePixelRatio)
1913 function getContainerSize(canvas, width, height) {
1914 let maxWidth, maxHeight;
1915 if (width === undefined || height === undefined) {
1916 const container = _getParentNode(canvas);
1918 width = canvas.clientWidth;
1919 height = canvas.clientHeight;
1921 const rect = container.getBoundingClientRect(); // this is the border box of the container
1922 const containerStyle = getComputedStyle(container);
1923 const containerBorder = getPositionedStyle(containerStyle, 'border', 'width');
1924 const containerPadding = getPositionedStyle(containerStyle, 'padding');
1925 width = rect.width - containerPadding.width - containerBorder.width;
1926 height = rect.height - containerPadding.height - containerBorder.height;
1927 maxWidth = parseMaxStyle(containerStyle.maxWidth, container, 'clientWidth');
1928 maxHeight = parseMaxStyle(containerStyle.maxHeight, container, 'clientHeight');
1934 maxWidth: maxWidth || INFINITY,
1935 maxHeight: maxHeight || INFINITY
1938 const round1 = (v)=>Math.round(v * 10) / 10;
1939 // eslint-disable-next-line complexity
1940 function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) {
1941 const style = getComputedStyle(canvas);
1942 const margins = getPositionedStyle(style, 'margin');
1943 const maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY;
1944 const maxHeight = parseMaxStyle(style.maxHeight, canvas, 'clientHeight') || INFINITY;
1945 const containerSize = getContainerSize(canvas, bbWidth, bbHeight);
1946 let { width , height } = containerSize;
1947 if (style.boxSizing === 'content-box') {
1948 const borders = getPositionedStyle(style, 'border', 'width');
1949 const paddings = getPositionedStyle(style, 'padding');
1950 width -= paddings.width + borders.width;
1951 height -= paddings.height + borders.height;
1953 width = Math.max(0, width - margins.width);
1954 height = Math.max(0, aspectRatio ? width / aspectRatio : height - margins.height);
1955 width = round1(Math.min(width, maxWidth, containerSize.maxWidth));
1956 height = round1(Math.min(height, maxHeight, containerSize.maxHeight));
1957 if (width && !height) {
1958 // https://github.com/chartjs/Chart.js/issues/4659
1959 // If the canvas has width, but no height, default to aspectRatio of 2 (canvas default)
1960 height = round1(width / 2);
1962 const maintainHeight = bbWidth !== undefined || bbHeight !== undefined;
1963 if (maintainHeight && aspectRatio && containerSize.height && height > containerSize.height) {
1964 height = containerSize.height;
1965 width = round1(Math.floor(height * aspectRatio));
1976 * @returns True if the canvas context size or transformation has changed.
1977 */ function retinaScale(chart, forceRatio, forceStyle) {
1978 const pixelRatio = forceRatio || 1;
1979 const deviceHeight = Math.floor(chart.height * pixelRatio);
1980 const deviceWidth = Math.floor(chart.width * pixelRatio);
1981 chart.height = Math.floor(chart.height);
1982 chart.width = Math.floor(chart.width);
1983 const canvas = chart.canvas;
1984 // If no style has been set on the canvas, the render size is used as display size,
1985 // making the chart visually bigger, so let's enforce it to the "correct" values.
1986 // See https://github.com/chartjs/Chart.js/issues/3575
1987 if (canvas.style && (forceStyle || !canvas.style.height && !canvas.style.width)) {
1988 canvas.style.height = `${chart.height}px`;
1989 canvas.style.width = `${chart.width}px`;
1991 if (chart.currentDevicePixelRatio !== pixelRatio || canvas.height !== deviceHeight || canvas.width !== deviceWidth) {
1992 chart.currentDevicePixelRatio = pixelRatio;
1993 canvas.height = deviceHeight;
1994 canvas.width = deviceWidth;
1995 chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
2001 * Detects support for options object argument in addEventListener.
2002 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
2004 */ const supportsEventListenerOptions = function() {
2005 let passiveSupported = false;
2009 passiveSupported = true;
2013 if (_isDomSupported()) {
2014 window.addEventListener('test', null, options);
2015 window.removeEventListener('test', null, options);
2018 // continue regardless of error
2020 return passiveSupported;
2023 * The "used" size is the final value of a dimension property after all calculations have
2024 * been performed. This method uses the computed style of `element` but returns undefined
2025 * if the computed style is not expressed in pixels. That can happen in some cases where
2026 * `element` has a size relative to its parent and this last one is not yet displayed,
2027 * for example because of `display: none` on a parent node.
2028 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
2029 * @returns Size in pixels or undefined if unknown.
2030 */ function readUsedSize(element, property) {
2031 const value = getStyle(element, property);
2032 const matches = value && value.match(/^(\d+)(\.\d+)?px$/);
2033 return matches ? +matches[1] : undefined;
2037 * Converts the given font object into a CSS font string.
2038 * @param font - A font object.
2039 * @return The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
2041 */ function toFontString(font) {
2042 if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) {
2045 return (font.style ? font.style + ' ' : '') + (font.weight ? font.weight + ' ' : '') + font.size + 'px ' + font.family;
2049 */ function _measureText(ctx, data, gc, longest, string) {
2050 let textWidth = data[string];
2052 textWidth = data[string] = ctx.measureText(string).width;
2055 if (textWidth > longest) {
2056 longest = textWidth;
2062 */ // eslint-disable-next-line complexity
2063 function _longestText(ctx, font, arrayOfThings, cache) {
2064 cache = cache || {};
2065 let data = cache.data = cache.data || {};
2066 let gc = cache.garbageCollect = cache.garbageCollect || [];
2067 if (cache.font !== font) {
2068 data = cache.data = {};
2069 gc = cache.garbageCollect = [];
2075 const ilen = arrayOfThings.length;
2076 let i, j, jlen, thing, nestedThing;
2077 for(i = 0; i < ilen; i++){
2078 thing = arrayOfThings[i];
2079 // Undefined strings and arrays should not be measured
2080 if (thing !== undefined && thing !== null && !isArray(thing)) {
2081 longest = _measureText(ctx, data, gc, longest, thing);
2082 } else if (isArray(thing)) {
2083 // if it is an array lets measure each element
2084 // to do maybe simplify this function a bit so we can do this more recursively?
2085 for(j = 0, jlen = thing.length; j < jlen; j++){
2086 nestedThing = thing[j];
2087 // Undefined strings and arrays should not be measured
2088 if (nestedThing !== undefined && nestedThing !== null && !isArray(nestedThing)) {
2089 longest = _measureText(ctx, data, gc, longest, nestedThing);
2095 const gcLen = gc.length / 2;
2096 if (gcLen > arrayOfThings.length) {
2097 for(i = 0; i < gcLen; i++){
2100 gc.splice(0, gcLen);
2105 * Returns the aligned pixel value to avoid anti-aliasing blur
2106 * @param chart - The chart instance.
2107 * @param pixel - A pixel value.
2108 * @param width - The width of the element.
2109 * @returns The aligned pixel value.
2111 */ function _alignPixel(chart, pixel, width) {
2112 const devicePixelRatio = chart.currentDevicePixelRatio;
2113 const halfWidth = width !== 0 ? Math.max(width / 2, 0.5) : 0;
2114 return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
2117 * Clears the entire canvas.
2118 */ function clearCanvas(canvas, ctx) {
2119 ctx = ctx || canvas.getContext('2d');
2121 // canvas.width and canvas.height do not consider the canvas transform,
2122 // while clearRect does
2123 ctx.resetTransform();
2124 ctx.clearRect(0, 0, canvas.width, canvas.height);
2127 function drawPoint(ctx, options, x, y) {
2128 // eslint-disable-next-line @typescript-eslint/no-use-before-define
2129 drawPointLegend(ctx, options, x, y, null);
2131 // eslint-disable-next-line complexity
2132 function drawPointLegend(ctx, options, x, y, w) {
2133 let type, xOffset, yOffset, size, cornerRadius, width, xOffsetW, yOffsetW;
2134 const style = options.pointStyle;
2135 const rotation = options.rotation;
2136 const radius = options.radius;
2137 let rad = (rotation || 0) * RAD_PER_DEG;
2138 if (style && typeof style === 'object') {
2139 type = style.toString();
2140 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
2142 ctx.translate(x, y);
2144 ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);
2149 if (isNaN(radius) || radius <= 0) {
2154 // Default includes circle
2157 ctx.ellipse(x, y, w / 2, radius, 0, 0, TAU);
2159 ctx.arc(x, y, radius, 0, TAU);
2164 width = w ? w / 2 : radius;
2165 ctx.moveTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
2166 rad += TWO_THIRDS_PI;
2167 ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
2168 rad += TWO_THIRDS_PI;
2169 ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
2173 // NOTE: the rounded rect implementation changed to use `arc` instead of
2174 // `quadraticCurveTo` since it generates better results when rect is
2175 // almost a circle. 0.516 (instead of 0.5) produces results with visually
2176 // closer proportion to the previous impl and it is inscribed in the
2177 // circle with `radius`. For more details, see the following PRs:
2178 // https://github.com/chartjs/Chart.js/issues/5597
2179 // https://github.com/chartjs/Chart.js/issues/5858
2180 cornerRadius = radius * 0.516;
2181 size = radius - cornerRadius;
2182 xOffset = Math.cos(rad + QUARTER_PI) * size;
2183 xOffsetW = Math.cos(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
2184 yOffset = Math.sin(rad + QUARTER_PI) * size;
2185 yOffsetW = Math.sin(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
2186 ctx.arc(x - xOffsetW, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
2187 ctx.arc(x + yOffsetW, y - xOffset, cornerRadius, rad - HALF_PI, rad);
2188 ctx.arc(x + xOffsetW, y + yOffset, cornerRadius, rad, rad + HALF_PI);
2189 ctx.arc(x - yOffsetW, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
2194 size = Math.SQRT1_2 * radius;
2195 width = w ? w / 2 : size;
2196 ctx.rect(x - width, y - size, 2 * width, 2 * size);
2200 /* falls through */ case 'rectRot':
2201 xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
2202 xOffset = Math.cos(rad) * radius;
2203 yOffset = Math.sin(rad) * radius;
2204 yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
2205 ctx.moveTo(x - xOffsetW, y - yOffset);
2206 ctx.lineTo(x + yOffsetW, y - xOffset);
2207 ctx.lineTo(x + xOffsetW, y + yOffset);
2208 ctx.lineTo(x - yOffsetW, y + xOffset);
2213 /* falls through */ case 'cross':
2214 xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
2215 xOffset = Math.cos(rad) * radius;
2216 yOffset = Math.sin(rad) * radius;
2217 yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
2218 ctx.moveTo(x - xOffsetW, y - yOffset);
2219 ctx.lineTo(x + xOffsetW, y + yOffset);
2220 ctx.moveTo(x + yOffsetW, y - xOffset);
2221 ctx.lineTo(x - yOffsetW, y + xOffset);
2224 xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
2225 xOffset = Math.cos(rad) * radius;
2226 yOffset = Math.sin(rad) * radius;
2227 yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
2228 ctx.moveTo(x - xOffsetW, y - yOffset);
2229 ctx.lineTo(x + xOffsetW, y + yOffset);
2230 ctx.moveTo(x + yOffsetW, y - xOffset);
2231 ctx.lineTo(x - yOffsetW, y + xOffset);
2233 xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
2234 xOffset = Math.cos(rad) * radius;
2235 yOffset = Math.sin(rad) * radius;
2236 yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
2237 ctx.moveTo(x - xOffsetW, y - yOffset);
2238 ctx.lineTo(x + xOffsetW, y + yOffset);
2239 ctx.moveTo(x + yOffsetW, y - xOffset);
2240 ctx.lineTo(x - yOffsetW, y + xOffset);
2243 xOffset = w ? w / 2 : Math.cos(rad) * radius;
2244 yOffset = Math.sin(rad) * radius;
2245 ctx.moveTo(x - xOffset, y - yOffset);
2246 ctx.lineTo(x + xOffset, y + yOffset);
2250 ctx.lineTo(x + Math.cos(rad) * (w ? w / 2 : radius), y + Math.sin(rad) * radius);
2257 if (options.borderWidth > 0) {
2262 * Returns true if the point is inside the rectangle
2263 * @param point - The point to test
2264 * @param area - The rectangle
2265 * @param margin - allowed margin
2267 */ function _isPointInArea(point, area, margin) {
2268 margin = margin || 0.5; // margin - default is to match rounded decimals
2269 return !area || point && point.x > area.left - margin && point.x < area.right + margin && point.y > area.top - margin && point.y < area.bottom + margin;
2271 function clipArea(ctx, area) {
2274 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
2277 function unclipArea(ctx) {
2282 */ function _steppedLineTo(ctx, previous, target, flip, mode) {
2284 return ctx.lineTo(target.x, target.y);
2286 if (mode === 'middle') {
2287 const midpoint = (previous.x + target.x) / 2.0;
2288 ctx.lineTo(midpoint, previous.y);
2289 ctx.lineTo(midpoint, target.y);
2290 } else if (mode === 'after' !== !!flip) {
2291 ctx.lineTo(previous.x, target.y);
2293 ctx.lineTo(target.x, previous.y);
2295 ctx.lineTo(target.x, target.y);
2299 */ function _bezierCurveTo(ctx, previous, target, flip) {
2301 return ctx.lineTo(target.x, target.y);
2303 ctx.bezierCurveTo(flip ? previous.cp1x : previous.cp2x, flip ? previous.cp1y : previous.cp2y, flip ? target.cp2x : target.cp1x, flip ? target.cp2y : target.cp1y, target.x, target.y);
2305 function setRenderOpts(ctx, opts) {
2306 if (opts.translation) {
2307 ctx.translate(opts.translation[0], opts.translation[1]);
2309 if (!isNullOrUndef(opts.rotation)) {
2310 ctx.rotate(opts.rotation);
2313 ctx.fillStyle = opts.color;
2315 if (opts.textAlign) {
2316 ctx.textAlign = opts.textAlign;
2318 if (opts.textBaseline) {
2319 ctx.textBaseline = opts.textBaseline;
2322 function decorateText(ctx, x, y, line, opts) {
2323 if (opts.strikethrough || opts.underline) {
2325 * Now that IE11 support has been dropped, we can use more
2326 * of the TextMetrics object. The actual bounding boxes
2327 * are unflagged in Chrome, Firefox, Edge, and Safari so they
2328 * can be safely used.
2329 * See https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Browser_compatibility
2330 */ const metrics = ctx.measureText(line);
2331 const left = x - metrics.actualBoundingBoxLeft;
2332 const right = x + metrics.actualBoundingBoxRight;
2333 const top = y - metrics.actualBoundingBoxAscent;
2334 const bottom = y + metrics.actualBoundingBoxDescent;
2335 const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;
2336 ctx.strokeStyle = ctx.fillStyle;
2338 ctx.lineWidth = opts.decorationWidth || 2;
2339 ctx.moveTo(left, yDecoration);
2340 ctx.lineTo(right, yDecoration);
2344 function drawBackdrop(ctx, opts) {
2345 const oldColor = ctx.fillStyle;
2346 ctx.fillStyle = opts.color;
2347 ctx.fillRect(opts.left, opts.top, opts.width, opts.height);
2348 ctx.fillStyle = oldColor;
2351 * Render text onto the canvas
2352 */ function renderText(ctx, text, x, y, font, opts = {}) {
2353 const lines = isArray(text) ? text : [
2356 const stroke = opts.strokeWidth > 0 && opts.strokeColor !== '';
2359 ctx.font = font.string;
2360 setRenderOpts(ctx, opts);
2361 for(i = 0; i < lines.length; ++i){
2363 if (opts.backdrop) {
2364 drawBackdrop(ctx, opts.backdrop);
2367 if (opts.strokeColor) {
2368 ctx.strokeStyle = opts.strokeColor;
2370 if (!isNullOrUndef(opts.strokeWidth)) {
2371 ctx.lineWidth = opts.strokeWidth;
2373 ctx.strokeText(line, x, y, opts.maxWidth);
2375 ctx.fillText(line, x, y, opts.maxWidth);
2376 decorateText(ctx, x, y, line, opts);
2377 y += Number(font.lineHeight);
2382 * Add a path of a rectangle with rounded corners to the current sub-path
2383 * @param ctx - Context
2384 * @param rect - Bounding rect
2385 */ function addRoundedRectPath(ctx, rect) {
2386 const { x , y , w , h , radius } = rect;
2388 ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, 1.5 * PI, PI, true);
2389 // line from top left to bottom left
2390 ctx.lineTo(x, y + h - radius.bottomLeft);
2392 ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true);
2393 // line from bottom left to bottom right
2394 ctx.lineTo(x + w - radius.bottomRight, y + h);
2396 ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true);
2397 // line from bottom right to top right
2398 ctx.lineTo(x + w, y + radius.topRight);
2400 ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true);
2401 // line from top right to top left
2402 ctx.lineTo(x + radius.topLeft, y);
2406 * Creates a Proxy for resolving raw values for options.
2407 * @param scopes - The option scopes to look for values, in resolution order
2408 * @param prefixes - The prefixes for values, in resolution order.
2409 * @param rootScopes - The root option scopes
2410 * @param fallback - Parent scopes fallback
2411 * @param getTarget - callback for getting the target for changed values
2414 */ function _createResolver(scopes, prefixes = [
2416 ], rootScopes, fallback, getTarget = ()=>scopes[0]) {
2417 const finalRootScopes = rootScopes || scopes;
2418 if (typeof fallback === 'undefined') {
2419 fallback = _resolve('_fallback', scopes);
2422 [Symbol.toStringTag]: 'Object',
2425 _rootScopes: finalRootScopes,
2426 _fallback: fallback,
2427 _getTarget: getTarget,
2428 override: (scope)=>_createResolver([
2431 ], prefixes, finalRootScopes, fallback)
2433 return new Proxy(cache, {
2435 * A trap for the delete operator.
2436 */ deleteProperty (target, prop) {
2437 delete target[prop]; // remove from cache
2438 delete target._keys; // remove cached keys
2439 delete scopes[0][prop]; // remove from top level scope
2443 * A trap for getting property values.
2444 */ get (target, prop) {
2445 return _cached(target, prop, ()=>_resolveWithPrefixes(prop, prefixes, scopes, target));
2448 * A trap for Object.getOwnPropertyDescriptor.
2449 * Also used by Object.hasOwnProperty.
2450 */ getOwnPropertyDescriptor (target, prop) {
2451 return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop);
2454 * A trap for Object.getPrototypeOf.
2455 */ getPrototypeOf () {
2456 return Reflect.getPrototypeOf(scopes[0]);
2459 * A trap for the in operator.
2460 */ has (target, prop) {
2461 return getKeysFromAllScopes(target).includes(prop);
2464 * A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
2465 */ ownKeys (target) {
2466 return getKeysFromAllScopes(target);
2469 * A trap for setting property values.
2470 */ set (target, prop, value) {
2471 const storage = target._storage || (target._storage = getTarget());
2472 target[prop] = storage[prop] = value; // set to top level scope + cache
2473 delete target._keys; // remove cached keys
2479 * Returns an Proxy for resolving option values with context.
2480 * @param proxy - The Proxy returned by `_createResolver`
2481 * @param context - Context object for scriptable/indexable options
2482 * @param subProxy - The proxy provided for scriptable options
2483 * @param descriptorDefaults - Defaults for descriptors
2485 */ function _attachContext(proxy, context, subProxy, descriptorDefaults) {
2490 _subProxy: subProxy,
2492 _descriptors: _descriptors(proxy, descriptorDefaults),
2493 setContext: (ctx)=>_attachContext(proxy, ctx, subProxy, descriptorDefaults),
2494 override: (scope)=>_attachContext(proxy.override(scope), context, subProxy, descriptorDefaults)
2496 return new Proxy(cache, {
2498 * A trap for the delete operator.
2499 */ deleteProperty (target, prop) {
2500 delete target[prop]; // remove from cache
2501 delete proxy[prop]; // remove from proxy
2505 * A trap for getting property values.
2506 */ get (target, prop, receiver) {
2507 return _cached(target, prop, ()=>_resolveWithContext(target, prop, receiver));
2510 * A trap for Object.getOwnPropertyDescriptor.
2511 * Also used by Object.hasOwnProperty.
2512 */ getOwnPropertyDescriptor (target, prop) {
2513 return target._descriptors.allKeys ? Reflect.has(proxy, prop) ? {
2516 } : undefined : Reflect.getOwnPropertyDescriptor(proxy, prop);
2519 * A trap for Object.getPrototypeOf.
2520 */ getPrototypeOf () {
2521 return Reflect.getPrototypeOf(proxy);
2524 * A trap for the in operator.
2525 */ has (target, prop) {
2526 return Reflect.has(proxy, prop);
2529 * A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
2531 return Reflect.ownKeys(proxy);
2534 * A trap for setting property values.
2535 */ set (target, prop, value) {
2536 proxy[prop] = value; // set to proxy
2537 delete target[prop]; // remove from cache
2544 */ function _descriptors(proxy, defaults = {
2548 const { _scriptable =defaults.scriptable , _indexable =defaults.indexable , _allKeys =defaults.allKeys } = proxy;
2551 scriptable: _scriptable,
2552 indexable: _indexable,
2553 isScriptable: isFunction(_scriptable) ? _scriptable : ()=>_scriptable,
2554 isIndexable: isFunction(_indexable) ? _indexable : ()=>_indexable
2557 const readKey = (prefix, name)=>prefix ? prefix + _capitalize(name) : name;
2558 const needsSubResolver = (prop, value)=>isObject(value) && prop !== 'adapters' && (Object.getPrototypeOf(value) === null || value.constructor === Object);
2559 function _cached(target, prop, resolve) {
2560 if (Object.prototype.hasOwnProperty.call(target, prop)) {
2561 return target[prop];
2563 const value = resolve();
2564 // cache the resolved value
2565 target[prop] = value;
2568 function _resolveWithContext(target, prop, receiver) {
2569 const { _proxy , _context , _subProxy , _descriptors: descriptors } = target;
2570 let value = _proxy[prop]; // resolve from proxy
2571 // resolve with context
2572 if (isFunction(value) && descriptors.isScriptable(prop)) {
2573 value = _resolveScriptable(prop, value, target, receiver);
2575 if (isArray(value) && value.length) {
2576 value = _resolveArray(prop, value, target, descriptors.isIndexable);
2578 if (needsSubResolver(prop, value)) {
2579 // if the resolved value is an object, create a sub resolver for it
2580 value = _attachContext(value, _context, _subProxy && _subProxy[prop], descriptors);
2584 function _resolveScriptable(prop, getValue, target, receiver) {
2585 const { _proxy , _context , _subProxy , _stack } = target;
2586 if (_stack.has(prop)) {
2587 throw new Error('Recursion detected: ' + Array.from(_stack).join('->') + '->' + prop);
2590 let value = getValue(_context, _subProxy || receiver);
2591 _stack.delete(prop);
2592 if (needsSubResolver(prop, value)) {
2593 // When scriptable option returns an object, create a resolver on that.
2594 value = createSubResolver(_proxy._scopes, _proxy, prop, value);
2598 function _resolveArray(prop, value, target, isIndexable) {
2599 const { _proxy , _context , _subProxy , _descriptors: descriptors } = target;
2600 if (typeof _context.index !== 'undefined' && isIndexable(prop)) {
2601 return value[_context.index % value.length];
2602 } else if (isObject(value[0])) {
2603 // Array of objects, return array or resolvers
2605 const scopes = _proxy._scopes.filter((s)=>s !== arr);
2607 for (const item of arr){
2608 const resolver = createSubResolver(scopes, _proxy, prop, item);
2609 value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop], descriptors));
2614 function resolveFallback(fallback, prop, value) {
2615 return isFunction(fallback) ? fallback(prop, value) : fallback;
2617 const getScope = (key, parent)=>key === true ? parent : typeof key === 'string' ? resolveObjectKey(parent, key) : undefined;
2618 function addScopes(set, parentScopes, key, parentFallback, value) {
2619 for (const parent of parentScopes){
2620 const scope = getScope(key, parent);
2623 const fallback = resolveFallback(scope._fallback, key, value);
2624 if (typeof fallback !== 'undefined' && fallback !== key && fallback !== parentFallback) {
2625 // When we reach the descriptor that defines a new _fallback, return that.
2626 // The fallback will resume to that new scope.
2629 } else if (scope === false && typeof parentFallback !== 'undefined' && key !== parentFallback) {
2630 // Fallback to `false` results to `false`, when falling back to different key.
2631 // For example `interaction` from `hover` or `plugins.tooltip` and `animation` from `animations`
2637 function createSubResolver(parentScopes, resolver, prop, value) {
2638 const rootScopes = resolver._rootScopes;
2639 const fallback = resolveFallback(resolver._fallback, prop, value);
2644 const set = new Set();
2646 let key = addScopesFromKey(set, allScopes, prop, fallback || prop, value);
2650 if (typeof fallback !== 'undefined' && fallback !== prop) {
2651 key = addScopesFromKey(set, allScopes, fallback, key, value);
2656 return _createResolver(Array.from(set), [
2658 ], rootScopes, fallback, ()=>subGetTarget(resolver, prop, value));
2660 function addScopesFromKey(set, allScopes, key, fallback, item) {
2662 key = addScopes(set, allScopes, key, fallback, item);
2666 function subGetTarget(resolver, prop, value) {
2667 const parent = resolver._getTarget();
2668 if (!(prop in parent)) {
2671 const target = parent[prop];
2672 if (isArray(target) && isObject(value)) {
2673 // For array of objects, the object is used to store updated values
2676 return target || {};
2678 function _resolveWithPrefixes(prop, prefixes, scopes, proxy) {
2680 for (const prefix of prefixes){
2681 value = _resolve(readKey(prefix, prop), scopes);
2682 if (typeof value !== 'undefined') {
2683 return needsSubResolver(prop, value) ? createSubResolver(scopes, proxy, prop, value) : value;
2687 function _resolve(key, scopes) {
2688 for (const scope of scopes){
2692 const value = scope[key];
2693 if (typeof value !== 'undefined') {
2698 function getKeysFromAllScopes(target) {
2699 let keys = target._keys;
2701 keys = target._keys = resolveKeysFromAllScopes(target._scopes);
2705 function resolveKeysFromAllScopes(scopes) {
2706 const set = new Set();
2707 for (const scope of scopes){
2708 for (const key of Object.keys(scope).filter((k)=>!k.startsWith('_'))){
2712 return Array.from(set);
2714 function _parseObjectDataRadialScale(meta, data, start, count) {
2715 const { iScale } = meta;
2716 const { key ='r' } = this._parsing;
2717 const parsed = new Array(count);
2718 let i, ilen, index, item;
2719 for(i = 0, ilen = count; i < ilen; ++i){
2723 r: iScale.parse(resolveObjectKey(item, key), index)
2729 const EPSILON = Number.EPSILON || 1e-14;
2730 const getPoint = (points, i)=>i < points.length && !points[i].skip && points[i];
2731 const getValueAxis = (indexAxis)=>indexAxis === 'x' ? 'y' : 'x';
2732 function splineCurve(firstPoint, middlePoint, afterPoint, t) {
2733 // Props to Rob Spencer at scaled innovation for his post on splining between points
2734 // http://scaledinnovation.com/analytics/splines/aboutSplines.html
2735 // This function must also respect "skipped" points
2736 const previous = firstPoint.skip ? middlePoint : firstPoint;
2737 const current = middlePoint;
2738 const next = afterPoint.skip ? middlePoint : afterPoint;
2739 const d01 = distanceBetweenPoints(current, previous);
2740 const d12 = distanceBetweenPoints(next, current);
2741 let s01 = d01 / (d01 + d12);
2742 let s12 = d12 / (d01 + d12);
2743 // If all points are the same, s01 & s02 will be inf
2744 s01 = isNaN(s01) ? 0 : s01;
2745 s12 = isNaN(s12) ? 0 : s12;
2746 const fa = t * s01; // scaling factor for triangle Ta
2750 x: current.x - fa * (next.x - previous.x),
2751 y: current.y - fa * (next.y - previous.y)
2754 x: current.x + fb * (next.x - previous.x),
2755 y: current.y + fb * (next.y - previous.y)
2760 * Adjust tangents to ensure monotonic properties
2761 */ function monotoneAdjust(points, deltaK, mK) {
2762 const pointsLen = points.length;
2763 let alphaK, betaK, tauK, squaredMagnitude, pointCurrent;
2764 let pointAfter = getPoint(points, 0);
2765 for(let i = 0; i < pointsLen - 1; ++i){
2766 pointCurrent = pointAfter;
2767 pointAfter = getPoint(points, i + 1);
2768 if (!pointCurrent || !pointAfter) {
2771 if (almostEquals(deltaK[i], 0, EPSILON)) {
2772 mK[i] = mK[i + 1] = 0;
2775 alphaK = mK[i] / deltaK[i];
2776 betaK = mK[i + 1] / deltaK[i];
2777 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
2778 if (squaredMagnitude <= 9) {
2781 tauK = 3 / Math.sqrt(squaredMagnitude);
2782 mK[i] = alphaK * tauK * deltaK[i];
2783 mK[i + 1] = betaK * tauK * deltaK[i];
2786 function monotoneCompute(points, mK, indexAxis = 'x') {
2787 const valueAxis = getValueAxis(indexAxis);
2788 const pointsLen = points.length;
2789 let delta, pointBefore, pointCurrent;
2790 let pointAfter = getPoint(points, 0);
2791 for(let i = 0; i < pointsLen; ++i){
2792 pointBefore = pointCurrent;
2793 pointCurrent = pointAfter;
2794 pointAfter = getPoint(points, i + 1);
2795 if (!pointCurrent) {
2798 const iPixel = pointCurrent[indexAxis];
2799 const vPixel = pointCurrent[valueAxis];
2801 delta = (iPixel - pointBefore[indexAxis]) / 3;
2802 pointCurrent[`cp1${indexAxis}`] = iPixel - delta;
2803 pointCurrent[`cp1${valueAxis}`] = vPixel - delta * mK[i];
2806 delta = (pointAfter[indexAxis] - iPixel) / 3;
2807 pointCurrent[`cp2${indexAxis}`] = iPixel + delta;
2808 pointCurrent[`cp2${valueAxis}`] = vPixel + delta * mK[i];
2813 * This function calculates Bézier control points in a similar way than |splineCurve|,
2814 * but preserves monotonicity of the provided data and ensures no local extremums are added
2815 * between the dataset discrete points due to the interpolation.
2816 * See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
2817 */ function splineCurveMonotone(points, indexAxis = 'x') {
2818 const valueAxis = getValueAxis(indexAxis);
2819 const pointsLen = points.length;
2820 const deltaK = Array(pointsLen).fill(0);
2821 const mK = Array(pointsLen);
2822 // Calculate slopes (deltaK) and initialize tangents (mK)
2823 let i, pointBefore, pointCurrent;
2824 let pointAfter = getPoint(points, 0);
2825 for(i = 0; i < pointsLen; ++i){
2826 pointBefore = pointCurrent;
2827 pointCurrent = pointAfter;
2828 pointAfter = getPoint(points, i + 1);
2829 if (!pointCurrent) {
2833 const slopeDelta = pointAfter[indexAxis] - pointCurrent[indexAxis];
2834 // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
2835 deltaK[i] = slopeDelta !== 0 ? (pointAfter[valueAxis] - pointCurrent[valueAxis]) / slopeDelta : 0;
2837 mK[i] = !pointBefore ? deltaK[i] : !pointAfter ? deltaK[i - 1] : sign(deltaK[i - 1]) !== sign(deltaK[i]) ? 0 : (deltaK[i - 1] + deltaK[i]) / 2;
2839 monotoneAdjust(points, deltaK, mK);
2840 monotoneCompute(points, mK, indexAxis);
2842 function capControlPoint(pt, min, max) {
2843 return Math.max(Math.min(pt, max), min);
2845 function capBezierPoints(points, area) {
2846 let i, ilen, point, inArea, inAreaPrev;
2847 let inAreaNext = _isPointInArea(points[0], area);
2848 for(i = 0, ilen = points.length; i < ilen; ++i){
2849 inAreaPrev = inArea;
2850 inArea = inAreaNext;
2851 inAreaNext = i < ilen - 1 && _isPointInArea(points[i + 1], area);
2857 point.cp1x = capControlPoint(point.cp1x, area.left, area.right);
2858 point.cp1y = capControlPoint(point.cp1y, area.top, area.bottom);
2861 point.cp2x = capControlPoint(point.cp2x, area.left, area.right);
2862 point.cp2y = capControlPoint(point.cp2y, area.top, area.bottom);
2868 */ function _updateBezierControlPoints(points, options, area, loop, indexAxis) {
2869 let i, ilen, point, controlPoints;
2870 // Only consider points that are drawn in case the spanGaps option is used
2871 if (options.spanGaps) {
2872 points = points.filter((pt)=>!pt.skip);
2874 if (options.cubicInterpolationMode === 'monotone') {
2875 splineCurveMonotone(points, indexAxis);
2877 let prev = loop ? points[points.length - 1] : points[0];
2878 for(i = 0, ilen = points.length; i < ilen; ++i){
2880 controlPoints = splineCurve(prev, point, points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen], options.tension);
2881 point.cp1x = controlPoints.previous.x;
2882 point.cp1y = controlPoints.previous.y;
2883 point.cp2x = controlPoints.next.x;
2884 point.cp2y = controlPoints.next.y;
2888 if (options.capBezierPoints) {
2889 capBezierPoints(points, area);
2893 const atEdge = (t)=>t === 0 || t === 1;
2894 const elasticIn = (t, s, p)=>-(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p));
2895 const elasticOut = (t, s, p)=>Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1;
2897 * Easing functions adapted from Robert Penner's easing equations.
2898 * @namespace Chart.helpers.easing.effects
2899 * @see http://www.robertpenner.com/easing/
2900 */ const effects = {
2902 easeInQuad: (t)=>t * t,
2903 easeOutQuad: (t)=>-t * (t - 2),
2904 easeInOutQuad: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t : -0.5 * (--t * (t - 2) - 1),
2905 easeInCubic: (t)=>t * t * t,
2906 easeOutCubic: (t)=>(t -= 1) * t * t + 1,
2907 easeInOutCubic: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t * t : 0.5 * ((t -= 2) * t * t + 2),
2908 easeInQuart: (t)=>t * t * t * t,
2909 easeOutQuart: (t)=>-((t -= 1) * t * t * t - 1),
2910 easeInOutQuart: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t * t * t : -0.5 * ((t -= 2) * t * t * t - 2),
2911 easeInQuint: (t)=>t * t * t * t * t,
2912 easeOutQuint: (t)=>(t -= 1) * t * t * t * t + 1,
2913 easeInOutQuint: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t * t * t * t : 0.5 * ((t -= 2) * t * t * t * t + 2),
2914 easeInSine: (t)=>-Math.cos(t * HALF_PI) + 1,
2915 easeOutSine: (t)=>Math.sin(t * HALF_PI),
2916 easeInOutSine: (t)=>-0.5 * (Math.cos(PI * t) - 1),
2917 easeInExpo: (t)=>t === 0 ? 0 : Math.pow(2, 10 * (t - 1)),
2918 easeOutExpo: (t)=>t === 1 ? 1 : -Math.pow(2, -10 * t) + 1,
2919 easeInOutExpo: (t)=>atEdge(t) ? t : t < 0.5 ? 0.5 * Math.pow(2, 10 * (t * 2 - 1)) : 0.5 * (-Math.pow(2, -10 * (t * 2 - 1)) + 2),
2920 easeInCirc: (t)=>t >= 1 ? t : -(Math.sqrt(1 - t * t) - 1),
2921 easeOutCirc: (t)=>Math.sqrt(1 - (t -= 1) * t),
2922 easeInOutCirc: (t)=>(t /= 0.5) < 1 ? -0.5 * (Math.sqrt(1 - t * t) - 1) : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1),
2923 easeInElastic: (t)=>atEdge(t) ? t : elasticIn(t, 0.075, 0.3),
2924 easeOutElastic: (t)=>atEdge(t) ? t : elasticOut(t, 0.075, 0.3),
2925 easeInOutElastic (t) {
2928 return atEdge(t) ? t : t < 0.5 ? 0.5 * elasticIn(t * 2, s, p) : 0.5 + 0.5 * elasticOut(t * 2 - 1, s, p);
2932 return t * t * ((s + 1) * t - s);
2936 return (t -= 1) * t * ((s + 1) * t + s) + 1;
2940 if ((t /= 0.5) < 1) {
2941 return 0.5 * (t * t * (((s *= 1.525) + 1) * t - s));
2943 return 0.5 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2);
2945 easeInBounce: (t)=>1 - effects.easeOutBounce(1 - t),
2953 return m * (t -= 1.5 / d) * t + 0.75;
2956 return m * (t -= 2.25 / d) * t + 0.9375;
2958 return m * (t -= 2.625 / d) * t + 0.984375;
2960 easeInOutBounce: (t)=>t < 0.5 ? effects.easeInBounce(t * 2) * 0.5 : effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5
2965 */ function _pointInLine(p1, p2, t, mode) {
2967 x: p1.x + t * (p2.x - p1.x),
2968 y: p1.y + t * (p2.y - p1.y)
2973 */ function _steppedInterpolation(p1, p2, t, mode) {
2975 x: p1.x + t * (p2.x - p1.x),
2976 y: mode === 'middle' ? t < 0.5 ? p1.y : p2.y : mode === 'after' ? t < 1 ? p1.y : p2.y : t > 0 ? p2.y : p1.y
2981 */ function _bezierInterpolation(p1, p2, t, mode) {
2990 const a = _pointInLine(p1, cp1, t);
2991 const b = _pointInLine(cp1, cp2, t);
2992 const c = _pointInLine(cp2, p2, t);
2993 const d = _pointInLine(a, b, t);
2994 const e = _pointInLine(b, c, t);
2995 return _pointInLine(d, e, t);
2998 const LINE_HEIGHT = /^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/;
2999 const FONT_STYLE = /^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;
3001 * @alias Chart.helpers.options
3004 * Converts the given line height `value` in pixels for a specific font `size`.
3005 * @param value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
3006 * @param size - The font size (in pixels) used to resolve relative `value`.
3007 * @returns The effective line height in pixels (size * 1.2 if value is invalid).
3008 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
3010 */ function toLineHeight(value, size) {
3011 const matches = ('' + value).match(LINE_HEIGHT);
3012 if (!matches || matches[1] === 'normal') {
3015 value = +matches[2];
3023 return size * value;
3025 const numberOrZero = (v)=>+v || 0;
3026 function _readValueToProps(value, props) {
3028 const objProps = isObject(props);
3029 const keys = objProps ? Object.keys(props) : props;
3030 const read = isObject(value) ? objProps ? (prop)=>valueOrDefault(value[prop], value[props[prop]]) : (prop)=>value[prop] : ()=>value;
3031 for (const prop of keys){
3032 ret[prop] = numberOrZero(read(prop));
3037 * Converts the given value into a TRBL object.
3038 * @param value - If a number, set the value to all TRBL component,
3039 * else, if an object, use defined properties and sets undefined ones to 0.
3040 * x / y are shorthands for same value for left/right and top/bottom.
3041 * @returns The padding values (top, right, bottom, left)
3043 */ function toTRBL(value) {
3044 return _readValueToProps(value, {
3052 * Converts the given value into a TRBL corners object (similar with css border-radius).
3053 * @param value - If a number, set the value to all TRBL corner components,
3054 * else, if an object, use defined properties and sets undefined ones to 0.
3055 * @returns The TRBL corner values (topLeft, topRight, bottomLeft, bottomRight)
3057 */ function toTRBLCorners(value) {
3058 return _readValueToProps(value, [
3066 * Converts the given value into a padding object with pre-computed width/height.
3067 * @param value - If a number, set the value to all TRBL component,
3068 * else, if an object, use defined properties and sets undefined ones to 0.
3069 * x / y are shorthands for same value for left/right and top/bottom.
3070 * @returns The padding values (top, right, bottom, left, width, height)
3072 */ function toPadding(value) {
3073 const obj = toTRBL(value);
3074 obj.width = obj.left + obj.right;
3075 obj.height = obj.top + obj.bottom;
3079 * Parses font options and returns the font object.
3080 * @param options - A object that contains font options to be parsed.
3081 * @param fallback - A object that contains fallback font options.
3082 * @return The font object.
3084 */ function toFont(options, fallback) {
3085 options = options || {};
3086 fallback = fallback || defaults.font;
3087 let size = valueOrDefault(options.size, fallback.size);
3088 if (typeof size === 'string') {
3089 size = parseInt(size, 10);
3091 let style = valueOrDefault(options.style, fallback.style);
3092 if (style && !('' + style).match(FONT_STYLE)) {
3093 console.warn('Invalid font style specified: "' + style + '"');
3097 family: valueOrDefault(options.family, fallback.family),
3098 lineHeight: toLineHeight(valueOrDefault(options.lineHeight, fallback.lineHeight), size),
3101 weight: valueOrDefault(options.weight, fallback.weight),
3104 font.string = toFontString(font);
3108 * Evaluates the given `inputs` sequentially and returns the first defined value.
3109 * @param inputs - An array of values, falling back to the last value.
3110 * @param context - If defined and the current value is a function, the value
3111 * is called with `context` as first argument and the result becomes the new input.
3112 * @param index - If defined and the current value is an array, the value
3113 * at `index` become the new input.
3114 * @param info - object to return information about resolution in
3115 * @param info.cacheable - Will be set to `false` if option is not cacheable.
3117 */ function resolve(inputs, context, index, info) {
3118 let cacheable = true;
3120 for(i = 0, ilen = inputs.length; i < ilen; ++i){
3122 if (value === undefined) {
3125 if (context !== undefined && typeof value === 'function') {
3126 value = value(context);
3129 if (index !== undefined && isArray(value)) {
3130 value = value[index % value.length];
3133 if (value !== undefined) {
3134 if (info && !cacheable) {
3135 info.cacheable = false;
3144 * @param beginAtZero
3146 */ function _addGrace(minmax, grace, beginAtZero) {
3147 const { min , max } = minmax;
3148 const change = toDimension(grace, (max - min) / 2);
3149 const keepZero = (value, add)=>beginAtZero && value === 0 ? 0 : value + add;
3151 min: keepZero(min, -Math.abs(change)),
3152 max: keepZero(max, change)
3155 function createContext(parentContext, context) {
3156 return Object.assign(Object.create(parentContext), context);
3159 const getRightToLeftAdapter = function(rectX, width) {
3162 return rectX + rectX + width - x;
3168 if (align === 'center') {
3171 return align === 'right' ? 'left' : 'right';
3176 leftForLtr (x, itemWidth) {
3177 return x - itemWidth;
3181 const getLeftToRightAdapter = function() {
3193 leftForLtr (x, _itemWidth) {
3198 function getRtlAdapter(rtl, rectX, width) {
3199 return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter();
3201 function overrideTextDirection(ctx, direction) {
3202 let style, original;
3203 if (direction === 'ltr' || direction === 'rtl') {
3204 style = ctx.canvas.style;
3206 style.getPropertyValue('direction'),
3207 style.getPropertyPriority('direction')
3209 style.setProperty('direction', direction, 'important');
3210 ctx.prevTextDirection = original;
3213 function restoreTextDirection(ctx, original) {
3214 if (original !== undefined) {
3215 delete ctx.prevTextDirection;
3216 ctx.canvas.style.setProperty('direction', original[0], original[1]);
3220 function propertyFn(property) {
3221 if (property === 'angle') {
3223 between: _angleBetween,
3224 compare: _angleDiff,
3225 normalize: _normalizeAngle
3229 between: _isBetween,
3230 compare: (a, b)=>a - b,
3234 function normalizeSegment({ start , end , count , loop , style }) {
3236 start: start % count,
3238 loop: loop && (end - start + 1) % count === 0,
3242 function getSegment(segment, points, bounds) {
3243 const { property , start: startBound , end: endBound } = bounds;
3244 const { between , normalize } = propertyFn(property);
3245 const count = points.length;
3246 let { start , end , loop } = segment;
3251 for(i = 0, ilen = count; i < ilen; ++i){
3252 if (!between(normalize(points[start % count][property]), startBound, endBound)) {
3268 style: segment.style
3271 function _boundSegment(segment, points, bounds) {
3277 const { property , start: startBound , end: endBound } = bounds;
3278 const count = points.length;
3279 const { compare , between , normalize } = propertyFn(property);
3280 const { start , end , loop , style } = getSegment(segment, points, bounds);
3283 let subStart = null;
3284 let value, point, prevValue;
3285 const startIsBefore = ()=>between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0;
3286 const endIsBefore = ()=>compare(endBound, value) === 0 || between(endBound, prevValue, value);
3287 const shouldStart = ()=>inside || startIsBefore();
3288 const shouldStop = ()=>!inside || endIsBefore();
3289 for(let i = start, prev = start; i <= end; ++i){
3290 point = points[i % count];
3294 value = normalize(point[property]);
3295 if (value === prevValue) {
3298 inside = between(value, startBound, endBound);
3299 if (subStart === null && shouldStart()) {
3300 subStart = compare(value, startBound) === 0 ? i : prev;
3302 if (subStart !== null && shouldStop()) {
3303 result.push(normalizeSegment({
3315 if (subStart !== null) {
3316 result.push(normalizeSegment({
3326 function _boundSegments(line, bounds) {
3328 const segments = line.segments;
3329 for(let i = 0; i < segments.length; i++){
3330 const sub = _boundSegment(segments[i], line.points, bounds);
3332 result.push(...sub);
3337 function findStartAndEnd(points, count, loop, spanGaps) {
3339 let end = count - 1;
3340 if (loop && !spanGaps) {
3341 while(start < count && !points[start].skip){
3345 while(start < count && points[start].skip){
3352 while(end > start && points[end % count].skip){
3361 function solidSegments(points, start, max, loop) {
3362 const count = points.length;
3365 let prev = points[start];
3367 for(end = start + 1; end <= max; ++end){
3368 const cur = points[end % count];
3369 if (cur.skip || cur.stop) {
3373 start: start % count,
3374 end: (end - 1) % count,
3377 start = last = cur.stop ? end : null;
3387 if (last !== null) {
3389 start: start % count,
3396 function _computeSegments(line, segmentOptions) {
3397 const points = line.points;
3398 const spanGaps = line.options.spanGaps;
3399 const count = points.length;
3403 const loop = !!line._loop;
3404 const { start , end } = findStartAndEnd(points, count, loop, spanGaps);
3405 if (spanGaps === true) {
3406 return splitByStyles(line, [
3412 ], points, segmentOptions);
3414 const max = end < start ? end + count : end;
3415 const completeLoop = !!line._fullLoop && start === 0 && end === count - 1;
3416 return splitByStyles(line, solidSegments(points, start, max, completeLoop), points, segmentOptions);
3418 function splitByStyles(line, segments, points, segmentOptions) {
3419 if (!segmentOptions || !segmentOptions.setContext || !points) {
3422 return doSplitByStyles(line, segments, points, segmentOptions);
3424 function doSplitByStyles(line, segments, points, segmentOptions) {
3425 const chartContext = line._chart.getContext();
3426 const baseStyle = readStyle(line.options);
3427 const { _datasetIndex: datasetIndex , options: { spanGaps } } = line;
3428 const count = points.length;
3430 let prevStyle = baseStyle;
3431 let start = segments[0].start;
3433 function addStyle(s, e, l, st) {
3434 const dir = spanGaps ? -1 : 1;
3439 while(points[s % count].skip){
3442 while(points[e % count].skip){
3445 if (s % count !== e % count) {
3456 for (const segment of segments){
3457 start = spanGaps ? start : segment.start;
3458 let prev = points[start % count];
3460 for(i = start + 1; i <= segment.end; i++){
3461 const pt = points[i % count];
3462 style = readStyle(segmentOptions.setContext(createContext(chartContext, {
3466 p0DataIndex: (i - 1) % count,
3467 p1DataIndex: i % count,
3470 if (styleChanged(style, prevStyle)) {
3471 addStyle(start, i - 1, segment.loop, prevStyle);
3476 if (start < i - 1) {
3477 addStyle(start, i - 1, segment.loop, prevStyle);
3482 function readStyle(options) {
3484 backgroundColor: options.backgroundColor,
3485 borderCapStyle: options.borderCapStyle,
3486 borderDash: options.borderDash,
3487 borderDashOffset: options.borderDashOffset,
3488 borderJoinStyle: options.borderJoinStyle,
3489 borderWidth: options.borderWidth,
3490 borderColor: options.borderColor
3493 function styleChanged(style, prevStyle) {
3498 const replacer = function(key, value) {
3499 if (!isPatternOrGradient(value)) {
3502 if (!cache.includes(value)) {
3505 return cache.indexOf(value);
3507 return JSON.stringify(style, replacer) !== JSON.stringify(prevStyle, replacer);
3510 var helpers = /*#__PURE__*/Object.freeze({
3516 QUARTER_PI: QUARTER_PI,
3517 RAD_PER_DEG: RAD_PER_DEG,
3519 TWO_THIRDS_PI: TWO_THIRDS_PI,
3520 _addGrace: _addGrace,
3521 _alignPixel: _alignPixel,
3522 _alignStartEnd: _alignStartEnd,
3523 _angleBetween: _angleBetween,
3524 _angleDiff: _angleDiff,
3525 _arrayUnique: _arrayUnique,
3526 _attachContext: _attachContext,
3527 _bezierCurveTo: _bezierCurveTo,
3528 _bezierInterpolation: _bezierInterpolation,
3529 _boundSegment: _boundSegment,
3530 _boundSegments: _boundSegments,
3531 _capitalize: _capitalize,
3532 _computeSegments: _computeSegments,
3533 _createResolver: _createResolver,
3534 _decimalPlaces: _decimalPlaces,
3535 _deprecated: _deprecated,
3536 _descriptors: _descriptors,
3537 _elementsEqual: _elementsEqual,
3538 _factorize: _factorize,
3539 _filterBetween: _filterBetween,
3540 _getParentNode: _getParentNode,
3541 _getStartAndCountOfVisiblePoints: _getStartAndCountOfVisiblePoints,
3542 _int16Range: _int16Range,
3543 _isBetween: _isBetween,
3544 _isClickEvent: _isClickEvent,
3545 _isDomSupported: _isDomSupported,
3546 _isPointInArea: _isPointInArea,
3547 _limitValue: _limitValue,
3548 _longestText: _longestText,
3550 _lookupByKey: _lookupByKey,
3551 _measureText: _measureText,
3553 _mergerIf: _mergerIf,
3554 _normalizeAngle: _normalizeAngle,
3555 _parseObjectDataRadialScale: _parseObjectDataRadialScale,
3556 _pointInLine: _pointInLine,
3557 _readValueToProps: _readValueToProps,
3558 _rlookupByKey: _rlookupByKey,
3559 _scaleRangesChanged: _scaleRangesChanged,
3560 _setMinAndMaxByKey: _setMinAndMaxByKey,
3561 _splitKey: _splitKey,
3562 _steppedInterpolation: _steppedInterpolation,
3563 _steppedLineTo: _steppedLineTo,
3565 _toLeftRightCenter: _toLeftRightCenter,
3566 _updateBezierControlPoints: _updateBezierControlPoints,
3567 addRoundedRectPath: addRoundedRectPath,
3568 almostEquals: almostEquals,
3569 almostWhole: almostWhole,
3571 clearCanvas: clearCanvas,
3575 createContext: createContext,
3578 distanceBetweenPoints: distanceBetweenPoints,
3579 drawPoint: drawPoint,
3580 drawPointLegend: drawPointLegend,
3582 easingEffects: effects,
3583 finiteOrDefault: finiteOrDefault,
3584 fontString: fontString,
3585 formatNumber: formatNumber,
3586 getAngleFromPoint: getAngleFromPoint,
3587 getHoverColor: getHoverColor,
3588 getMaximumSize: getMaximumSize,
3589 getRelativePosition: getRelativePosition,
3590 getRtlAdapter: getRtlAdapter,
3593 isFinite: isNumberFinite,
3594 isFunction: isFunction,
3595 isNullOrUndef: isNullOrUndef,
3598 isPatternOrGradient: isPatternOrGradient,
3599 listenArrayEvents: listenArrayEvents,
3605 overrideTextDirection: overrideTextDirection,
3606 readUsedSize: readUsedSize,
3607 renderText: renderText,
3608 requestAnimFrame: requestAnimFrame,
3610 resolveObjectKey: resolveObjectKey,
3611 restoreTextDirection: restoreTextDirection,
3612 retinaScale: retinaScale,
3613 setsEqual: setsEqual,
3615 splineCurve: splineCurve,
3616 splineCurveMonotone: splineCurveMonotone,
3617 supportsEventListenerOptions: supportsEventListenerOptions,
3618 throttled: throttled,
3619 toDegrees: toDegrees,
3620 toDimension: toDimension,
3622 toFontString: toFontString,
3623 toLineHeight: toLineHeight,
3624 toPadding: toPadding,
3625 toPercentage: toPercentage,
3626 toRadians: toRadians,
3628 toTRBLCorners: toTRBLCorners,
3630 unclipArea: unclipArea,
3631 unlistenArrayEvents: unlistenArrayEvents,
3632 valueOrDefault: valueOrDefault
3635 function binarySearch(metaset, axis, value, intersect) {
3636 const { controller , data , _sorted } = metaset;
3637 const iScale = controller._cachedMeta.iScale;
3638 if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {
3639 const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey;
3641 return lookupMethod(data, axis, value);
3642 } else if (controller._sharedOptions) {
3644 const range = typeof el.getRange === 'function' && el.getRange(axis);
3646 const start = lookupMethod(data, axis, value - range);
3647 const end = lookupMethod(data, axis, value + range);
3660 function evaluateInteractionItems(chart, axis, position, handler, intersect) {
3661 const metasets = chart.getSortedVisibleDatasetMetas();
3662 const value = position[axis];
3663 for(let i = 0, ilen = metasets.length; i < ilen; ++i){
3664 const { index , data } = metasets[i];
3665 const { lo , hi } = binarySearch(metasets[i], axis, value, intersect);
3666 for(let j = lo; j <= hi; ++j){
3667 const element = data[j];
3668 if (!element.skip) {
3669 handler(element, index, j);
3674 function getDistanceMetricForAxis(axis) {
3675 const useX = axis.indexOf('x') !== -1;
3676 const useY = axis.indexOf('y') !== -1;
3677 return function(pt1, pt2) {
3678 const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
3679 const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
3680 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
3683 function getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) {
3685 if (!includeInvisible && !chart.isPointInArea(position)) {
3688 const evaluationFunc = function(element, datasetIndex, index) {
3689 if (!includeInvisible && !_isPointInArea(element, chart.chartArea, 0)) {
3692 if (element.inRange(position.x, position.y, useFinalPosition)) {
3700 evaluateInteractionItems(chart, axis, position, evaluationFunc, true);
3703 function getNearestRadialItems(chart, position, axis, useFinalPosition) {
3705 function evaluationFunc(element, datasetIndex, index) {
3706 const { startAngle , endAngle } = element.getProps([
3709 ], useFinalPosition);
3710 const { angle } = getAngleFromPoint(element, {
3714 if (_angleBetween(angle, startAngle, endAngle)) {
3722 evaluateInteractionItems(chart, axis, position, evaluationFunc);
3725 function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
3727 const distanceMetric = getDistanceMetricForAxis(axis);
3728 let minDistance = Number.POSITIVE_INFINITY;
3729 function evaluationFunc(element, datasetIndex, index) {
3730 const inRange = element.inRange(position.x, position.y, useFinalPosition);
3731 if (intersect && !inRange) {
3734 const center = element.getCenterPoint(useFinalPosition);
3735 const pointInArea = !!includeInvisible || chart.isPointInArea(center);
3736 if (!pointInArea && !inRange) {
3739 const distance = distanceMetric(position, center);
3740 if (distance < minDistance) {
3748 minDistance = distance;
3749 } else if (distance === minDistance) {
3757 evaluateInteractionItems(chart, axis, position, evaluationFunc);
3760 function getNearestItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
3761 if (!includeInvisible && !chart.isPointInArea(position)) {
3764 return axis === 'r' && !intersect ? getNearestRadialItems(chart, position, axis, useFinalPosition) : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible);
3766 function getAxisItems(chart, position, axis, intersect, useFinalPosition) {
3768 const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
3769 let intersectsItem = false;
3770 evaluateInteractionItems(chart, axis, position, (element, datasetIndex, index)=>{
3771 if (element[rangeMethod](position[axis], useFinalPosition)) {
3777 intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition);
3780 if (intersect && !intersectsItem) {
3786 evaluateInteractionItems,
3788 index (chart, e, options, useFinalPosition) {
3789 const position = getRelativePosition(e, chart);
3790 const axis = options.axis || 'x';
3791 const includeInvisible = options.includeInvisible || false;
3792 const items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
3793 const elements = [];
3794 if (!items.length) {
3797 chart.getSortedVisibleDatasetMetas().forEach((meta)=>{
3798 const index = items[0].index;
3799 const element = meta.data[index];
3800 if (element && !element.skip) {
3803 datasetIndex: meta.index,
3810 dataset (chart, e, options, useFinalPosition) {
3811 const position = getRelativePosition(e, chart);
3812 const axis = options.axis || 'xy';
3813 const includeInvisible = options.includeInvisible || false;
3814 let items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
3815 if (items.length > 0) {
3816 const datasetIndex = items[0].datasetIndex;
3817 const data = chart.getDatasetMeta(datasetIndex).data;
3819 for(let i = 0; i < data.length; ++i){
3829 point (chart, e, options, useFinalPosition) {
3830 const position = getRelativePosition(e, chart);
3831 const axis = options.axis || 'xy';
3832 const includeInvisible = options.includeInvisible || false;
3833 return getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible);
3835 nearest (chart, e, options, useFinalPosition) {
3836 const position = getRelativePosition(e, chart);
3837 const axis = options.axis || 'xy';
3838 const includeInvisible = options.includeInvisible || false;
3839 return getNearestItems(chart, position, axis, options.intersect, useFinalPosition, includeInvisible);
3841 x (chart, e, options, useFinalPosition) {
3842 const position = getRelativePosition(e, chart);
3843 return getAxisItems(chart, position, 'x', options.intersect, useFinalPosition);
3845 y (chart, e, options, useFinalPosition) {
3846 const position = getRelativePosition(e, chart);
3847 return getAxisItems(chart, position, 'y', options.intersect, useFinalPosition);
3852 const STATIC_POSITIONS = [
3858 function filterByPosition(array, position) {
3859 return array.filter((v)=>v.pos === position);
3861 function filterDynamicPositionByAxis(array, axis) {
3862 return array.filter((v)=>STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);
3864 function sortByWeight(array, reverse) {
3865 return array.sort((a, b)=>{
3866 const v0 = reverse ? b : a;
3867 const v1 = reverse ? a : b;
3868 return v0.weight === v1.weight ? v0.index - v1.index : v0.weight - v1.weight;
3871 function wrapBoxes(boxes) {
3872 const layoutBoxes = [];
3873 let i, ilen, box, pos, stack, stackWeight;
3874 for(i = 0, ilen = (boxes || []).length; i < ilen; ++i){
3876 ({ position: pos , options: { stack , stackWeight =1 } } = box);
3881 horizontal: box.isHorizontal(),
3883 stack: stack && pos + stack,
3889 function buildStacks(layouts) {
3891 for (const wrap of layouts){
3892 const { stack , pos , stackWeight } = wrap;
3893 if (!stack || !STATIC_POSITIONS.includes(pos)) {
3896 const _stack = stacks[stack] || (stacks[stack] = {
3903 _stack.weight += stackWeight;
3907 function setLayoutDims(layouts, params) {
3908 const stacks = buildStacks(layouts);
3909 const { vBoxMaxWidth , hBoxMaxHeight } = params;
3910 let i, ilen, layout;
3911 for(i = 0, ilen = layouts.length; i < ilen; ++i){
3912 layout = layouts[i];
3913 const { fullSize } = layout.box;
3914 const stack = stacks[layout.stack];
3915 const factor = stack && layout.stackWeight / stack.weight;
3916 if (layout.horizontal) {
3917 layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth;
3918 layout.height = hBoxMaxHeight;
3920 layout.width = vBoxMaxWidth;
3921 layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight;
3926 function buildLayoutBoxes(boxes) {
3927 const layoutBoxes = wrapBoxes(boxes);
3928 const fullSize = sortByWeight(layoutBoxes.filter((wrap)=>wrap.box.fullSize), true);
3929 const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
3930 const right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
3931 const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
3932 const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
3933 const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');
3934 const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');
3937 leftAndTop: left.concat(top),
3938 rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),
3939 chartArea: filterByPosition(layoutBoxes, 'chartArea'),
3940 vertical: left.concat(right).concat(centerVertical),
3941 horizontal: top.concat(bottom).concat(centerHorizontal)
3944 function getCombinedMax(maxPadding, chartArea, a, b) {
3945 return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
3947 function updateMaxPadding(maxPadding, boxPadding) {
3948 maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
3949 maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
3950 maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
3951 maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
3953 function updateDims(chartArea, params, layout, stacks) {
3954 const { pos , box } = layout;
3955 const maxPadding = chartArea.maxPadding;
3956 if (!isObject(pos)) {
3958 chartArea[pos] -= layout.size;
3960 const stack = stacks[layout.stack] || {
3964 stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width);
3965 layout.size = stack.size / stack.count;
3966 chartArea[pos] += layout.size;
3968 if (box.getPadding) {
3969 updateMaxPadding(maxPadding, box.getPadding());
3971 const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));
3972 const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));
3973 const widthChanged = newWidth !== chartArea.w;
3974 const heightChanged = newHeight !== chartArea.h;
3975 chartArea.w = newWidth;
3976 chartArea.h = newHeight;
3977 return layout.horizontal ? {
3979 other: heightChanged
3981 same: heightChanged,
3985 function handleMaxPadding(chartArea) {
3986 const maxPadding = chartArea.maxPadding;
3987 function updatePos(pos) {
3988 const change = Math.max(maxPadding[pos] - chartArea[pos], 0);
3989 chartArea[pos] += change;
3992 chartArea.y += updatePos('top');
3993 chartArea.x += updatePos('left');
3995 updatePos('bottom');
3997 function getMargins(horizontal, chartArea) {
3998 const maxPadding = chartArea.maxPadding;
3999 function marginForPositions(positions) {
4006 positions.forEach((pos)=>{
4007 margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
4011 return horizontal ? marginForPositions([
4014 ]) : marginForPositions([
4019 function fitBoxes(boxes, chartArea, params, stacks) {
4020 const refitBoxes = [];
4021 let i, ilen, layout, box, refit, changed;
4022 for(i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i){
4025 box.update(layout.width || chartArea.w, layout.height || chartArea.h, getMargins(layout.horizontal, chartArea));
4026 const { same , other } = updateDims(chartArea, params, layout, stacks);
4027 refit |= same && refitBoxes.length;
4028 changed = changed || other;
4029 if (!box.fullSize) {
4030 refitBoxes.push(layout);
4033 return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed;
4035 function setBoxDims(box, left, top, width, height) {
4038 box.right = left + width;
4039 box.bottom = top + height;
4041 box.height = height;
4043 function placeBoxes(boxes, chartArea, params, stacks) {
4044 const userPadding = params.padding;
4045 let { x , y } = chartArea;
4046 for (const layout of boxes){
4047 const box = layout.box;
4048 const stack = stacks[layout.stack] || {
4053 const weight = layout.stackWeight / stack.weight || 1;
4054 if (layout.horizontal) {
4055 const width = chartArea.w * weight;
4056 const height = stack.size || box.height;
4057 if (defined(stack.start)) {
4061 setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height);
4063 setBoxDims(box, chartArea.left + stack.placed, y, width, height);
4066 stack.placed += width;
4069 const height = chartArea.h * weight;
4070 const width = stack.size || box.width;
4071 if (defined(stack.start)) {
4075 setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top);
4077 setBoxDims(box, x, chartArea.top + stack.placed, width, height);
4080 stack.placed += height;
4088 addBox (chart, item) {
4092 item.fullSize = item.fullSize || false;
4093 item.position = item.position || 'top';
4094 item.weight = item.weight || 0;
4095 item._layers = item._layers || function() {
4100 item.draw(chartArea);
4105 chart.boxes.push(item);
4107 removeBox (chart, layoutItem) {
4108 const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
4110 chart.boxes.splice(index, 1);
4113 configure (chart, item, options) {
4114 item.fullSize = options.fullSize;
4115 item.position = options.position;
4116 item.weight = options.weight;
4118 update (chart, width, height, minPadding) {
4122 const padding = toPadding(chart.options.layout.padding);
4123 const availableWidth = Math.max(width - padding.width, 0);
4124 const availableHeight = Math.max(height - padding.height, 0);
4125 const boxes = buildLayoutBoxes(chart.boxes);
4126 const verticalBoxes = boxes.vertical;
4127 const horizontalBoxes = boxes.horizontal;
4128 each(chart.boxes, (box)=>{
4129 if (typeof box.beforeLayout === 'function') {
4133 const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap)=>wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1;
4134 const params = Object.freeze({
4136 outerHeight: height,
4140 vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
4141 hBoxMaxHeight: availableHeight / 2
4143 const maxPadding = Object.assign({}, padding);
4144 updateMaxPadding(maxPadding, toPadding(minPadding));
4145 const chartArea = Object.assign({
4152 const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
4153 fitBoxes(boxes.fullSize, chartArea, params, stacks);
4154 fitBoxes(verticalBoxes, chartArea, params, stacks);
4155 if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) {
4156 fitBoxes(verticalBoxes, chartArea, params, stacks);
4158 handleMaxPadding(chartArea);
4159 placeBoxes(boxes.leftAndTop, chartArea, params, stacks);
4160 chartArea.x += chartArea.w;
4161 chartArea.y += chartArea.h;
4162 placeBoxes(boxes.rightAndBottom, chartArea, params, stacks);
4164 left: chartArea.left,
4166 right: chartArea.left + chartArea.w,
4167 bottom: chartArea.top + chartArea.h,
4168 height: chartArea.h,
4171 each(boxes.chartArea, (layout)=>{
4172 const box = layout.box;
4173 Object.assign(box, chart.chartArea);
4174 box.update(chartArea.w, chartArea.h, {
4184 class BasePlatform {
4185 acquireContext(canvas, aspectRatio) {}
4186 releaseContext(context) {
4189 addEventListener(chart, type, listener) {}
4190 removeEventListener(chart, type, listener) {}
4191 getDevicePixelRatio() {
4194 getMaximumSize(element, width, height, aspectRatio) {
4195 width = Math.max(0, width || element.width);
4196 height = height || element.height;
4199 height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)
4202 isAttached(canvas) {
4205 updateConfig(config) {
4209 class BasicPlatform extends BasePlatform {
4210 acquireContext(item) {
4211 return item && item.getContext && item.getContext('2d') || null;
4213 updateConfig(config) {
4214 config.options.animation = false;
4218 const EXPANDO_KEY = '$chartjs';
4219 const EVENT_TYPES = {
4220 touchstart: 'mousedown',
4221 touchmove: 'mousemove',
4222 touchend: 'mouseup',
4223 pointerenter: 'mouseenter',
4224 pointerdown: 'mousedown',
4225 pointermove: 'mousemove',
4226 pointerup: 'mouseup',
4227 pointerleave: 'mouseout',
4228 pointerout: 'mouseout'
4230 const isNullOrEmpty = (value)=>value === null || value === '';
4231 function initCanvas(canvas, aspectRatio) {
4232 const style = canvas.style;
4233 const renderHeight = canvas.getAttribute('height');
4234 const renderWidth = canvas.getAttribute('width');
4235 canvas[EXPANDO_KEY] = {
4237 height: renderHeight,
4240 display: style.display,
4241 height: style.height,
4246 style.display = style.display || 'block';
4247 style.boxSizing = style.boxSizing || 'border-box';
4248 if (isNullOrEmpty(renderWidth)) {
4249 const displayWidth = readUsedSize(canvas, 'width');
4250 if (displayWidth !== undefined) {
4251 canvas.width = displayWidth;
4254 if (isNullOrEmpty(renderHeight)) {
4255 if (canvas.style.height === '') {
4256 canvas.height = canvas.width / (aspectRatio || 2);
4258 const displayHeight = readUsedSize(canvas, 'height');
4259 if (displayHeight !== undefined) {
4260 canvas.height = displayHeight;
4266 const eventListenerOptions = supportsEventListenerOptions ? {
4269 function addListener(node, type, listener) {
4271 node.addEventListener(type, listener, eventListenerOptions);
4274 function removeListener(chart, type, listener) {
4275 if (chart && chart.canvas) {
4276 chart.canvas.removeEventListener(type, listener, eventListenerOptions);
4279 function fromNativeEvent(event, chart) {
4280 const type = EVENT_TYPES[event.type] || event.type;
4281 const { x , y } = getRelativePosition(event, chart);
4286 x: x !== undefined ? x : null,
4287 y: y !== undefined ? y : null
4290 function nodeListContains(nodeList, canvas) {
4291 for (const node of nodeList){
4292 if (node === canvas || node.contains(canvas)) {
4297 function createAttachObserver(chart, type, listener) {
4298 const canvas = chart.canvas;
4299 const observer = new MutationObserver((entries)=>{
4300 let trigger = false;
4301 for (const entry of entries){
4302 trigger = trigger || nodeListContains(entry.addedNodes, canvas);
4303 trigger = trigger && !nodeListContains(entry.removedNodes, canvas);
4309 observer.observe(document, {
4315 function createDetachObserver(chart, type, listener) {
4316 const canvas = chart.canvas;
4317 const observer = new MutationObserver((entries)=>{
4318 let trigger = false;
4319 for (const entry of entries){
4320 trigger = trigger || nodeListContains(entry.removedNodes, canvas);
4321 trigger = trigger && !nodeListContains(entry.addedNodes, canvas);
4327 observer.observe(document, {
4333 const drpListeningCharts = new Map();
4334 let oldDevicePixelRatio = 0;
4335 function onWindowResize() {
4336 const dpr = window.devicePixelRatio;
4337 if (dpr === oldDevicePixelRatio) {
4340 oldDevicePixelRatio = dpr;
4341 drpListeningCharts.forEach((resize, chart)=>{
4342 if (chart.currentDevicePixelRatio !== dpr) {
4347 function listenDevicePixelRatioChanges(chart, resize) {
4348 if (!drpListeningCharts.size) {
4349 window.addEventListener('resize', onWindowResize);
4351 drpListeningCharts.set(chart, resize);
4353 function unlistenDevicePixelRatioChanges(chart) {
4354 drpListeningCharts.delete(chart);
4355 if (!drpListeningCharts.size) {
4356 window.removeEventListener('resize', onWindowResize);
4359 function createResizeObserver(chart, type, listener) {
4360 const canvas = chart.canvas;
4361 const container = canvas && _getParentNode(canvas);
4365 const resize = throttled((width, height)=>{
4366 const w = container.clientWidth;
4367 listener(width, height);
4368 if (w < container.clientWidth) {
4372 const observer = new ResizeObserver((entries)=>{
4373 const entry = entries[0];
4374 const width = entry.contentRect.width;
4375 const height = entry.contentRect.height;
4376 if (width === 0 && height === 0) {
4379 resize(width, height);
4381 observer.observe(container);
4382 listenDevicePixelRatioChanges(chart, resize);
4385 function releaseObserver(chart, type, observer) {
4387 observer.disconnect();
4389 if (type === 'resize') {
4390 unlistenDevicePixelRatioChanges(chart);
4393 function createProxyAndListen(chart, type, listener) {
4394 const canvas = chart.canvas;
4395 const proxy = throttled((event)=>{
4396 if (chart.ctx !== null) {
4397 listener(fromNativeEvent(event, chart));
4400 addListener(canvas, type, proxy);
4403 class DomPlatform extends BasePlatform {
4404 acquireContext(canvas, aspectRatio) {
4405 const context = canvas && canvas.getContext && canvas.getContext('2d');
4406 if (context && context.canvas === canvas) {
4407 initCanvas(canvas, aspectRatio);
4412 releaseContext(context) {
4413 const canvas = context.canvas;
4414 if (!canvas[EXPANDO_KEY]) {
4417 const initial = canvas[EXPANDO_KEY].initial;
4422 const value = initial[prop];
4423 if (isNullOrUndef(value)) {
4424 canvas.removeAttribute(prop);
4426 canvas.setAttribute(prop, value);
4429 const style = initial.style || {};
4430 Object.keys(style).forEach((key)=>{
4431 canvas.style[key] = style[key];
4433 canvas.width = canvas.width;
4434 delete canvas[EXPANDO_KEY];
4437 addEventListener(chart, type, listener) {
4438 this.removeEventListener(chart, type);
4439 const proxies = chart.$proxies || (chart.$proxies = {});
4441 attach: createAttachObserver,
4442 detach: createDetachObserver,
4443 resize: createResizeObserver
4445 const handler = handlers[type] || createProxyAndListen;
4446 proxies[type] = handler(chart, type, listener);
4448 removeEventListener(chart, type) {
4449 const proxies = chart.$proxies || (chart.$proxies = {});
4450 const proxy = proxies[type];
4455 attach: releaseObserver,
4456 detach: releaseObserver,
4457 resize: releaseObserver
4459 const handler = handlers[type] || removeListener;
4460 handler(chart, type, proxy);
4461 proxies[type] = undefined;
4463 getDevicePixelRatio() {
4464 return window.devicePixelRatio;
4466 getMaximumSize(canvas, width, height, aspectRatio) {
4467 return getMaximumSize(canvas, width, height, aspectRatio);
4469 isAttached(canvas) {
4470 const container = _getParentNode(canvas);
4471 return !!(container && container.isConnected);
4475 function _detectPlatform(canvas) {
4476 if (!_isDomSupported() || typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
4477 return BasicPlatform;
4482 var platforms = /*#__PURE__*/Object.freeze({
4484 BasePlatform: BasePlatform,
4485 BasicPlatform: BasicPlatform,
4486 DomPlatform: DomPlatform,
4487 _detectPlatform: _detectPlatform
4490 const transparent = 'transparent';
4491 const interpolators = {
4492 boolean (from, to, factor) {
4493 return factor > 0.5 ? to : from;
4495 color (from, to, factor) {
4496 const c0 = color(from || transparent);
4497 const c1 = c0.valid && color(to || transparent);
4498 return c1 && c1.valid ? c1.mix(c0, factor).hexString() : to;
4500 number (from, to, factor) {
4501 return from + (to - from) * factor;
4505 constructor(cfg, target, prop, to){
4506 const currentValue = target[prop];
4513 const from = resolve([
4518 this._active = true;
4519 this._fn = cfg.fn || interpolators[cfg.type || typeof from];
4520 this._easing = effects[cfg.easing] || effects.linear;
4521 this._start = Math.floor(Date.now() + (cfg.delay || 0));
4522 this._duration = this._total = Math.floor(cfg.duration);
4523 this._loop = !!cfg.loop;
4524 this._target = target;
4528 this._promises = undefined;
4531 return this._active;
4533 update(cfg, to, date) {
4535 this._notify(false);
4536 const currentValue = this._target[this._prop];
4537 const elapsed = date - this._start;
4538 const remain = this._duration - elapsed;
4540 this._duration = Math.floor(Math.max(remain, cfg.duration));
4541 this._total += elapsed;
4542 this._loop = !!cfg.loop;
4543 this._to = resolve([
4549 this._from = resolve([
4558 this.tick(Date.now());
4559 this._active = false;
4560 this._notify(false);
4564 const elapsed = date - this._start;
4565 const duration = this._duration;
4566 const prop = this._prop;
4567 const from = this._from;
4568 const loop = this._loop;
4569 const to = this._to;
4571 this._active = from !== to && (loop || elapsed < duration);
4572 if (!this._active) {
4573 this._target[prop] = to;
4578 this._target[prop] = from;
4581 factor = elapsed / duration % 2;
4582 factor = loop && factor > 1 ? 2 - factor : factor;
4583 factor = this._easing(Math.min(1, Math.max(0, factor)));
4584 this._target[prop] = this._fn(from, to, factor);
4587 const promises = this._promises || (this._promises = []);
4588 return new Promise((res, rej)=>{
4596 const method = resolved ? 'res' : 'rej';
4597 const promises = this._promises || [];
4598 for(let i = 0; i < promises.length; i++){
4599 promises[i][method]();
4605 constructor(chart, config){
4606 this._chart = chart;
4607 this._properties = new Map();
4608 this.configure(config);
4611 if (!isObject(config)) {
4614 const animationOptions = Object.keys(defaults.animation);
4615 const animatedProps = this._properties;
4616 Object.getOwnPropertyNames(config).forEach((key)=>{
4617 const cfg = config[key];
4618 if (!isObject(cfg)) {
4621 const resolved = {};
4622 for (const option of animationOptions){
4623 resolved[option] = cfg[option];
4625 (isArray(cfg.properties) && cfg.properties || [
4627 ]).forEach((prop)=>{
4628 if (prop === key || !animatedProps.has(prop)) {
4629 animatedProps.set(prop, resolved);
4634 _animateOptions(target, values) {
4635 const newOptions = values.options;
4636 const options = resolveTargetOptions(target, newOptions);
4640 const animations = this._createAnimations(options, newOptions);
4641 if (newOptions.$shared) {
4642 awaitAll(target.options.$animations, newOptions).then(()=>{
4643 target.options = newOptions;
4649 _createAnimations(target, values) {
4650 const animatedProps = this._properties;
4651 const animations = [];
4652 const running = target.$animations || (target.$animations = {});
4653 const props = Object.keys(values);
4654 const date = Date.now();
4656 for(i = props.length - 1; i >= 0; --i){
4657 const prop = props[i];
4658 if (prop.charAt(0) === '$') {
4661 if (prop === 'options') {
4662 animations.push(...this._animateOptions(target, values));
4665 const value = values[prop];
4666 let animation = running[prop];
4667 const cfg = animatedProps.get(prop);
4669 if (cfg && animation.active()) {
4670 animation.update(cfg, value, date);
4676 if (!cfg || !cfg.duration) {
4677 target[prop] = value;
4680 running[prop] = animation = new Animation(cfg, target, prop, value);
4681 animations.push(animation);
4685 update(target, values) {
4686 if (this._properties.size === 0) {
4687 Object.assign(target, values);
4690 const animations = this._createAnimations(target, values);
4691 if (animations.length) {
4692 animator.add(this._chart, animations);
4697 function awaitAll(animations, properties) {
4699 const keys = Object.keys(properties);
4700 for(let i = 0; i < keys.length; i++){
4701 const anim = animations[keys[i]];
4702 if (anim && anim.active()) {
4703 running.push(anim.wait());
4706 return Promise.all(running);
4708 function resolveTargetOptions(target, newOptions) {
4712 let options = target.options;
4714 target.options = newOptions;
4717 if (options.$shared) {
4718 target.options = options = Object.assign({}, options, {
4726 function scaleClip(scale, allowedOverflow) {
4727 const opts = scale && scale.options || {};
4728 const reverse = opts.reverse;
4729 const min = opts.min === undefined ? allowedOverflow : 0;
4730 const max = opts.max === undefined ? allowedOverflow : 0;
4732 start: reverse ? max : min,
4733 end: reverse ? min : max
4736 function defaultClip(xScale, yScale, allowedOverflow) {
4737 if (allowedOverflow === false) {
4740 const x = scaleClip(xScale, allowedOverflow);
4741 const y = scaleClip(yScale, allowedOverflow);
4749 function toClip(value) {
4751 if (isObject(value)) {
4757 t = r = b = l = value;
4764 disabled: value === false
4767 function getSortedDatasetIndices(chart, filterVisible) {
4769 const metasets = chart._getSortedDatasetMetas(filterVisible);
4771 for(i = 0, ilen = metasets.length; i < ilen; ++i){
4772 keys.push(metasets[i].index);
4776 function applyStack(stack, value, dsIndex, options = {}) {
4777 const keys = stack.keys;
4778 const singleMode = options.mode === 'single';
4779 let i, ilen, datasetIndex, otherValue;
4780 if (value === null) {
4783 for(i = 0, ilen = keys.length; i < ilen; ++i){
4784 datasetIndex = +keys[i];
4785 if (datasetIndex === dsIndex) {
4791 otherValue = stack.values[datasetIndex];
4792 if (isNumberFinite(otherValue) && (singleMode || value === 0 || sign(value) === sign(otherValue))) {
4793 value += otherValue;
4798 function convertObjectDataToArray(data) {
4799 const keys = Object.keys(data);
4800 const adata = new Array(keys.length);
4802 for(i = 0, ilen = keys.length; i < ilen; ++i){
4811 function isStacked(scale, meta) {
4812 const stacked = scale && scale.options.stacked;
4813 return stacked || stacked === undefined && meta.stack !== undefined;
4815 function getStackKey(indexScale, valueScale, meta) {
4816 return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;
4818 function getUserBounds(scale) {
4819 const { min , max , minDefined , maxDefined } = scale.getUserBounds();
4821 min: minDefined ? min : Number.NEGATIVE_INFINITY,
4822 max: maxDefined ? max : Number.POSITIVE_INFINITY
4825 function getOrCreateStack(stacks, stackKey, indexValue) {
4826 const subStack = stacks[stackKey] || (stacks[stackKey] = {});
4827 return subStack[indexValue] || (subStack[indexValue] = {});
4829 function getLastIndexInStack(stack, vScale, positive, type) {
4830 for (const meta of vScale.getMatchingVisibleMetas(type).reverse()){
4831 const value = stack[meta.index];
4832 if (positive && value > 0 || !positive && value < 0) {
4838 function updateStacks(controller, parsed) {
4839 const { chart , _cachedMeta: meta } = controller;
4840 const stacks = chart._stacks || (chart._stacks = {});
4841 const { iScale , vScale , index: datasetIndex } = meta;
4842 const iAxis = iScale.axis;
4843 const vAxis = vScale.axis;
4844 const key = getStackKey(iScale, vScale, meta);
4845 const ilen = parsed.length;
4847 for(let i = 0; i < ilen; ++i){
4848 const item = parsed[i];
4849 const { [iAxis]: index , [vAxis]: value } = item;
4850 const itemStacks = item._stacks || (item._stacks = {});
4851 stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index);
4852 stack[datasetIndex] = value;
4853 stack._top = getLastIndexInStack(stack, vScale, true, meta.type);
4854 stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type);
4855 const visualValues = stack._visualValues || (stack._visualValues = {});
4856 visualValues[datasetIndex] = value;
4859 function getFirstScaleId(chart, axis) {
4860 const scales = chart.scales;
4861 return Object.keys(scales).filter((key)=>scales[key].axis === axis).shift();
4863 function createDatasetContext(parent, index) {
4864 return createContext(parent, {
4867 datasetIndex: index,
4873 function createDataContext(parent, index, element) {
4874 return createContext(parent, {
4885 function clearStacks(meta, items) {
4886 const datasetIndex = meta.controller.index;
4887 const axis = meta.vScale && meta.vScale.axis;
4891 items = items || meta._parsed;
4892 for (const parsed of items){
4893 const stacks = parsed._stacks;
4894 if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) {
4897 delete stacks[axis][datasetIndex];
4898 if (stacks[axis]._visualValues !== undefined && stacks[axis]._visualValues[datasetIndex] !== undefined) {
4899 delete stacks[axis]._visualValues[datasetIndex];
4903 const isDirectUpdateMode = (mode)=>mode === 'reset' || mode === 'none';
4904 const cloneIfNotShared = (cached, shared)=>shared ? cached : Object.assign({}, cached);
4905 const createStack = (canStack, meta, chart)=>canStack && !meta.hidden && meta._stacked && {
4906 keys: getSortedDatasetIndices(chart, true),
4909 class DatasetController {
4910 static defaults = {};
4911 static datasetElementType = null;
4912 static dataElementType = null;
4913 constructor(chart, datasetIndex){
4915 this._ctx = chart.ctx;
4916 this.index = datasetIndex;
4917 this._cachedDataOpts = {};
4918 this._cachedMeta = this.getMeta();
4919 this._type = this._cachedMeta.type;
4920 this.options = undefined;
4921 this._parsing = false;
4922 this._data = undefined;
4923 this._objectData = undefined;
4924 this._sharedOptions = undefined;
4925 this._drawStart = undefined;
4926 this._drawCount = undefined;
4927 this.enableOptionSharing = false;
4928 this.supportsDecimation = false;
4929 this.$context = undefined;
4930 this._syncList = [];
4931 this.datasetElementType = new.target.datasetElementType;
4932 this.dataElementType = new.target.dataElementType;
4936 const meta = this._cachedMeta;
4939 meta._stacked = isStacked(meta.vScale, meta);
4941 if (this.options.fill && !this.chart.isPluginEnabled('filler')) {
4942 console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options");
4945 updateIndex(datasetIndex) {
4946 if (this.index !== datasetIndex) {
4947 clearStacks(this._cachedMeta);
4949 this.index = datasetIndex;
4952 const chart = this.chart;
4953 const meta = this._cachedMeta;
4954 const dataset = this.getDataset();
4955 const chooseId = (axis, x, y, r)=>axis === 'x' ? x : axis === 'r' ? r : y;
4956 const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));
4957 const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));
4958 const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));
4959 const indexAxis = meta.indexAxis;
4960 const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);
4961 const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);
4962 meta.xScale = this.getScaleForId(xid);
4963 meta.yScale = this.getScaleForId(yid);
4964 meta.rScale = this.getScaleForId(rid);
4965 meta.iScale = this.getScaleForId(iid);
4966 meta.vScale = this.getScaleForId(vid);
4969 return this.chart.data.datasets[this.index];
4972 return this.chart.getDatasetMeta(this.index);
4974 getScaleForId(scaleID) {
4975 return this.chart.scales[scaleID];
4977 _getOtherScale(scale) {
4978 const meta = this._cachedMeta;
4979 return scale === meta.iScale ? meta.vScale : meta.iScale;
4982 this._update('reset');
4985 const meta = this._cachedMeta;
4987 unlistenArrayEvents(this._data, this);
4989 if (meta._stacked) {
4994 const dataset = this.getDataset();
4995 const data = dataset.data || (dataset.data = []);
4996 const _data = this._data;
4997 if (isObject(data)) {
4998 this._data = convertObjectDataToArray(data);
4999 } else if (_data !== data) {
5001 unlistenArrayEvents(_data, this);
5002 const meta = this._cachedMeta;
5006 if (data && Object.isExtensible(data)) {
5007 listenArrayEvents(data, this);
5009 this._syncList = [];
5014 const meta = this._cachedMeta;
5016 if (this.datasetElementType) {
5017 meta.dataset = new this.datasetElementType();
5020 buildOrUpdateElements(resetNewElements) {
5021 const meta = this._cachedMeta;
5022 const dataset = this.getDataset();
5023 let stackChanged = false;
5025 const oldStacked = meta._stacked;
5026 meta._stacked = isStacked(meta.vScale, meta);
5027 if (meta.stack !== dataset.stack) {
5028 stackChanged = true;
5030 meta.stack = dataset.stack;
5032 this._resyncElements(resetNewElements);
5033 if (stackChanged || oldStacked !== meta._stacked) {
5034 updateStacks(this, meta._parsed);
5038 const config = this.chart.config;
5039 const scopeKeys = config.datasetScopeKeys(this._type);
5040 const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true);
5041 this.options = config.createResolver(scopes, this.getContext());
5042 this._parsing = this.options.parsing;
5043 this._cachedDataOpts = {};
5045 parse(start, count) {
5046 const { _cachedMeta: meta , _data: data } = this;
5047 const { iScale , _stacked } = meta;
5048 const iAxis = iScale.axis;
5049 let sorted = start === 0 && count === data.length ? true : meta._sorted;
5050 let prev = start > 0 && meta._parsed[start - 1];
5052 if (this._parsing === false) {
5053 meta._parsed = data;
5054 meta._sorted = true;
5057 if (isArray(data[start])) {
5058 parsed = this.parseArrayData(meta, data, start, count);
5059 } else if (isObject(data[start])) {
5060 parsed = this.parseObjectData(meta, data, start, count);
5062 parsed = this.parsePrimitiveData(meta, data, start, count);
5064 const isNotInOrderComparedToPrev = ()=>cur[iAxis] === null || prev && cur[iAxis] < prev[iAxis];
5065 for(i = 0; i < count; ++i){
5066 meta._parsed[i + start] = cur = parsed[i];
5068 if (isNotInOrderComparedToPrev()) {
5074 meta._sorted = sorted;
5077 updateStacks(this, parsed);
5080 parsePrimitiveData(meta, data, start, count) {
5081 const { iScale , vScale } = meta;
5082 const iAxis = iScale.axis;
5083 const vAxis = vScale.axis;
5084 const labels = iScale.getLabels();
5085 const singleScale = iScale === vScale;
5086 const parsed = new Array(count);
5088 for(i = 0, ilen = count; i < ilen; ++i){
5091 [iAxis]: singleScale || iScale.parse(labels[index], index),
5092 [vAxis]: vScale.parse(data[index], index)
5097 parseArrayData(meta, data, start, count) {
5098 const { xScale , yScale } = meta;
5099 const parsed = new Array(count);
5100 let i, ilen, index, item;
5101 for(i = 0, ilen = count; i < ilen; ++i){
5105 x: xScale.parse(item[0], index),
5106 y: yScale.parse(item[1], index)
5111 parseObjectData(meta, data, start, count) {
5112 const { xScale , yScale } = meta;
5113 const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
5114 const parsed = new Array(count);
5115 let i, ilen, index, item;
5116 for(i = 0, ilen = count; i < ilen; ++i){
5120 x: xScale.parse(resolveObjectKey(item, xAxisKey), index),
5121 y: yScale.parse(resolveObjectKey(item, yAxisKey), index)
5127 return this._cachedMeta._parsed[index];
5129 getDataElement(index) {
5130 return this._cachedMeta.data[index];
5132 applyStack(scale, parsed, mode) {
5133 const chart = this.chart;
5134 const meta = this._cachedMeta;
5135 const value = parsed[scale.axis];
5137 keys: getSortedDatasetIndices(chart, true),
5138 values: parsed._stacks[scale.axis]._visualValues
5140 return applyStack(stack, value, meta.index, {
5144 updateRangeFromParsed(range, scale, parsed, stack) {
5145 const parsedValue = parsed[scale.axis];
5146 let value = parsedValue === null ? NaN : parsedValue;
5147 const values = stack && parsed._stacks[scale.axis];
5148 if (stack && values) {
5149 stack.values = values;
5150 value = applyStack(stack, parsedValue, this._cachedMeta.index);
5152 range.min = Math.min(range.min, value);
5153 range.max = Math.max(range.max, value);
5155 getMinMax(scale, canStack) {
5156 const meta = this._cachedMeta;
5157 const _parsed = meta._parsed;
5158 const sorted = meta._sorted && scale === meta.iScale;
5159 const ilen = _parsed.length;
5160 const otherScale = this._getOtherScale(scale);
5161 const stack = createStack(canStack, meta, this.chart);
5163 min: Number.POSITIVE_INFINITY,
5164 max: Number.NEGATIVE_INFINITY
5166 const { min: otherMin , max: otherMax } = getUserBounds(otherScale);
5169 parsed = _parsed[i];
5170 const otherValue = parsed[otherScale.axis];
5171 return !isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue;
5173 for(i = 0; i < ilen; ++i){
5177 this.updateRangeFromParsed(range, scale, parsed, stack);
5183 for(i = ilen - 1; i >= 0; --i){
5187 this.updateRangeFromParsed(range, scale, parsed, stack);
5193 getAllParsedValues(scale) {
5194 const parsed = this._cachedMeta._parsed;
5197 for(i = 0, ilen = parsed.length; i < ilen; ++i){
5198 value = parsed[i][scale.axis];
5199 if (isNumberFinite(value)) {
5208 getLabelAndValue(index) {
5209 const meta = this._cachedMeta;
5210 const iScale = meta.iScale;
5211 const vScale = meta.vScale;
5212 const parsed = this.getParsed(index);
5214 label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',
5215 value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''
5219 const meta = this._cachedMeta;
5220 this.update(mode || 'default');
5221 meta._clip = toClip(valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow())));
5225 const ctx = this._ctx;
5226 const chart = this.chart;
5227 const meta = this._cachedMeta;
5228 const elements = meta.data || [];
5229 const area = chart.chartArea;
5231 const start = this._drawStart || 0;
5232 const count = this._drawCount || elements.length - start;
5233 const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop;
5236 meta.dataset.draw(ctx, area, start, count);
5238 for(i = start; i < start + count; ++i){
5239 const element = elements[i];
5240 if (element.hidden) {
5243 if (element.active && drawActiveElementsOnTop) {
5244 active.push(element);
5246 element.draw(ctx, area);
5249 for(i = 0; i < active.length; ++i){
5250 active[i].draw(ctx, area);
5253 getStyle(index, active) {
5254 const mode = active ? 'active' : 'default';
5255 return index === undefined && this._cachedMeta.dataset ? this.resolveDatasetElementOptions(mode) : this.resolveDataElementOptions(index || 0, mode);
5257 getContext(index, active, mode) {
5258 const dataset = this.getDataset();
5260 if (index >= 0 && index < this._cachedMeta.data.length) {
5261 const element = this._cachedMeta.data[index];
5262 context = element.$context || (element.$context = createDataContext(this.getContext(), index, element));
5263 context.parsed = this.getParsed(index);
5264 context.raw = dataset.data[index];
5265 context.index = context.dataIndex = index;
5267 context = this.$context || (this.$context = createDatasetContext(this.chart.getContext(), this.index));
5268 context.dataset = dataset;
5269 context.index = context.datasetIndex = this.index;
5271 context.active = !!active;
5272 context.mode = mode;
5275 resolveDatasetElementOptions(mode) {
5276 return this._resolveElementOptions(this.datasetElementType.id, mode);
5278 resolveDataElementOptions(index, mode) {
5279 return this._resolveElementOptions(this.dataElementType.id, mode, index);
5281 _resolveElementOptions(elementType, mode = 'default', index) {
5282 const active = mode === 'active';
5283 const cache = this._cachedDataOpts;
5284 const cacheKey = elementType + '-' + mode;
5285 const cached = cache[cacheKey];
5286 const sharing = this.enableOptionSharing && defined(index);
5288 return cloneIfNotShared(cached, sharing);
5290 const config = this.chart.config;
5291 const scopeKeys = config.datasetElementScopeKeys(this._type, elementType);
5292 const prefixes = active ? [
5293 `${elementType}Hover`,
5301 const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
5302 const names = Object.keys(defaults.elements[elementType]);
5303 const context = ()=>this.getContext(index, active, mode);
5304 const values = config.resolveNamedOptions(scopes, names, context, prefixes);
5305 if (values.$shared) {
5306 values.$shared = sharing;
5307 cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));
5311 _resolveAnimations(index, transition, active) {
5312 const chart = this.chart;
5313 const cache = this._cachedDataOpts;
5314 const cacheKey = `animation-${transition}`;
5315 const cached = cache[cacheKey];
5320 if (chart.options.animation !== false) {
5321 const config = this.chart.config;
5322 const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition);
5323 const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
5324 options = config.createResolver(scopes, this.getContext(index, active, transition));
5326 const animations = new Animations(chart, options && options.animations);
5327 if (options && options._cacheable) {
5328 cache[cacheKey] = Object.freeze(animations);
5332 getSharedOptions(options) {
5333 if (!options.$shared) {
5336 return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
5338 includeOptions(mode, sharedOptions) {
5339 return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;
5341 _getSharedOptions(start, mode) {
5342 const firstOpts = this.resolveDataElementOptions(start, mode);
5343 const previouslySharedOptions = this._sharedOptions;
5344 const sharedOptions = this.getSharedOptions(firstOpts);
5345 const includeOptions = this.includeOptions(mode, sharedOptions) || sharedOptions !== previouslySharedOptions;
5346 this.updateSharedOptions(sharedOptions, mode, firstOpts);
5352 updateElement(element, index, properties, mode) {
5353 if (isDirectUpdateMode(mode)) {
5354 Object.assign(element, properties);
5356 this._resolveAnimations(index, mode).update(element, properties);
5359 updateSharedOptions(sharedOptions, mode, newOptions) {
5360 if (sharedOptions && !isDirectUpdateMode(mode)) {
5361 this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);
5364 _setStyle(element, index, mode, active) {
5365 element.active = active;
5366 const options = this.getStyle(index, active);
5367 this._resolveAnimations(index, mode, active).update(element, {
5368 options: !active && this.getSharedOptions(options) || options
5371 removeHoverStyle(element, datasetIndex, index) {
5372 this._setStyle(element, index, 'active', false);
5374 setHoverStyle(element, datasetIndex, index) {
5375 this._setStyle(element, index, 'active', true);
5377 _removeDatasetHoverStyle() {
5378 const element = this._cachedMeta.dataset;
5380 this._setStyle(element, undefined, 'active', false);
5383 _setDatasetHoverStyle() {
5384 const element = this._cachedMeta.dataset;
5386 this._setStyle(element, undefined, 'active', true);
5389 _resyncElements(resetNewElements) {
5390 const data = this._data;
5391 const elements = this._cachedMeta.data;
5392 for (const [method, arg1, arg2] of this._syncList){
5393 this[method](arg1, arg2);
5395 this._syncList = [];
5396 const numMeta = elements.length;
5397 const numData = data.length;
5398 const count = Math.min(numData, numMeta);
5400 this.parse(0, count);
5402 if (numData > numMeta) {
5403 this._insertElements(numMeta, numData - numMeta, resetNewElements);
5404 } else if (numData < numMeta) {
5405 this._removeElements(numData, numMeta - numData);
5408 _insertElements(start, count, resetNewElements = true) {
5409 const meta = this._cachedMeta;
5410 const data = meta.data;
5411 const end = start + count;
5413 const move = (arr)=>{
5414 arr.length += count;
5415 for(i = arr.length - 1; i >= end; i--){
5416 arr[i] = arr[i - count];
5420 for(i = start; i < end; ++i){
5421 data[i] = new this.dataElementType();
5423 if (this._parsing) {
5426 this.parse(start, count);
5427 if (resetNewElements) {
5428 this.updateElements(data, start, count, 'reset');
5431 updateElements(element, start, count, mode) {}
5432 _removeElements(start, count) {
5433 const meta = this._cachedMeta;
5434 if (this._parsing) {
5435 const removed = meta._parsed.splice(start, count);
5436 if (meta._stacked) {
5437 clearStacks(meta, removed);
5440 meta.data.splice(start, count);
5443 if (this._parsing) {
5444 this._syncList.push(args);
5446 const [method, arg1, arg2] = args;
5447 this[method](arg1, arg2);
5449 this.chart._dataChanges.push([
5455 const count = arguments.length;
5458 this.getDataset().data.length - count,
5465 this._cachedMeta.data.length - 1,
5476 _onDataSplice(start, count) {
5484 const newCount = arguments.length - 2;
5503 static defaults = {};
5504 static defaultRoutes = undefined;
5510 tooltipPosition(useFinalPosition) {
5511 const { x , y } = this.getProps([
5514 ], useFinalPosition);
5521 return isNumber(this.x) && isNumber(this.y);
5523 getProps(props, final) {
5524 const anims = this.$animations;
5525 if (!final || !anims) {
5526 // let's not create an object, if not needed
5530 props.forEach((prop)=>{
5531 ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop];
5537 function autoSkip(scale, ticks) {
5538 const tickOpts = scale.options.ticks;
5539 const determinedMaxTicks = determineMaxTicks(scale);
5540 const ticksLimit = Math.min(tickOpts.maxTicksLimit || determinedMaxTicks, determinedMaxTicks);
5541 const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
5542 const numMajorIndices = majorIndices.length;
5543 const first = majorIndices[0];
5544 const last = majorIndices[numMajorIndices - 1];
5545 const newTicks = [];
5546 if (numMajorIndices > ticksLimit) {
5547 skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);
5550 const spacing = calculateSpacing(majorIndices, ticks, ticksLimit);
5551 if (numMajorIndices > 0) {
5553 const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;
5554 skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
5555 for(i = 0, ilen = numMajorIndices - 1; i < ilen; i++){
5556 skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);
5558 skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
5561 skip(ticks, newTicks, spacing);
5564 function determineMaxTicks(scale) {
5565 const offset = scale.options.offset;
5566 const tickLength = scale._tickSize();
5567 const maxScale = scale._length / tickLength + (offset ? 0 : 1);
5568 const maxChart = scale._maxLength / tickLength;
5569 return Math.floor(Math.min(maxScale, maxChart));
5571 function calculateSpacing(majorIndices, ticks, ticksLimit) {
5572 const evenMajorSpacing = getEvenSpacing(majorIndices);
5573 const spacing = ticks.length / ticksLimit;
5574 if (!evenMajorSpacing) {
5575 return Math.max(spacing, 1);
5577 const factors = _factorize(evenMajorSpacing);
5578 for(let i = 0, ilen = factors.length - 1; i < ilen; i++){
5579 const factor = factors[i];
5580 if (factor > spacing) {
5584 return Math.max(spacing, 1);
5586 function getMajorIndices(ticks) {
5589 for(i = 0, ilen = ticks.length; i < ilen; i++){
5590 if (ticks[i].major) {
5596 function skipMajors(ticks, newTicks, majorIndices, spacing) {
5598 let next = majorIndices[0];
5600 spacing = Math.ceil(spacing);
5601 for(i = 0; i < ticks.length; i++){
5603 newTicks.push(ticks[i]);
5605 next = majorIndices[count * spacing];
5609 function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
5610 const start = valueOrDefault(majorStart, 0);
5611 const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
5613 let length, i, next;
5614 spacing = Math.ceil(spacing);
5616 length = majorEnd - majorStart;
5617 spacing = length / Math.floor(length / spacing);
5622 next = Math.round(start + count * spacing);
5624 for(i = Math.max(start, 0); i < end; i++){
5626 newTicks.push(ticks[i]);
5628 next = Math.round(start + count * spacing);
5632 function getEvenSpacing(arr) {
5633 const len = arr.length;
5638 for(diff = arr[0], i = 1; i < len; ++i){
5639 if (arr[i] - arr[i - 1] !== diff) {
5646 const reverseAlign = (align)=>align === 'left' ? 'right' : align === 'right' ? 'left' : align;
5647 const offsetFromEdge = (scale, edge, offset)=>edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
5648 const getTicksLimit = (ticksLength, maxTicksLimit)=>Math.min(maxTicksLimit || ticksLength, ticksLength);
5649 function sample(arr, numItems) {
5651 const increment = arr.length / numItems;
5652 const len = arr.length;
5654 for(; i < len; i += increment){
5655 result.push(arr[Math.floor(i)]);
5659 function getPixelForGridLine(scale, index, offsetGridLines) {
5660 const length = scale.ticks.length;
5661 const validIndex = Math.min(index, length - 1);
5662 const start = scale._startPixel;
5663 const end = scale._endPixel;
5664 const epsilon = 1e-6;
5665 let lineValue = scale.getPixelForTick(validIndex);
5667 if (offsetGridLines) {
5669 offset = Math.max(lineValue - start, end - lineValue);
5670 } else if (index === 0) {
5671 offset = (scale.getPixelForTick(1) - lineValue) / 2;
5673 offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
5675 lineValue += validIndex < index ? offset : -offset;
5676 if (lineValue < start - epsilon || lineValue > end + epsilon) {
5682 function garbageCollect(caches, length) {
5683 each(caches, (cache)=>{
5684 const gc = cache.gc;
5685 const gcLen = gc.length / 2;
5687 if (gcLen > length) {
5688 for(i = 0; i < gcLen; ++i){
5689 delete cache.data[gc[i]];
5691 gc.splice(0, gcLen);
5695 function getTickMarkLength(options) {
5696 return options.drawTicks ? options.tickLength : 0;
5698 function getTitleHeight(options, fallback) {
5699 if (!options.display) {
5702 const font = toFont(options.font, fallback);
5703 const padding = toPadding(options.padding);
5704 const lines = isArray(options.text) ? options.text.length : 1;
5705 return lines * font.lineHeight + padding.height;
5707 function createScaleContext(parent, scale) {
5708 return createContext(parent, {
5713 function createTickContext(parent, index, tick) {
5714 return createContext(parent, {
5720 function titleAlign(align, position, reverse) {
5721 let ret = _toLeftRightCenter(align);
5722 if (reverse && position !== 'right' || !reverse && position === 'right') {
5723 ret = reverseAlign(ret);
5727 function titleArgs(scale, offset, position, align) {
5728 const { top , left , bottom , right , chart } = scale;
5729 const { chartArea , scales } = chart;
5731 let maxWidth, titleX, titleY;
5732 const height = bottom - top;
5733 const width = right - left;
5734 if (scale.isHorizontal()) {
5735 titleX = _alignStartEnd(align, left, right);
5736 if (isObject(position)) {
5737 const positionAxisID = Object.keys(position)[0];
5738 const value = position[positionAxisID];
5739 titleY = scales[positionAxisID].getPixelForValue(value) + height - offset;
5740 } else if (position === 'center') {
5741 titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset;
5743 titleY = offsetFromEdge(scale, position, offset);
5745 maxWidth = right - left;
5747 if (isObject(position)) {
5748 const positionAxisID = Object.keys(position)[0];
5749 const value = position[positionAxisID];
5750 titleX = scales[positionAxisID].getPixelForValue(value) - width + offset;
5751 } else if (position === 'center') {
5752 titleX = (chartArea.left + chartArea.right) / 2 - width + offset;
5754 titleX = offsetFromEdge(scale, position, offset);
5756 titleY = _alignStartEnd(align, bottom, top);
5757 rotation = position === 'left' ? -HALF_PI : HALF_PI;
5766 class Scale extends Element {
5770 this.type = cfg.type;
5771 this.options = undefined;
5773 this.chart = cfg.chart;
5774 this.top = undefined;
5775 this.bottom = undefined;
5776 this.left = undefined;
5777 this.right = undefined;
5778 this.width = undefined;
5779 this.height = undefined;
5786 this.maxWidth = undefined;
5787 this.maxHeight = undefined;
5788 this.paddingTop = undefined;
5789 this.paddingBottom = undefined;
5790 this.paddingLeft = undefined;
5791 this.paddingRight = undefined;
5792 this.axis = undefined;
5793 this.labelRotation = undefined;
5794 this.min = undefined;
5795 this.max = undefined;
5796 this._range = undefined;
5798 this._gridLineItems = null;
5799 this._labelItems = null;
5800 this._labelSizes = null;
5802 this._maxLength = 0;
5803 this._longestTextCache = {};
5804 this._startPixel = undefined;
5805 this._endPixel = undefined;
5806 this._reversePixels = false;
5807 this._userMax = undefined;
5808 this._userMin = undefined;
5809 this._suggestedMax = undefined;
5810 this._suggestedMin = undefined;
5811 this._ticksLength = 0;
5812 this._borderValue = 0;
5814 this._dataLimitsCached = false;
5815 this.$context = undefined;
5818 this.options = options.setContext(this.getContext());
5819 this.axis = options.axis;
5820 this._userMin = this.parse(options.min);
5821 this._userMax = this.parse(options.max);
5822 this._suggestedMin = this.parse(options.suggestedMin);
5823 this._suggestedMax = this.parse(options.suggestedMax);
5829 let { _userMin , _userMax , _suggestedMin , _suggestedMax } = this;
5830 _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);
5831 _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);
5832 _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);
5833 _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);
5835 min: finiteOrDefault(_userMin, _suggestedMin),
5836 max: finiteOrDefault(_userMax, _suggestedMax),
5837 minDefined: isNumberFinite(_userMin),
5838 maxDefined: isNumberFinite(_userMax)
5841 getMinMax(canStack) {
5842 let { min , max , minDefined , maxDefined } = this.getUserBounds();
5844 if (minDefined && maxDefined) {
5850 const metas = this.getMatchingVisibleMetas();
5851 for(let i = 0, ilen = metas.length; i < ilen; ++i){
5852 range = metas[i].controller.getMinMax(this, canStack);
5854 min = Math.min(min, range.min);
5857 max = Math.max(max, range.max);
5860 min = maxDefined && min > max ? max : min;
5861 max = minDefined && min > max ? min : max;
5863 min: finiteOrDefault(min, finiteOrDefault(max, min)),
5864 max: finiteOrDefault(max, finiteOrDefault(min, max))
5869 left: this.paddingLeft || 0,
5870 top: this.paddingTop || 0,
5871 right: this.paddingRight || 0,
5872 bottom: this.paddingBottom || 0
5879 const data = this.chart.data;
5880 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
5882 getLabelItems(chartArea = this.chart.chartArea) {
5883 const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea));
5888 this._dataLimitsCached = false;
5891 callback(this.options.beforeUpdate, [
5895 update(maxWidth, maxHeight, margins) {
5896 const { beginAtZero , grace , ticks: tickOpts } = this.options;
5897 const sampleSize = tickOpts.sampleSize;
5898 this.beforeUpdate();
5899 this.maxWidth = maxWidth;
5900 this.maxHeight = maxHeight;
5901 this._margins = margins = Object.assign({
5908 this._labelSizes = null;
5909 this._gridLineItems = null;
5910 this._labelItems = null;
5911 this.beforeSetDimensions();
5912 this.setDimensions();
5913 this.afterSetDimensions();
5914 this._maxLength = this.isHorizontal() ? this.width + margins.left + margins.right : this.height + margins.top + margins.bottom;
5915 if (!this._dataLimitsCached) {
5916 this.beforeDataLimits();
5917 this.determineDataLimits();
5918 this.afterDataLimits();
5919 this._range = _addGrace(this, grace, beginAtZero);
5920 this._dataLimitsCached = true;
5922 this.beforeBuildTicks();
5923 this.ticks = this.buildTicks() || [];
5924 this.afterBuildTicks();
5925 const samplingEnabled = sampleSize < this.ticks.length;
5926 this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks);
5928 this.beforeCalculateLabelRotation();
5929 this.calculateLabelRotation();
5930 this.afterCalculateLabelRotation();
5931 if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
5932 this.ticks = autoSkip(this, this.ticks);
5933 this._labelSizes = null;
5934 this.afterAutoSkip();
5936 if (samplingEnabled) {
5937 this._convertTicksToLabels(this.ticks);
5945 let reversePixels = this.options.reverse;
5946 let startPixel, endPixel;
5947 if (this.isHorizontal()) {
5948 startPixel = this.left;
5949 endPixel = this.right;
5951 startPixel = this.top;
5952 endPixel = this.bottom;
5953 reversePixels = !reversePixels;
5955 this._startPixel = startPixel;
5956 this._endPixel = endPixel;
5957 this._reversePixels = reversePixels;
5958 this._length = endPixel - startPixel;
5959 this._alignToPixels = this.options.alignToPixels;
5962 callback(this.options.afterUpdate, [
5966 beforeSetDimensions() {
5967 callback(this.options.beforeSetDimensions, [
5972 if (this.isHorizontal()) {
5973 this.width = this.maxWidth;
5975 this.right = this.width;
5977 this.height = this.maxHeight;
5979 this.bottom = this.height;
5981 this.paddingLeft = 0;
5982 this.paddingTop = 0;
5983 this.paddingRight = 0;
5984 this.paddingBottom = 0;
5986 afterSetDimensions() {
5987 callback(this.options.afterSetDimensions, [
5992 this.chart.notifyPlugins(name, this.getContext());
5993 callback(this.options[name], [
5997 beforeDataLimits() {
5998 this._callHooks('beforeDataLimits');
6000 determineDataLimits() {}
6002 this._callHooks('afterDataLimits');
6004 beforeBuildTicks() {
6005 this._callHooks('beforeBuildTicks');
6011 this._callHooks('afterBuildTicks');
6013 beforeTickToLabelConversion() {
6014 callback(this.options.beforeTickToLabelConversion, [
6018 generateTickLabels(ticks) {
6019 const tickOpts = this.options.ticks;
6021 for(i = 0, ilen = ticks.length; i < ilen; i++){
6023 tick.label = callback(tickOpts.callback, [
6030 afterTickToLabelConversion() {
6031 callback(this.options.afterTickToLabelConversion, [
6035 beforeCalculateLabelRotation() {
6036 callback(this.options.beforeCalculateLabelRotation, [
6040 calculateLabelRotation() {
6041 const options = this.options;
6042 const tickOpts = options.ticks;
6043 const numTicks = getTicksLimit(this.ticks.length, options.ticks.maxTicksLimit);
6044 const minRotation = tickOpts.minRotation || 0;
6045 const maxRotation = tickOpts.maxRotation;
6046 let labelRotation = minRotation;
6047 let tickWidth, maxHeight, maxLabelDiagonal;
6048 if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) {
6049 this.labelRotation = minRotation;
6052 const labelSizes = this._getLabelSizes();
6053 const maxLabelWidth = labelSizes.widest.width;
6054 const maxLabelHeight = labelSizes.highest.height;
6055 const maxWidth = _limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth);
6056 tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1);
6057 if (maxLabelWidth + 6 > tickWidth) {
6058 tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
6059 maxHeight = this.maxHeight - getTickMarkLength(options.grid) - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font);
6060 maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
6061 labelRotation = toDegrees(Math.min(Math.asin(_limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)), Math.asin(_limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(_limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1))));
6062 labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
6064 this.labelRotation = labelRotation;
6066 afterCalculateLabelRotation() {
6067 callback(this.options.afterCalculateLabelRotation, [
6073 callback(this.options.beforeFit, [
6082 const { chart , options: { ticks: tickOpts , title: titleOpts , grid: gridOpts } } = this;
6083 const display = this._isVisible();
6084 const isHorizontal = this.isHorizontal();
6086 const titleHeight = getTitleHeight(titleOpts, chart.options.font);
6088 minSize.width = this.maxWidth;
6089 minSize.height = getTickMarkLength(gridOpts) + titleHeight;
6091 minSize.height = this.maxHeight;
6092 minSize.width = getTickMarkLength(gridOpts) + titleHeight;
6094 if (tickOpts.display && this.ticks.length) {
6095 const { first , last , widest , highest } = this._getLabelSizes();
6096 const tickPadding = tickOpts.padding * 2;
6097 const angleRadians = toRadians(this.labelRotation);
6098 const cos = Math.cos(angleRadians);
6099 const sin = Math.sin(angleRadians);
6101 const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;
6102 minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding);
6104 const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
6105 minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding);
6107 this._calculatePadding(first, last, sin, cos);
6110 this._handleMargins();
6112 this.width = this._length = chart.width - this._margins.left - this._margins.right;
6113 this.height = minSize.height;
6115 this.width = minSize.width;
6116 this.height = this._length = chart.height - this._margins.top - this._margins.bottom;
6119 _calculatePadding(first, last, sin, cos) {
6120 const { ticks: { align , padding } , position } = this.options;
6121 const isRotated = this.labelRotation !== 0;
6122 const labelsBelowTicks = position !== 'top' && this.axis === 'x';
6123 if (this.isHorizontal()) {
6124 const offsetLeft = this.getPixelForTick(0) - this.left;
6125 const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1);
6126 let paddingLeft = 0;
6127 let paddingRight = 0;
6129 if (labelsBelowTicks) {
6130 paddingLeft = cos * first.width;
6131 paddingRight = sin * last.height;
6133 paddingLeft = sin * first.height;
6134 paddingRight = cos * last.width;
6136 } else if (align === 'start') {
6137 paddingRight = last.width;
6138 } else if (align === 'end') {
6139 paddingLeft = first.width;
6140 } else if (align !== 'inner') {
6141 paddingLeft = first.width / 2;
6142 paddingRight = last.width / 2;
6144 this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0);
6145 this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0);
6147 let paddingTop = last.height / 2;
6148 let paddingBottom = first.height / 2;
6149 if (align === 'start') {
6151 paddingBottom = first.height;
6152 } else if (align === 'end') {
6153 paddingTop = last.height;
6156 this.paddingTop = paddingTop + padding;
6157 this.paddingBottom = paddingBottom + padding;
6161 if (this._margins) {
6162 this._margins.left = Math.max(this.paddingLeft, this._margins.left);
6163 this._margins.top = Math.max(this.paddingTop, this._margins.top);
6164 this._margins.right = Math.max(this.paddingRight, this._margins.right);
6165 this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom);
6169 callback(this.options.afterFit, [
6174 const { axis , position } = this.options;
6175 return position === 'top' || position === 'bottom' || axis === 'x';
6178 return this.options.fullSize;
6180 _convertTicksToLabels(ticks) {
6181 this.beforeTickToLabelConversion();
6182 this.generateTickLabels(ticks);
6184 for(i = 0, ilen = ticks.length; i < ilen; i++){
6185 if (isNullOrUndef(ticks[i].label)) {
6191 this.afterTickToLabelConversion();
6194 let labelSizes = this._labelSizes;
6196 const sampleSize = this.options.ticks.sampleSize;
6197 let ticks = this.ticks;
6198 if (sampleSize < ticks.length) {
6199 ticks = sample(ticks, sampleSize);
6201 this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length, this.options.ticks.maxTicksLimit);
6205 _computeLabelSizes(ticks, length, maxTicksLimit) {
6206 const { ctx , _longestTextCache: caches } = this;
6209 const increment = Math.floor(length / getTicksLimit(length, maxTicksLimit));
6210 let widestLabelSize = 0;
6211 let highestLabelSize = 0;
6212 let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
6213 for(i = 0; i < length; i += increment){
6214 label = ticks[i].label;
6215 tickFont = this._resolveTickFontOptions(i);
6216 ctx.font = fontString = tickFont.string;
6217 cache = caches[fontString] = caches[fontString] || {
6221 lineHeight = tickFont.lineHeight;
6223 if (!isNullOrUndef(label) && !isArray(label)) {
6224 width = _measureText(ctx, cache.data, cache.gc, width, label);
6225 height = lineHeight;
6226 } else if (isArray(label)) {
6227 for(j = 0, jlen = label.length; j < jlen; ++j){
6228 nestedLabel = label[j];
6229 if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
6230 width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel);
6231 height += lineHeight;
6236 heights.push(height);
6237 widestLabelSize = Math.max(width, widestLabelSize);
6238 highestLabelSize = Math.max(height, highestLabelSize);
6240 garbageCollect(caches, length);
6241 const widest = widths.indexOf(widestLabelSize);
6242 const highest = heights.indexOf(highestLabelSize);
6243 const valueAt = (idx)=>({
6244 width: widths[idx] || 0,
6245 height: heights[idx] || 0
6249 last: valueAt(length - 1),
6250 widest: valueAt(widest),
6251 highest: valueAt(highest),
6256 getLabelForValue(value) {
6259 getPixelForValue(value, index) {
6262 getValueForPixel(pixel) {}
6263 getPixelForTick(index) {
6264 const ticks = this.ticks;
6265 if (index < 0 || index > ticks.length - 1) {
6268 return this.getPixelForValue(ticks[index].value);
6270 getPixelForDecimal(decimal) {
6271 if (this._reversePixels) {
6272 decimal = 1 - decimal;
6274 const pixel = this._startPixel + decimal * this._length;
6275 return _int16Range(this._alignToPixels ? _alignPixel(this.chart, pixel, 0) : pixel);
6277 getDecimalForPixel(pixel) {
6278 const decimal = (pixel - this._startPixel) / this._length;
6279 return this._reversePixels ? 1 - decimal : decimal;
6282 return this.getPixelForValue(this.getBaseValue());
6285 const { min , max } = this;
6286 return min < 0 && max < 0 ? max : min > 0 && max > 0 ? min : 0;
6289 const ticks = this.ticks || [];
6290 if (index >= 0 && index < ticks.length) {
6291 const tick = ticks[index];
6292 return tick.$context || (tick.$context = createTickContext(this.getContext(), index, tick));
6294 return this.$context || (this.$context = createScaleContext(this.chart.getContext(), this));
6297 const optionTicks = this.options.ticks;
6298 const rot = toRadians(this.labelRotation);
6299 const cos = Math.abs(Math.cos(rot));
6300 const sin = Math.abs(Math.sin(rot));
6301 const labelSizes = this._getLabelSizes();
6302 const padding = optionTicks.autoSkipPadding || 0;
6303 const w = labelSizes ? labelSizes.widest.width + padding : 0;
6304 const h = labelSizes ? labelSizes.highest.height + padding : 0;
6305 return this.isHorizontal() ? h * cos > w * sin ? w / cos : h / sin : h * sin < w * cos ? h / cos : w / sin;
6308 const display = this.options.display;
6309 if (display !== 'auto') {
6312 return this.getMatchingVisibleMetas().length > 0;
6314 _computeGridLineItems(chartArea) {
6315 const axis = this.axis;
6316 const chart = this.chart;
6317 const options = this.options;
6318 const { grid , position , border } = options;
6319 const offset = grid.offset;
6320 const isHorizontal = this.isHorizontal();
6321 const ticks = this.ticks;
6322 const ticksLength = ticks.length + (offset ? 1 : 0);
6323 const tl = getTickMarkLength(grid);
6325 const borderOpts = border.setContext(this.getContext());
6326 const axisWidth = borderOpts.display ? borderOpts.width : 0;
6327 const axisHalfWidth = axisWidth / 2;
6328 const alignBorderValue = function(pixel) {
6329 return _alignPixel(chart, pixel, axisWidth);
6331 let borderValue, i, lineValue, alignedLineValue;
6332 let tx1, ty1, tx2, ty2, x1, y1, x2, y2;
6333 if (position === 'top') {
6334 borderValue = alignBorderValue(this.bottom);
6335 ty1 = this.bottom - tl;
6336 ty2 = borderValue - axisHalfWidth;
6337 y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
6338 y2 = chartArea.bottom;
6339 } else if (position === 'bottom') {
6340 borderValue = alignBorderValue(this.top);
6342 y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
6343 ty1 = borderValue + axisHalfWidth;
6344 ty2 = this.top + tl;
6345 } else if (position === 'left') {
6346 borderValue = alignBorderValue(this.right);
6347 tx1 = this.right - tl;
6348 tx2 = borderValue - axisHalfWidth;
6349 x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
6350 x2 = chartArea.right;
6351 } else if (position === 'right') {
6352 borderValue = alignBorderValue(this.left);
6353 x1 = chartArea.left;
6354 x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
6355 tx1 = borderValue + axisHalfWidth;
6356 tx2 = this.left + tl;
6357 } else if (axis === 'x') {
6358 if (position === 'center') {
6359 borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
6360 } else if (isObject(position)) {
6361 const positionAxisID = Object.keys(position)[0];
6362 const value = position[positionAxisID];
6363 borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
6366 y2 = chartArea.bottom;
6367 ty1 = borderValue + axisHalfWidth;
6369 } else if (axis === 'y') {
6370 if (position === 'center') {
6371 borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);
6372 } else if (isObject(position)) {
6373 const positionAxisID = Object.keys(position)[0];
6374 const value = position[positionAxisID];
6375 borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
6377 tx1 = borderValue - axisHalfWidth;
6379 x1 = chartArea.left;
6380 x2 = chartArea.right;
6382 const limit = valueOrDefault(options.ticks.maxTicksLimit, ticksLength);
6383 const step = Math.max(1, Math.ceil(ticksLength / limit));
6384 for(i = 0; i < ticksLength; i += step){
6385 const context = this.getContext(i);
6386 const optsAtIndex = grid.setContext(context);
6387 const optsAtIndexBorder = border.setContext(context);
6388 const lineWidth = optsAtIndex.lineWidth;
6389 const lineColor = optsAtIndex.color;
6390 const borderDash = optsAtIndexBorder.dash || [];
6391 const borderDashOffset = optsAtIndexBorder.dashOffset;
6392 const tickWidth = optsAtIndex.tickWidth;
6393 const tickColor = optsAtIndex.tickColor;
6394 const tickBorderDash = optsAtIndex.tickBorderDash || [];
6395 const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;
6396 lineValue = getPixelForGridLine(this, i, offset);
6397 if (lineValue === undefined) {
6400 alignedLineValue = _alignPixel(chart, lineValue, lineWidth);
6402 tx1 = tx2 = x1 = x2 = alignedLineValue;
6404 ty1 = ty2 = y1 = y2 = alignedLineValue;
6422 tickBorderDashOffset
6425 this._ticksLength = ticksLength;
6426 this._borderValue = borderValue;
6429 _computeLabelItems(chartArea) {
6430 const axis = this.axis;
6431 const options = this.options;
6432 const { position , ticks: optionTicks } = options;
6433 const isHorizontal = this.isHorizontal();
6434 const ticks = this.ticks;
6435 const { align , crossAlign , padding , mirror } = optionTicks;
6436 const tl = getTickMarkLength(options.grid);
6437 const tickAndPadding = tl + padding;
6438 const hTickAndPadding = mirror ? -padding : tickAndPadding;
6439 const rotation = -toRadians(this.labelRotation);
6441 let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
6442 let textBaseline = 'middle';
6443 if (position === 'top') {
6444 y = this.bottom - hTickAndPadding;
6445 textAlign = this._getXAxisLabelAlignment();
6446 } else if (position === 'bottom') {
6447 y = this.top + hTickAndPadding;
6448 textAlign = this._getXAxisLabelAlignment();
6449 } else if (position === 'left') {
6450 const ret = this._getYAxisLabelAlignment(tl);
6451 textAlign = ret.textAlign;
6453 } else if (position === 'right') {
6454 const ret = this._getYAxisLabelAlignment(tl);
6455 textAlign = ret.textAlign;
6457 } else if (axis === 'x') {
6458 if (position === 'center') {
6459 y = (chartArea.top + chartArea.bottom) / 2 + tickAndPadding;
6460 } else if (isObject(position)) {
6461 const positionAxisID = Object.keys(position)[0];
6462 const value = position[positionAxisID];
6463 y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
6465 textAlign = this._getXAxisLabelAlignment();
6466 } else if (axis === 'y') {
6467 if (position === 'center') {
6468 x = (chartArea.left + chartArea.right) / 2 - tickAndPadding;
6469 } else if (isObject(position)) {
6470 const positionAxisID = Object.keys(position)[0];
6471 const value = position[positionAxisID];
6472 x = this.chart.scales[positionAxisID].getPixelForValue(value);
6474 textAlign = this._getYAxisLabelAlignment(tl).textAlign;
6477 if (align === 'start') {
6478 textBaseline = 'top';
6479 } else if (align === 'end') {
6480 textBaseline = 'bottom';
6483 const labelSizes = this._getLabelSizes();
6484 for(i = 0, ilen = ticks.length; i < ilen; ++i){
6487 const optsAtIndex = optionTicks.setContext(this.getContext(i));
6488 pixel = this.getPixelForTick(i) + optionTicks.labelOffset;
6489 font = this._resolveTickFontOptions(i);
6490 lineHeight = font.lineHeight;
6491 lineCount = isArray(label) ? label.length : 1;
6492 const halfCount = lineCount / 2;
6493 const color = optsAtIndex.color;
6494 const strokeColor = optsAtIndex.textStrokeColor;
6495 const strokeWidth = optsAtIndex.textStrokeWidth;
6496 let tickTextAlign = textAlign;
6499 if (textAlign === 'inner') {
6500 if (i === ilen - 1) {
6501 tickTextAlign = !this.options.reverse ? 'right' : 'left';
6502 } else if (i === 0) {
6503 tickTextAlign = !this.options.reverse ? 'left' : 'right';
6505 tickTextAlign = 'center';
6508 if (position === 'top') {
6509 if (crossAlign === 'near' || rotation !== 0) {
6510 textOffset = -lineCount * lineHeight + lineHeight / 2;
6511 } else if (crossAlign === 'center') {
6512 textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
6514 textOffset = -labelSizes.highest.height + lineHeight / 2;
6517 if (crossAlign === 'near' || rotation !== 0) {
6518 textOffset = lineHeight / 2;
6519 } else if (crossAlign === 'center') {
6520 textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
6522 textOffset = labelSizes.highest.height - lineCount * lineHeight;
6528 if (rotation !== 0 && !optsAtIndex.showLabelBackdrop) {
6529 x += lineHeight / 2 * Math.sin(rotation);
6533 textOffset = (1 - lineCount) * lineHeight / 2;
6536 if (optsAtIndex.showLabelBackdrop) {
6537 const labelPadding = toPadding(optsAtIndex.backdropPadding);
6538 const height = labelSizes.heights[i];
6539 const width = labelSizes.widths[i];
6540 let top = textOffset - labelPadding.top;
6541 let left = 0 - labelPadding.left;
6542 switch(textBaseline){
6558 if (i === ilen - 1) {
6568 width: width + labelPadding.width,
6569 height: height + labelPadding.height,
6570 color: optsAtIndex.backdropColor
6582 textAlign: tickTextAlign,
6594 _getXAxisLabelAlignment() {
6595 const { position , ticks } = this.options;
6596 const rotation = -toRadians(this.labelRotation);
6598 return position === 'top' ? 'left' : 'right';
6600 let align = 'center';
6601 if (ticks.align === 'start') {
6603 } else if (ticks.align === 'end') {
6605 } else if (ticks.align === 'inner') {
6610 _getYAxisLabelAlignment(tl) {
6611 const { position , ticks: { crossAlign , mirror , padding } } = this.options;
6612 const labelSizes = this._getLabelSizes();
6613 const tickAndPadding = tl + padding;
6614 const widest = labelSizes.widest.width;
6617 if (position === 'left') {
6619 x = this.right + padding;
6620 if (crossAlign === 'near') {
6622 } else if (crossAlign === 'center') {
6623 textAlign = 'center';
6626 textAlign = 'right';
6630 x = this.right - tickAndPadding;
6631 if (crossAlign === 'near') {
6632 textAlign = 'right';
6633 } else if (crossAlign === 'center') {
6634 textAlign = 'center';
6641 } else if (position === 'right') {
6643 x = this.left + padding;
6644 if (crossAlign === 'near') {
6645 textAlign = 'right';
6646 } else if (crossAlign === 'center') {
6647 textAlign = 'center';
6654 x = this.left + tickAndPadding;
6655 if (crossAlign === 'near') {
6657 } else if (crossAlign === 'center') {
6658 textAlign = 'center';
6661 textAlign = 'right';
6666 textAlign = 'right';
6673 _computeLabelArea() {
6674 if (this.options.ticks.mirror) {
6677 const chart = this.chart;
6678 const position = this.options.position;
6679 if (position === 'left' || position === 'right') {
6683 bottom: chart.height,
6687 if (position === 'top' || position === 'bottom') {
6691 bottom: this.bottom,
6697 const { ctx , options: { backgroundColor } , left , top , width , height } = this;
6698 if (backgroundColor) {
6700 ctx.fillStyle = backgroundColor;
6701 ctx.fillRect(left, top, width, height);
6705 getLineWidthForValue(value) {
6706 const grid = this.options.grid;
6707 if (!this._isVisible() || !grid.display) {
6710 const ticks = this.ticks;
6711 const index = ticks.findIndex((t)=>t.value === value);
6713 const opts = grid.setContext(this.getContext(index));
6714 return opts.lineWidth;
6718 drawGrid(chartArea) {
6719 const grid = this.options.grid;
6720 const ctx = this.ctx;
6721 const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea));
6723 const drawLine = (p1, p2, style)=>{
6724 if (!style.width || !style.color) {
6728 ctx.lineWidth = style.width;
6729 ctx.strokeStyle = style.color;
6730 ctx.setLineDash(style.borderDash || []);
6731 ctx.lineDashOffset = style.borderDashOffset;
6733 ctx.moveTo(p1.x, p1.y);
6734 ctx.lineTo(p2.x, p2.y);
6739 for(i = 0, ilen = items.length; i < ilen; ++i){
6740 const item = items[i];
6741 if (grid.drawOnChartArea) {
6750 if (grid.drawTicks) {
6758 color: item.tickColor,
6759 width: item.tickWidth,
6760 borderDash: item.tickBorderDash,
6761 borderDashOffset: item.tickBorderDashOffset
6768 const { chart , ctx , options: { border , grid } } = this;
6769 const borderOpts = border.setContext(this.getContext());
6770 const axisWidth = border.display ? borderOpts.width : 0;
6774 const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth;
6775 const borderValue = this._borderValue;
6777 if (this.isHorizontal()) {
6778 x1 = _alignPixel(chart, this.left, axisWidth) - axisWidth / 2;
6779 x2 = _alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2;
6780 y1 = y2 = borderValue;
6782 y1 = _alignPixel(chart, this.top, axisWidth) - axisWidth / 2;
6783 y2 = _alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2;
6784 x1 = x2 = borderValue;
6787 ctx.lineWidth = borderOpts.width;
6788 ctx.strokeStyle = borderOpts.color;
6795 drawLabels(chartArea) {
6796 const optionTicks = this.options.ticks;
6797 if (!optionTicks.display) {
6800 const ctx = this.ctx;
6801 const area = this._computeLabelArea();
6803 clipArea(ctx, area);
6805 const items = this.getLabelItems(chartArea);
6806 for (const item of items){
6807 const renderTextOptions = item.options;
6808 const tickFont = item.font;
6809 const label = item.label;
6810 const y = item.textOffset;
6811 renderText(ctx, label, 0, y, tickFont, renderTextOptions);
6818 const { ctx , options: { position , title , reverse } } = this;
6819 if (!title.display) {
6822 const font = toFont(title.font);
6823 const padding = toPadding(title.padding);
6824 const align = title.align;
6825 let offset = font.lineHeight / 2;
6826 if (position === 'bottom' || position === 'center' || isObject(position)) {
6827 offset += padding.bottom;
6828 if (isArray(title.text)) {
6829 offset += font.lineHeight * (title.text.length - 1);
6832 offset += padding.top;
6834 const { titleX , titleY , maxWidth , rotation } = titleArgs(this, offset, position, align);
6835 renderText(ctx, title.text, 0, 0, font, {
6839 textAlign: titleAlign(align, position, reverse),
6840 textBaseline: 'middle',
6848 if (!this._isVisible()) {
6851 this.drawBackground();
6852 this.drawGrid(chartArea);
6855 this.drawLabels(chartArea);
6858 const opts = this.options;
6859 const tz = opts.ticks && opts.ticks.z || 0;
6860 const gz = valueOrDefault(opts.grid && opts.grid.z, -1);
6861 const bz = valueOrDefault(opts.border && opts.border.z, 0);
6862 if (!this._isVisible() || this.draw !== Scale.prototype.draw) {
6866 draw: (chartArea)=>{
6867 this.draw(chartArea);
6875 draw: (chartArea)=>{
6876 this.drawBackground();
6877 this.drawGrid(chartArea);
6889 draw: (chartArea)=>{
6890 this.drawLabels(chartArea);
6895 getMatchingVisibleMetas(type) {
6896 const metas = this.chart.getSortedVisibleDatasetMetas();
6897 const axisID = this.axis + 'AxisID';
6900 for(i = 0, ilen = metas.length; i < ilen; ++i){
6901 const meta = metas[i];
6902 if (meta[axisID] === this.id && (!type || meta.type === type)) {
6908 _resolveTickFontOptions(index) {
6909 const opts = this.options.ticks.setContext(this.getContext(index));
6910 return toFont(opts.font);
6913 const fontSize = this._resolveTickFontOptions(0).lineHeight;
6914 return (this.isHorizontal() ? this.width : this.height) / fontSize;
6918 class TypedRegistry {
6919 constructor(type, scope, override){
6922 this.override = override;
6923 this.items = Object.create(null);
6926 return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);
6929 const proto = Object.getPrototypeOf(item);
6931 if (isIChartComponent(proto)) {
6932 parentScope = this.register(proto);
6934 const items = this.items;
6936 const scope = this.scope + '.' + id;
6938 throw new Error('class does not have id: ' + item);
6944 registerDefaults(item, scope, parentScope);
6945 if (this.override) {
6946 defaults.override(item.id, item.overrides);
6951 return this.items[id];
6954 const items = this.items;
6956 const scope = this.scope;
6960 if (scope && id in defaults[scope]) {
6961 delete defaults[scope][id];
6962 if (this.override) {
6963 delete overrides[id];
6968 function registerDefaults(item, scope, parentScope) {
6969 const itemDefaults = merge(Object.create(null), [
6970 parentScope ? defaults.get(parentScope) : {},
6971 defaults.get(scope),
6974 defaults.set(scope, itemDefaults);
6975 if (item.defaultRoutes) {
6976 routeDefaults(scope, item.defaultRoutes);
6978 if (item.descriptors) {
6979 defaults.describe(scope, item.descriptors);
6982 function routeDefaults(scope, routes) {
6983 Object.keys(routes).forEach((property)=>{
6984 const propertyParts = property.split('.');
6985 const sourceName = propertyParts.pop();
6986 const sourceScope = [
6988 ].concat(propertyParts).join('.');
6989 const parts = routes[property].split('.');
6990 const targetName = parts.pop();
6991 const targetScope = parts.join('.');
6992 defaults.route(sourceScope, sourceName, targetScope, targetName);
6995 function isIChartComponent(proto) {
6996 return 'id' in proto && 'defaults' in proto;
7001 this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
7002 this.elements = new TypedRegistry(Element, 'elements');
7003 this.plugins = new TypedRegistry(Object, 'plugins');
7004 this.scales = new TypedRegistry(Scale, 'scales');
7005 this._typedRegistries = [
7012 this._each('register', args);
7015 this._each('unregister', args);
7017 addControllers(...args) {
7018 this._each('register', args, this.controllers);
7020 addElements(...args) {
7021 this._each('register', args, this.elements);
7023 addPlugins(...args) {
7024 this._each('register', args, this.plugins);
7026 addScales(...args) {
7027 this._each('register', args, this.scales);
7030 return this._get(id, this.controllers, 'controller');
7033 return this._get(id, this.elements, 'element');
7036 return this._get(id, this.plugins, 'plugin');
7039 return this._get(id, this.scales, 'scale');
7041 removeControllers(...args) {
7042 this._each('unregister', args, this.controllers);
7044 removeElements(...args) {
7045 this._each('unregister', args, this.elements);
7047 removePlugins(...args) {
7048 this._each('unregister', args, this.plugins);
7050 removeScales(...args) {
7051 this._each('unregister', args, this.scales);
7053 _each(method, args, typedRegistry) {
7057 const reg = typedRegistry || this._getRegistryForType(arg);
7058 if (typedRegistry || reg.isForType(arg) || reg === this.plugins && arg.id) {
7059 this._exec(method, reg, arg);
7062 const itemReg = typedRegistry || this._getRegistryForType(item);
7063 this._exec(method, itemReg, item);
7068 _exec(method, registry, component) {
7069 const camelMethod = _capitalize(method);
7070 callback(component['before' + camelMethod], [], component);
7071 registry[method](component);
7072 callback(component['after' + camelMethod], [], component);
7074 _getRegistryForType(type) {
7075 for(let i = 0; i < this._typedRegistries.length; i++){
7076 const reg = this._typedRegistries[i];
7077 if (reg.isForType(type)) {
7081 return this.plugins;
7083 _get(id, typedRegistry, type) {
7084 const item = typedRegistry.get(id);
7085 if (item === undefined) {
7086 throw new Error('"' + id + '" is not a registered ' + type + '.');
7091 var registry = /* #__PURE__ */ new Registry();
7093 class PluginService {
7097 notify(chart, hook, args, filter) {
7098 if (hook === 'beforeInit') {
7099 this._init = this._createDescriptors(chart, true);
7100 this._notify(this._init, chart, 'install');
7102 const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart);
7103 const result = this._notify(descriptors, chart, hook, args);
7104 if (hook === 'afterDestroy') {
7105 this._notify(descriptors, chart, 'stop');
7106 this._notify(this._init, chart, 'uninstall');
7110 _notify(descriptors, chart, hook, args) {
7112 for (const descriptor of descriptors){
7113 const plugin = descriptor.plugin;
7114 const method = plugin[hook];
7120 if (callback(method, params, plugin) === false && args.cancelable) {
7127 if (!isNullOrUndef(this._cache)) {
7128 this._oldCache = this._cache;
7129 this._cache = undefined;
7132 _descriptors(chart) {
7136 const descriptors = this._cache = this._createDescriptors(chart);
7137 this._notifyStateChanges(chart);
7140 _createDescriptors(chart, all) {
7141 const config = chart && chart.config;
7142 const options = valueOrDefault(config.options && config.options.plugins, {});
7143 const plugins = allPlugins(config);
7144 return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);
7146 _notifyStateChanges(chart) {
7147 const previousDescriptors = this._oldCache || [];
7148 const descriptors = this._cache;
7149 const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.plugin.id === y.plugin.id));
7150 this._notify(diff(previousDescriptors, descriptors), chart, 'stop');
7151 this._notify(diff(descriptors, previousDescriptors), chart, 'start');
7154 function allPlugins(config) {
7155 const localIds = {};
7157 const keys = Object.keys(registry.plugins.items);
7158 for(let i = 0; i < keys.length; i++){
7159 plugins.push(registry.getPlugin(keys[i]));
7161 const local = config.plugins || [];
7162 for(let i = 0; i < local.length; i++){
7163 const plugin = local[i];
7164 if (plugins.indexOf(plugin) === -1) {
7165 plugins.push(plugin);
7166 localIds[plugin.id] = true;
7174 function getOpts(options, all) {
7175 if (!all && options === false) {
7178 if (options === true) {
7183 function createDescriptors(chart, { plugins , localIds }, options, all) {
7185 const context = chart.getContext();
7186 for (const plugin of plugins){
7187 const id = plugin.id;
7188 const opts = getOpts(options[id], all);
7189 if (opts === null) {
7194 options: pluginOpts(chart.config, {
7202 function pluginOpts(config, { plugin , local }, opts, context) {
7203 const keys = config.pluginScopeKeys(plugin);
7204 const scopes = config.getOptionScopes(opts, keys);
7205 if (local && plugin.defaults) {
7206 scopes.push(plugin.defaults);
7208 return config.createResolver(scopes, context, [
7217 function getIndexAxis(type, options) {
7218 const datasetDefaults = defaults.datasets[type] || {};
7219 const datasetOptions = (options.datasets || {})[type] || {};
7220 return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
7222 function getAxisFromDefaultScaleID(id, indexAxis) {
7224 if (id === '_index_') {
7226 } else if (id === '_value_') {
7227 axis = indexAxis === 'x' ? 'y' : 'x';
7231 function getDefaultScaleIDFromAxis(axis, indexAxis) {
7232 return axis === indexAxis ? '_index_' : '_value_';
7234 function idMatchesAxis(id) {
7235 if (id === 'x' || id === 'y' || id === 'r') {
7239 function axisFromPosition(position) {
7240 if (position === 'top' || position === 'bottom') {
7243 if (position === 'left' || position === 'right') {
7247 function determineAxis(id, ...scaleOptions) {
7248 if (idMatchesAxis(id)) {
7251 for (const opts of scaleOptions){
7252 const axis = opts.axis || axisFromPosition(opts.position) || id.length > 1 && idMatchesAxis(id[0].toLowerCase());
7257 throw new Error(`Cannot determine type of '${id}' axis. Please provide 'axis' or 'position' option.`);
7259 function getAxisFromDataset(id, axis, dataset) {
7260 if (dataset[axis + 'AxisID'] === id) {
7266 function retrieveAxisFromDatasets(id, config) {
7267 if (config.data && config.data.datasets) {
7268 const boundDs = config.data.datasets.filter((d)=>d.xAxisID === id || d.yAxisID === id);
7269 if (boundDs.length) {
7270 return getAxisFromDataset(id, 'x', boundDs[0]) || getAxisFromDataset(id, 'y', boundDs[0]);
7275 function mergeScaleConfig(config, options) {
7276 const chartDefaults = overrides[config.type] || {
7279 const configScales = options.scales || {};
7280 const chartIndexAxis = getIndexAxis(config.type, options);
7281 const scales = Object.create(null);
7282 Object.keys(configScales).forEach((id)=>{
7283 const scaleConf = configScales[id];
7284 if (!isObject(scaleConf)) {
7285 return console.error(`Invalid scale configuration for scale: ${id}`);
7287 if (scaleConf._proxy) {
7288 return console.warn(`Ignoring resolver passed as options for scale: ${id}`);
7290 const axis = determineAxis(id, scaleConf, retrieveAxisFromDatasets(id, config), defaults.scales[scaleConf.type]);
7291 const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
7292 const defaultScaleOptions = chartDefaults.scales || {};
7293 scales[id] = mergeIf(Object.create(null), [
7298 defaultScaleOptions[axis],
7299 defaultScaleOptions[defaultId]
7302 config.data.datasets.forEach((dataset)=>{
7303 const type = dataset.type || config.type;
7304 const indexAxis = dataset.indexAxis || getIndexAxis(type, options);
7305 const datasetDefaults = overrides[type] || {};
7306 const defaultScaleOptions = datasetDefaults.scales || {};
7307 Object.keys(defaultScaleOptions).forEach((defaultID)=>{
7308 const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
7309 const id = dataset[axis + 'AxisID'] || axis;
7310 scales[id] = scales[id] || Object.create(null);
7311 mergeIf(scales[id], [
7316 defaultScaleOptions[defaultID]
7320 Object.keys(scales).forEach((key)=>{
7321 const scale = scales[key];
7323 defaults.scales[scale.type],
7329 function initOptions(config) {
7330 const options = config.options || (config.options = {});
7331 options.plugins = valueOrDefault(options.plugins, {});
7332 options.scales = mergeScaleConfig(config, options);
7334 function initData(data) {
7336 data.datasets = data.datasets || [];
7337 data.labels = data.labels || [];
7340 function initConfig(config) {
7341 config = config || {};
7342 config.data = initData(config.data);
7343 initOptions(config);
7346 const keyCache = new Map();
7347 const keysCached = new Set();
7348 function cachedKeys(cacheKey, generate) {
7349 let keys = keyCache.get(cacheKey);
7352 keyCache.set(cacheKey, keys);
7353 keysCached.add(keys);
7357 const addIfFound = (set, obj, key)=>{
7358 const opts = resolveObjectKey(obj, key);
7359 if (opts !== undefined) {
7364 constructor(config){
7365 this._config = initConfig(config);
7366 this._scopeCache = new Map();
7367 this._resolverCache = new Map();
7370 return this._config.platform;
7373 return this._config.type;
7376 this._config.type = type;
7379 return this._config.data;
7382 this._config.data = initData(data);
7385 return this._config.options;
7387 set options(options) {
7388 this._config.options = options;
7391 return this._config.plugins;
7394 const config = this._config;
7396 initOptions(config);
7399 this._scopeCache.clear();
7400 this._resolverCache.clear();
7402 datasetScopeKeys(datasetType) {
7403 return cachedKeys(datasetType, ()=>[
7405 `datasets.${datasetType}`,
7410 datasetAnimationScopeKeys(datasetType, transition) {
7411 return cachedKeys(`${datasetType}.transition.${transition}`, ()=>[
7413 `datasets.${datasetType}.transitions.${transition}`,
7414 `transitions.${transition}`
7417 `datasets.${datasetType}`,
7422 datasetElementScopeKeys(datasetType, elementType) {
7423 return cachedKeys(`${datasetType}-${elementType}`, ()=>[
7425 `datasets.${datasetType}.elements.${elementType}`,
7426 `datasets.${datasetType}`,
7427 `elements.${elementType}`,
7432 pluginScopeKeys(plugin) {
7433 const id = plugin.id;
7434 const type = this.type;
7435 return cachedKeys(`${type}-plugin-${id}`, ()=>[
7438 ...plugin.additionalOptionScopes || []
7442 _cachedScopes(mainScope, resetCache) {
7443 const _scopeCache = this._scopeCache;
7444 let cache = _scopeCache.get(mainScope);
7445 if (!cache || resetCache) {
7447 _scopeCache.set(mainScope, cache);
7451 getOptionScopes(mainScope, keyLists, resetCache) {
7452 const { options , type } = this;
7453 const cache = this._cachedScopes(mainScope, resetCache);
7454 const cached = cache.get(keyLists);
7458 const scopes = new Set();
7459 keyLists.forEach((keys)=>{
7461 scopes.add(mainScope);
7462 keys.forEach((key)=>addIfFound(scopes, mainScope, key));
7464 keys.forEach((key)=>addIfFound(scopes, options, key));
7465 keys.forEach((key)=>addIfFound(scopes, overrides[type] || {}, key));
7466 keys.forEach((key)=>addIfFound(scopes, defaults, key));
7467 keys.forEach((key)=>addIfFound(scopes, descriptors, key));
7469 const array = Array.from(scopes);
7470 if (array.length === 0) {
7471 array.push(Object.create(null));
7473 if (keysCached.has(keyLists)) {
7474 cache.set(keyLists, array);
7478 chartOptionScopes() {
7479 const { options , type } = this;
7482 overrides[type] || {},
7483 defaults.datasets[type] || {},
7491 resolveNamedOptions(scopes, names, context, prefixes = [
7497 const { resolver , subPrefixes } = getResolver(this._resolverCache, scopes, prefixes);
7498 let options = resolver;
7499 if (needContext(resolver, names)) {
7500 result.$shared = false;
7501 context = isFunction(context) ? context() : context;
7502 const subResolver = this.createResolver(scopes, context, subPrefixes);
7503 options = _attachContext(resolver, context, subResolver);
7505 for (const prop of names){
7506 result[prop] = options[prop];
7510 createResolver(scopes, context, prefixes = [
7512 ], descriptorDefaults) {
7513 const { resolver } = getResolver(this._resolverCache, scopes, prefixes);
7514 return isObject(context) ? _attachContext(resolver, context, undefined, descriptorDefaults) : resolver;
7517 function getResolver(resolverCache, scopes, prefixes) {
7518 let cache = resolverCache.get(scopes);
7521 resolverCache.set(scopes, cache);
7523 const cacheKey = prefixes.join();
7524 let cached = cache.get(cacheKey);
7526 const resolver = _createResolver(scopes, prefixes);
7529 subPrefixes: prefixes.filter((p)=>!p.toLowerCase().includes('hover'))
7531 cache.set(cacheKey, cached);
7535 const hasFunction = (value)=>isObject(value) && Object.getOwnPropertyNames(value).some((key)=>isFunction(value[key]));
7536 function needContext(proxy, names) {
7537 const { isScriptable , isIndexable } = _descriptors(proxy);
7538 for (const prop of names){
7539 const scriptable = isScriptable(prop);
7540 const indexable = isIndexable(prop);
7541 const value = (indexable || scriptable) && proxy[prop];
7542 if (scriptable && (isFunction(value) || hasFunction(value)) || indexable && isArray(value)) {
7549 var version = "4.4.2";
7551 const KNOWN_POSITIONS = [
7558 function positionIsHorizontal(position, axis) {
7559 return position === 'top' || position === 'bottom' || KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x';
7561 function compare2Level(l1, l2) {
7562 return function(a, b) {
7563 return a[l1] === b[l1] ? a[l2] - b[l2] : a[l1] - b[l1];
7566 function onAnimationsComplete(context) {
7567 const chart = context.chart;
7568 const animationOptions = chart.options.animation;
7569 chart.notifyPlugins('afterRender');
7570 callback(animationOptions && animationOptions.onComplete, [
7574 function onAnimationProgress(context) {
7575 const chart = context.chart;
7576 const animationOptions = chart.options.animation;
7577 callback(animationOptions && animationOptions.onProgress, [
7581 function getCanvas(item) {
7582 if (_isDomSupported() && typeof item === 'string') {
7583 item = document.getElementById(item);
7584 } else if (item && item.length) {
7587 if (item && item.canvas) {
7592 const instances = {};
7593 const getChart = (key)=>{
7594 const canvas = getCanvas(key);
7595 return Object.values(instances).filter((c)=>c.canvas === canvas).pop();
7597 function moveNumericKeys(obj, start, move) {
7598 const keys = Object.keys(obj);
7599 for (const key of keys){
7600 const intKey = +key;
7601 if (intKey >= start) {
7602 const value = obj[key];
7604 if (move > 0 || intKey > start) {
7605 obj[intKey + move] = value;
7610 function determineLastEvent(e, lastEvent, inChartArea, isClick) {
7611 if (!inChartArea || e.type === 'mouseout') {
7619 function getSizeForArea(scale, chartArea, field) {
7620 return scale.options.clip ? scale[field] : chartArea[field];
7622 function getDatasetArea(meta, chartArea) {
7623 const { xScale , yScale } = meta;
7624 if (xScale && yScale) {
7626 left: getSizeForArea(xScale, chartArea, 'left'),
7627 right: getSizeForArea(xScale, chartArea, 'right'),
7628 top: getSizeForArea(yScale, chartArea, 'top'),
7629 bottom: getSizeForArea(yScale, chartArea, 'bottom')
7635 static defaults = defaults;
7636 static instances = instances;
7637 static overrides = overrides;
7638 static registry = registry;
7639 static version = version;
7640 static getChart = getChart;
7641 static register(...items) {
7642 registry.add(...items);
7643 invalidatePlugins();
7645 static unregister(...items) {
7646 registry.remove(...items);
7647 invalidatePlugins();
7649 constructor(item, userConfig){
7650 const config = this.config = new Config(userConfig);
7651 const initialCanvas = getCanvas(item);
7652 const existingChart = getChart(initialCanvas);
7653 if (existingChart) {
7654 throw new Error('Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' + ' must be destroyed before the canvas with ID \'' + existingChart.canvas.id + '\' can be reused.');
7656 const options = config.createResolver(config.chartOptionScopes(), this.getContext());
7657 this.platform = new (config.platform || _detectPlatform(initialCanvas))();
7658 this.platform.updateConfig(config);
7659 const context = this.platform.acquireContext(initialCanvas, options.aspectRatio);
7660 const canvas = context && context.canvas;
7661 const height = canvas && canvas.height;
7662 const width = canvas && canvas.width;
7665 this.canvas = canvas;
7667 this.height = height;
7668 this._options = options;
7669 this._aspectRatio = this.aspectRatio;
7671 this._metasets = [];
7672 this._stacks = undefined;
7674 this.currentDevicePixelRatio = undefined;
7675 this.chartArea = undefined;
7677 this._lastEvent = undefined;
7678 this._listeners = {};
7679 this._responsiveListeners = undefined;
7680 this._sortedMetasets = [];
7682 this._plugins = new PluginService();
7684 this._hiddenIndices = {};
7685 this.attached = false;
7686 this._animationsDisabled = undefined;
7687 this.$context = undefined;
7688 this._doResize = debounce((mode)=>this.update(mode), options.resizeDelay || 0);
7689 this._dataChanges = [];
7690 instances[this.id] = this;
7691 if (!context || !canvas) {
7692 console.error("Failed to create chart: can't acquire context from the given item");
7695 animator.listen(this, 'complete', onAnimationsComplete);
7696 animator.listen(this, 'progress', onAnimationProgress);
7698 if (this.attached) {
7703 const { options: { aspectRatio , maintainAspectRatio } , width , height , _aspectRatio } = this;
7704 if (!isNullOrUndef(aspectRatio)) {
7707 if (maintainAspectRatio && _aspectRatio) {
7708 return _aspectRatio;
7710 return height ? width / height : null;
7713 return this.config.data;
7716 this.config.data = data;
7719 return this._options;
7721 set options(options) {
7722 this.config.options = options;
7728 this.notifyPlugins('beforeInit');
7729 if (this.options.responsive) {
7732 retinaScale(this, this.options.devicePixelRatio);
7735 this.notifyPlugins('afterInit');
7739 clearCanvas(this.canvas, this.ctx);
7743 animator.stop(this);
7746 resize(width, height) {
7747 if (!animator.running(this)) {
7748 this._resize(width, height);
7750 this._resizeBeforeDraw = {
7756 _resize(width, height) {
7757 const options = this.options;
7758 const canvas = this.canvas;
7759 const aspectRatio = options.maintainAspectRatio && this.aspectRatio;
7760 const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio);
7761 const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio();
7762 const mode = this.width ? 'resize' : 'attach';
7763 this.width = newSize.width;
7764 this.height = newSize.height;
7765 this._aspectRatio = this.aspectRatio;
7766 if (!retinaScale(this, newRatio, true)) {
7769 this.notifyPlugins('resize', {
7772 callback(options.onResize, [
7776 if (this.attached) {
7777 if (this._doResize(mode)) {
7782 ensureScalesHaveIDs() {
7783 const options = this.options;
7784 const scalesOptions = options.scales || {};
7785 each(scalesOptions, (axisOptions, axisID)=>{
7786 axisOptions.id = axisID;
7789 buildOrUpdateScales() {
7790 const options = this.options;
7791 const scaleOpts = options.scales;
7792 const scales = this.scales;
7793 const updated = Object.keys(scales).reduce((obj, id)=>{
7799 items = items.concat(Object.keys(scaleOpts).map((id)=>{
7800 const scaleOptions = scaleOpts[id];
7801 const axis = determineAxis(id, scaleOptions);
7802 const isRadial = axis === 'r';
7803 const isHorizontal = axis === 'x';
7805 options: scaleOptions,
7806 dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',
7807 dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'
7811 each(items, (item)=>{
7812 const scaleOptions = item.options;
7813 const id = scaleOptions.id;
7814 const axis = determineAxis(id, scaleOptions);
7815 const scaleType = valueOrDefault(scaleOptions.type, item.dtype);
7816 if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {
7817 scaleOptions.position = item.dposition;
7821 if (id in scales && scales[id].type === scaleType) {
7824 const scaleClass = registry.getScale(scaleType);
7825 scale = new scaleClass({
7831 scales[scale.id] = scale;
7833 scale.init(scaleOptions, options);
7835 each(updated, (hasUpdated, id)=>{
7840 each(scales, (scale)=>{
7841 layouts.configure(this, scale, scale.options);
7842 layouts.addBox(this, scale);
7846 const metasets = this._metasets;
7847 const numData = this.data.datasets.length;
7848 const numMeta = metasets.length;
7849 metasets.sort((a, b)=>a.index - b.index);
7850 if (numMeta > numData) {
7851 for(let i = numData; i < numMeta; ++i){
7852 this._destroyDatasetMeta(i);
7854 metasets.splice(numData, numMeta - numData);
7856 this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
7858 _removeUnreferencedMetasets() {
7859 const { _metasets: metasets , data: { datasets } } = this;
7860 if (metasets.length > datasets.length) {
7861 delete this._stacks;
7863 metasets.forEach((meta, index)=>{
7864 if (datasets.filter((x)=>x === meta._dataset).length === 0) {
7865 this._destroyDatasetMeta(index);
7869 buildOrUpdateControllers() {
7870 const newControllers = [];
7871 const datasets = this.data.datasets;
7873 this._removeUnreferencedMetasets();
7874 for(i = 0, ilen = datasets.length; i < ilen; i++){
7875 const dataset = datasets[i];
7876 let meta = this.getDatasetMeta(i);
7877 const type = dataset.type || this.config.type;
7878 if (meta.type && meta.type !== type) {
7879 this._destroyDatasetMeta(i);
7880 meta = this.getDatasetMeta(i);
7883 meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options);
7884 meta.order = dataset.order || 0;
7886 meta.label = '' + dataset.label;
7887 meta.visible = this.isDatasetVisible(i);
7888 if (meta.controller) {
7889 meta.controller.updateIndex(i);
7890 meta.controller.linkScales();
7892 const ControllerClass = registry.getController(type);
7893 const { datasetElementType , dataElementType } = defaults.datasets[type];
7894 Object.assign(ControllerClass, {
7895 dataElementType: registry.getElement(dataElementType),
7896 datasetElementType: datasetElementType && registry.getElement(datasetElementType)
7898 meta.controller = new ControllerClass(this, i);
7899 newControllers.push(meta.controller);
7902 this._updateMetasets();
7903 return newControllers;
7906 each(this.data.datasets, (dataset, datasetIndex)=>{
7907 this.getDatasetMeta(datasetIndex).controller.reset();
7911 this._resetElements();
7912 this.notifyPlugins('reset');
7915 const config = this.config;
7917 const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext());
7918 const animsDisabled = this._animationsDisabled = !options.animation;
7919 this._updateScales();
7920 this._checkEventBindings();
7921 this._updateHiddenIndices();
7922 this._plugins.invalidate();
7923 if (this.notifyPlugins('beforeUpdate', {
7929 const newControllers = this.buildOrUpdateControllers();
7930 this.notifyPlugins('beforeElementsUpdate');
7932 for(let i = 0, ilen = this.data.datasets.length; i < ilen; i++){
7933 const { controller } = this.getDatasetMeta(i);
7934 const reset = !animsDisabled && newControllers.indexOf(controller) === -1;
7935 controller.buildOrUpdateElements(reset);
7936 minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
7938 minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0;
7939 this._updateLayout(minPadding);
7940 if (!animsDisabled) {
7941 each(newControllers, (controller)=>{
7945 this._updateDatasets(mode);
7946 this.notifyPlugins('afterUpdate', {
7949 this._layers.sort(compare2Level('z', '_idx'));
7950 const { _active , _lastEvent } = this;
7952 this._eventHandler(_lastEvent, true);
7953 } else if (_active.length) {
7954 this._updateHoverStyles(_active, _active, true);
7959 each(this.scales, (scale)=>{
7960 layouts.removeBox(this, scale);
7962 this.ensureScalesHaveIDs();
7963 this.buildOrUpdateScales();
7965 _checkEventBindings() {
7966 const options = this.options;
7967 const existingEvents = new Set(Object.keys(this._listeners));
7968 const newEvents = new Set(options.events);
7969 if (!setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) {
7970 this.unbindEvents();
7974 _updateHiddenIndices() {
7975 const { _hiddenIndices } = this;
7976 const changes = this._getUniformDataChanges() || [];
7977 for (const { method , start , count } of changes){
7978 const move = method === '_removeElements' ? -count : count;
7979 moveNumericKeys(_hiddenIndices, start, move);
7982 _getUniformDataChanges() {
7983 const _dataChanges = this._dataChanges;
7984 if (!_dataChanges || !_dataChanges.length) {
7987 this._dataChanges = [];
7988 const datasetCount = this.data.datasets.length;
7989 const makeSet = (idx)=>new Set(_dataChanges.filter((c)=>c[0] === idx).map((c, i)=>i + ',' + c.splice(1).join(',')));
7990 const changeSet = makeSet(0);
7991 for(let i = 1; i < datasetCount; i++){
7992 if (!setsEqual(changeSet, makeSet(i))) {
7996 return Array.from(changeSet).map((c)=>c.split(',')).map((a)=>({
8002 _updateLayout(minPadding) {
8003 if (this.notifyPlugins('beforeLayout', {
8008 layouts.update(this, this.width, this.height, minPadding);
8009 const area = this.chartArea;
8010 const noArea = area.width <= 0 || area.height <= 0;
8012 each(this.boxes, (box)=>{
8013 if (noArea && box.position === 'chartArea') {
8016 if (box.configure) {
8019 this._layers.push(...box._layers());
8021 this._layers.forEach((item, index)=>{
8024 this.notifyPlugins('afterLayout');
8026 _updateDatasets(mode) {
8027 if (this.notifyPlugins('beforeDatasetsUpdate', {
8033 for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
8034 this.getDatasetMeta(i).controller.configure();
8036 for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
8037 this._updateDataset(i, isFunction(mode) ? mode({
8041 this.notifyPlugins('afterDatasetsUpdate', {
8045 _updateDataset(index, mode) {
8046 const meta = this.getDatasetMeta(index);
8053 if (this.notifyPlugins('beforeDatasetUpdate', args) === false) {
8056 meta.controller._update(mode);
8057 args.cancelable = false;
8058 this.notifyPlugins('afterDatasetUpdate', args);
8061 if (this.notifyPlugins('beforeRender', {
8066 if (animator.has(this)) {
8067 if (this.attached && !animator.running(this)) {
8068 animator.start(this);
8072 onAnimationsComplete({
8079 if (this._resizeBeforeDraw) {
8080 const { width , height } = this._resizeBeforeDraw;
8081 this._resize(width, height);
8082 this._resizeBeforeDraw = null;
8085 if (this.width <= 0 || this.height <= 0) {
8088 if (this.notifyPlugins('beforeDraw', {
8093 const layers = this._layers;
8094 for(i = 0; i < layers.length && layers[i].z <= 0; ++i){
8095 layers[i].draw(this.chartArea);
8097 this._drawDatasets();
8098 for(; i < layers.length; ++i){
8099 layers[i].draw(this.chartArea);
8101 this.notifyPlugins('afterDraw');
8103 _getSortedDatasetMetas(filterVisible) {
8104 const metasets = this._sortedMetasets;
8107 for(i = 0, ilen = metasets.length; i < ilen; ++i){
8108 const meta = metasets[i];
8109 if (!filterVisible || meta.visible) {
8115 getSortedVisibleDatasetMetas() {
8116 return this._getSortedDatasetMetas(true);
8119 if (this.notifyPlugins('beforeDatasetsDraw', {
8124 const metasets = this.getSortedVisibleDatasetMetas();
8125 for(let i = metasets.length - 1; i >= 0; --i){
8126 this._drawDataset(metasets[i]);
8128 this.notifyPlugins('afterDatasetsDraw');
8130 _drawDataset(meta) {
8131 const ctx = this.ctx;
8132 const clip = meta._clip;
8133 const useClip = !clip.disabled;
8134 const area = getDatasetArea(meta, this.chartArea);
8140 if (this.notifyPlugins('beforeDatasetDraw', args) === false) {
8145 left: clip.left === false ? 0 : area.left - clip.left,
8146 right: clip.right === false ? this.width : area.right + clip.right,
8147 top: clip.top === false ? 0 : area.top - clip.top,
8148 bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom
8151 meta.controller.draw();
8155 args.cancelable = false;
8156 this.notifyPlugins('afterDatasetDraw', args);
8158 isPointInArea(point) {
8159 return _isPointInArea(point, this.chartArea, this._minPadding);
8161 getElementsAtEventForMode(e, mode, options, useFinalPosition) {
8162 const method = Interaction.modes[mode];
8163 if (typeof method === 'function') {
8164 return method(this, e, options, useFinalPosition);
8168 getDatasetMeta(datasetIndex) {
8169 const dataset = this.data.datasets[datasetIndex];
8170 const metasets = this._metasets;
8171 let meta = metasets.filter((x)=>x && x._dataset === dataset).pop();
8181 order: dataset && dataset.order || 0,
8182 index: datasetIndex,
8187 metasets.push(meta);
8192 return this.$context || (this.$context = createContext(null, {
8197 getVisibleDatasetCount() {
8198 return this.getSortedVisibleDatasetMetas().length;
8200 isDatasetVisible(datasetIndex) {
8201 const dataset = this.data.datasets[datasetIndex];
8205 const meta = this.getDatasetMeta(datasetIndex);
8206 return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;
8208 setDatasetVisibility(datasetIndex, visible) {
8209 const meta = this.getDatasetMeta(datasetIndex);
8210 meta.hidden = !visible;
8212 toggleDataVisibility(index) {
8213 this._hiddenIndices[index] = !this._hiddenIndices[index];
8215 getDataVisibility(index) {
8216 return !this._hiddenIndices[index];
8218 _updateVisibility(datasetIndex, dataIndex, visible) {
8219 const mode = visible ? 'show' : 'hide';
8220 const meta = this.getDatasetMeta(datasetIndex);
8221 const anims = meta.controller._resolveAnimations(undefined, mode);
8222 if (defined(dataIndex)) {
8223 meta.data[dataIndex].hidden = !visible;
8226 this.setDatasetVisibility(datasetIndex, visible);
8227 anims.update(meta, {
8230 this.update((ctx)=>ctx.datasetIndex === datasetIndex ? mode : undefined);
8233 hide(datasetIndex, dataIndex) {
8234 this._updateVisibility(datasetIndex, dataIndex, false);
8236 show(datasetIndex, dataIndex) {
8237 this._updateVisibility(datasetIndex, dataIndex, true);
8239 _destroyDatasetMeta(datasetIndex) {
8240 const meta = this._metasets[datasetIndex];
8241 if (meta && meta.controller) {
8242 meta.controller._destroy();
8244 delete this._metasets[datasetIndex];
8249 animator.remove(this);
8250 for(i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
8251 this._destroyDatasetMeta(i);
8255 this.notifyPlugins('beforeDestroy');
8256 const { canvas , ctx } = this;
8258 this.config.clearCache();
8260 this.unbindEvents();
8261 clearCanvas(canvas, ctx);
8262 this.platform.releaseContext(ctx);
8266 delete instances[this.id];
8267 this.notifyPlugins('afterDestroy');
8269 toBase64Image(...args) {
8270 return this.canvas.toDataURL(...args);
8273 this.bindUserEvents();
8274 if (this.options.responsive) {
8275 this.bindResponsiveEvents();
8277 this.attached = true;
8281 const listeners = this._listeners;
8282 const platform = this.platform;
8283 const _add = (type, listener)=>{
8284 platform.addEventListener(this, type, listener);
8285 listeners[type] = listener;
8287 const listener = (e, x, y)=>{
8290 this._eventHandler(e);
8292 each(this.options.events, (type)=>_add(type, listener));
8294 bindResponsiveEvents() {
8295 if (!this._responsiveListeners) {
8296 this._responsiveListeners = {};
8298 const listeners = this._responsiveListeners;
8299 const platform = this.platform;
8300 const _add = (type, listener)=>{
8301 platform.addEventListener(this, type, listener);
8302 listeners[type] = listener;
8304 const _remove = (type, listener)=>{
8305 if (listeners[type]) {
8306 platform.removeEventListener(this, type, listener);
8307 delete listeners[type];
8310 const listener = (width, height)=>{
8312 this.resize(width, height);
8316 const attached = ()=>{
8317 _remove('attach', attached);
8318 this.attached = true;
8320 _add('resize', listener);
8321 _add('detach', detached);
8324 this.attached = false;
8325 _remove('resize', listener);
8328 _add('attach', attached);
8330 if (platform.isAttached(this.canvas)) {
8337 each(this._listeners, (listener, type)=>{
8338 this.platform.removeEventListener(this, type, listener);
8340 this._listeners = {};
8341 each(this._responsiveListeners, (listener, type)=>{
8342 this.platform.removeEventListener(this, type, listener);
8344 this._responsiveListeners = undefined;
8346 updateHoverStyle(items, mode, enabled) {
8347 const prefix = enabled ? 'set' : 'remove';
8348 let meta, item, i, ilen;
8349 if (mode === 'dataset') {
8350 meta = this.getDatasetMeta(items[0].datasetIndex);
8351 meta.controller['_' + prefix + 'DatasetHoverStyle']();
8353 for(i = 0, ilen = items.length; i < ilen; ++i){
8355 const controller = item && this.getDatasetMeta(item.datasetIndex).controller;
8357 controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);
8361 getActiveElements() {
8362 return this._active || [];
8364 setActiveElements(activeElements) {
8365 const lastActive = this._active || [];
8366 const active = activeElements.map(({ datasetIndex , index })=>{
8367 const meta = this.getDatasetMeta(datasetIndex);
8369 throw new Error('No dataset found at index ' + datasetIndex);
8373 element: meta.data[index],
8377 const changed = !_elementsEqual(active, lastActive);
8379 this._active = active;
8380 this._lastEvent = null;
8381 this._updateHoverStyles(active, lastActive);
8384 notifyPlugins(hook, args, filter) {
8385 return this._plugins.notify(this, hook, args, filter);
8387 isPluginEnabled(pluginId) {
8388 return this._plugins._cache.filter((p)=>p.plugin.id === pluginId).length === 1;
8390 _updateHoverStyles(active, lastActive, replay) {
8391 const hoverOptions = this.options.hover;
8392 const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.datasetIndex === y.datasetIndex && x.index === y.index));
8393 const deactivated = diff(lastActive, active);
8394 const activated = replay ? active : diff(active, lastActive);
8395 if (deactivated.length) {
8396 this.updateHoverStyle(deactivated, hoverOptions.mode, false);
8398 if (activated.length && hoverOptions.mode) {
8399 this.updateHoverStyle(activated, hoverOptions.mode, true);
8402 _eventHandler(e, replay) {
8407 inChartArea: this.isPointInArea(e)
8409 const eventFilter = (plugin)=>(plugin.options.events || this.options.events).includes(e.native.type);
8410 if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) {
8413 const changed = this._handleEvent(e, replay, args.inChartArea);
8414 args.cancelable = false;
8415 this.notifyPlugins('afterEvent', args, eventFilter);
8416 if (changed || args.changed) {
8421 _handleEvent(e, replay, inChartArea) {
8422 const { _active: lastActive = [] , options } = this;
8423 const useFinalPosition = replay;
8424 const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition);
8425 const isClick = _isClickEvent(e);
8426 const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick);
8428 this._lastEvent = null;
8429 callback(options.onHover, [
8435 callback(options.onClick, [
8442 const changed = !_elementsEqual(active, lastActive);
8443 if (changed || replay) {
8444 this._active = active;
8445 this._updateHoverStyles(active, lastActive, replay);
8447 this._lastEvent = lastEvent;
8450 _getActiveElements(e, lastActive, inChartArea, useFinalPosition) {
8451 if (e.type === 'mouseout') {
8457 const hoverOptions = this.options.hover;
8458 return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
8461 function invalidatePlugins() {
8462 return each(Chart.instances, (chart)=>chart._plugins.invalidate());
8466 * @namespace Chart._adapters
8469 */ function abstract() {
8470 throw new Error('This method is not implemented: Check that a complete date adapter is provided.');
8473 * Date adapter (current used by the time scale)
8474 * @namespace Chart._adapters._date
8475 * @memberof Chart._adapters
8477 */ class DateAdapterBase {
8479 * Override default date adapter methods.
8480 * Accepts type parameter to define options type.
8482 * Chart._adapters._date.override<{myAdapterOption: string}>({
8484 * console.log(this.options.myAdapterOption);
8487 */ static override(members) {
8488 Object.assign(DateAdapterBase.prototype, members);
8491 constructor(options){
8492 this.options = options || {};
8494 // eslint-disable-next-line @typescript-eslint/no-empty-function
8519 _date: DateAdapterBase
8522 function getAllScaleValues(scale, type) {
8523 if (!scale._cache.$bar) {
8524 const visibleMetas = scale.getMatchingVisibleMetas(type);
8526 for(let i = 0, ilen = visibleMetas.length; i < ilen; i++){
8527 values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale));
8529 scale._cache.$bar = _arrayUnique(values.sort((a, b)=>a - b));
8531 return scale._cache.$bar;
8533 function computeMinSampleSize(meta) {
8534 const scale = meta.iScale;
8535 const values = getAllScaleValues(scale, meta.type);
8536 let min = scale._length;
8537 let i, ilen, curr, prev;
8538 const updateMinAndPrev = ()=>{
8539 if (curr === 32767 || curr === -32768) {
8542 if (defined(prev)) {
8543 min = Math.min(min, Math.abs(curr - prev) || min);
8547 for(i = 0, ilen = values.length; i < ilen; ++i){
8548 curr = scale.getPixelForValue(values[i]);
8552 for(i = 0, ilen = scale.ticks.length; i < ilen; ++i){
8553 curr = scale.getPixelForTick(i);
8558 function computeFitCategoryTraits(index, ruler, options, stackCount) {
8559 const thickness = options.barThickness;
8561 if (isNullOrUndef(thickness)) {
8562 size = ruler.min * options.categoryPercentage;
8563 ratio = options.barPercentage;
8565 size = thickness * stackCount;
8569 chunk: size / stackCount,
8571 start: ruler.pixels[index] - size / 2
8574 function computeFlexCategoryTraits(index, ruler, options, stackCount) {
8575 const pixels = ruler.pixels;
8576 const curr = pixels[index];
8577 let prev = index > 0 ? pixels[index - 1] : null;
8578 let next = index < pixels.length - 1 ? pixels[index + 1] : null;
8579 const percent = options.categoryPercentage;
8580 if (prev === null) {
8581 prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
8583 if (next === null) {
8584 next = curr + curr - prev;
8586 const start = curr - (curr - Math.min(prev, next)) / 2 * percent;
8587 const size = Math.abs(next - prev) / 2 * percent;
8589 chunk: size / stackCount,
8590 ratio: options.barPercentage,
8594 function parseFloatBar(entry, item, vScale, i) {
8595 const startValue = vScale.parse(entry[0], i);
8596 const endValue = vScale.parse(entry[1], i);
8597 const min = Math.min(startValue, endValue);
8598 const max = Math.max(startValue, endValue);
8601 if (Math.abs(min) > Math.abs(max)) {
8605 item[vScale.axis] = barEnd;
8615 function parseValue(entry, item, vScale, i) {
8616 if (isArray(entry)) {
8617 parseFloatBar(entry, item, vScale, i);
8619 item[vScale.axis] = vScale.parse(entry, i);
8623 function parseArrayOrPrimitive(meta, data, start, count) {
8624 const iScale = meta.iScale;
8625 const vScale = meta.vScale;
8626 const labels = iScale.getLabels();
8627 const singleScale = iScale === vScale;
8629 let i, ilen, item, entry;
8630 for(i = start, ilen = start + count; i < ilen; ++i){
8633 item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
8634 parsed.push(parseValue(entry, item, vScale, i));
8638 function isFloatBar(custom) {
8639 return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
8641 function barSign(size, vScale, actualBase) {
8645 return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1);
8647 function borderProps(properties) {
8648 let reverse, start, end, top, bottom;
8649 if (properties.horizontal) {
8650 reverse = properties.base > properties.x;
8654 reverse = properties.base < properties.y;
8673 function setBorderSkipped(properties, options, stack, index) {
8674 let edge = options.borderSkipped;
8677 properties.borderSkipped = res;
8680 if (edge === true) {
8681 properties.borderSkipped = {
8689 const { start , end , reverse , top , bottom } = borderProps(properties);
8690 if (edge === 'middle' && stack) {
8691 properties.enableBorderRadius = true;
8692 if ((stack._top || 0) === index) {
8694 } else if ((stack._bottom || 0) === index) {
8697 res[parseEdge(bottom, start, end, reverse)] = true;
8701 res[parseEdge(edge, start, end, reverse)] = true;
8702 properties.borderSkipped = res;
8704 function parseEdge(edge, a, b, reverse) {
8706 edge = swap(edge, a, b);
8707 edge = startEnd(edge, b, a);
8709 edge = startEnd(edge, a, b);
8713 function swap(orig, v1, v2) {
8714 return orig === v1 ? v2 : orig === v2 ? v1 : orig;
8716 function startEnd(v, start, end) {
8717 return v === 'start' ? start : v === 'end' ? end : v;
8719 function setInflateAmount(properties, { inflateAmount }, ratio) {
8720 properties.inflateAmount = inflateAmount === 'auto' ? ratio === 1 ? 0.33 : 0 : inflateAmount;
8722 class BarController extends DatasetController {
8725 datasetElementType: false,
8726 dataElementType: 'bar',
8727 categoryPercentage: 0.8,
8743 static overrides = {
8758 parsePrimitiveData(meta, data, start, count) {
8759 return parseArrayOrPrimitive(meta, data, start, count);
8761 parseArrayData(meta, data, start, count) {
8762 return parseArrayOrPrimitive(meta, data, start, count);
8764 parseObjectData(meta, data, start, count) {
8765 const { iScale , vScale } = meta;
8766 const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
8767 const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
8768 const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
8770 let i, ilen, item, obj;
8771 for(i = start, ilen = start + count; i < ilen; ++i){
8774 item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i);
8775 parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i));
8779 updateRangeFromParsed(range, scale, parsed, stack) {
8780 super.updateRangeFromParsed(range, scale, parsed, stack);
8781 const custom = parsed._custom;
8782 if (custom && scale === this._cachedMeta.vScale) {
8783 range.min = Math.min(range.min, custom.min);
8784 range.max = Math.max(range.max, custom.max);
8790 getLabelAndValue(index) {
8791 const meta = this._cachedMeta;
8792 const { iScale , vScale } = meta;
8793 const parsed = this.getParsed(index);
8794 const custom = parsed._custom;
8795 const value = isFloatBar(custom) ? '[' + custom.start + ', ' + custom.end + ']' : '' + vScale.getLabelForValue(parsed[vScale.axis]);
8797 label: '' + iScale.getLabelForValue(parsed[iScale.axis]),
8802 this.enableOptionSharing = true;
8804 const meta = this._cachedMeta;
8805 meta.stack = this.getDataset().stack;
8808 const meta = this._cachedMeta;
8809 this.updateElements(meta.data, 0, meta.data.length, mode);
8811 updateElements(bars, start, count, mode) {
8812 const reset = mode === 'reset';
8813 const { index , _cachedMeta: { vScale } } = this;
8814 const base = vScale.getBasePixel();
8815 const horizontal = vScale.isHorizontal();
8816 const ruler = this._getRuler();
8817 const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
8818 for(let i = start; i < start + count; i++){
8819 const parsed = this.getParsed(i);
8820 const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {
8823 } : this._calculateBarValuePixels(i);
8824 const ipixels = this._calculateBarIndexPixels(i, ruler);
8825 const stack = (parsed._stacks || {})[vScale.axis];
8826 const properties = {
8829 enableBorderRadius: !stack || isFloatBar(parsed._custom) || index === stack._top || index === stack._bottom,
8830 x: horizontal ? vpixels.head : ipixels.center,
8831 y: horizontal ? ipixels.center : vpixels.head,
8832 height: horizontal ? ipixels.size : Math.abs(vpixels.size),
8833 width: horizontal ? Math.abs(vpixels.size) : ipixels.size
8835 if (includeOptions) {
8836 properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode);
8838 const options = properties.options || bars[i].options;
8839 setBorderSkipped(properties, options, stack, index);
8840 setInflateAmount(properties, options, ruler.ratio);
8841 this.updateElement(bars[i], i, properties, mode);
8844 _getStacks(last, dataIndex) {
8845 const { iScale } = this._cachedMeta;
8846 const metasets = iScale.getMatchingVisibleMetas(this._type).filter((meta)=>meta.controller.options.grouped);
8847 const stacked = iScale.options.stacked;
8849 const skipNull = (meta)=>{
8850 const parsed = meta.controller.getParsed(dataIndex);
8851 const val = parsed && parsed[meta.vScale.axis];
8852 if (isNullOrUndef(val) || isNaN(val)) {
8856 for (const meta of metasets){
8857 if (dataIndex !== undefined && skipNull(meta)) {
8860 if (stacked === false || stacks.indexOf(meta.stack) === -1 || stacked === undefined && meta.stack === undefined) {
8861 stacks.push(meta.stack);
8863 if (meta.index === last) {
8867 if (!stacks.length) {
8868 stacks.push(undefined);
8872 _getStackCount(index) {
8873 return this._getStacks(undefined, index).length;
8875 _getStackIndex(datasetIndex, name, dataIndex) {
8876 const stacks = this._getStacks(datasetIndex, dataIndex);
8877 const index = name !== undefined ? stacks.indexOf(name) : -1;
8878 return index === -1 ? stacks.length - 1 : index;
8881 const opts = this.options;
8882 const meta = this._cachedMeta;
8883 const iScale = meta.iScale;
8886 for(i = 0, ilen = meta.data.length; i < ilen; ++i){
8887 pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i));
8889 const barThickness = opts.barThickness;
8890 const min = barThickness || computeMinSampleSize(meta);
8894 start: iScale._startPixel,
8895 end: iScale._endPixel,
8896 stackCount: this._getStackCount(),
8898 grouped: opts.grouped,
8899 ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage
8902 _calculateBarValuePixels(index) {
8903 const { _cachedMeta: { vScale , _stacked , index: datasetIndex } , options: { base: baseValue , minBarLength } } = this;
8904 const actualBase = baseValue || 0;
8905 const parsed = this.getParsed(index);
8906 const custom = parsed._custom;
8907 const floating = isFloatBar(custom);
8908 let value = parsed[vScale.axis];
8910 let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value;
8912 if (length !== value) {
8913 start = length - value;
8917 value = custom.barStart;
8918 length = custom.barEnd - custom.barStart;
8919 if (value !== 0 && sign(value) !== sign(custom.barEnd)) {
8924 const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start;
8925 let base = vScale.getPixelForValue(startValue);
8926 if (this.chart.getDataVisibility(index)) {
8927 head = vScale.getPixelForValue(start + length);
8932 if (Math.abs(size) < minBarLength) {
8933 size = barSign(size, vScale, actualBase) * minBarLength;
8934 if (value === actualBase) {
8937 const startPixel = vScale.getPixelForDecimal(0);
8938 const endPixel = vScale.getPixelForDecimal(1);
8939 const min = Math.min(startPixel, endPixel);
8940 const max = Math.max(startPixel, endPixel);
8941 base = Math.max(Math.min(base, max), min);
8943 if (_stacked && !floating) {
8944 parsed._stacks[vScale.axis]._visualValues[datasetIndex] = vScale.getValueForPixel(head) - vScale.getValueForPixel(base);
8947 if (base === vScale.getPixelForValue(actualBase)) {
8948 const halfGrid = sign(size) * vScale.getLineWidthForValue(actualBase) / 2;
8956 center: head + size / 2
8959 _calculateBarIndexPixels(index, ruler) {
8960 const scale = ruler.scale;
8961 const options = this.options;
8962 const skipNull = options.skipNull;
8963 const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity);
8965 if (ruler.grouped) {
8966 const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount;
8967 const range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options, stackCount) : computeFitCategoryTraits(index, ruler, options, stackCount);
8968 const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined);
8969 center = range.start + range.chunk * stackIndex + range.chunk / 2;
8970 size = Math.min(maxBarThickness, range.chunk * range.ratio);
8972 center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index);
8973 size = Math.min(maxBarThickness, ruler.min * ruler.ratio);
8976 base: center - size / 2,
8977 head: center + size / 2,
8983 const meta = this._cachedMeta;
8984 const vScale = meta.vScale;
8985 const rects = meta.data;
8986 const ilen = rects.length;
8988 for(; i < ilen; ++i){
8989 if (this.getParsed(i)[vScale.axis] !== null) {
8990 rects[i].draw(this._ctx);
8996 class BubbleController extends DatasetController {
8997 static id = 'bubble';
8999 datasetElementType: false,
9000 dataElementType: 'point',
9013 static overrides = {
9024 this.enableOptionSharing = true;
9027 parsePrimitiveData(meta, data, start, count) {
9028 const parsed = super.parsePrimitiveData(meta, data, start, count);
9029 for(let i = 0; i < parsed.length; i++){
9030 parsed[i]._custom = this.resolveDataElementOptions(i + start).radius;
9034 parseArrayData(meta, data, start, count) {
9035 const parsed = super.parseArrayData(meta, data, start, count);
9036 for(let i = 0; i < parsed.length; i++){
9037 const item = data[start + i];
9038 parsed[i]._custom = valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius);
9042 parseObjectData(meta, data, start, count) {
9043 const parsed = super.parseObjectData(meta, data, start, count);
9044 for(let i = 0; i < parsed.length; i++){
9045 const item = data[start + i];
9046 parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius);
9051 const data = this._cachedMeta.data;
9053 for(let i = data.length - 1; i >= 0; --i){
9054 max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
9056 return max > 0 && max;
9058 getLabelAndValue(index) {
9059 const meta = this._cachedMeta;
9060 const labels = this.chart.data.labels || [];
9061 const { xScale , yScale } = meta;
9062 const parsed = this.getParsed(index);
9063 const x = xScale.getLabelForValue(parsed.x);
9064 const y = yScale.getLabelForValue(parsed.y);
9065 const r = parsed._custom;
9067 label: labels[index] || '',
9068 value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'
9072 const points = this._cachedMeta.data;
9073 this.updateElements(points, 0, points.length, mode);
9075 updateElements(points, start, count, mode) {
9076 const reset = mode === 'reset';
9077 const { iScale , vScale } = this._cachedMeta;
9078 const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
9079 const iAxis = iScale.axis;
9080 const vAxis = vScale.axis;
9081 for(let i = start; i < start + count; i++){
9082 const point = points[i];
9083 const parsed = !reset && this.getParsed(i);
9084 const properties = {};
9085 const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]);
9086 const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]);
9087 properties.skip = isNaN(iPixel) || isNaN(vPixel);
9088 if (includeOptions) {
9089 properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
9091 properties.options.radius = 0;
9094 this.updateElement(point, i, properties, mode);
9097 resolveDataElementOptions(index, mode) {
9098 const parsed = this.getParsed(index);
9099 let values = super.resolveDataElementOptions(index, mode);
9100 if (values.$shared) {
9101 values = Object.assign({}, values, {
9105 const radius = values.radius;
9106 if (mode !== 'active') {
9109 values.radius += valueOrDefault(parsed && parsed._custom, radius);
9114 function getRatioAndOffset(rotation, circumference, cutout) {
9119 if (circumference < TAU) {
9120 const startAngle = rotation;
9121 const endAngle = startAngle + circumference;
9122 const startX = Math.cos(startAngle);
9123 const startY = Math.sin(startAngle);
9124 const endX = Math.cos(endAngle);
9125 const endY = Math.sin(endAngle);
9126 const calcMax = (angle, a, b)=>_angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout);
9127 const calcMin = (angle, a, b)=>_angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout);
9128 const maxX = calcMax(0, startX, endX);
9129 const maxY = calcMax(HALF_PI, startY, endY);
9130 const minX = calcMin(PI, startX, endX);
9131 const minY = calcMin(PI + HALF_PI, startY, endY);
9132 ratioX = (maxX - minX) / 2;
9133 ratioY = (maxY - minY) / 2;
9134 offsetX = -(maxX + minX) / 2;
9135 offsetY = -(maxY + minY) / 2;
9144 class DoughnutController extends DatasetController {
9145 static id = 'doughnut';
9147 datasetElementType: false,
9148 dataElementType: 'arc',
9150 animateRotate: true,
9177 static descriptors = {
9178 _scriptable: (name)=>name !== 'spacing',
9179 _indexable: (name)=>name !== 'spacing' && !name.startsWith('borderDash') && !name.startsWith('hoverBorderDash')
9181 static overrides = {
9186 generateLabels (chart) {
9187 const data = chart.data;
9188 if (data.labels.length && data.datasets.length) {
9189 const { labels: { pointStyle , color } } = chart.legend.options;
9190 return data.labels.map((label, i)=>{
9191 const meta = chart.getDatasetMeta(0);
9192 const style = meta.controller.getStyle(i);
9195 fillStyle: style.backgroundColor,
9196 strokeStyle: style.borderColor,
9198 lineWidth: style.borderWidth,
9199 pointStyle: pointStyle,
9200 hidden: !chart.getDataVisibility(i),
9208 onClick (e, legendItem, legend) {
9209 legend.chart.toggleDataVisibility(legendItem.index);
9210 legend.chart.update();
9215 constructor(chart, datasetIndex){
9216 super(chart, datasetIndex);
9217 this.enableOptionSharing = true;
9218 this.innerRadius = undefined;
9219 this.outerRadius = undefined;
9220 this.offsetX = undefined;
9221 this.offsetY = undefined;
9224 parse(start, count) {
9225 const data = this.getDataset().data;
9226 const meta = this._cachedMeta;
9227 if (this._parsing === false) {
9228 meta._parsed = data;
9230 let getter = (i)=>+data[i];
9231 if (isObject(data[start])) {
9232 const { key ='value' } = this._parsing;
9233 getter = (i)=>+resolveObjectKey(data[i], key);
9236 for(i = start, ilen = start + count; i < ilen; ++i){
9237 meta._parsed[i] = getter(i);
9242 return toRadians(this.options.rotation - 90);
9244 _getCircumference() {
9245 return toRadians(this.options.circumference);
9247 _getRotationExtents() {
9250 for(let i = 0; i < this.chart.data.datasets.length; ++i){
9251 if (this.chart.isDatasetVisible(i) && this.chart.getDatasetMeta(i).type === this._type) {
9252 const controller = this.chart.getDatasetMeta(i).controller;
9253 const rotation = controller._getRotation();
9254 const circumference = controller._getCircumference();
9255 min = Math.min(min, rotation);
9256 max = Math.max(max, rotation + circumference);
9261 circumference: max - min
9265 const chart = this.chart;
9266 const { chartArea } = chart;
9267 const meta = this._cachedMeta;
9268 const arcs = meta.data;
9269 const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing;
9270 const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
9271 const cutout = Math.min(toPercentage(this.options.cutout, maxSize), 1);
9272 const chartWeight = this._getRingWeight(this.index);
9273 const { circumference , rotation } = this._getRotationExtents();
9274 const { ratioX , ratioY , offsetX , offsetY } = getRatioAndOffset(rotation, circumference, cutout);
9275 const maxWidth = (chartArea.width - spacing) / ratioX;
9276 const maxHeight = (chartArea.height - spacing) / ratioY;
9277 const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
9278 const outerRadius = toDimension(this.options.radius, maxRadius);
9279 const innerRadius = Math.max(outerRadius * cutout, 0);
9280 const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal();
9281 this.offsetX = offsetX * outerRadius;
9282 this.offsetY = offsetY * outerRadius;
9283 meta.total = this.calculateTotal();
9284 this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);
9285 this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);
9286 this.updateElements(arcs, 0, arcs.length, mode);
9288 _circumference(i, reset) {
9289 const opts = this.options;
9290 const meta = this._cachedMeta;
9291 const circumference = this._getCircumference();
9292 if (reset && opts.animation.animateRotate || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) {
9295 return this.calculateCircumference(meta._parsed[i] * circumference / TAU);
9297 updateElements(arcs, start, count, mode) {
9298 const reset = mode === 'reset';
9299 const chart = this.chart;
9300 const chartArea = chart.chartArea;
9301 const opts = chart.options;
9302 const animationOpts = opts.animation;
9303 const centerX = (chartArea.left + chartArea.right) / 2;
9304 const centerY = (chartArea.top + chartArea.bottom) / 2;
9305 const animateScale = reset && animationOpts.animateScale;
9306 const innerRadius = animateScale ? 0 : this.innerRadius;
9307 const outerRadius = animateScale ? 0 : this.outerRadius;
9308 const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
9309 let startAngle = this._getRotation();
9311 for(i = 0; i < start; ++i){
9312 startAngle += this._circumference(i, reset);
9314 for(i = start; i < start + count; ++i){
9315 const circumference = this._circumference(i, reset);
9316 const arc = arcs[i];
9317 const properties = {
9318 x: centerX + this.offsetX,
9319 y: centerY + this.offsetY,
9321 endAngle: startAngle + circumference,
9326 if (includeOptions) {
9327 properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode);
9329 startAngle += circumference;
9330 this.updateElement(arc, i, properties, mode);
9334 const meta = this._cachedMeta;
9335 const metaData = meta.data;
9338 for(i = 0; i < metaData.length; i++){
9339 const value = meta._parsed[i];
9340 if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) {
9341 total += Math.abs(value);
9346 calculateCircumference(value) {
9347 const total = this._cachedMeta.total;
9348 if (total > 0 && !isNaN(value)) {
9349 return TAU * (Math.abs(value) / total);
9353 getLabelAndValue(index) {
9354 const meta = this._cachedMeta;
9355 const chart = this.chart;
9356 const labels = chart.data.labels || [];
9357 const value = formatNumber(meta._parsed[index], chart.options.locale);
9359 label: labels[index] || '',
9363 getMaxBorderWidth(arcs) {
9365 const chart = this.chart;
9366 let i, ilen, meta, controller, options;
9368 for(i = 0, ilen = chart.data.datasets.length; i < ilen; ++i){
9369 if (chart.isDatasetVisible(i)) {
9370 meta = chart.getDatasetMeta(i);
9372 controller = meta.controller;
9380 for(i = 0, ilen = arcs.length; i < ilen; ++i){
9381 options = controller.resolveDataElementOptions(i);
9382 if (options.borderAlign !== 'inner') {
9383 max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
9388 getMaxOffset(arcs) {
9390 for(let i = 0, ilen = arcs.length; i < ilen; ++i){
9391 const options = this.resolveDataElementOptions(i);
9392 max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
9396 _getRingWeightOffset(datasetIndex) {
9397 let ringWeightOffset = 0;
9398 for(let i = 0; i < datasetIndex; ++i){
9399 if (this.chart.isDatasetVisible(i)) {
9400 ringWeightOffset += this._getRingWeight(i);
9403 return ringWeightOffset;
9405 _getRingWeight(datasetIndex) {
9406 return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
9408 _getVisibleDatasetWeightTotal() {
9409 return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
9413 class LineController extends DatasetController {
9416 datasetElementType: 'line',
9417 dataElementType: 'point',
9421 static overrides = {
9432 this.enableOptionSharing = true;
9433 this.supportsDecimation = true;
9437 const meta = this._cachedMeta;
9438 const { dataset: line , data: points = [] , _dataset } = meta;
9439 const animationsDisabled = this.chart._animationsDisabled;
9440 let { start , count } = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
9441 this._drawStart = start;
9442 this._drawCount = count;
9443 if (_scaleRangesChanged(meta)) {
9445 count = points.length;
9447 line._chart = this.chart;
9448 line._datasetIndex = this.index;
9449 line._decimated = !!_dataset._decimated;
9450 line.points = points;
9451 const options = this.resolveDatasetElementOptions(mode);
9452 if (!this.options.showLine) {
9453 options.borderWidth = 0;
9455 options.segment = this.options.segment;
9456 this.updateElement(line, undefined, {
9457 animated: !animationsDisabled,
9460 this.updateElements(points, start, count, mode);
9462 updateElements(points, start, count, mode) {
9463 const reset = mode === 'reset';
9464 const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
9465 const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
9466 const iAxis = iScale.axis;
9467 const vAxis = vScale.axis;
9468 const { spanGaps , segment } = this.options;
9469 const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
9470 const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
9471 const end = start + count;
9472 const pointsCount = points.length;
9473 let prevParsed = start > 0 && this.getParsed(start - 1);
9474 for(let i = 0; i < pointsCount; ++i){
9475 const point = points[i];
9476 const properties = directUpdate ? point : {};
9477 if (i < start || i >= end) {
9478 properties.skip = true;
9481 const parsed = this.getParsed(i);
9482 const nullData = isNullOrUndef(parsed[vAxis]);
9483 const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
9484 const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
9485 properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
9486 properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
9488 properties.parsed = parsed;
9489 properties.raw = _dataset.data[i];
9491 if (includeOptions) {
9492 properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
9494 if (!directUpdate) {
9495 this.updateElement(point, i, properties, mode);
9497 prevParsed = parsed;
9501 const meta = this._cachedMeta;
9502 const dataset = meta.dataset;
9503 const border = dataset.options && dataset.options.borderWidth || 0;
9504 const data = meta.data || [];
9508 const firstPoint = data[0].size(this.resolveDataElementOptions(0));
9509 const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
9510 return Math.max(border, firstPoint, lastPoint) / 2;
9513 const meta = this._cachedMeta;
9514 meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
9519 class PolarAreaController extends DatasetController {
9520 static id = 'polarArea';
9522 dataElementType: 'arc',
9524 animateRotate: true,
9543 static overrides = {
9548 generateLabels (chart) {
9549 const data = chart.data;
9550 if (data.labels.length && data.datasets.length) {
9551 const { labels: { pointStyle , color } } = chart.legend.options;
9552 return data.labels.map((label, i)=>{
9553 const meta = chart.getDatasetMeta(0);
9554 const style = meta.controller.getStyle(i);
9557 fillStyle: style.backgroundColor,
9558 strokeStyle: style.borderColor,
9560 lineWidth: style.borderWidth,
9561 pointStyle: pointStyle,
9562 hidden: !chart.getDataVisibility(i),
9570 onClick (e, legendItem, legend) {
9571 legend.chart.toggleDataVisibility(legendItem.index);
9572 legend.chart.update();
9578 type: 'radialLinear',
9593 constructor(chart, datasetIndex){
9594 super(chart, datasetIndex);
9595 this.innerRadius = undefined;
9596 this.outerRadius = undefined;
9598 getLabelAndValue(index) {
9599 const meta = this._cachedMeta;
9600 const chart = this.chart;
9601 const labels = chart.data.labels || [];
9602 const value = formatNumber(meta._parsed[index].r, chart.options.locale);
9604 label: labels[index] || '',
9608 parseObjectData(meta, data, start, count) {
9609 return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
9612 const arcs = this._cachedMeta.data;
9613 this._updateRadius();
9614 this.updateElements(arcs, 0, arcs.length, mode);
9617 const meta = this._cachedMeta;
9619 min: Number.POSITIVE_INFINITY,
9620 max: Number.NEGATIVE_INFINITY
9622 meta.data.forEach((element, index)=>{
9623 const parsed = this.getParsed(index).r;
9624 if (!isNaN(parsed) && this.chart.getDataVisibility(index)) {
9625 if (parsed < range.min) {
9628 if (parsed > range.max) {
9636 const chart = this.chart;
9637 const chartArea = chart.chartArea;
9638 const opts = chart.options;
9639 const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
9640 const outerRadius = Math.max(minSize / 2, 0);
9641 const innerRadius = Math.max(opts.cutoutPercentage ? outerRadius / 100 * opts.cutoutPercentage : 1, 0);
9642 const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
9643 this.outerRadius = outerRadius - radiusLength * this.index;
9644 this.innerRadius = this.outerRadius - radiusLength;
9646 updateElements(arcs, start, count, mode) {
9647 const reset = mode === 'reset';
9648 const chart = this.chart;
9649 const opts = chart.options;
9650 const animationOpts = opts.animation;
9651 const scale = this._cachedMeta.rScale;
9652 const centerX = scale.xCenter;
9653 const centerY = scale.yCenter;
9654 const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI;
9655 let angle = datasetStartAngle;
9657 const defaultAngle = 360 / this.countVisibleElements();
9658 for(i = 0; i < start; ++i){
9659 angle += this._computeAngle(i, mode, defaultAngle);
9661 for(i = start; i < start + count; i++){
9662 const arc = arcs[i];
9663 let startAngle = angle;
9664 let endAngle = angle + this._computeAngle(i, mode, defaultAngle);
9665 let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(this.getParsed(i).r) : 0;
9668 if (animationOpts.animateScale) {
9671 if (animationOpts.animateRotate) {
9672 startAngle = endAngle = datasetStartAngle;
9675 const properties = {
9682 options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode)
9684 this.updateElement(arc, i, properties, mode);
9687 countVisibleElements() {
9688 const meta = this._cachedMeta;
9690 meta.data.forEach((element, index)=>{
9691 if (!isNaN(this.getParsed(index).r) && this.chart.getDataVisibility(index)) {
9697 _computeAngle(index, mode, defaultAngle) {
9698 return this.chart.getDataVisibility(index) ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle) : 0;
9702 class PieController extends DoughnutController {
9712 class RadarController extends DatasetController {
9713 static id = 'radar';
9715 datasetElementType: 'line',
9716 dataElementType: 'point',
9725 static overrides = {
9729 type: 'radialLinear'
9733 getLabelAndValue(index) {
9734 const vScale = this._cachedMeta.vScale;
9735 const parsed = this.getParsed(index);
9737 label: vScale.getLabels()[index],
9738 value: '' + vScale.getLabelForValue(parsed[vScale.axis])
9741 parseObjectData(meta, data, start, count) {
9742 return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
9745 const meta = this._cachedMeta;
9746 const line = meta.dataset;
9747 const points = meta.data || [];
9748 const labels = meta.iScale.getLabels();
9749 line.points = points;
9750 if (mode !== 'resize') {
9751 const options = this.resolveDatasetElementOptions(mode);
9752 if (!this.options.showLine) {
9753 options.borderWidth = 0;
9755 const properties = {
9757 _fullLoop: labels.length === points.length,
9760 this.updateElement(line, undefined, properties, mode);
9762 this.updateElements(points, 0, points.length, mode);
9764 updateElements(points, start, count, mode) {
9765 const scale = this._cachedMeta.rScale;
9766 const reset = mode === 'reset';
9767 for(let i = start; i < start + count; i++){
9768 const point = points[i];
9769 const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
9770 const pointPosition = scale.getPointPositionForValue(i, this.getParsed(i).r);
9771 const x = reset ? scale.xCenter : pointPosition.x;
9772 const y = reset ? scale.yCenter : pointPosition.y;
9773 const properties = {
9776 angle: pointPosition.angle,
9777 skip: isNaN(x) || isNaN(y),
9780 this.updateElement(point, i, properties, mode);
9785 class ScatterController extends DatasetController {
9786 static id = 'scatter';
9788 datasetElementType: false,
9789 dataElementType: 'point',
9793 static overrides = {
9806 getLabelAndValue(index) {
9807 const meta = this._cachedMeta;
9808 const labels = this.chart.data.labels || [];
9809 const { xScale , yScale } = meta;
9810 const parsed = this.getParsed(index);
9811 const x = xScale.getLabelForValue(parsed.x);
9812 const y = yScale.getLabelForValue(parsed.y);
9814 label: labels[index] || '',
9815 value: '(' + x + ', ' + y + ')'
9819 const meta = this._cachedMeta;
9820 const { data: points = [] } = meta;
9821 const animationsDisabled = this.chart._animationsDisabled;
9822 let { start , count } = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
9823 this._drawStart = start;
9824 this._drawCount = count;
9825 if (_scaleRangesChanged(meta)) {
9827 count = points.length;
9829 if (this.options.showLine) {
9830 if (!this.datasetElementType) {
9833 const { dataset: line , _dataset } = meta;
9834 line._chart = this.chart;
9835 line._datasetIndex = this.index;
9836 line._decimated = !!_dataset._decimated;
9837 line.points = points;
9838 const options = this.resolveDatasetElementOptions(mode);
9839 options.segment = this.options.segment;
9840 this.updateElement(line, undefined, {
9841 animated: !animationsDisabled,
9844 } else if (this.datasetElementType) {
9845 delete meta.dataset;
9846 this.datasetElementType = false;
9848 this.updateElements(points, start, count, mode);
9851 const { showLine } = this.options;
9852 if (!this.datasetElementType && showLine) {
9853 this.datasetElementType = this.chart.registry.getElement('line');
9855 super.addElements();
9857 updateElements(points, start, count, mode) {
9858 const reset = mode === 'reset';
9859 const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
9860 const firstOpts = this.resolveDataElementOptions(start, mode);
9861 const sharedOptions = this.getSharedOptions(firstOpts);
9862 const includeOptions = this.includeOptions(mode, sharedOptions);
9863 const iAxis = iScale.axis;
9864 const vAxis = vScale.axis;
9865 const { spanGaps , segment } = this.options;
9866 const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
9867 const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
9868 let prevParsed = start > 0 && this.getParsed(start - 1);
9869 for(let i = start; i < start + count; ++i){
9870 const point = points[i];
9871 const parsed = this.getParsed(i);
9872 const properties = directUpdate ? point : {};
9873 const nullData = isNullOrUndef(parsed[vAxis]);
9874 const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
9875 const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
9876 properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
9877 properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
9879 properties.parsed = parsed;
9880 properties.raw = _dataset.data[i];
9882 if (includeOptions) {
9883 properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
9885 if (!directUpdate) {
9886 this.updateElement(point, i, properties, mode);
9888 prevParsed = parsed;
9890 this.updateSharedOptions(sharedOptions, mode, firstOpts);
9893 const meta = this._cachedMeta;
9894 const data = meta.data || [];
9895 if (!this.options.showLine) {
9897 for(let i = data.length - 1; i >= 0; --i){
9898 max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
9900 return max > 0 && max;
9902 const dataset = meta.dataset;
9903 const border = dataset.options && dataset.options.borderWidth || 0;
9907 const firstPoint = data[0].size(this.resolveDataElementOptions(0));
9908 const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
9909 return Math.max(border, firstPoint, lastPoint) / 2;
9913 var controllers = /*#__PURE__*/Object.freeze({
9915 BarController: BarController,
9916 BubbleController: BubbleController,
9917 DoughnutController: DoughnutController,
9918 LineController: LineController,
9919 PieController: PieController,
9920 PolarAreaController: PolarAreaController,
9921 RadarController: RadarController,
9922 ScatterController: ScatterController
9925 function clipArc(ctx, element, endAngle) {
9926 const { startAngle , pixelMargin , x , y , outerRadius , innerRadius } = element;
9927 let angleMargin = pixelMargin / outerRadius;
9928 // Draw an inner border by clipping the arc and drawing a double-width border
9929 // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
9931 ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
9932 if (innerRadius > pixelMargin) {
9933 angleMargin = pixelMargin / innerRadius;
9934 ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
9936 ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI);
9941 function toRadiusCorners(value) {
9942 return _readValueToProps(value, [
9950 * Parse border radius from the provided options
9951 */ function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) {
9952 const o = toRadiusCorners(arc.options.borderRadius);
9953 const halfThickness = (outerRadius - innerRadius) / 2;
9954 const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);
9955 // Outer limits are complicated. We want to compute the available angular distance at
9956 // a radius of outerRadius - borderRadius because for small angular distances, this term limits.
9957 // We compute at r = outerRadius - borderRadius because this circle defines the center of the border corners.
9959 // If the borderRadius is large, that value can become negative.
9960 // This causes the outer borders to lose their radius entirely, which is rather unexpected. To solve that, if borderRadius > outerRadius
9961 // we know that the thickness term will dominate and compute the limits at that point
9962 const computeOuterLimit = (val)=>{
9963 const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;
9964 return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit));
9967 outerStart: computeOuterLimit(o.outerStart),
9968 outerEnd: computeOuterLimit(o.outerEnd),
9969 innerStart: _limitValue(o.innerStart, 0, innerLimit),
9970 innerEnd: _limitValue(o.innerEnd, 0, innerLimit)
9974 * Convert (r, 𝜃) to (x, y)
9975 */ function rThetaToXY(r, theta, x, y) {
9977 x: x + r * Math.cos(theta),
9978 y: y + r * Math.sin(theta)
9982 * Path the arc, respecting border radius by separating into left and right halves.
9994 */ function pathArc(ctx, element, offset, spacing, end, circular) {
9995 const { x , y , startAngle: start , pixelMargin , innerRadius: innerR } = element;
9996 const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);
9997 const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0;
9998 let spacingOffset = 0;
9999 const alpha = end - start;
10001 // When spacing is present, it is the same for all items
10002 // So we adjust the start and end angle of the arc such that
10003 // the distance is the same as it would be without the spacing
10004 const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0;
10005 const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0;
10006 const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2;
10007 const adjustedAngle = avNogSpacingRadius !== 0 ? alpha * avNogSpacingRadius / (avNogSpacingRadius + spacing) : alpha;
10008 spacingOffset = (alpha - adjustedAngle) / 2;
10010 const beta = Math.max(0.001, alpha * outerRadius - offset / PI) / outerRadius;
10011 const angleOffset = (alpha - beta) / 2;
10012 const startAngle = start + angleOffset + spacingOffset;
10013 const endAngle = end - angleOffset - spacingOffset;
10014 const { outerStart , outerEnd , innerStart , innerEnd } = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle);
10015 const outerStartAdjustedRadius = outerRadius - outerStart;
10016 const outerEndAdjustedRadius = outerRadius - outerEnd;
10017 const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;
10018 const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;
10019 const innerStartAdjustedRadius = innerRadius + innerStart;
10020 const innerEndAdjustedRadius = innerRadius + innerEnd;
10021 const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;
10022 const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;
10025 // The first arc segments from point 1 to point a to point 2
10026 const outerMidAdjustedAngle = (outerStartAdjustedAngle + outerEndAdjustedAngle) / 2;
10027 ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerMidAdjustedAngle);
10028 ctx.arc(x, y, outerRadius, outerMidAdjustedAngle, outerEndAdjustedAngle);
10029 // The corner segment from point 2 to point 3
10030 if (outerEnd > 0) {
10031 const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
10032 ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);
10034 // The line from point 3 to point 4
10035 const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
10036 ctx.lineTo(p4.x, p4.y);
10037 // The corner segment from point 4 to point 5
10038 if (innerEnd > 0) {
10039 const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
10040 ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);
10042 // The inner arc from point 5 to point b to point 6
10043 const innerMidAdjustedAngle = (endAngle - innerEnd / innerRadius + (startAngle + innerStart / innerRadius)) / 2;
10044 ctx.arc(x, y, innerRadius, endAngle - innerEnd / innerRadius, innerMidAdjustedAngle, true);
10045 ctx.arc(x, y, innerRadius, innerMidAdjustedAngle, startAngle + innerStart / innerRadius, true);
10046 // The corner segment from point 6 to point 7
10047 if (innerStart > 0) {
10048 const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
10049 ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);
10051 // The line from point 7 to point 8
10052 const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
10053 ctx.lineTo(p8.x, p8.y);
10054 // The corner segment from point 8 to point 1
10055 if (outerStart > 0) {
10056 const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
10057 ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);
10061 const outerStartX = Math.cos(outerStartAdjustedAngle) * outerRadius + x;
10062 const outerStartY = Math.sin(outerStartAdjustedAngle) * outerRadius + y;
10063 ctx.lineTo(outerStartX, outerStartY);
10064 const outerEndX = Math.cos(outerEndAdjustedAngle) * outerRadius + x;
10065 const outerEndY = Math.sin(outerEndAdjustedAngle) * outerRadius + y;
10066 ctx.lineTo(outerEndX, outerEndY);
10070 function drawArc(ctx, element, offset, spacing, circular) {
10071 const { fullCircles , startAngle , circumference } = element;
10072 let endAngle = element.endAngle;
10074 pathArc(ctx, element, offset, spacing, endAngle, circular);
10075 for(let i = 0; i < fullCircles; ++i){
10078 if (!isNaN(circumference)) {
10079 endAngle = startAngle + (circumference % TAU || TAU);
10082 pathArc(ctx, element, offset, spacing, endAngle, circular);
10086 function drawBorder(ctx, element, offset, spacing, circular) {
10087 const { fullCircles , startAngle , circumference , options } = element;
10088 const { borderWidth , borderJoinStyle , borderDash , borderDashOffset } = options;
10089 const inner = options.borderAlign === 'inner';
10090 if (!borderWidth) {
10093 ctx.setLineDash(borderDash || []);
10094 ctx.lineDashOffset = borderDashOffset;
10096 ctx.lineWidth = borderWidth * 2;
10097 ctx.lineJoin = borderJoinStyle || 'round';
10099 ctx.lineWidth = borderWidth;
10100 ctx.lineJoin = borderJoinStyle || 'bevel';
10102 let endAngle = element.endAngle;
10104 pathArc(ctx, element, offset, spacing, endAngle, circular);
10105 for(let i = 0; i < fullCircles; ++i){
10108 if (!isNaN(circumference)) {
10109 endAngle = startAngle + (circumference % TAU || TAU);
10113 clipArc(ctx, element, endAngle);
10115 if (!fullCircles) {
10116 pathArc(ctx, element, offset, spacing, endAngle, circular);
10120 class ArcElement extends Element {
10122 static defaults = {
10123 borderAlign: 'center',
10124 borderColor: '#fff',
10126 borderDashOffset: 0,
10127 borderJoinStyle: undefined,
10135 static defaultRoutes = {
10136 backgroundColor: 'backgroundColor'
10138 static descriptors = {
10140 _indexable: (name)=>name !== 'borderDash'
10151 this.options = undefined;
10152 this.circumference = undefined;
10153 this.startAngle = undefined;
10154 this.endAngle = undefined;
10155 this.innerRadius = undefined;
10156 this.outerRadius = undefined;
10157 this.pixelMargin = 0;
10158 this.fullCircles = 0;
10160 Object.assign(this, cfg);
10163 inRange(chartX, chartY, useFinalPosition) {
10164 const point = this.getProps([
10167 ], useFinalPosition);
10168 const { angle , distance } = getAngleFromPoint(point, {
10172 const { startAngle , endAngle , innerRadius , outerRadius , circumference } = this.getProps([
10178 ], useFinalPosition);
10179 const rAdjust = (this.options.spacing + this.options.borderWidth) / 2;
10180 const _circumference = valueOrDefault(circumference, endAngle - startAngle);
10181 const betweenAngles = _circumference >= TAU || _angleBetween(angle, startAngle, endAngle);
10182 const withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);
10183 return betweenAngles && withinRadius;
10185 getCenterPoint(useFinalPosition) {
10186 const { x , y , startAngle , endAngle , innerRadius , outerRadius } = this.getProps([
10193 ], useFinalPosition);
10194 const { offset , spacing } = this.options;
10195 const halfAngle = (startAngle + endAngle) / 2;
10196 const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2;
10198 x: x + Math.cos(halfAngle) * halfRadius,
10199 y: y + Math.sin(halfAngle) * halfRadius
10202 tooltipPosition(useFinalPosition) {
10203 return this.getCenterPoint(useFinalPosition);
10206 const { options , circumference } = this;
10207 const offset = (options.offset || 0) / 4;
10208 const spacing = (options.spacing || 0) / 2;
10209 const circular = options.circular;
10210 this.pixelMargin = options.borderAlign === 'inner' ? 0.33 : 0;
10211 this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0;
10212 if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) {
10216 const halfAngle = (this.startAngle + this.endAngle) / 2;
10217 ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset);
10218 const fix = 1 - Math.sin(Math.min(PI, circumference || 0));
10219 const radiusOffset = offset * fix;
10220 ctx.fillStyle = options.backgroundColor;
10221 ctx.strokeStyle = options.borderColor;
10222 drawArc(ctx, this, radiusOffset, spacing, circular);
10223 drawBorder(ctx, this, radiusOffset, spacing, circular);
10228 function setStyle(ctx, options, style = options) {
10229 ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle);
10230 ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash));
10231 ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset);
10232 ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);
10233 ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth);
10234 ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor);
10236 function lineTo(ctx, previous, target) {
10237 ctx.lineTo(target.x, target.y);
10239 function getLineMethod(options) {
10240 if (options.stepped) {
10241 return _steppedLineTo;
10243 if (options.tension || options.cubicInterpolationMode === 'monotone') {
10244 return _bezierCurveTo;
10248 function pathVars(points, segment, params = {}) {
10249 const count = points.length;
10250 const { start: paramsStart = 0 , end: paramsEnd = count - 1 } = params;
10251 const { start: segmentStart , end: segmentEnd } = segment;
10252 const start = Math.max(paramsStart, segmentStart);
10253 const end = Math.min(paramsEnd, segmentEnd);
10254 const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;
10258 loop: segment.loop,
10259 ilen: end < start && !outside ? count + end - start : end - start
10262 function pathSegment(ctx, line, segment, params) {
10263 const { points , options } = line;
10264 const { count , start , loop , ilen } = pathVars(points, segment, params);
10265 const lineMethod = getLineMethod(options);
10266 let { move =true , reverse } = params || {};
10267 let i, point, prev;
10268 for(i = 0; i <= ilen; ++i){
10269 point = points[(start + (reverse ? ilen - i : i)) % count];
10273 ctx.moveTo(point.x, point.y);
10276 lineMethod(ctx, prev, point, reverse, options.stepped);
10281 point = points[(start + (reverse ? ilen : 0)) % count];
10282 lineMethod(ctx, prev, point, reverse, options.stepped);
10286 function fastPathSegment(ctx, line, segment, params) {
10287 const points = line.points;
10288 const { count , start , ilen } = pathVars(points, segment, params);
10289 const { move =true , reverse } = params || {};
10292 let i, point, prevX, minY, maxY, lastY;
10293 const pointIndex = (index)=>(start + (reverse ? ilen - index : index)) % count;
10294 const drawX = ()=>{
10295 if (minY !== maxY) {
10296 ctx.lineTo(avgX, maxY);
10297 ctx.lineTo(avgX, minY);
10298 ctx.lineTo(avgX, lastY);
10302 point = points[pointIndex(0)];
10303 ctx.moveTo(point.x, point.y);
10305 for(i = 0; i <= ilen; ++i){
10306 point = points[pointIndex(i)];
10312 const truncX = x | 0;
10313 if (truncX === prevX) {
10316 } else if (y > maxY) {
10319 avgX = (countX * avgX + x) / ++countX;
10331 function _getSegmentMethod(line) {
10332 const opts = line.options;
10333 const borderDash = opts.borderDash && opts.borderDash.length;
10334 const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash;
10335 return useFastPath ? fastPathSegment : pathSegment;
10337 function _getInterpolationMethod(options) {
10338 if (options.stepped) {
10339 return _steppedInterpolation;
10341 if (options.tension || options.cubicInterpolationMode === 'monotone') {
10342 return _bezierInterpolation;
10344 return _pointInLine;
10346 function strokePathWithCache(ctx, line, start, count) {
10347 let path = line._path;
10349 path = line._path = new Path2D();
10350 if (line.path(path, start, count)) {
10354 setStyle(ctx, line.options);
10357 function strokePathDirect(ctx, line, start, count) {
10358 const { segments , options } = line;
10359 const segmentMethod = _getSegmentMethod(line);
10360 for (const segment of segments){
10361 setStyle(ctx, options, segment.style);
10363 if (segmentMethod(ctx, line, segment, {
10365 end: start + count - 1
10372 const usePath2D = typeof Path2D === 'function';
10373 function draw(ctx, line, start, count) {
10374 if (usePath2D && !line.options.segment) {
10375 strokePathWithCache(ctx, line, start, count);
10377 strokePathDirect(ctx, line, start, count);
10380 class LineElement extends Element {
10381 static id = 'line';
10382 static defaults = {
10383 borderCapStyle: 'butt',
10385 borderDashOffset: 0,
10386 borderJoinStyle: 'miter',
10388 capBezierPoints: true,
10389 cubicInterpolationMode: 'default',
10395 static defaultRoutes = {
10396 backgroundColor: 'backgroundColor',
10397 borderColor: 'borderColor'
10399 static descriptors = {
10401 _indexable: (name)=>name !== 'borderDash' && name !== 'fill'
10405 this.animated = true;
10406 this.options = undefined;
10407 this._chart = undefined;
10408 this._loop = undefined;
10409 this._fullLoop = undefined;
10410 this._path = undefined;
10411 this._points = undefined;
10412 this._segments = undefined;
10413 this._decimated = false;
10414 this._pointsUpdated = false;
10415 this._datasetIndex = undefined;
10417 Object.assign(this, cfg);
10420 updateControlPoints(chartArea, indexAxis) {
10421 const options = this.options;
10422 if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) {
10423 const loop = options.spanGaps ? this._loop : this._fullLoop;
10424 _updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis);
10425 this._pointsUpdated = true;
10428 set points(points) {
10429 this._points = points;
10430 delete this._segments;
10432 this._pointsUpdated = false;
10435 return this._points;
10438 return this._segments || (this._segments = _computeSegments(this, this.options.segment));
10441 const segments = this.segments;
10442 const points = this.points;
10443 return segments.length && points[segments[0].start];
10446 const segments = this.segments;
10447 const points = this.points;
10448 const count = segments.length;
10449 return count && points[segments[count - 1].end];
10451 interpolate(point, property) {
10452 const options = this.options;
10453 const value = point[property];
10454 const points = this.points;
10455 const segments = _boundSegments(this, {
10460 if (!segments.length) {
10464 const _interpolate = _getInterpolationMethod(options);
10466 for(i = 0, ilen = segments.length; i < ilen; ++i){
10467 const { start , end } = segments[i];
10468 const p1 = points[start];
10469 const p2 = points[end];
10474 const t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));
10475 const interpolated = _interpolate(p1, p2, t, options.stepped);
10476 interpolated[property] = point[property];
10477 result.push(interpolated);
10479 return result.length === 1 ? result[0] : result;
10481 pathSegment(ctx, segment, params) {
10482 const segmentMethod = _getSegmentMethod(this);
10483 return segmentMethod(ctx, this, segment, params);
10485 path(ctx, start, count) {
10486 const segments = this.segments;
10487 const segmentMethod = _getSegmentMethod(this);
10488 let loop = this._loop;
10489 start = start || 0;
10490 count = count || this.points.length - start;
10491 for (const segment of segments){
10492 loop &= segmentMethod(ctx, this, segment, {
10494 end: start + count - 1
10499 draw(ctx, chartArea, start, count) {
10500 const options = this.options || {};
10501 const points = this.points || [];
10502 if (points.length && options.borderWidth) {
10504 draw(ctx, this, start, count);
10507 if (this.animated) {
10508 this._pointsUpdated = false;
10509 this._path = undefined;
10514 function inRange$1(el, pos, axis, useFinalPosition) {
10515 const options = el.options;
10516 const { [axis]: value } = el.getProps([
10518 ], useFinalPosition);
10519 return Math.abs(pos - value) < options.radius + options.hitRadius;
10521 class PointElement extends Element {
10522 static id = 'point';
10528 */ static defaults = {
10531 hoverBorderWidth: 1,
10533 pointStyle: 'circle',
10539 */ static defaultRoutes = {
10540 backgroundColor: 'backgroundColor',
10541 borderColor: 'borderColor'
10545 this.options = undefined;
10546 this.parsed = undefined;
10547 this.skip = undefined;
10548 this.stop = undefined;
10550 Object.assign(this, cfg);
10553 inRange(mouseX, mouseY, useFinalPosition) {
10554 const options = this.options;
10555 const { x , y } = this.getProps([
10558 ], useFinalPosition);
10559 return Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2) < Math.pow(options.hitRadius + options.radius, 2);
10561 inXRange(mouseX, useFinalPosition) {
10562 return inRange$1(this, mouseX, 'x', useFinalPosition);
10564 inYRange(mouseY, useFinalPosition) {
10565 return inRange$1(this, mouseY, 'y', useFinalPosition);
10567 getCenterPoint(useFinalPosition) {
10568 const { x , y } = this.getProps([
10571 ], useFinalPosition);
10578 options = options || this.options || {};
10579 let radius = options.radius || 0;
10580 radius = Math.max(radius, radius && options.hoverRadius || 0);
10581 const borderWidth = radius && options.borderWidth || 0;
10582 return (radius + borderWidth) * 2;
10585 const options = this.options;
10586 if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) {
10589 ctx.strokeStyle = options.borderColor;
10590 ctx.lineWidth = options.borderWidth;
10591 ctx.fillStyle = options.backgroundColor;
10592 drawPoint(ctx, options, this.x, this.y);
10595 const options = this.options || {};
10596 // @ts-expect-error Fallbacks should never be hit in practice
10597 return options.radius + options.hitRadius;
10601 function getBarBounds(bar, useFinalPosition) {
10602 const { x , y , base , width , height } = bar.getProps([
10608 ], useFinalPosition);
10609 let left, right, top, bottom, half;
10610 if (bar.horizontal) {
10612 left = Math.min(x, base);
10613 right = Math.max(x, base);
10620 top = Math.min(y, base);
10621 bottom = Math.max(y, base);
10630 function skipOrLimit(skip, value, min, max) {
10631 return skip ? 0 : _limitValue(value, min, max);
10633 function parseBorderWidth(bar, maxW, maxH) {
10634 const value = bar.options.borderWidth;
10635 const skip = bar.borderSkipped;
10636 const o = toTRBL(value);
10638 t: skipOrLimit(skip.top, o.top, 0, maxH),
10639 r: skipOrLimit(skip.right, o.right, 0, maxW),
10640 b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
10641 l: skipOrLimit(skip.left, o.left, 0, maxW)
10644 function parseBorderRadius(bar, maxW, maxH) {
10645 const { enableBorderRadius } = bar.getProps([
10646 'enableBorderRadius'
10648 const value = bar.options.borderRadius;
10649 const o = toTRBLCorners(value);
10650 const maxR = Math.min(maxW, maxH);
10651 const skip = bar.borderSkipped;
10652 const enableBorder = enableBorderRadius || isObject(value);
10654 topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
10655 topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
10656 bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
10657 bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
10660 function boundingRects(bar) {
10661 const bounds = getBarBounds(bar);
10662 const width = bounds.right - bounds.left;
10663 const height = bounds.bottom - bounds.top;
10664 const border = parseBorderWidth(bar, width / 2, height / 2);
10665 const radius = parseBorderRadius(bar, width / 2, height / 2);
10675 x: bounds.left + border.l,
10676 y: bounds.top + border.t,
10677 w: width - border.l - border.r,
10678 h: height - border.t - border.b,
10680 topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
10681 topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
10682 bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
10683 bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r))
10688 function inRange(bar, x, y, useFinalPosition) {
10689 const skipX = x === null;
10690 const skipY = y === null;
10691 const skipBoth = skipX && skipY;
10692 const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
10693 return bounds && (skipX || _isBetween(x, bounds.left, bounds.right)) && (skipY || _isBetween(y, bounds.top, bounds.bottom));
10695 function hasRadius(radius) {
10696 return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
10698 function addNormalRectPath(ctx, rect) {
10699 ctx.rect(rect.x, rect.y, rect.w, rect.h);
10701 function inflateRect(rect, amount, refRect = {}) {
10702 const x = rect.x !== refRect.x ? -amount : 0;
10703 const y = rect.y !== refRect.y ? -amount : 0;
10704 const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;
10705 const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;
10711 radius: rect.radius
10714 class BarElement extends Element {
10716 static defaults = {
10717 borderSkipped: 'start',
10720 inflateAmount: 'auto',
10721 pointStyle: undefined
10723 static defaultRoutes = {
10724 backgroundColor: 'backgroundColor',
10725 borderColor: 'borderColor'
10729 this.options = undefined;
10730 this.horizontal = undefined;
10731 this.base = undefined;
10732 this.width = undefined;
10733 this.height = undefined;
10734 this.inflateAmount = undefined;
10736 Object.assign(this, cfg);
10740 const { inflateAmount , options: { borderColor , backgroundColor } } = this;
10741 const { inner , outer } = boundingRects(this);
10742 const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
10744 if (outer.w !== inner.w || outer.h !== inner.h) {
10746 addRectPath(ctx, inflateRect(outer, inflateAmount, inner));
10748 addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));
10749 ctx.fillStyle = borderColor;
10750 ctx.fill('evenodd');
10753 addRectPath(ctx, inflateRect(inner, inflateAmount));
10754 ctx.fillStyle = backgroundColor;
10758 inRange(mouseX, mouseY, useFinalPosition) {
10759 return inRange(this, mouseX, mouseY, useFinalPosition);
10761 inXRange(mouseX, useFinalPosition) {
10762 return inRange(this, mouseX, null, useFinalPosition);
10764 inYRange(mouseY, useFinalPosition) {
10765 return inRange(this, null, mouseY, useFinalPosition);
10767 getCenterPoint(useFinalPosition) {
10768 const { x , y , base , horizontal } = this.getProps([
10773 ], useFinalPosition);
10775 x: horizontal ? (x + base) / 2 : x,
10776 y: horizontal ? y : (y + base) / 2
10780 return axis === 'x' ? this.width / 2 : this.height / 2;
10784 var elements = /*#__PURE__*/Object.freeze({
10786 ArcElement: ArcElement,
10787 BarElement: BarElement,
10788 LineElement: LineElement,
10789 PointElement: PointElement
10792 const addIfString = (labels, raw, index, addedLabels)=>{
10793 if (typeof raw === 'string') {
10794 index = labels.push(raw) - 1;
10795 addedLabels.unshift({
10799 } else if (isNaN(raw)) {
10804 function findOrAddLabel(labels, raw, index, addedLabels) {
10805 const first = labels.indexOf(raw);
10806 if (first === -1) {
10807 return addIfString(labels, raw, index, addedLabels);
10809 const last = labels.lastIndexOf(raw);
10810 return first !== last ? index : first;
10812 const validIndex = (index, max)=>index === null ? null : _limitValue(Math.round(index), 0, max);
10813 function _getLabelForValue(value) {
10814 const labels = this.getLabels();
10815 if (value >= 0 && value < labels.length) {
10816 return labels[value];
10820 class CategoryScale extends Scale {
10821 static id = 'category';
10822 static defaults = {
10824 callback: _getLabelForValue
10829 this._startValue = undefined;
10830 this._valueRange = 0;
10831 this._addedLabels = [];
10833 init(scaleOptions) {
10834 const added = this._addedLabels;
10835 if (added.length) {
10836 const labels = this.getLabels();
10837 for (const { index , label } of added){
10838 if (labels[index] === label) {
10839 labels.splice(index, 1);
10842 this._addedLabels = [];
10844 super.init(scaleOptions);
10846 parse(raw, index) {
10847 if (isNullOrUndef(raw)) {
10850 const labels = this.getLabels();
10851 index = isFinite(index) && labels[index] === raw ? index : findOrAddLabel(labels, raw, valueOrDefault(index, raw), this._addedLabels);
10852 return validIndex(index, labels.length - 1);
10854 determineDataLimits() {
10855 const { minDefined , maxDefined } = this.getUserBounds();
10856 let { min , max } = this.getMinMax(true);
10857 if (this.options.bounds === 'ticks') {
10862 max = this.getLabels().length - 1;
10869 const min = this.min;
10870 const max = this.max;
10871 const offset = this.options.offset;
10873 let labels = this.getLabels();
10874 labels = min === 0 && max === labels.length - 1 ? labels : labels.slice(min, max + 1);
10875 this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);
10876 this._startValue = this.min - (offset ? 0.5 : 0);
10877 for(let value = min; value <= max; value++){
10884 getLabelForValue(value) {
10885 return _getLabelForValue.call(this, value);
10889 if (!this.isHorizontal()) {
10890 this._reversePixels = !this._reversePixels;
10893 getPixelForValue(value) {
10894 if (typeof value !== 'number') {
10895 value = this.parse(value);
10897 return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
10899 getPixelForTick(index) {
10900 const ticks = this.ticks;
10901 if (index < 0 || index > ticks.length - 1) {
10904 return this.getPixelForValue(ticks[index].value);
10906 getValueForPixel(pixel) {
10907 return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange);
10910 return this.bottom;
10914 function generateTicks$1(generationOptions, dataRange) {
10916 const MIN_SPACING = 1e-14;
10917 const { bounds , step , min , max , precision , count , maxTicks , maxDigits , includeBounds } = generationOptions;
10918 const unit = step || 1;
10919 const maxSpaces = maxTicks - 1;
10920 const { min: rmin , max: rmax } = dataRange;
10921 const minDefined = !isNullOrUndef(min);
10922 const maxDefined = !isNullOrUndef(max);
10923 const countDefined = !isNullOrUndef(count);
10924 const minSpacing = (rmax - rmin) / (maxDigits + 1);
10925 let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit;
10926 let factor, niceMin, niceMax, numSpaces;
10927 if (spacing < MIN_SPACING && !minDefined && !maxDefined) {
10937 numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
10938 if (numSpaces > maxSpaces) {
10939 spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
10941 if (!isNullOrUndef(precision)) {
10942 factor = Math.pow(10, precision);
10943 spacing = Math.ceil(spacing * factor) / factor;
10945 if (bounds === 'ticks') {
10946 niceMin = Math.floor(rmin / spacing) * spacing;
10947 niceMax = Math.ceil(rmax / spacing) * spacing;
10952 if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) {
10953 numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks));
10954 spacing = (max - min) / numSpaces;
10957 } else if (countDefined) {
10958 niceMin = minDefined ? min : niceMin;
10959 niceMax = maxDefined ? max : niceMax;
10960 numSpaces = count - 1;
10961 spacing = (niceMax - niceMin) / numSpaces;
10963 numSpaces = (niceMax - niceMin) / spacing;
10964 if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
10965 numSpaces = Math.round(numSpaces);
10967 numSpaces = Math.ceil(numSpaces);
10970 const decimalPlaces = Math.max(_decimalPlaces(spacing), _decimalPlaces(niceMin));
10971 factor = Math.pow(10, isNullOrUndef(precision) ? decimalPlaces : precision);
10972 niceMin = Math.round(niceMin * factor) / factor;
10973 niceMax = Math.round(niceMax * factor) / factor;
10976 if (includeBounds && niceMin !== min) {
10980 if (niceMin < min) {
10983 if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {
10986 } else if (niceMin < min) {
10990 for(; j < numSpaces; ++j){
10991 const tickValue = Math.round((niceMin + j * spacing) * factor) / factor;
10992 if (maxDefined && tickValue > max) {
10999 if (maxDefined && includeBounds && niceMax !== max) {
11000 if (ticks.length && almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {
11001 ticks[ticks.length - 1].value = max;
11007 } else if (!maxDefined || niceMax === max) {
11014 function relativeLabelSize(value, minSpacing, { horizontal , minRotation }) {
11015 const rad = toRadians(minRotation);
11016 const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001;
11017 const length = 0.75 * minSpacing * ('' + value).length;
11018 return Math.min(minSpacing / ratio, length);
11020 class LinearScaleBase extends Scale {
11023 this.start = undefined;
11024 this.end = undefined;
11025 this._startValue = undefined;
11026 this._endValue = undefined;
11027 this._valueRange = 0;
11029 parse(raw, index) {
11030 if (isNullOrUndef(raw)) {
11033 if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
11038 handleTickRangeOptions() {
11039 const { beginAtZero } = this.options;
11040 const { minDefined , maxDefined } = this.getUserBounds();
11041 let { min , max } = this;
11042 const setMin = (v)=>min = minDefined ? min : v;
11043 const setMax = (v)=>max = maxDefined ? max : v;
11045 const minSign = sign(min);
11046 const maxSign = sign(max);
11047 if (minSign < 0 && maxSign < 0) {
11049 } else if (minSign > 0 && maxSign > 0) {
11054 let offset = max === 0 ? 1 : Math.abs(max * 0.05);
11055 setMax(max + offset);
11056 if (!beginAtZero) {
11057 setMin(min - offset);
11064 const tickOpts = this.options.ticks;
11065 let { maxTicksLimit , stepSize } = tickOpts;
11068 maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1;
11069 if (maxTicks > 1000) {
11070 console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`);
11074 maxTicks = this.computeTickLimit();
11075 maxTicksLimit = maxTicksLimit || 11;
11077 if (maxTicksLimit) {
11078 maxTicks = Math.min(maxTicksLimit, maxTicks);
11082 computeTickLimit() {
11083 return Number.POSITIVE_INFINITY;
11086 const opts = this.options;
11087 const tickOpts = opts.ticks;
11088 let maxTicks = this.getTickLimit();
11089 maxTicks = Math.max(2, maxTicks);
11090 const numericGeneratorOptions = {
11092 bounds: opts.bounds,
11095 precision: tickOpts.precision,
11096 step: tickOpts.stepSize,
11097 count: tickOpts.count,
11098 maxDigits: this._maxDigits(),
11099 horizontal: this.isHorizontal(),
11100 minRotation: tickOpts.minRotation || 0,
11101 includeBounds: tickOpts.includeBounds !== false
11103 const dataRange = this._range || this;
11104 const ticks = generateTicks$1(numericGeneratorOptions, dataRange);
11105 if (opts.bounds === 'ticks') {
11106 _setMinAndMaxByKey(ticks, this, 'value');
11108 if (opts.reverse) {
11110 this.start = this.max;
11111 this.end = this.min;
11113 this.start = this.min;
11114 this.end = this.max;
11119 const ticks = this.ticks;
11120 let start = this.min;
11121 let end = this.max;
11123 if (this.options.offset && ticks.length) {
11124 const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
11128 this._startValue = start;
11129 this._endValue = end;
11130 this._valueRange = end - start;
11132 getLabelForValue(value) {
11133 return formatNumber(value, this.chart.options.locale, this.options.ticks.format);
11137 class LinearScale extends LinearScaleBase {
11138 static id = 'linear';
11139 static defaults = {
11141 callback: Ticks.formatters.numeric
11144 determineDataLimits() {
11145 const { min , max } = this.getMinMax(true);
11146 this.min = isNumberFinite(min) ? min : 0;
11147 this.max = isNumberFinite(max) ? max : 1;
11148 this.handleTickRangeOptions();
11150 computeTickLimit() {
11151 const horizontal = this.isHorizontal();
11152 const length = horizontal ? this.width : this.height;
11153 const minRotation = toRadians(this.options.ticks.minRotation);
11154 const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001;
11155 const tickFont = this._resolveTickFontOptions(0);
11156 return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio));
11158 getPixelForValue(value) {
11159 return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
11161 getValueForPixel(pixel) {
11162 return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
11166 const log10Floor = (v)=>Math.floor(log10(v));
11167 const changeExponent = (v, m)=>Math.pow(10, log10Floor(v) + m);
11168 function isMajor(tickVal) {
11169 const remain = tickVal / Math.pow(10, log10Floor(tickVal));
11170 return remain === 1;
11172 function steps(min, max, rangeExp) {
11173 const rangeStep = Math.pow(10, rangeExp);
11174 const start = Math.floor(min / rangeStep);
11175 const end = Math.ceil(max / rangeStep);
11176 return end - start;
11178 function startExp(min, max) {
11179 const range = max - min;
11180 let rangeExp = log10Floor(range);
11181 while(steps(min, max, rangeExp) > 10){
11184 while(steps(min, max, rangeExp) < 10){
11187 return Math.min(rangeExp, log10Floor(min));
11189 function generateTicks(generationOptions, { min , max }) {
11190 min = finiteOrDefault(generationOptions.min, min);
11192 const minExp = log10Floor(min);
11193 let exp = startExp(min, max);
11194 let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
11195 const stepSize = Math.pow(10, exp);
11196 const base = minExp > exp ? Math.pow(10, minExp) : 0;
11197 const start = Math.round((min - base) * precision) / precision;
11198 const offset = Math.floor((min - base) / stepSize / 10) * stepSize * 10;
11199 let significand = Math.floor((start - offset) / Math.pow(10, exp));
11200 let value = finiteOrDefault(generationOptions.min, Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision);
11201 while(value < max){
11204 major: isMajor(value),
11207 if (significand >= 10) {
11208 significand = significand < 15 ? 15 : 20;
11212 if (significand >= 20) {
11215 precision = exp >= 0 ? 1 : precision;
11217 value = Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision;
11219 const lastTick = finiteOrDefault(generationOptions.max, value);
11222 major: isMajor(lastTick),
11227 class LogarithmicScale extends Scale {
11228 static id = 'logarithmic';
11229 static defaults = {
11231 callback: Ticks.formatters.logarithmic,
11239 this.start = undefined;
11240 this.end = undefined;
11241 this._startValue = undefined;
11242 this._valueRange = 0;
11244 parse(raw, index) {
11245 const value = LinearScaleBase.prototype.parse.apply(this, [
11253 return isNumberFinite(value) && value > 0 ? value : null;
11255 determineDataLimits() {
11256 const { min , max } = this.getMinMax(true);
11257 this.min = isNumberFinite(min) ? Math.max(0, min) : null;
11258 this.max = isNumberFinite(max) ? Math.max(0, max) : null;
11259 if (this.options.beginAtZero) {
11262 if (this._zero && this.min !== this._suggestedMin && !isNumberFinite(this._userMin)) {
11263 this.min = min === changeExponent(this.min, 0) ? changeExponent(this.min, -1) : changeExponent(this.min, 0);
11265 this.handleTickRangeOptions();
11267 handleTickRangeOptions() {
11268 const { minDefined , maxDefined } = this.getUserBounds();
11269 let min = this.min;
11270 let max = this.max;
11271 const setMin = (v)=>min = minDefined ? min : v;
11272 const setMax = (v)=>max = maxDefined ? max : v;
11278 setMin(changeExponent(min, -1));
11279 setMax(changeExponent(max, +1));
11283 setMin(changeExponent(max, -1));
11286 setMax(changeExponent(min, +1));
11292 const opts = this.options;
11293 const generationOptions = {
11294 min: this._userMin,
11297 const ticks = generateTicks(generationOptions, this);
11298 if (opts.bounds === 'ticks') {
11299 _setMinAndMaxByKey(ticks, this, 'value');
11301 if (opts.reverse) {
11303 this.start = this.max;
11304 this.end = this.min;
11306 this.start = this.min;
11307 this.end = this.max;
11311 getLabelForValue(value) {
11312 return value === undefined ? '0' : formatNumber(value, this.chart.options.locale, this.options.ticks.format);
11315 const start = this.min;
11317 this._startValue = log10(start);
11318 this._valueRange = log10(this.max) - log10(start);
11320 getPixelForValue(value) {
11321 if (value === undefined || value === 0) {
11324 if (value === null || isNaN(value)) {
11327 return this.getPixelForDecimal(value === this.min ? 0 : (log10(value) - this._startValue) / this._valueRange);
11329 getValueForPixel(pixel) {
11330 const decimal = this.getDecimalForPixel(pixel);
11331 return Math.pow(10, this._startValue + decimal * this._valueRange);
11335 function getTickBackdropHeight(opts) {
11336 const tickOpts = opts.ticks;
11337 if (tickOpts.display && opts.display) {
11338 const padding = toPadding(tickOpts.backdropPadding);
11339 return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height;
11343 function measureLabelSize(ctx, font, label) {
11344 label = isArray(label) ? label : [
11348 w: _longestText(ctx, font.string, label),
11349 h: label.length * font.lineHeight
11352 function determineLimits(angle, pos, size, min, max) {
11353 if (angle === min || angle === max) {
11355 start: pos - size / 2,
11356 end: pos + size / 2
11358 } else if (angle < min || angle > max) {
11369 function fitWithPointLabels(scale) {
11371 l: scale.left + scale._padding.left,
11372 r: scale.right - scale._padding.right,
11373 t: scale.top + scale._padding.top,
11374 b: scale.bottom - scale._padding.bottom
11376 const limits = Object.assign({}, orig);
11377 const labelSizes = [];
11378 const padding = [];
11379 const valueCount = scale._pointLabels.length;
11380 const pointLabelOpts = scale.options.pointLabels;
11381 const additionalAngle = pointLabelOpts.centerPointLabels ? PI / valueCount : 0;
11382 for(let i = 0; i < valueCount; i++){
11383 const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i));
11384 padding[i] = opts.padding;
11385 const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle);
11386 const plFont = toFont(opts.font);
11387 const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);
11388 labelSizes[i] = textSize;
11389 const angleRadians = _normalizeAngle(scale.getIndexAngle(i) + additionalAngle);
11390 const angle = Math.round(toDegrees(angleRadians));
11391 const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
11392 const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
11393 updateLimits(limits, orig, angleRadians, hLimits, vLimits);
11395 scale.setCenterPoint(orig.l - limits.l, limits.r - orig.r, orig.t - limits.t, limits.b - orig.b);
11396 scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);
11398 function updateLimits(limits, orig, angle, hLimits, vLimits) {
11399 const sin = Math.abs(Math.sin(angle));
11400 const cos = Math.abs(Math.cos(angle));
11403 if (hLimits.start < orig.l) {
11404 x = (orig.l - hLimits.start) / sin;
11405 limits.l = Math.min(limits.l, orig.l - x);
11406 } else if (hLimits.end > orig.r) {
11407 x = (hLimits.end - orig.r) / sin;
11408 limits.r = Math.max(limits.r, orig.r + x);
11410 if (vLimits.start < orig.t) {
11411 y = (orig.t - vLimits.start) / cos;
11412 limits.t = Math.min(limits.t, orig.t - y);
11413 } else if (vLimits.end > orig.b) {
11414 y = (vLimits.end - orig.b) / cos;
11415 limits.b = Math.max(limits.b, orig.b + y);
11418 function createPointLabelItem(scale, index, itemOpts) {
11419 const outerDistance = scale.drawingArea;
11420 const { extra , additionalAngle , padding , size } = itemOpts;
11421 const pointLabelPosition = scale.getPointPosition(index, outerDistance + extra + padding, additionalAngle);
11422 const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI)));
11423 const y = yForAngle(pointLabelPosition.y, size.h, angle);
11424 const textAlign = getTextAlignForAngle(angle);
11425 const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);
11428 x: pointLabelPosition.x,
11433 right: left + size.w,
11437 function isNotOverlapped(item, area) {
11441 const { left , top , right , bottom } = item;
11442 const apexesInArea = _isPointInArea({
11445 }, area) || _isPointInArea({
11448 }, area) || _isPointInArea({
11451 }, area) || _isPointInArea({
11455 return !apexesInArea;
11457 function buildPointLabelItems(scale, labelSizes, padding) {
11459 const valueCount = scale._pointLabels.length;
11460 const opts = scale.options;
11461 const { centerPointLabels , display } = opts.pointLabels;
11463 extra: getTickBackdropHeight(opts) / 2,
11464 additionalAngle: centerPointLabels ? PI / valueCount : 0
11467 for(let i = 0; i < valueCount; i++){
11468 itemOpts.padding = padding[i];
11469 itemOpts.size = labelSizes[i];
11470 const item = createPointLabelItem(scale, i, itemOpts);
11472 if (display === 'auto') {
11473 item.visible = isNotOverlapped(item, area);
11474 if (item.visible) {
11481 function getTextAlignForAngle(angle) {
11482 if (angle === 0 || angle === 180) {
11484 } else if (angle < 180) {
11489 function leftForTextAlign(x, w, align) {
11490 if (align === 'right') {
11492 } else if (align === 'center') {
11497 function yForAngle(y, h, angle) {
11498 if (angle === 90 || angle === 270) {
11500 } else if (angle > 270 || angle < 90) {
11505 function drawPointLabelBox(ctx, opts, item) {
11506 const { left , top , right , bottom } = item;
11507 const { backdropColor } = opts;
11508 if (!isNullOrUndef(backdropColor)) {
11509 const borderRadius = toTRBLCorners(opts.borderRadius);
11510 const padding = toPadding(opts.backdropPadding);
11511 ctx.fillStyle = backdropColor;
11512 const backdropLeft = left - padding.left;
11513 const backdropTop = top - padding.top;
11514 const backdropWidth = right - left + padding.width;
11515 const backdropHeight = bottom - top + padding.height;
11516 if (Object.values(borderRadius).some((v)=>v !== 0)) {
11518 addRoundedRectPath(ctx, {
11523 radius: borderRadius
11527 ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight);
11531 function drawPointLabels(scale, labelCount) {
11532 const { ctx , options: { pointLabels } } = scale;
11533 for(let i = labelCount - 1; i >= 0; i--){
11534 const item = scale._pointLabelItems[i];
11535 if (!item.visible) {
11538 const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
11539 drawPointLabelBox(ctx, optsAtIndex, item);
11540 const plFont = toFont(optsAtIndex.font);
11541 const { x , y , textAlign } = item;
11542 renderText(ctx, scale._pointLabels[i], x, y + plFont.lineHeight / 2, plFont, {
11543 color: optsAtIndex.color,
11544 textAlign: textAlign,
11545 textBaseline: 'middle'
11549 function pathRadiusLine(scale, radius, circular, labelCount) {
11550 const { ctx } = scale;
11552 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU);
11554 let pointPosition = scale.getPointPosition(0, radius);
11555 ctx.moveTo(pointPosition.x, pointPosition.y);
11556 for(let i = 1; i < labelCount; i++){
11557 pointPosition = scale.getPointPosition(i, radius);
11558 ctx.lineTo(pointPosition.x, pointPosition.y);
11562 function drawRadiusLine(scale, gridLineOpts, radius, labelCount, borderOpts) {
11563 const ctx = scale.ctx;
11564 const circular = gridLineOpts.circular;
11565 const { color , lineWidth } = gridLineOpts;
11566 if (!circular && !labelCount || !color || !lineWidth || radius < 0) {
11570 ctx.strokeStyle = color;
11571 ctx.lineWidth = lineWidth;
11572 ctx.setLineDash(borderOpts.dash);
11573 ctx.lineDashOffset = borderOpts.dashOffset;
11575 pathRadiusLine(scale, radius, circular, labelCount);
11580 function createPointLabelContext(parent, index, label) {
11581 return createContext(parent, {
11587 class RadialLinearScale extends LinearScaleBase {
11588 static id = 'radialLinear';
11589 static defaults = {
11592 position: 'chartArea',
11597 borderDashOffset: 0.0
11604 showLabelBackdrop: true,
11605 callback: Ticks.formatters.numeric
11608 backdropColor: undefined,
11609 backdropPadding: 2,
11618 centerPointLabels: false
11621 static defaultRoutes = {
11622 'angleLines.color': 'borderColor',
11623 'pointLabels.color': 'color',
11624 'ticks.color': 'color'
11626 static descriptors = {
11633 this.xCenter = undefined;
11634 this.yCenter = undefined;
11635 this.drawingArea = undefined;
11636 this._pointLabels = [];
11637 this._pointLabelItems = [];
11640 const padding = this._padding = toPadding(getTickBackdropHeight(this.options) / 2);
11641 const w = this.width = this.maxWidth - padding.width;
11642 const h = this.height = this.maxHeight - padding.height;
11643 this.xCenter = Math.floor(this.left + w / 2 + padding.left);
11644 this.yCenter = Math.floor(this.top + h / 2 + padding.top);
11645 this.drawingArea = Math.floor(Math.min(w, h) / 2);
11647 determineDataLimits() {
11648 const { min , max } = this.getMinMax(false);
11649 this.min = isNumberFinite(min) && !isNaN(min) ? min : 0;
11650 this.max = isNumberFinite(max) && !isNaN(max) ? max : 0;
11651 this.handleTickRangeOptions();
11653 computeTickLimit() {
11654 return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
11656 generateTickLabels(ticks) {
11657 LinearScaleBase.prototype.generateTickLabels.call(this, ticks);
11658 this._pointLabels = this.getLabels().map((value, index)=>{
11659 const label = callback(this.options.pointLabels.callback, [
11663 return label || label === 0 ? label : '';
11664 }).filter((v, i)=>this.chart.getDataVisibility(i));
11667 const opts = this.options;
11668 if (opts.display && opts.pointLabels.display) {
11669 fitWithPointLabels(this);
11671 this.setCenterPoint(0, 0, 0, 0);
11674 setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {
11675 this.xCenter += Math.floor((leftMovement - rightMovement) / 2);
11676 this.yCenter += Math.floor((topMovement - bottomMovement) / 2);
11677 this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement));
11679 getIndexAngle(index) {
11680 const angleMultiplier = TAU / (this._pointLabels.length || 1);
11681 const startAngle = this.options.startAngle || 0;
11682 return _normalizeAngle(index * angleMultiplier + toRadians(startAngle));
11684 getDistanceFromCenterForValue(value) {
11685 if (isNullOrUndef(value)) {
11688 const scalingFactor = this.drawingArea / (this.max - this.min);
11689 if (this.options.reverse) {
11690 return (this.max - value) * scalingFactor;
11692 return (value - this.min) * scalingFactor;
11694 getValueForDistanceFromCenter(distance) {
11695 if (isNullOrUndef(distance)) {
11698 const scaledDistance = distance / (this.drawingArea / (this.max - this.min));
11699 return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance;
11701 getPointLabelContext(index) {
11702 const pointLabels = this._pointLabels || [];
11703 if (index >= 0 && index < pointLabels.length) {
11704 const pointLabel = pointLabels[index];
11705 return createPointLabelContext(this.getContext(), index, pointLabel);
11708 getPointPosition(index, distanceFromCenter, additionalAngle = 0) {
11709 const angle = this.getIndexAngle(index) - HALF_PI + additionalAngle;
11711 x: Math.cos(angle) * distanceFromCenter + this.xCenter,
11712 y: Math.sin(angle) * distanceFromCenter + this.yCenter,
11716 getPointPositionForValue(index, value) {
11717 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
11719 getBasePosition(index) {
11720 return this.getPointPositionForValue(index || 0, this.getBaseValue());
11722 getPointLabelPosition(index) {
11723 const { left , top , right , bottom } = this._pointLabelItems[index];
11732 const { backgroundColor , grid: { circular } } = this.options;
11733 if (backgroundColor) {
11734 const ctx = this.ctx;
11737 pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length);
11739 ctx.fillStyle = backgroundColor;
11745 const ctx = this.ctx;
11746 const opts = this.options;
11747 const { angleLines , grid , border } = opts;
11748 const labelCount = this._pointLabels.length;
11749 let i, offset, position;
11750 if (opts.pointLabels.display) {
11751 drawPointLabels(this, labelCount);
11753 if (grid.display) {
11754 this.ticks.forEach((tick, index)=>{
11755 if (index !== 0 || index === 0 && this.min < 0) {
11756 offset = this.getDistanceFromCenterForValue(tick.value);
11757 const context = this.getContext(index);
11758 const optsAtIndex = grid.setContext(context);
11759 const optsAtIndexBorder = border.setContext(context);
11760 drawRadiusLine(this, optsAtIndex, offset, labelCount, optsAtIndexBorder);
11764 if (angleLines.display) {
11766 for(i = labelCount - 1; i >= 0; i--){
11767 const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i));
11768 const { color , lineWidth } = optsAtIndex;
11769 if (!lineWidth || !color) {
11772 ctx.lineWidth = lineWidth;
11773 ctx.strokeStyle = color;
11774 ctx.setLineDash(optsAtIndex.borderDash);
11775 ctx.lineDashOffset = optsAtIndex.borderDashOffset;
11776 offset = this.getDistanceFromCenterForValue(opts.ticks.reverse ? this.min : this.max);
11777 position = this.getPointPosition(i, offset);
11779 ctx.moveTo(this.xCenter, this.yCenter);
11780 ctx.lineTo(position.x, position.y);
11788 const ctx = this.ctx;
11789 const opts = this.options;
11790 const tickOpts = opts.ticks;
11791 if (!tickOpts.display) {
11794 const startAngle = this.getIndexAngle(0);
11797 ctx.translate(this.xCenter, this.yCenter);
11798 ctx.rotate(startAngle);
11799 ctx.textAlign = 'center';
11800 ctx.textBaseline = 'middle';
11801 this.ticks.forEach((tick, index)=>{
11802 if (index === 0 && this.min >= 0 && !opts.reverse) {
11805 const optsAtIndex = tickOpts.setContext(this.getContext(index));
11806 const tickFont = toFont(optsAtIndex.font);
11807 offset = this.getDistanceFromCenterForValue(this.ticks[index].value);
11808 if (optsAtIndex.showLabelBackdrop) {
11809 ctx.font = tickFont.string;
11810 width = ctx.measureText(tick.label).width;
11811 ctx.fillStyle = optsAtIndex.backdropColor;
11812 const padding = toPadding(optsAtIndex.backdropPadding);
11813 ctx.fillRect(-width / 2 - padding.left, -offset - tickFont.size / 2 - padding.top, width + padding.width, tickFont.size + padding.height);
11815 renderText(ctx, tick.label, 0, -offset, tickFont, {
11816 color: optsAtIndex.color,
11817 strokeColor: optsAtIndex.textStrokeColor,
11818 strokeWidth: optsAtIndex.textStrokeWidth
11826 const INTERVALS = {
11872 const UNITS = /* #__PURE__ */ Object.keys(INTERVALS);
11873 function sorter(a, b) {
11876 function parse(scale, input) {
11877 if (isNullOrUndef(input)) {
11880 const adapter = scale._adapter;
11881 const { parser , round , isoWeekday } = scale._parseOpts;
11883 if (typeof parser === 'function') {
11884 value = parser(value);
11886 if (!isNumberFinite(value)) {
11887 value = typeof parser === 'string' ? adapter.parse(value, parser) : adapter.parse(value);
11889 if (value === null) {
11893 value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true) ? adapter.startOf(value, 'isoWeek', isoWeekday) : adapter.startOf(value, round);
11897 function determineUnitForAutoTicks(minUnit, min, max, capacity) {
11898 const ilen = UNITS.length;
11899 for(let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i){
11900 const interval = INTERVALS[UNITS[i]];
11901 const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;
11902 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
11906 return UNITS[ilen - 1];
11908 function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
11909 for(let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--){
11910 const unit = UNITS[i];
11911 if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
11915 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
11917 function determineMajorUnit(unit) {
11918 for(let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i){
11919 if (INTERVALS[UNITS[i]].common) {
11924 function addTick(ticks, time, timestamps) {
11926 ticks[time] = true;
11927 } else if (timestamps.length) {
11928 const { lo , hi } = _lookup(timestamps, time);
11929 const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
11930 ticks[timestamp] = true;
11933 function setMajorTicks(scale, ticks, map, majorUnit) {
11934 const adapter = scale._adapter;
11935 const first = +adapter.startOf(ticks[0].value, majorUnit);
11936 const last = ticks[ticks.length - 1].value;
11938 for(major = first; major <= last; major = +adapter.add(major, 1, majorUnit)){
11939 index = map[major];
11941 ticks[index].major = true;
11946 function ticksFromTimestamps(scale, values, majorUnit) {
11949 const ilen = values.length;
11951 for(i = 0; i < ilen; ++i){
11959 return ilen === 0 || !majorUnit ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
11961 class TimeScale extends Scale {
11962 static id = 'time';
11963 static defaults = {
11971 minUnit: 'millisecond',
11982 constructor(props){
11989 this._unit = 'day';
11990 this._majorUnit = undefined;
11991 this._offsets = {};
11992 this._normalized = false;
11993 this._parseOpts = undefined;
11995 init(scaleOpts, opts = {}) {
11996 const time = scaleOpts.time || (scaleOpts.time = {});
11997 const adapter = this._adapter = new _adapters._date(scaleOpts.adapters.date);
11998 adapter.init(opts);
11999 mergeIf(time.displayFormats, adapter.formats());
12000 this._parseOpts = {
12001 parser: time.parser,
12003 isoWeekday: time.isoWeekday
12005 super.init(scaleOpts);
12006 this._normalized = opts.normalized;
12008 parse(raw, index) {
12009 if (raw === undefined) {
12012 return parse(this, raw);
12015 super.beforeLayout();
12022 determineDataLimits() {
12023 const options = this.options;
12024 const adapter = this._adapter;
12025 const unit = options.time.unit || 'day';
12026 let { min , max , minDefined , maxDefined } = this.getUserBounds();
12027 function _applyBounds(bounds) {
12028 if (!minDefined && !isNaN(bounds.min)) {
12029 min = Math.min(min, bounds.min);
12031 if (!maxDefined && !isNaN(bounds.max)) {
12032 max = Math.max(max, bounds.max);
12035 if (!minDefined || !maxDefined) {
12036 _applyBounds(this._getLabelBounds());
12037 if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {
12038 _applyBounds(this.getMinMax(false));
12041 min = isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
12042 max = isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
12043 this.min = Math.min(min, max - 1);
12044 this.max = Math.max(min + 1, max);
12046 _getLabelBounds() {
12047 const arr = this.getLabelTimestamps();
12048 let min = Number.POSITIVE_INFINITY;
12049 let max = Number.NEGATIVE_INFINITY;
12052 max = arr[arr.length - 1];
12060 const options = this.options;
12061 const timeOpts = options.time;
12062 const tickOpts = options.ticks;
12063 const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate();
12064 if (options.bounds === 'ticks' && timestamps.length) {
12065 this.min = this._userMin || timestamps[0];
12066 this.max = this._userMax || timestamps[timestamps.length - 1];
12068 const min = this.min;
12069 const max = this.max;
12070 const ticks = _filterBetween(timestamps, min, max);
12071 this._unit = timeOpts.unit || (tickOpts.autoSkip ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min)) : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max));
12072 this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined : determineMajorUnit(this._unit);
12073 this.initOffsets(timestamps);
12074 if (options.reverse) {
12077 return ticksFromTimestamps(this, ticks, this._majorUnit);
12080 if (this.options.offsetAfterAutoskip) {
12081 this.initOffsets(this.ticks.map((tick)=>+tick.value));
12084 initOffsets(timestamps = []) {
12088 if (this.options.offset && timestamps.length) {
12089 first = this.getDecimalForValue(timestamps[0]);
12090 if (timestamps.length === 1) {
12093 start = (this.getDecimalForValue(timestamps[1]) - first) / 2;
12095 last = this.getDecimalForValue(timestamps[timestamps.length - 1]);
12096 if (timestamps.length === 1) {
12099 end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
12102 const limit = timestamps.length < 3 ? 0.5 : 0.25;
12103 start = _limitValue(start, 0, limit);
12104 end = _limitValue(end, 0, limit);
12108 factor: 1 / (start + 1 + end)
12112 const adapter = this._adapter;
12113 const min = this.min;
12114 const max = this.max;
12115 const options = this.options;
12116 const timeOpts = options.time;
12117 const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min));
12118 const stepSize = valueOrDefault(options.ticks.stepSize, 1);
12119 const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
12120 const hasWeekday = isNumber(weekday) || weekday === true;
12125 first = +adapter.startOf(first, 'isoWeek', weekday);
12127 first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
12128 if (adapter.diff(max, min, minor) > 100000 * stepSize) {
12129 throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
12131 const timestamps = options.ticks.source === 'data' && this.getDataTimestamps();
12132 for(time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++){
12133 addTick(ticks, time, timestamps);
12135 if (time === max || options.bounds === 'ticks' || count === 1) {
12136 addTick(ticks, time, timestamps);
12138 return Object.keys(ticks).sort(sorter).map((x)=>+x);
12140 getLabelForValue(value) {
12141 const adapter = this._adapter;
12142 const timeOpts = this.options.time;
12143 if (timeOpts.tooltipFormat) {
12144 return adapter.format(value, timeOpts.tooltipFormat);
12146 return adapter.format(value, timeOpts.displayFormats.datetime);
12148 format(value, format) {
12149 const options = this.options;
12150 const formats = options.time.displayFormats;
12151 const unit = this._unit;
12152 const fmt = format || formats[unit];
12153 return this._adapter.format(value, fmt);
12155 _tickFormatFunction(time, index, ticks, format) {
12156 const options = this.options;
12157 const formatter = options.ticks.callback;
12159 return callback(formatter, [
12165 const formats = options.time.displayFormats;
12166 const unit = this._unit;
12167 const majorUnit = this._majorUnit;
12168 const minorFormat = unit && formats[unit];
12169 const majorFormat = majorUnit && formats[majorUnit];
12170 const tick = ticks[index];
12171 const major = majorUnit && majorFormat && tick && tick.major;
12172 return this._adapter.format(time, format || (major ? majorFormat : minorFormat));
12174 generateTickLabels(ticks) {
12176 for(i = 0, ilen = ticks.length; i < ilen; ++i){
12178 tick.label = this._tickFormatFunction(tick.value, i, ticks);
12181 getDecimalForValue(value) {
12182 return value === null ? NaN : (value - this.min) / (this.max - this.min);
12184 getPixelForValue(value) {
12185 const offsets = this._offsets;
12186 const pos = this.getDecimalForValue(value);
12187 return this.getPixelForDecimal((offsets.start + pos) * offsets.factor);
12189 getValueForPixel(pixel) {
12190 const offsets = this._offsets;
12191 const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
12192 return this.min + pos * (this.max - this.min);
12194 _getLabelSize(label) {
12195 const ticksOpts = this.options.ticks;
12196 const tickLabelWidth = this.ctx.measureText(label).width;
12197 const angle = toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
12198 const cosRotation = Math.cos(angle);
12199 const sinRotation = Math.sin(angle);
12200 const tickFontSize = this._resolveTickFontOptions(0).size;
12202 w: tickLabelWidth * cosRotation + tickFontSize * sinRotation,
12203 h: tickLabelWidth * sinRotation + tickFontSize * cosRotation
12206 _getLabelCapacity(exampleTime) {
12207 const timeOpts = this.options.time;
12208 const displayFormats = timeOpts.displayFormats;
12209 const format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
12210 const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [
12212 ], this._majorUnit), format);
12213 const size = this._getLabelSize(exampleLabel);
12214 const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1;
12215 return capacity > 0 ? capacity : 1;
12217 getDataTimestamps() {
12218 let timestamps = this._cache.data || [];
12220 if (timestamps.length) {
12223 const metas = this.getMatchingVisibleMetas();
12224 if (this._normalized && metas.length) {
12225 return this._cache.data = metas[0].controller.getAllParsedValues(this);
12227 for(i = 0, ilen = metas.length; i < ilen; ++i){
12228 timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this));
12230 return this._cache.data = this.normalize(timestamps);
12232 getLabelTimestamps() {
12233 const timestamps = this._cache.labels || [];
12235 if (timestamps.length) {
12238 const labels = this.getLabels();
12239 for(i = 0, ilen = labels.length; i < ilen; ++i){
12240 timestamps.push(parse(this, labels[i]));
12242 return this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps);
12244 normalize(values) {
12245 return _arrayUnique(values.sort(sorter));
12249 function interpolate(table, val, reverse) {
12251 let hi = table.length - 1;
12252 let prevSource, nextSource, prevTarget, nextTarget;
12254 if (val >= table[lo].pos && val <= table[hi].pos) {
12255 ({ lo , hi } = _lookupByKey(table, 'pos', val));
12257 ({ pos: prevSource , time: prevTarget } = table[lo]);
12258 ({ pos: nextSource , time: nextTarget } = table[hi]);
12260 if (val >= table[lo].time && val <= table[hi].time) {
12261 ({ lo , hi } = _lookupByKey(table, 'time', val));
12263 ({ time: prevSource , pos: prevTarget } = table[lo]);
12264 ({ time: nextSource , pos: nextTarget } = table[hi]);
12266 const span = nextSource - prevSource;
12267 return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;
12269 class TimeSeriesScale extends TimeScale {
12270 static id = 'timeseries';
12271 static defaults = TimeScale.defaults;
12272 constructor(props){
12275 this._minPos = undefined;
12276 this._tableRange = undefined;
12279 const timestamps = this._getTimestampsForTable();
12280 const table = this._table = this.buildLookupTable(timestamps);
12281 this._minPos = interpolate(table, this.min);
12282 this._tableRange = interpolate(table, this.max) - this._minPos;
12283 super.initOffsets(timestamps);
12285 buildLookupTable(timestamps) {
12286 const { min , max } = this;
12289 let i, ilen, prev, curr, next;
12290 for(i = 0, ilen = timestamps.length; i < ilen; ++i){
12291 curr = timestamps[i];
12292 if (curr >= min && curr <= max) {
12296 if (items.length < 2) {
12308 for(i = 0, ilen = items.length; i < ilen; ++i){
12309 next = items[i + 1];
12310 prev = items[i - 1];
12312 if (Math.round((next + prev) / 2) !== curr) {
12315 pos: i / (ilen - 1)
12322 const min = this.min;
12323 const max = this.max;
12324 let timestamps = super.getDataTimestamps();
12325 if (!timestamps.includes(min) || !timestamps.length) {
12326 timestamps.splice(0, 0, min);
12328 if (!timestamps.includes(max) || timestamps.length === 1) {
12329 timestamps.push(max);
12331 return timestamps.sort((a, b)=>a - b);
12333 _getTimestampsForTable() {
12334 let timestamps = this._cache.all || [];
12335 if (timestamps.length) {
12338 const data = this.getDataTimestamps();
12339 const label = this.getLabelTimestamps();
12340 if (data.length && label.length) {
12341 timestamps = this.normalize(data.concat(label));
12343 timestamps = data.length ? data : label;
12345 timestamps = this._cache.all = timestamps;
12348 getDecimalForValue(value) {
12349 return (interpolate(this._table, value) - this._minPos) / this._tableRange;
12351 getValueForPixel(pixel) {
12352 const offsets = this._offsets;
12353 const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
12354 return interpolate(this._table, decimal * this._tableRange + this._minPos, true);
12358 var scales = /*#__PURE__*/Object.freeze({
12360 CategoryScale: CategoryScale,
12361 LinearScale: LinearScale,
12362 LogarithmicScale: LogarithmicScale,
12363 RadialLinearScale: RadialLinearScale,
12364 TimeScale: TimeScale,
12365 TimeSeriesScale: TimeSeriesScale
12368 const BORDER_COLORS = [
12369 'rgb(54, 162, 235)',
12370 'rgb(255, 99, 132)',
12371 'rgb(255, 159, 64)',
12372 'rgb(255, 205, 86)',
12373 'rgb(75, 192, 192)',
12374 'rgb(153, 102, 255)',
12375 'rgb(201, 203, 207)' // grey
12377 // Border colors with 50% transparency
12378 const BACKGROUND_COLORS = /* #__PURE__ */ BORDER_COLORS.map((color)=>color.replace('rgb(', 'rgba(').replace(')', ', 0.5)'));
12379 function getBorderColor(i) {
12380 return BORDER_COLORS[i % BORDER_COLORS.length];
12382 function getBackgroundColor(i) {
12383 return BACKGROUND_COLORS[i % BACKGROUND_COLORS.length];
12385 function colorizeDefaultDataset(dataset, i) {
12386 dataset.borderColor = getBorderColor(i);
12387 dataset.backgroundColor = getBackgroundColor(i);
12390 function colorizeDoughnutDataset(dataset, i) {
12391 dataset.backgroundColor = dataset.data.map(()=>getBorderColor(i++));
12394 function colorizePolarAreaDataset(dataset, i) {
12395 dataset.backgroundColor = dataset.data.map(()=>getBackgroundColor(i++));
12398 function getColorizer(chart) {
12400 return (dataset, datasetIndex)=>{
12401 const controller = chart.getDatasetMeta(datasetIndex).controller;
12402 if (controller instanceof DoughnutController) {
12403 i = colorizeDoughnutDataset(dataset, i);
12404 } else if (controller instanceof PolarAreaController) {
12405 i = colorizePolarAreaDataset(dataset, i);
12406 } else if (controller) {
12407 i = colorizeDefaultDataset(dataset, i);
12411 function containsColorsDefinitions(descriptors) {
12413 for(k in descriptors){
12414 if (descriptors[k].borderColor || descriptors[k].backgroundColor) {
12420 function containsColorsDefinition(descriptor) {
12421 return descriptor && (descriptor.borderColor || descriptor.backgroundColor);
12423 var plugin_colors = {
12427 forceOverride: false
12429 beforeLayout (chart, _args, options) {
12430 if (!options.enabled) {
12433 const { data: { datasets } , options: chartOptions } = chart.config;
12434 const { elements } = chartOptions;
12435 if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chartOptions) || elements && containsColorsDefinitions(elements))) {
12438 const colorizer = getColorizer(chart);
12439 datasets.forEach(colorizer);
12443 function lttbDecimation(data, start, count, availableWidth, options) {
12444 const samples = options.samples || availableWidth;
12445 if (samples >= count) {
12446 return data.slice(start, start + count);
12448 const decimated = [];
12449 const bucketWidth = (count - 2) / (samples - 2);
12450 let sampledIndex = 0;
12451 const endIndex = start + count - 1;
12453 let i, maxAreaPoint, maxArea, area, nextA;
12454 decimated[sampledIndex++] = data[a];
12455 for(i = 0; i < samples - 2; i++){
12459 const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
12460 const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
12461 const avgRangeLength = avgRangeEnd - avgRangeStart;
12462 for(j = avgRangeStart; j < avgRangeEnd; j++){
12466 avgX /= avgRangeLength;
12467 avgY /= avgRangeLength;
12468 const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
12469 const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start;
12470 const { x: pointAx , y: pointAy } = data[a];
12471 maxArea = area = -1;
12472 for(j = rangeOffs; j < rangeTo; j++){
12473 area = 0.5 * Math.abs((pointAx - avgX) * (data[j].y - pointAy) - (pointAx - data[j].x) * (avgY - pointAy));
12474 if (area > maxArea) {
12476 maxAreaPoint = data[j];
12480 decimated[sampledIndex++] = maxAreaPoint;
12483 decimated[sampledIndex++] = data[endIndex];
12486 function minMaxDecimation(data, start, count, availableWidth) {
12489 let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
12490 const decimated = [];
12491 const endIndex = start + count - 1;
12492 const xMin = data[start].x;
12493 const xMax = data[endIndex].x;
12494 const dx = xMax - xMin;
12495 for(i = start; i < start + count; ++i){
12497 x = (point.x - xMin) / dx * availableWidth;
12499 const truncX = x | 0;
12500 if (truncX === prevX) {
12504 } else if (y > maxY) {
12508 avgX = (countX * avgX + point.x) / ++countX;
12510 const lastIndex = i - 1;
12511 if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) {
12512 const intermediateIndex1 = Math.min(minIndex, maxIndex);
12513 const intermediateIndex2 = Math.max(minIndex, maxIndex);
12514 if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {
12516 ...data[intermediateIndex1],
12520 if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {
12522 ...data[intermediateIndex2],
12527 if (i > 0 && lastIndex !== startIndex) {
12528 decimated.push(data[lastIndex]);
12530 decimated.push(point);
12534 minIndex = maxIndex = startIndex = i;
12539 function cleanDecimatedDataset(dataset) {
12540 if (dataset._decimated) {
12541 const data = dataset._data;
12542 delete dataset._decimated;
12543 delete dataset._data;
12544 Object.defineProperty(dataset, 'data', {
12545 configurable: true,
12552 function cleanDecimatedData(chart) {
12553 chart.data.datasets.forEach((dataset)=>{
12554 cleanDecimatedDataset(dataset);
12557 function getStartAndCountOfVisiblePointsSimplified(meta, points) {
12558 const pointCount = points.length;
12561 const { iScale } = meta;
12562 const { min , max , minDefined , maxDefined } = iScale.getUserBounds();
12564 start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
12567 count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
12569 count = pointCount - start;
12576 var plugin_decimation = {
12579 algorithm: 'min-max',
12582 beforeElementsUpdate: (chart, args, options)=>{
12583 if (!options.enabled) {
12584 cleanDecimatedData(chart);
12587 const availableWidth = chart.width;
12588 chart.data.datasets.forEach((dataset, datasetIndex)=>{
12589 const { _data , indexAxis } = dataset;
12590 const meta = chart.getDatasetMeta(datasetIndex);
12591 const data = _data || dataset.data;
12594 chart.options.indexAxis
12598 if (!meta.controller.supportsDecimation) {
12601 const xAxis = chart.scales[meta.xAxisID];
12602 if (xAxis.type !== 'linear' && xAxis.type !== 'time') {
12605 if (chart.options.parsing) {
12608 let { start , count } = getStartAndCountOfVisiblePointsSimplified(meta, data);
12609 const threshold = options.threshold || 4 * availableWidth;
12610 if (count <= threshold) {
12611 cleanDecimatedDataset(dataset);
12614 if (isNullOrUndef(_data)) {
12615 dataset._data = data;
12616 delete dataset.data;
12617 Object.defineProperty(dataset, 'data', {
12618 configurable: true,
12621 return this._decimated;
12629 switch(options.algorithm){
12631 decimated = lttbDecimation(data, start, count, availableWidth, options);
12634 decimated = minMaxDecimation(data, start, count, availableWidth);
12637 throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);
12639 dataset._decimated = decimated;
12643 cleanDecimatedData(chart);
12647 function _segments(line, target, property) {
12648 const segments = line.segments;
12649 const points = line.points;
12650 const tpoints = target.points;
12652 for (const segment of segments){
12653 let { start , end } = segment;
12654 end = _findSegmentEnd(start, end, points);
12655 const bounds = _getBounds(property, points[start], points[end], segment.loop);
12656 if (!target.segments) {
12660 start: points[start],
12665 const targetSegments = _boundSegments(target, bounds);
12666 for (const tgt of targetSegments){
12667 const subBounds = _getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
12668 const fillSources = _boundSegment(segment, points, subBounds);
12669 for (const fillSource of fillSources){
12671 source: fillSource,
12674 [property]: _getEdge(bounds, subBounds, 'start', Math.max)
12677 [property]: _getEdge(bounds, subBounds, 'end', Math.min)
12685 function _getBounds(property, first, last, loop) {
12689 let start = first[property];
12690 let end = last[property];
12691 if (property === 'angle') {
12692 start = _normalizeAngle(start);
12693 end = _normalizeAngle(end);
12701 function _pointsFromSegments(boundary, line) {
12702 const { x =null , y =null } = boundary || {};
12703 const linePoints = line.points;
12705 line.segments.forEach(({ start , end })=>{
12706 end = _findSegmentEnd(start, end, linePoints);
12707 const first = linePoints[start];
12708 const last = linePoints[end];
12718 } else if (x !== null) {
12731 function _findSegmentEnd(start, end, points) {
12732 for(; end > start; end--){
12733 const point = points[end];
12734 if (!isNaN(point.x) && !isNaN(point.y)) {
12740 function _getEdge(a, b, prop, fn) {
12742 return fn(a[prop], b[prop]);
12744 return a ? a[prop] : b ? b[prop] : 0;
12747 function _createBoundaryLine(boundary, line) {
12750 if (isArray(boundary)) {
12754 points = _pointsFromSegments(boundary, line);
12756 return points.length ? new LineElement({
12765 function _shouldApplyFill(source) {
12766 return source && source.fill !== false;
12769 function _resolveTarget(sources, index, propagate) {
12770 const source = sources[index];
12771 let fill = source.fill;
12779 while(fill !== false && visited.indexOf(fill) === -1){
12780 if (!isNumberFinite(fill)) {
12783 target = sources[fill];
12787 if (target.visible) {
12790 visited.push(fill);
12791 fill = target.fill;
12795 function _decodeFill(line, index, count) {
12796 const fill = parseFillOption(line);
12797 if (isObject(fill)) {
12798 return isNaN(fill.value) ? false : fill;
12800 let target = parseFloat(fill);
12801 if (isNumberFinite(target) && Math.floor(target) === target) {
12802 return decodeTargetIndex(fill[0], index, target, count);
12810 ].indexOf(fill) >= 0 && fill;
12812 function decodeTargetIndex(firstCh, index, target, count) {
12813 if (firstCh === '-' || firstCh === '+') {
12814 target = index + target;
12816 if (target === index || target < 0 || target >= count) {
12821 function _getTargetPixel(fill, scale) {
12823 if (fill === 'start') {
12824 pixel = scale.bottom;
12825 } else if (fill === 'end') {
12827 } else if (isObject(fill)) {
12828 pixel = scale.getPixelForValue(fill.value);
12829 } else if (scale.getBasePixel) {
12830 pixel = scale.getBasePixel();
12834 function _getTargetValue(fill, scale, startValue) {
12836 if (fill === 'start') {
12837 value = startValue;
12838 } else if (fill === 'end') {
12839 value = scale.options.reverse ? scale.min : scale.max;
12840 } else if (isObject(fill)) {
12841 value = fill.value;
12843 value = scale.getBaseValue();
12847 function parseFillOption(line) {
12848 const options = line.options;
12849 const fillOption = options.fill;
12850 let fill = valueOrDefault(fillOption && fillOption.target, fillOption);
12851 if (fill === undefined) {
12852 fill = !!options.backgroundColor;
12854 if (fill === false || fill === null) {
12857 if (fill === true) {
12863 function _buildStackLine(source) {
12864 const { scale , index , line } = source;
12866 const segments = line.segments;
12867 const sourcePoints = line.points;
12868 const linesBelow = getLinesBelow(scale, index);
12869 linesBelow.push(_createBoundaryLine({
12873 for(let i = 0; i < segments.length; i++){
12874 const segment = segments[i];
12875 for(let j = segment.start; j <= segment.end; j++){
12876 addPointsBelow(points, sourcePoints[j], linesBelow);
12879 return new LineElement({
12884 function getLinesBelow(scale, index) {
12886 const metas = scale.getMatchingVisibleMetas('line');
12887 for(let i = 0; i < metas.length; i++){
12888 const meta = metas[i];
12889 if (meta.index === index) {
12892 if (!meta.hidden) {
12893 below.unshift(meta.dataset);
12898 function addPointsBelow(points, sourcePoint, linesBelow) {
12899 const postponed = [];
12900 for(let j = 0; j < linesBelow.length; j++){
12901 const line = linesBelow[j];
12902 const { first , last , point } = findPoint(line, sourcePoint, 'x');
12903 if (!point || first && last) {
12907 postponed.unshift(point);
12909 points.push(point);
12915 points.push(...postponed);
12917 function findPoint(line, sourcePoint, property) {
12918 const point = line.interpolate(sourcePoint, property);
12922 const pointValue = point[property];
12923 const segments = line.segments;
12924 const linePoints = line.points;
12927 for(let i = 0; i < segments.length; i++){
12928 const segment = segments[i];
12929 const firstValue = linePoints[segment.start][property];
12930 const lastValue = linePoints[segment.end][property];
12931 if (_isBetween(pointValue, firstValue, lastValue)) {
12932 first = pointValue === firstValue;
12933 last = pointValue === lastValue;
12948 this.radius = opts.radius;
12950 pathSegment(ctx, bounds, opts) {
12951 const { x , y , radius } = this;
12952 bounds = bounds || {
12956 ctx.arc(x, y, radius, bounds.end, bounds.start, true);
12957 return !opts.bounds;
12959 interpolate(point) {
12960 const { x , y , radius } = this;
12961 const angle = point.angle;
12963 x: x + Math.cos(angle) * radius,
12964 y: y + Math.sin(angle) * radius,
12970 function _getTarget(source) {
12971 const { chart , fill , line } = source;
12972 if (isNumberFinite(fill)) {
12973 return getLineByIndex(chart, fill);
12975 if (fill === 'stack') {
12976 return _buildStackLine(source);
12978 if (fill === 'shape') {
12981 const boundary = computeBoundary(source);
12982 if (boundary instanceof simpleArc) {
12985 return _createBoundaryLine(boundary, line);
12987 function getLineByIndex(chart, index) {
12988 const meta = chart.getDatasetMeta(index);
12989 const visible = meta && chart.isDatasetVisible(index);
12990 return visible ? meta.dataset : null;
12992 function computeBoundary(source) {
12993 const scale = source.scale || {};
12994 if (scale.getPointPositionForValue) {
12995 return computeCircularBoundary(source);
12997 return computeLinearBoundary(source);
12999 function computeLinearBoundary(source) {
13000 const { scale ={} , fill } = source;
13001 const pixel = _getTargetPixel(fill, scale);
13002 if (isNumberFinite(pixel)) {
13003 const horizontal = scale.isHorizontal();
13005 x: horizontal ? pixel : null,
13006 y: horizontal ? null : pixel
13011 function computeCircularBoundary(source) {
13012 const { scale , fill } = source;
13013 const options = scale.options;
13014 const length = scale.getLabels().length;
13015 const start = options.reverse ? scale.max : scale.min;
13016 const value = _getTargetValue(fill, scale, start);
13018 if (options.grid.circular) {
13019 const center = scale.getPointPositionForValue(0, start);
13020 return new simpleArc({
13023 radius: scale.getDistanceFromCenterForValue(value)
13026 for(let i = 0; i < length; ++i){
13027 target.push(scale.getPointPositionForValue(i, value));
13032 function _drawfill(ctx, source, area) {
13033 const target = _getTarget(source);
13034 const { line , scale , axis } = source;
13035 const lineOpts = line.options;
13036 const fillOption = lineOpts.fill;
13037 const color = lineOpts.backgroundColor;
13038 const { above =color , below =color } = fillOption || {};
13039 if (target && line.points.length) {
13040 clipArea(ctx, area);
13053 function doFill(ctx, cfg) {
13054 const { line , target , above , below , area , scale } = cfg;
13055 const property = line._loop ? 'angle' : cfg.axis;
13057 if (property === 'x' && below !== above) {
13058 clipVertical(ctx, target, area.top);
13068 clipVertical(ctx, target, area.bottom);
13079 function clipVertical(ctx, target, clipY) {
13080 const { segments , points } = target;
13082 let lineLoop = false;
13084 for (const segment of segments){
13085 const { start , end } = segment;
13086 const firstPoint = points[start];
13087 const lastPoint = points[_findSegmentEnd(start, end, points)];
13089 ctx.moveTo(firstPoint.x, firstPoint.y);
13092 ctx.lineTo(firstPoint.x, clipY);
13093 ctx.lineTo(firstPoint.x, firstPoint.y);
13095 lineLoop = !!target.pathSegment(ctx, segment, {
13101 ctx.lineTo(lastPoint.x, clipY);
13104 ctx.lineTo(target.first().x, clipY);
13108 function fill(ctx, cfg) {
13109 const { line , target , property , color , scale } = cfg;
13110 const segments = _segments(line, target, property);
13111 for (const { source: src , target: tgt , start , end } of segments){
13112 const { style: { backgroundColor =color } = {} } = src;
13113 const notShape = target !== true;
13115 ctx.fillStyle = backgroundColor;
13116 clipBounds(ctx, scale, notShape && _getBounds(property, start, end));
13118 const lineLoop = !!line.pathSegment(ctx, src);
13124 interpolatedLineTo(ctx, target, end, property);
13126 const targetLoop = !!target.pathSegment(ctx, tgt, {
13130 loop = lineLoop && targetLoop;
13132 interpolatedLineTo(ctx, target, start, property);
13136 ctx.fill(loop ? 'evenodd' : 'nonzero');
13140 function clipBounds(ctx, scale, bounds) {
13141 const { top , bottom } = scale.chart.chartArea;
13142 const { property , start , end } = bounds || {};
13143 if (property === 'x') {
13145 ctx.rect(start, top, end - start, bottom - top);
13149 function interpolatedLineTo(ctx, target, point, property) {
13150 const interpolatedPoint = target.interpolate(point, property);
13151 if (interpolatedPoint) {
13152 ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
13158 afterDatasetsUpdate (chart, _args, options) {
13159 const count = (chart.data.datasets || []).length;
13160 const sources = [];
13161 let meta, i, line, source;
13162 for(i = 0; i < count; ++i){
13163 meta = chart.getDatasetMeta(i);
13164 line = meta.dataset;
13166 if (line && line.options && line instanceof LineElement) {
13168 visible: chart.isDatasetVisible(i),
13170 fill: _decodeFill(line, i, count),
13172 axis: meta.controller.options.indexAxis,
13173 scale: meta.vScale,
13177 meta.$filler = source;
13178 sources.push(source);
13180 for(i = 0; i < count; ++i){
13181 source = sources[i];
13182 if (!source || source.fill === false) {
13185 source.fill = _resolveTarget(sources, i, options.propagate);
13188 beforeDraw (chart, _args, options) {
13189 const draw = options.drawTime === 'beforeDraw';
13190 const metasets = chart.getSortedVisibleDatasetMetas();
13191 const area = chart.chartArea;
13192 for(let i = metasets.length - 1; i >= 0; --i){
13193 const source = metasets[i].$filler;
13197 source.line.updateControlPoints(area, source.axis);
13198 if (draw && source.fill) {
13199 _drawfill(chart.ctx, source, area);
13203 beforeDatasetsDraw (chart, _args, options) {
13204 if (options.drawTime !== 'beforeDatasetsDraw') {
13207 const metasets = chart.getSortedVisibleDatasetMetas();
13208 for(let i = metasets.length - 1; i >= 0; --i){
13209 const source = metasets[i].$filler;
13210 if (_shouldApplyFill(source)) {
13211 _drawfill(chart.ctx, source, chart.chartArea);
13215 beforeDatasetDraw (chart, args, options) {
13216 const source = args.meta.$filler;
13217 if (!_shouldApplyFill(source) || options.drawTime !== 'beforeDatasetDraw') {
13220 _drawfill(chart.ctx, source, chart.chartArea);
13224 drawTime: 'beforeDatasetDraw'
13228 const getBoxSize = (labelOpts, fontSize)=>{
13229 let { boxHeight =fontSize , boxWidth =fontSize } = labelOpts;
13230 if (labelOpts.usePointStyle) {
13231 boxHeight = Math.min(boxHeight, fontSize);
13232 boxWidth = labelOpts.pointStyleWidth || Math.min(boxWidth, fontSize);
13237 itemHeight: Math.max(fontSize, boxHeight)
13240 const itemsEqual = (a, b)=>a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
13241 class Legend extends Element {
13242 constructor(config){
13244 this._added = false;
13245 this.legendHitBoxes = [];
13246 this._hoveredItem = null;
13247 this.doughnutMode = false;
13248 this.chart = config.chart;
13249 this.options = config.options;
13250 this.ctx = config.ctx;
13251 this.legendItems = undefined;
13252 this.columnSizes = undefined;
13253 this.lineWidths = undefined;
13254 this.maxHeight = undefined;
13255 this.maxWidth = undefined;
13256 this.top = undefined;
13257 this.bottom = undefined;
13258 this.left = undefined;
13259 this.right = undefined;
13260 this.height = undefined;
13261 this.width = undefined;
13262 this._margins = undefined;
13263 this.position = undefined;
13264 this.weight = undefined;
13265 this.fullSize = undefined;
13267 update(maxWidth, maxHeight, margins) {
13268 this.maxWidth = maxWidth;
13269 this.maxHeight = maxHeight;
13270 this._margins = margins;
13271 this.setDimensions();
13272 this.buildLabels();
13276 if (this.isHorizontal()) {
13277 this.width = this.maxWidth;
13278 this.left = this._margins.left;
13279 this.right = this.width;
13281 this.height = this.maxHeight;
13282 this.top = this._margins.top;
13283 this.bottom = this.height;
13287 const labelOpts = this.options.labels || {};
13288 let legendItems = callback(labelOpts.generateLabels, [
13291 if (labelOpts.filter) {
13292 legendItems = legendItems.filter((item)=>labelOpts.filter(item, this.chart.data));
13294 if (labelOpts.sort) {
13295 legendItems = legendItems.sort((a, b)=>labelOpts.sort(a, b, this.chart.data));
13297 if (this.options.reverse) {
13298 legendItems.reverse();
13300 this.legendItems = legendItems;
13303 const { options , ctx } = this;
13304 if (!options.display) {
13305 this.width = this.height = 0;
13308 const labelOpts = options.labels;
13309 const labelFont = toFont(labelOpts.font);
13310 const fontSize = labelFont.size;
13311 const titleHeight = this._computeTitleHeight();
13312 const { boxWidth , itemHeight } = getBoxSize(labelOpts, fontSize);
13314 ctx.font = labelFont.string;
13315 if (this.isHorizontal()) {
13316 width = this.maxWidth;
13317 height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
13319 height = this.maxHeight;
13320 width = this._fitCols(titleHeight, labelFont, boxWidth, itemHeight) + 10;
13322 this.width = Math.min(width, options.maxWidth || this.maxWidth);
13323 this.height = Math.min(height, options.maxHeight || this.maxHeight);
13325 _fitRows(titleHeight, fontSize, boxWidth, itemHeight) {
13326 const { ctx , maxWidth , options: { labels: { padding } } } = this;
13327 const hitboxes = this.legendHitBoxes = [];
13328 const lineWidths = this.lineWidths = [
13331 const lineHeight = itemHeight + padding;
13332 let totalHeight = titleHeight;
13333 ctx.textAlign = 'left';
13334 ctx.textBaseline = 'middle';
13336 let top = -lineHeight;
13337 this.legendItems.forEach((legendItem, i)=>{
13338 const itemWidth = boxWidth + fontSize / 2 + ctx.measureText(legendItem.text).width;
13339 if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {
13340 totalHeight += lineHeight;
13341 lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
13352 lineWidths[lineWidths.length - 1] += itemWidth + padding;
13354 return totalHeight;
13356 _fitCols(titleHeight, labelFont, boxWidth, _itemHeight) {
13357 const { ctx , maxHeight , options: { labels: { padding } } } = this;
13358 const hitboxes = this.legendHitBoxes = [];
13359 const columnSizes = this.columnSizes = [];
13360 const heightLimit = maxHeight - titleHeight;
13361 let totalWidth = padding;
13362 let currentColWidth = 0;
13363 let currentColHeight = 0;
13366 this.legendItems.forEach((legendItem, i)=>{
13367 const { itemWidth , itemHeight } = calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight);
13368 if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {
13369 totalWidth += currentColWidth + padding;
13371 width: currentColWidth,
13372 height: currentColHeight
13374 left += currentColWidth + padding;
13376 currentColWidth = currentColHeight = 0;
13380 top: currentColHeight,
13385 currentColWidth = Math.max(currentColWidth, itemWidth);
13386 currentColHeight += itemHeight + padding;
13388 totalWidth += currentColWidth;
13390 width: currentColWidth,
13391 height: currentColHeight
13396 if (!this.options.display) {
13399 const titleHeight = this._computeTitleHeight();
13400 const { legendHitBoxes: hitboxes , options: { align , labels: { padding } , rtl } } = this;
13401 const rtlHelper = getRtlAdapter(rtl, this.left, this.width);
13402 if (this.isHorizontal()) {
13404 let left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
13405 for (const hitbox of hitboxes){
13406 if (row !== hitbox.row) {
13408 left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
13410 hitbox.top += this.top + titleHeight + padding;
13411 hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width);
13412 left += hitbox.width + padding;
13416 let top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
13417 for (const hitbox of hitboxes){
13418 if (hitbox.col !== col) {
13420 top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
13423 hitbox.left += this.left + padding;
13424 hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width);
13425 top += hitbox.height + padding;
13430 return this.options.position === 'top' || this.options.position === 'bottom';
13433 if (this.options.display) {
13434 const ctx = this.ctx;
13435 clipArea(ctx, this);
13441 const { options: opts , columnSizes , lineWidths , ctx } = this;
13442 const { align , labels: labelOpts } = opts;
13443 const defaultColor = defaults.color;
13444 const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
13445 const labelFont = toFont(labelOpts.font);
13446 const { padding } = labelOpts;
13447 const fontSize = labelFont.size;
13448 const halfFontSize = fontSize / 2;
13451 ctx.textAlign = rtlHelper.textAlign('left');
13452 ctx.textBaseline = 'middle';
13453 ctx.lineWidth = 0.5;
13454 ctx.font = labelFont.string;
13455 const { boxWidth , boxHeight , itemHeight } = getBoxSize(labelOpts, fontSize);
13456 const drawLegendBox = function(x, y, legendItem) {
13457 if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {
13461 const lineWidth = valueOrDefault(legendItem.lineWidth, 1);
13462 ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor);
13463 ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt');
13464 ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0);
13465 ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter');
13466 ctx.lineWidth = lineWidth;
13467 ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor);
13468 ctx.setLineDash(valueOrDefault(legendItem.lineDash, []));
13469 if (labelOpts.usePointStyle) {
13470 const drawOptions = {
13471 radius: boxHeight * Math.SQRT2 / 2,
13472 pointStyle: legendItem.pointStyle,
13473 rotation: legendItem.rotation,
13474 borderWidth: lineWidth
13476 const centerX = rtlHelper.xPlus(x, boxWidth / 2);
13477 const centerY = y + halfFontSize;
13478 drawPointLegend(ctx, drawOptions, centerX, centerY, labelOpts.pointStyleWidth && boxWidth);
13480 const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);
13481 const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);
13482 const borderRadius = toTRBLCorners(legendItem.borderRadius);
13484 if (Object.values(borderRadius).some((v)=>v !== 0)) {
13485 addRoundedRectPath(ctx, {
13490 radius: borderRadius
13493 ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);
13496 if (lineWidth !== 0) {
13502 const fillText = function(x, y, legendItem) {
13503 renderText(ctx, legendItem.text, x, y + itemHeight / 2, labelFont, {
13504 strikethrough: legendItem.hidden,
13505 textAlign: rtlHelper.textAlign(legendItem.textAlign)
13508 const isHorizontal = this.isHorizontal();
13509 const titleHeight = this._computeTitleHeight();
13510 if (isHorizontal) {
13512 x: _alignStartEnd(align, this.left + padding, this.right - lineWidths[0]),
13513 y: this.top + padding + titleHeight,
13518 x: this.left + padding,
13519 y: _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height),
13523 overrideTextDirection(this.ctx, opts.textDirection);
13524 const lineHeight = itemHeight + padding;
13525 this.legendItems.forEach((legendItem, i)=>{
13526 ctx.strokeStyle = legendItem.fontColor;
13527 ctx.fillStyle = legendItem.fontColor;
13528 const textWidth = ctx.measureText(legendItem.text).width;
13529 const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
13530 const width = boxWidth + halfFontSize + textWidth;
13533 rtlHelper.setWidth(this.width);
13534 if (isHorizontal) {
13535 if (i > 0 && x + width + padding > this.right) {
13536 y = cursor.y += lineHeight;
13538 x = cursor.x = _alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]);
13540 } else if (i > 0 && y + lineHeight > this.bottom) {
13541 x = cursor.x = x + columnSizes[cursor.line].width + padding;
13543 y = cursor.y = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height);
13545 const realX = rtlHelper.x(x);
13546 drawLegendBox(realX, y, legendItem);
13547 x = _textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl);
13548 fillText(rtlHelper.x(x), y, legendItem);
13549 if (isHorizontal) {
13550 cursor.x += width + padding;
13551 } else if (typeof legendItem.text !== 'string') {
13552 const fontLineHeight = labelFont.lineHeight;
13553 cursor.y += calculateLegendItemHeight(legendItem, fontLineHeight) + padding;
13555 cursor.y += lineHeight;
13558 restoreTextDirection(this.ctx, opts.textDirection);
13561 const opts = this.options;
13562 const titleOpts = opts.title;
13563 const titleFont = toFont(titleOpts.font);
13564 const titlePadding = toPadding(titleOpts.padding);
13565 if (!titleOpts.display) {
13568 const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
13569 const ctx = this.ctx;
13570 const position = titleOpts.position;
13571 const halfFontSize = titleFont.size / 2;
13572 const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;
13574 let left = this.left;
13575 let maxWidth = this.width;
13576 if (this.isHorizontal()) {
13577 maxWidth = Math.max(...this.lineWidths);
13578 y = this.top + topPaddingPlusHalfFontSize;
13579 left = _alignStartEnd(opts.align, left, this.right - maxWidth);
13581 const maxHeight = this.columnSizes.reduce((acc, size)=>Math.max(acc, size.height), 0);
13582 y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight());
13584 const x = _alignStartEnd(position, left, left + maxWidth);
13585 ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position));
13586 ctx.textBaseline = 'middle';
13587 ctx.strokeStyle = titleOpts.color;
13588 ctx.fillStyle = titleOpts.color;
13589 ctx.font = titleFont.string;
13590 renderText(ctx, titleOpts.text, x, y, titleFont);
13592 _computeTitleHeight() {
13593 const titleOpts = this.options.title;
13594 const titleFont = toFont(titleOpts.font);
13595 const titlePadding = toPadding(titleOpts.padding);
13596 return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;
13598 _getLegendItemAt(x, y) {
13600 if (_isBetween(x, this.left, this.right) && _isBetween(y, this.top, this.bottom)) {
13601 lh = this.legendHitBoxes;
13602 for(i = 0; i < lh.length; ++i){
13604 if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width) && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {
13605 return this.legendItems[i];
13612 const opts = this.options;
13613 if (!isListened(e.type, opts)) {
13616 const hoveredItem = this._getLegendItemAt(e.x, e.y);
13617 if (e.type === 'mousemove' || e.type === 'mouseout') {
13618 const previous = this._hoveredItem;
13619 const sameItem = itemsEqual(previous, hoveredItem);
13620 if (previous && !sameItem) {
13621 callback(opts.onLeave, [
13627 this._hoveredItem = hoveredItem;
13628 if (hoveredItem && !sameItem) {
13629 callback(opts.onHover, [
13635 } else if (hoveredItem) {
13636 callback(opts.onClick, [
13644 function calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight) {
13645 const itemWidth = calculateItemWidth(legendItem, boxWidth, labelFont, ctx);
13646 const itemHeight = calculateItemHeight(_itemHeight, legendItem, labelFont.lineHeight);
13652 function calculateItemWidth(legendItem, boxWidth, labelFont, ctx) {
13653 let legendItemText = legendItem.text;
13654 if (legendItemText && typeof legendItemText !== 'string') {
13655 legendItemText = legendItemText.reduce((a, b)=>a.length > b.length ? a : b);
13657 return boxWidth + labelFont.size / 2 + ctx.measureText(legendItemText).width;
13659 function calculateItemHeight(_itemHeight, legendItem, fontLineHeight) {
13660 let itemHeight = _itemHeight;
13661 if (typeof legendItem.text !== 'string') {
13662 itemHeight = calculateLegendItemHeight(legendItem, fontLineHeight);
13666 function calculateLegendItemHeight(legendItem, fontLineHeight) {
13667 const labelHeight = legendItem.text ? legendItem.text.length : 0;
13668 return fontLineHeight * labelHeight;
13670 function isListened(type, opts) {
13671 if ((type === 'mousemove' || type === 'mouseout') && (opts.onHover || opts.onLeave)) {
13674 if (opts.onClick && (type === 'click' || type === 'mouseup')) {
13679 var plugin_legend = {
13682 start (chart, _args, options) {
13683 const legend = chart.legend = new Legend({
13688 layouts.configure(chart, legend, options);
13689 layouts.addBox(chart, legend);
13692 layouts.removeBox(chart, chart.legend);
13693 delete chart.legend;
13695 beforeUpdate (chart, _args, options) {
13696 const legend = chart.legend;
13697 layouts.configure(chart, legend, options);
13698 legend.options = options;
13700 afterUpdate (chart) {
13701 const legend = chart.legend;
13702 legend.buildLabels();
13703 legend.adjustHitBoxes();
13705 afterEvent (chart, args) {
13706 if (!args.replay) {
13707 chart.legend.handleEvent(args.event);
13717 onClick (e, legendItem, legend) {
13718 const index = legendItem.datasetIndex;
13719 const ci = legend.chart;
13720 if (ci.isDatasetVisible(index)) {
13722 legendItem.hidden = true;
13725 legendItem.hidden = false;
13731 color: (ctx)=>ctx.chart.options.color,
13734 generateLabels (chart) {
13735 const datasets = chart.data.datasets;
13736 const { labels: { usePointStyle , pointStyle , textAlign , color , useBorderRadius , borderRadius } } = chart.legend.options;
13737 return chart._getSortedDatasetMetas().map((meta)=>{
13738 const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
13739 const borderWidth = toPadding(style.borderWidth);
13741 text: datasets[meta.index].label,
13742 fillStyle: style.backgroundColor,
13744 hidden: !meta.visible,
13745 lineCap: style.borderCapStyle,
13746 lineDash: style.borderDash,
13747 lineDashOffset: style.borderDashOffset,
13748 lineJoin: style.borderJoinStyle,
13749 lineWidth: (borderWidth.width + borderWidth.height) / 4,
13750 strokeStyle: style.borderColor,
13751 pointStyle: pointStyle || style.pointStyle,
13752 rotation: style.rotation,
13753 textAlign: textAlign || style.textAlign,
13754 borderRadius: useBorderRadius && (borderRadius || style.borderRadius),
13755 datasetIndex: meta.index
13761 color: (ctx)=>ctx.chart.options.color,
13763 position: 'center',
13768 _scriptable: (name)=>!name.startsWith('on'),
13770 _scriptable: (name)=>![
13779 class Title extends Element {
13780 constructor(config){
13782 this.chart = config.chart;
13783 this.options = config.options;
13784 this.ctx = config.ctx;
13785 this._padding = undefined;
13786 this.top = undefined;
13787 this.bottom = undefined;
13788 this.left = undefined;
13789 this.right = undefined;
13790 this.width = undefined;
13791 this.height = undefined;
13792 this.position = undefined;
13793 this.weight = undefined;
13794 this.fullSize = undefined;
13796 update(maxWidth, maxHeight) {
13797 const opts = this.options;
13800 if (!opts.display) {
13801 this.width = this.height = this.right = this.bottom = 0;
13804 this.width = this.right = maxWidth;
13805 this.height = this.bottom = maxHeight;
13806 const lineCount = isArray(opts.text) ? opts.text.length : 1;
13807 this._padding = toPadding(opts.padding);
13808 const textSize = lineCount * toFont(opts.font).lineHeight + this._padding.height;
13809 if (this.isHorizontal()) {
13810 this.height = textSize;
13812 this.width = textSize;
13816 const pos = this.options.position;
13817 return pos === 'top' || pos === 'bottom';
13819 _drawArgs(offset) {
13820 const { top , left , bottom , right , options } = this;
13821 const align = options.align;
13823 let maxWidth, titleX, titleY;
13824 if (this.isHorizontal()) {
13825 titleX = _alignStartEnd(align, left, right);
13826 titleY = top + offset;
13827 maxWidth = right - left;
13829 if (options.position === 'left') {
13830 titleX = left + offset;
13831 titleY = _alignStartEnd(align, bottom, top);
13832 rotation = PI * -0.5;
13834 titleX = right - offset;
13835 titleY = _alignStartEnd(align, top, bottom);
13836 rotation = PI * 0.5;
13838 maxWidth = bottom - top;
13848 const ctx = this.ctx;
13849 const opts = this.options;
13850 if (!opts.display) {
13853 const fontOpts = toFont(opts.font);
13854 const lineHeight = fontOpts.lineHeight;
13855 const offset = lineHeight / 2 + this._padding.top;
13856 const { titleX , titleY , maxWidth , rotation } = this._drawArgs(offset);
13857 renderText(ctx, opts.text, 0, 0, fontOpts, {
13861 textAlign: _toLeftRightCenter(opts.align),
13862 textBaseline: 'middle',
13870 function createTitle(chart, titleOpts) {
13871 const title = new Title({
13873 options: titleOpts,
13876 layouts.configure(chart, title, titleOpts);
13877 layouts.addBox(chart, title);
13878 chart.titleBlock = title;
13880 var plugin_title = {
13883 start (chart, _args, options) {
13884 createTitle(chart, options);
13887 const titleBlock = chart.titleBlock;
13888 layouts.removeBox(chart, titleBlock);
13889 delete chart.titleBlock;
13891 beforeUpdate (chart, _args, options) {
13892 const title = chart.titleBlock;
13893 layouts.configure(chart, title, options);
13894 title.options = options;
13917 const map = new WeakMap();
13918 var plugin_subtitle = {
13920 start (chart, _args, options) {
13921 const title = new Title({
13926 layouts.configure(chart, title, options);
13927 layouts.addBox(chart, title);
13928 map.set(chart, title);
13931 layouts.removeBox(chart, map.get(chart));
13934 beforeUpdate (chart, _args, options) {
13935 const title = map.get(chart);
13936 layouts.configure(chart, title, options);
13937 title.options = options;
13960 const positioners = {
13962 if (!items.length) {
13966 let xSet = new Set();
13969 for(i = 0, len = items.length; i < len; ++i){
13970 const el = items[i].element;
13971 if (el && el.hasValue()) {
13972 const pos = el.tooltipPosition();
13980 ].reduce((a, b)=>a + b) / xSet.size;
13986 nearest (items, eventPosition) {
13987 if (!items.length) {
13990 let x = eventPosition.x;
13991 let y = eventPosition.y;
13992 let minDistance = Number.POSITIVE_INFINITY;
13993 let i, len, nearestElement;
13994 for(i = 0, len = items.length; i < len; ++i){
13995 const el = items[i].element;
13996 if (el && el.hasValue()) {
13997 const center = el.getCenterPoint();
13998 const d = distanceBetweenPoints(eventPosition, center);
13999 if (d < minDistance) {
14001 nearestElement = el;
14005 if (nearestElement) {
14006 const tp = nearestElement.tooltipPosition();
14016 function pushOrConcat(base, toPush) {
14018 if (isArray(toPush)) {
14019 Array.prototype.push.apply(base, toPush);
14026 function splitNewlines(str) {
14027 if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
14028 return str.split('\n');
14032 function createTooltipItem(chart, item) {
14033 const { element , datasetIndex , index } = item;
14034 const controller = chart.getDatasetMeta(datasetIndex).controller;
14035 const { label , value } = controller.getLabelAndValue(index);
14039 parsed: controller.getParsed(index),
14040 raw: chart.data.datasets[datasetIndex].data[index],
14041 formattedValue: value,
14042 dataset: controller.getDataset(),
14048 function getTooltipSize(tooltip, options) {
14049 const ctx = tooltip.chart.ctx;
14050 const { body , footer , title } = tooltip;
14051 const { boxWidth , boxHeight } = options;
14052 const bodyFont = toFont(options.bodyFont);
14053 const titleFont = toFont(options.titleFont);
14054 const footerFont = toFont(options.footerFont);
14055 const titleLineCount = title.length;
14056 const footerLineCount = footer.length;
14057 const bodyLineItemCount = body.length;
14058 const padding = toPadding(options.padding);
14059 let height = padding.height;
14061 let combinedBodyLength = body.reduce((count, bodyItem)=>count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0);
14062 combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
14063 if (titleLineCount) {
14064 height += titleLineCount * titleFont.lineHeight + (titleLineCount - 1) * options.titleSpacing + options.titleMarginBottom;
14066 if (combinedBodyLength) {
14067 const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;
14068 height += bodyLineItemCount * bodyLineHeight + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight + (combinedBodyLength - 1) * options.bodySpacing;
14070 if (footerLineCount) {
14071 height += options.footerMarginTop + footerLineCount * footerFont.lineHeight + (footerLineCount - 1) * options.footerSpacing;
14073 let widthPadding = 0;
14074 const maxLineWidth = function(line) {
14075 width = Math.max(width, ctx.measureText(line).width + widthPadding);
14078 ctx.font = titleFont.string;
14079 each(tooltip.title, maxLineWidth);
14080 ctx.font = bodyFont.string;
14081 each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
14082 widthPadding = options.displayColors ? boxWidth + 2 + options.boxPadding : 0;
14083 each(body, (bodyItem)=>{
14084 each(bodyItem.before, maxLineWidth);
14085 each(bodyItem.lines, maxLineWidth);
14086 each(bodyItem.after, maxLineWidth);
14089 ctx.font = footerFont.string;
14090 each(tooltip.footer, maxLineWidth);
14092 width += padding.width;
14098 function determineYAlign(chart, size) {
14099 const { y , height } = size;
14100 if (y < height / 2) {
14102 } else if (y > chart.height - height / 2) {
14107 function doesNotFitWithAlign(xAlign, chart, options, size) {
14108 const { x , width } = size;
14109 const caret = options.caretSize + options.caretPadding;
14110 if (xAlign === 'left' && x + width + caret > chart.width) {
14113 if (xAlign === 'right' && x - width - caret < 0) {
14117 function determineXAlign(chart, options, size, yAlign) {
14118 const { x , width } = size;
14119 const { width: chartWidth , chartArea: { left , right } } = chart;
14120 let xAlign = 'center';
14121 if (yAlign === 'center') {
14122 xAlign = x <= (left + right) / 2 ? 'left' : 'right';
14123 } else if (x <= width / 2) {
14125 } else if (x >= chartWidth - width / 2) {
14128 if (doesNotFitWithAlign(xAlign, chart, options, size)) {
14133 function determineAlignment(chart, options, size) {
14134 const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size);
14136 xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign),
14140 function alignX(size, xAlign) {
14141 let { x , width } = size;
14142 if (xAlign === 'right') {
14144 } else if (xAlign === 'center') {
14149 function alignY(size, yAlign, paddingAndSize) {
14150 let { y , height } = size;
14151 if (yAlign === 'top') {
14152 y += paddingAndSize;
14153 } else if (yAlign === 'bottom') {
14154 y -= height + paddingAndSize;
14160 function getBackgroundPoint(options, size, alignment, chart) {
14161 const { caretSize , caretPadding , cornerRadius } = options;
14162 const { xAlign , yAlign } = alignment;
14163 const paddingAndSize = caretSize + caretPadding;
14164 const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(cornerRadius);
14165 let x = alignX(size, xAlign);
14166 const y = alignY(size, yAlign, paddingAndSize);
14167 if (yAlign === 'center') {
14168 if (xAlign === 'left') {
14169 x += paddingAndSize;
14170 } else if (xAlign === 'right') {
14171 x -= paddingAndSize;
14173 } else if (xAlign === 'left') {
14174 x -= Math.max(topLeft, bottomLeft) + caretSize;
14175 } else if (xAlign === 'right') {
14176 x += Math.max(topRight, bottomRight) + caretSize;
14179 x: _limitValue(x, 0, chart.width - size.width),
14180 y: _limitValue(y, 0, chart.height - size.height)
14183 function getAlignedX(tooltip, align, options) {
14184 const padding = toPadding(options.padding);
14185 return align === 'center' ? tooltip.x + tooltip.width / 2 : align === 'right' ? tooltip.x + tooltip.width - padding.right : tooltip.x + padding.left;
14187 function getBeforeAfterBodyLines(callback) {
14188 return pushOrConcat([], splitNewlines(callback));
14190 function createTooltipContext(parent, tooltip, tooltipItems) {
14191 return createContext(parent, {
14197 function overrideCallbacks(callbacks, context) {
14198 const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
14199 return override ? callbacks.override(override) : callbacks;
14201 const defaultCallbacks = {
14203 title (tooltipItems) {
14204 if (tooltipItems.length > 0) {
14205 const item = tooltipItems[0];
14206 const labels = item.chart.data.labels;
14207 const labelCount = labels ? labels.length : 0;
14208 if (this && this.options && this.options.mode === 'dataset') {
14209 return item.dataset.label || '';
14210 } else if (item.label) {
14212 } else if (labelCount > 0 && item.dataIndex < labelCount) {
14213 return labels[item.dataIndex];
14221 label (tooltipItem) {
14222 if (this && this.options && this.options.mode === 'dataset') {
14223 return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;
14225 let label = tooltipItem.dataset.label || '';
14229 const value = tooltipItem.formattedValue;
14230 if (!isNullOrUndef(value)) {
14235 labelColor (tooltipItem) {
14236 const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
14237 const options = meta.controller.getStyle(tooltipItem.dataIndex);
14239 borderColor: options.borderColor,
14240 backgroundColor: options.backgroundColor,
14241 borderWidth: options.borderWidth,
14242 borderDash: options.borderDash,
14243 borderDashOffset: options.borderDashOffset,
14247 labelTextColor () {
14248 return this.options.bodyColor;
14250 labelPointStyle (tooltipItem) {
14251 const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
14252 const options = meta.controller.getStyle(tooltipItem.dataIndex);
14254 pointStyle: options.pointStyle,
14255 rotation: options.rotation
14260 beforeFooter: noop,
14264 function invokeCallbackWithFallback(callbacks, name, ctx, arg) {
14265 const result = callbacks[name].call(ctx, arg);
14266 if (typeof result === 'undefined') {
14267 return defaultCallbacks[name].call(ctx, arg);
14271 class Tooltip extends Element {
14272 static positioners = positioners;
14273 constructor(config){
14277 this._eventPosition = undefined;
14278 this._size = undefined;
14279 this._cachedAnimations = undefined;
14280 this._tooltipItems = [];
14281 this.$animations = undefined;
14282 this.$context = undefined;
14283 this.chart = config.chart;
14284 this.options = config.options;
14285 this.dataPoints = undefined;
14286 this.title = undefined;
14287 this.beforeBody = undefined;
14288 this.body = undefined;
14289 this.afterBody = undefined;
14290 this.footer = undefined;
14291 this.xAlign = undefined;
14292 this.yAlign = undefined;
14293 this.x = undefined;
14294 this.y = undefined;
14295 this.height = undefined;
14296 this.width = undefined;
14297 this.caretX = undefined;
14298 this.caretY = undefined;
14299 this.labelColors = undefined;
14300 this.labelPointStyles = undefined;
14301 this.labelTextColors = undefined;
14303 initialize(options) {
14304 this.options = options;
14305 this._cachedAnimations = undefined;
14306 this.$context = undefined;
14308 _resolveAnimations() {
14309 const cached = this._cachedAnimations;
14313 const chart = this.chart;
14314 const options = this.options.setContext(this.getContext());
14315 const opts = options.enabled && chart.options.animation && options.animations;
14316 const animations = new Animations(this.chart, opts);
14317 if (opts._cacheable) {
14318 this._cachedAnimations = Object.freeze(animations);
14323 return this.$context || (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems));
14325 getTitle(context, options) {
14326 const { callbacks } = options;
14327 const beforeTitle = invokeCallbackWithFallback(callbacks, 'beforeTitle', this, context);
14328 const title = invokeCallbackWithFallback(callbacks, 'title', this, context);
14329 const afterTitle = invokeCallbackWithFallback(callbacks, 'afterTitle', this, context);
14331 lines = pushOrConcat(lines, splitNewlines(beforeTitle));
14332 lines = pushOrConcat(lines, splitNewlines(title));
14333 lines = pushOrConcat(lines, splitNewlines(afterTitle));
14336 getBeforeBody(tooltipItems, options) {
14337 return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'beforeBody', this, tooltipItems));
14339 getBody(tooltipItems, options) {
14340 const { callbacks } = options;
14341 const bodyItems = [];
14342 each(tooltipItems, (context)=>{
14348 const scoped = overrideCallbacks(callbacks, context);
14349 pushOrConcat(bodyItem.before, splitNewlines(invokeCallbackWithFallback(scoped, 'beforeLabel', this, context)));
14350 pushOrConcat(bodyItem.lines, invokeCallbackWithFallback(scoped, 'label', this, context));
14351 pushOrConcat(bodyItem.after, splitNewlines(invokeCallbackWithFallback(scoped, 'afterLabel', this, context)));
14352 bodyItems.push(bodyItem);
14356 getAfterBody(tooltipItems, options) {
14357 return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'afterBody', this, tooltipItems));
14359 getFooter(tooltipItems, options) {
14360 const { callbacks } = options;
14361 const beforeFooter = invokeCallbackWithFallback(callbacks, 'beforeFooter', this, tooltipItems);
14362 const footer = invokeCallbackWithFallback(callbacks, 'footer', this, tooltipItems);
14363 const afterFooter = invokeCallbackWithFallback(callbacks, 'afterFooter', this, tooltipItems);
14365 lines = pushOrConcat(lines, splitNewlines(beforeFooter));
14366 lines = pushOrConcat(lines, splitNewlines(footer));
14367 lines = pushOrConcat(lines, splitNewlines(afterFooter));
14370 _createItems(options) {
14371 const active = this._active;
14372 const data = this.chart.data;
14373 const labelColors = [];
14374 const labelPointStyles = [];
14375 const labelTextColors = [];
14376 let tooltipItems = [];
14378 for(i = 0, len = active.length; i < len; ++i){
14379 tooltipItems.push(createTooltipItem(this.chart, active[i]));
14381 if (options.filter) {
14382 tooltipItems = tooltipItems.filter((element, index, array)=>options.filter(element, index, array, data));
14384 if (options.itemSort) {
14385 tooltipItems = tooltipItems.sort((a, b)=>options.itemSort(a, b, data));
14387 each(tooltipItems, (context)=>{
14388 const scoped = overrideCallbacks(options.callbacks, context);
14389 labelColors.push(invokeCallbackWithFallback(scoped, 'labelColor', this, context));
14390 labelPointStyles.push(invokeCallbackWithFallback(scoped, 'labelPointStyle', this, context));
14391 labelTextColors.push(invokeCallbackWithFallback(scoped, 'labelTextColor', this, context));
14393 this.labelColors = labelColors;
14394 this.labelPointStyles = labelPointStyles;
14395 this.labelTextColors = labelTextColors;
14396 this.dataPoints = tooltipItems;
14397 return tooltipItems;
14399 update(changed, replay) {
14400 const options = this.options.setContext(this.getContext());
14401 const active = this._active;
14403 let tooltipItems = [];
14404 if (!active.length) {
14405 if (this.opacity !== 0) {
14411 const position = positioners[options.position].call(this, active, this._eventPosition);
14412 tooltipItems = this._createItems(options);
14413 this.title = this.getTitle(tooltipItems, options);
14414 this.beforeBody = this.getBeforeBody(tooltipItems, options);
14415 this.body = this.getBody(tooltipItems, options);
14416 this.afterBody = this.getAfterBody(tooltipItems, options);
14417 this.footer = this.getFooter(tooltipItems, options);
14418 const size = this._size = getTooltipSize(this, options);
14419 const positionAndSize = Object.assign({}, position, size);
14420 const alignment = determineAlignment(this.chart, options, positionAndSize);
14421 const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart);
14422 this.xAlign = alignment.xAlign;
14423 this.yAlign = alignment.yAlign;
14426 x: backgroundPoint.x,
14427 y: backgroundPoint.y,
14429 height: size.height,
14430 caretX: position.x,
14434 this._tooltipItems = tooltipItems;
14435 this.$context = undefined;
14437 this._resolveAnimations().update(this, properties);
14439 if (changed && options.external) {
14440 options.external.call(this, {
14447 drawCaret(tooltipPoint, ctx, size, options) {
14448 const caretPosition = this.getCaretPosition(tooltipPoint, size, options);
14449 ctx.lineTo(caretPosition.x1, caretPosition.y1);
14450 ctx.lineTo(caretPosition.x2, caretPosition.y2);
14451 ctx.lineTo(caretPosition.x3, caretPosition.y3);
14453 getCaretPosition(tooltipPoint, size, options) {
14454 const { xAlign , yAlign } = this;
14455 const { caretSize , cornerRadius } = options;
14456 const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(cornerRadius);
14457 const { x: ptX , y: ptY } = tooltipPoint;
14458 const { width , height } = size;
14459 let x1, x2, x3, y1, y2, y3;
14460 if (yAlign === 'center') {
14461 y2 = ptY + height / 2;
14462 if (xAlign === 'left') {
14464 x2 = x1 - caretSize;
14465 y1 = y2 + caretSize;
14466 y3 = y2 - caretSize;
14469 x2 = x1 + caretSize;
14470 y1 = y2 - caretSize;
14471 y3 = y2 + caretSize;
14475 if (xAlign === 'left') {
14476 x2 = ptX + Math.max(topLeft, bottomLeft) + caretSize;
14477 } else if (xAlign === 'right') {
14478 x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize;
14482 if (yAlign === 'top') {
14484 y2 = y1 - caretSize;
14485 x1 = x2 - caretSize;
14486 x3 = x2 + caretSize;
14489 y2 = y1 + caretSize;
14490 x1 = x2 + caretSize;
14491 x3 = x2 - caretSize;
14504 drawTitle(pt, ctx, options) {
14505 const title = this.title;
14506 const length = title.length;
14507 let titleFont, titleSpacing, i;
14509 const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
14510 pt.x = getAlignedX(this, options.titleAlign, options);
14511 ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
14512 ctx.textBaseline = 'middle';
14513 titleFont = toFont(options.titleFont);
14514 titleSpacing = options.titleSpacing;
14515 ctx.fillStyle = options.titleColor;
14516 ctx.font = titleFont.string;
14517 for(i = 0; i < length; ++i){
14518 ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);
14519 pt.y += titleFont.lineHeight + titleSpacing;
14520 if (i + 1 === length) {
14521 pt.y += options.titleMarginBottom - titleSpacing;
14526 _drawColorBox(ctx, pt, i, rtlHelper, options) {
14527 const labelColor = this.labelColors[i];
14528 const labelPointStyle = this.labelPointStyles[i];
14529 const { boxHeight , boxWidth } = options;
14530 const bodyFont = toFont(options.bodyFont);
14531 const colorX = getAlignedX(this, 'left', options);
14532 const rtlColorX = rtlHelper.x(colorX);
14533 const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;
14534 const colorY = pt.y + yOffSet;
14535 if (options.usePointStyle) {
14536 const drawOptions = {
14537 radius: Math.min(boxWidth, boxHeight) / 2,
14538 pointStyle: labelPointStyle.pointStyle,
14539 rotation: labelPointStyle.rotation,
14542 const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;
14543 const centerY = colorY + boxHeight / 2;
14544 ctx.strokeStyle = options.multiKeyBackground;
14545 ctx.fillStyle = options.multiKeyBackground;
14546 drawPoint(ctx, drawOptions, centerX, centerY);
14547 ctx.strokeStyle = labelColor.borderColor;
14548 ctx.fillStyle = labelColor.backgroundColor;
14549 drawPoint(ctx, drawOptions, centerX, centerY);
14551 ctx.lineWidth = isObject(labelColor.borderWidth) ? Math.max(...Object.values(labelColor.borderWidth)) : labelColor.borderWidth || 1;
14552 ctx.strokeStyle = labelColor.borderColor;
14553 ctx.setLineDash(labelColor.borderDash || []);
14554 ctx.lineDashOffset = labelColor.borderDashOffset || 0;
14555 const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth);
14556 const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2);
14557 const borderRadius = toTRBLCorners(labelColor.borderRadius);
14558 if (Object.values(borderRadius).some((v)=>v !== 0)) {
14560 ctx.fillStyle = options.multiKeyBackground;
14561 addRoundedRectPath(ctx, {
14566 radius: borderRadius
14570 ctx.fillStyle = labelColor.backgroundColor;
14572 addRoundedRectPath(ctx, {
14577 radius: borderRadius
14581 ctx.fillStyle = options.multiKeyBackground;
14582 ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
14583 ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
14584 ctx.fillStyle = labelColor.backgroundColor;
14585 ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
14588 ctx.fillStyle = this.labelTextColors[i];
14590 drawBody(pt, ctx, options) {
14591 const { body } = this;
14592 const { bodySpacing , bodyAlign , displayColors , boxHeight , boxWidth , boxPadding } = options;
14593 const bodyFont = toFont(options.bodyFont);
14594 let bodyLineHeight = bodyFont.lineHeight;
14595 let xLinePadding = 0;
14596 const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
14597 const fillLineOfText = function(line) {
14598 ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);
14599 pt.y += bodyLineHeight + bodySpacing;
14601 const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
14602 let bodyItem, textColor, lines, i, j, ilen, jlen;
14603 ctx.textAlign = bodyAlign;
14604 ctx.textBaseline = 'middle';
14605 ctx.font = bodyFont.string;
14606 pt.x = getAlignedX(this, bodyAlignForCalculation, options);
14607 ctx.fillStyle = options.bodyColor;
14608 each(this.beforeBody, fillLineOfText);
14609 xLinePadding = displayColors && bodyAlignForCalculation !== 'right' ? bodyAlign === 'center' ? boxWidth / 2 + boxPadding : boxWidth + 2 + boxPadding : 0;
14610 for(i = 0, ilen = body.length; i < ilen; ++i){
14611 bodyItem = body[i];
14612 textColor = this.labelTextColors[i];
14613 ctx.fillStyle = textColor;
14614 each(bodyItem.before, fillLineOfText);
14615 lines = bodyItem.lines;
14616 if (displayColors && lines.length) {
14617 this._drawColorBox(ctx, pt, i, rtlHelper, options);
14618 bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);
14620 for(j = 0, jlen = lines.length; j < jlen; ++j){
14621 fillLineOfText(lines[j]);
14622 bodyLineHeight = bodyFont.lineHeight;
14624 each(bodyItem.after, fillLineOfText);
14627 bodyLineHeight = bodyFont.lineHeight;
14628 each(this.afterBody, fillLineOfText);
14629 pt.y -= bodySpacing;
14631 drawFooter(pt, ctx, options) {
14632 const footer = this.footer;
14633 const length = footer.length;
14636 const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
14637 pt.x = getAlignedX(this, options.footerAlign, options);
14638 pt.y += options.footerMarginTop;
14639 ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
14640 ctx.textBaseline = 'middle';
14641 footerFont = toFont(options.footerFont);
14642 ctx.fillStyle = options.footerColor;
14643 ctx.font = footerFont.string;
14644 for(i = 0; i < length; ++i){
14645 ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);
14646 pt.y += footerFont.lineHeight + options.footerSpacing;
14650 drawBackground(pt, ctx, tooltipSize, options) {
14651 const { xAlign , yAlign } = this;
14652 const { x , y } = pt;
14653 const { width , height } = tooltipSize;
14654 const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(options.cornerRadius);
14655 ctx.fillStyle = options.backgroundColor;
14656 ctx.strokeStyle = options.borderColor;
14657 ctx.lineWidth = options.borderWidth;
14659 ctx.moveTo(x + topLeft, y);
14660 if (yAlign === 'top') {
14661 this.drawCaret(pt, ctx, tooltipSize, options);
14663 ctx.lineTo(x + width - topRight, y);
14664 ctx.quadraticCurveTo(x + width, y, x + width, y + topRight);
14665 if (yAlign === 'center' && xAlign === 'right') {
14666 this.drawCaret(pt, ctx, tooltipSize, options);
14668 ctx.lineTo(x + width, y + height - bottomRight);
14669 ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height);
14670 if (yAlign === 'bottom') {
14671 this.drawCaret(pt, ctx, tooltipSize, options);
14673 ctx.lineTo(x + bottomLeft, y + height);
14674 ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft);
14675 if (yAlign === 'center' && xAlign === 'left') {
14676 this.drawCaret(pt, ctx, tooltipSize, options);
14678 ctx.lineTo(x, y + topLeft);
14679 ctx.quadraticCurveTo(x, y, x + topLeft, y);
14682 if (options.borderWidth > 0) {
14686 _updateAnimationTarget(options) {
14687 const chart = this.chart;
14688 const anims = this.$animations;
14689 const animX = anims && anims.x;
14690 const animY = anims && anims.y;
14691 if (animX || animY) {
14692 const position = positioners[options.position].call(this, this._active, this._eventPosition);
14696 const size = this._size = getTooltipSize(this, options);
14697 const positionAndSize = Object.assign({}, position, this._size);
14698 const alignment = determineAlignment(chart, options, positionAndSize);
14699 const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
14700 if (animX._to !== point.x || animY._to !== point.y) {
14701 this.xAlign = alignment.xAlign;
14702 this.yAlign = alignment.yAlign;
14703 this.width = size.width;
14704 this.height = size.height;
14705 this.caretX = position.x;
14706 this.caretY = position.y;
14707 this._resolveAnimations().update(this, point);
14712 return !!this.opacity;
14715 const options = this.options.setContext(this.getContext());
14716 let opacity = this.opacity;
14720 this._updateAnimationTarget(options);
14721 const tooltipSize = {
14723 height: this.height
14729 opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;
14730 const padding = toPadding(options.padding);
14731 const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length;
14732 if (options.enabled && hasTooltipContent) {
14734 ctx.globalAlpha = opacity;
14735 this.drawBackground(pt, ctx, tooltipSize, options);
14736 overrideTextDirection(ctx, options.textDirection);
14737 pt.y += padding.top;
14738 this.drawTitle(pt, ctx, options);
14739 this.drawBody(pt, ctx, options);
14740 this.drawFooter(pt, ctx, options);
14741 restoreTextDirection(ctx, options.textDirection);
14745 getActiveElements() {
14746 return this._active || [];
14748 setActiveElements(activeElements, eventPosition) {
14749 const lastActive = this._active;
14750 const active = activeElements.map(({ datasetIndex , index })=>{
14751 const meta = this.chart.getDatasetMeta(datasetIndex);
14753 throw new Error('Cannot find a dataset at index ' + datasetIndex);
14757 element: meta.data[index],
14761 const changed = !_elementsEqual(lastActive, active);
14762 const positionChanged = this._positionChanged(active, eventPosition);
14763 if (changed || positionChanged) {
14764 this._active = active;
14765 this._eventPosition = eventPosition;
14766 this._ignoreReplayEvents = true;
14770 handleEvent(e, replay, inChartArea = true) {
14771 if (replay && this._ignoreReplayEvents) {
14774 this._ignoreReplayEvents = false;
14775 const options = this.options;
14776 const lastActive = this._active || [];
14777 const active = this._getActiveElements(e, lastActive, replay, inChartArea);
14778 const positionChanged = this._positionChanged(active, e);
14779 const changed = replay || !_elementsEqual(active, lastActive) || positionChanged;
14781 this._active = active;
14782 if (options.enabled || options.external) {
14783 this._eventPosition = {
14787 this.update(true, replay);
14792 _getActiveElements(e, lastActive, replay, inChartArea) {
14793 const options = this.options;
14794 if (e.type === 'mouseout') {
14797 if (!inChartArea) {
14798 return lastActive.filter((i)=>this.chart.data.datasets[i.datasetIndex] && this.chart.getDatasetMeta(i.datasetIndex).controller.getParsed(i.index) !== undefined);
14800 const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay);
14801 if (options.reverse) {
14806 _positionChanged(active, e) {
14807 const { caretX , caretY , options } = this;
14808 const position = positioners[options.position].call(this, active, e);
14809 return position !== false && (caretX !== position.x || caretY !== position.y);
14812 var plugin_tooltip = {
14816 afterInit (chart, _args, options) {
14818 chart.tooltip = new Tooltip({
14824 beforeUpdate (chart, _args, options) {
14825 if (chart.tooltip) {
14826 chart.tooltip.initialize(options);
14829 reset (chart, _args, options) {
14830 if (chart.tooltip) {
14831 chart.tooltip.initialize(options);
14834 afterDraw (chart) {
14835 const tooltip = chart.tooltip;
14836 if (tooltip && tooltip._willRender()) {
14840 if (chart.notifyPlugins('beforeTooltipDraw', {
14846 tooltip.draw(chart.ctx);
14847 chart.notifyPlugins('afterTooltipDraw', args);
14850 afterEvent (chart, args) {
14851 if (chart.tooltip) {
14852 const useFinalPosition = args.replay;
14853 if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) {
14854 args.changed = true;
14861 position: 'average',
14862 backgroundColor: 'rgba(0,0,0,0.8)',
14863 titleColor: '#fff',
14868 titleMarginBottom: 6,
14869 titleAlign: 'left',
14874 footerColor: '#fff',
14876 footerMarginTop: 6,
14880 footerAlign: 'left',
14885 boxHeight: (ctx, opts)=>opts.bodyFont.size,
14886 boxWidth: (ctx, opts)=>opts.bodyFont.size,
14887 multiKeyBackground: '#fff',
14888 displayColors: true,
14890 borderColor: 'rgba(0,0,0,0)',
14894 easing: 'easeOutQuart'
14913 callbacks: defaultCallbacks
14917 footerFont: 'font',
14921 _scriptable: (name)=>name !== 'filter' && name !== 'itemSort' && name !== 'external',
14924 _scriptable: false,
14931 _fallback: 'animation'
14934 additionalOptionScopes: [
14939 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
14940 // Register built-ins
14941 Chart.register(controllers, scales, elements, plugins);
14945 Chart._adapters = _adapters;
14946 Chart.Animation = Animation;
14947 Chart.Animations = Animations;
14948 Chart.animator = animator;
14949 Chart.controllers = registry.controllers.items;
14950 Chart.DatasetController = DatasetController;
14951 Chart.Element = Element;
14952 Chart.elements = elements;
14953 Chart.Interaction = Interaction;
14954 Chart.layouts = layouts;
14955 Chart.platforms = platforms;
14956 Chart.Scale = Scale;
14957 Chart.Ticks = Ticks;
14958 // Compatibility with ESM extensions
14959 Object.assign(Chart, controllers, scales, elements, plugins, platforms);
14960 Chart.Chart = Chart;
14961 if (typeof window !== 'undefined') {
14962 window.Chart = Chart;