PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.4.5
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.4.5
3.5.4 3.5.3 3.5.2 3.5.1 3.4.9 3.5.0 3.4.8 3.4.7 trunk 2.3.1 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6
supportcandy / asset / libs / chartjs / dist / chart.esm.js

chart.esm.js in SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent 3.4.5, at asset/libs/chartjs/dist/chart.esm.js

10,253 lines 310.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*!
2 * Chart.js v3.2.0
3 * https://www.chartjs.org
4 * (c) 2021 Chart.js Contributors
5 * Released under the MIT License
6 */
7 import { r as requestAnimFrame, a as resolve, e as effects, c as color, i as isObject, b as isArray, d as defaults, v as valueOrDefault, u as unlistenArrayEvents, l as listenArrayEvents, f as resolveObjectKey, g as isNumberFinite, h as defined, s as sign, j as isNullOrUndef, k as clipArea, m as unclipArea, _ as _arrayUnique, t as toRadians, n as toPercentage, o as toDimension, T as TAU, p as formatNumber, q as _angleBetween, H as HALF_PI, P as PI, w as isNumber, x as _limitValue, y as _lookupByKey, z as getRelativePosition$1, A as _isPointInArea, B as _rlookupByKey, C as toPadding, D as each, E as getMaximumSize, F as _getParentNode, G as readUsedSize, I as throttled, J as supportsEventListenerOptions, K as log10, L as _factorize, M as finiteOrDefault, N as callback, O as _addGrace, Q as toDegrees, R as _measureText, S as _int16Range, U as _alignPixel, V as renderText, W as toFont, X as _toLeftRightCenter, Y as _alignStartEnd, Z as overrides, $ as merge, a0 as _capitalize, a1 as descriptors, a2 as isFunction, a3 as _attachContext, a4 as _createResolver, a5 as _descriptors, a6 as mergeIf, a7 as uid, a8 as debounce, a9 as retinaScale, aa as clearCanvas, ab as setsEqual, ac as _elementsEqual, ad as getAngleFromPoint, ae as _readValueToProps, af as _updateBezierControlPoints, ag as _computeSegments, ah as _boundSegments, ai as _steppedInterpolation, aj as _bezierInterpolation, ak as _pointInLine, al as _steppedLineTo, am as _bezierCurveTo, an as drawPoint, ao as addRoundedRectPath, ap as toTRBL, aq as toTRBLCorners, ar as _boundSegment, as as _normalizeAngle, at as getRtlAdapter, au as overrideTextDirection, av as _textX, aw as restoreTextDirection, ax as noop, ay as distanceBetweenPoints, az as _setMinAndMaxByKey, aA as niceNum, aB as almostWhole, aC as almostEquals, aD as _decimalPlaces, aE as _longestText, aF as _filterBetween, aG as _lookup } from './chunks/helpers.segment.js';
8 export { d as defaults } from './chunks/helpers.segment.js';
9
10 class Animator {
11 constructor() {
12 this._request = null;
13 this._charts = new Map();
14 this._running = false;
15 this._lastDate = undefined;
16 }
17 _notify(chart, anims, date, type) {
18 const callbacks = anims.listeners[type];
19 const numSteps = anims.duration;
20 callbacks.forEach(fn => fn({
21 chart,
22 initial: anims.initial,
23 numSteps,
24 currentStep: Math.min(date - anims.start, numSteps)
25 }));
26 }
27 _refresh() {
28 const me = this;
29 if (me._request) {
30 return;
31 }
32 me._running = true;
33 me._request = requestAnimFrame.call(window, () => {
34 me._update();
35 me._request = null;
36 if (me._running) {
37 me._refresh();
38 }
39 });
40 }
41 _update(date = Date.now()) {
42 const me = this;
43 let remaining = 0;
44 me._charts.forEach((anims, chart) => {
45 if (!anims.running || !anims.items.length) {
46 return;
47 }
48 const items = anims.items;
49 let i = items.length - 1;
50 let draw = false;
51 let item;
52 for (; i >= 0; --i) {
53 item = items[i];
54 if (item._active) {
55 if (item._total > anims.duration) {
56 anims.duration = item._total;
57 }
58 item.tick(date);
59 draw = true;
60 } else {
61 items[i] = items[items.length - 1];
62 items.pop();
63 }
64 }
65 if (draw) {
66 chart.draw();
67 me._notify(chart, anims, date, 'progress');
68 }
69 if (!items.length) {
70 anims.running = false;
71 me._notify(chart, anims, date, 'complete');
72 anims.initial = false;
73 }
74 remaining += items.length;
75 });
76 me._lastDate = date;
77 if (remaining === 0) {
78 me._running = false;
79 }
80 }
81 _getAnims(chart) {
82 const charts = this._charts;
83 let anims = charts.get(chart);
84 if (!anims) {
85 anims = {
86 running: false,
87 initial: true,
88 items: [],
89 listeners: {
90 complete: [],
91 progress: []
92 }
93 };
94 charts.set(chart, anims);
95 }
96 return anims;
97 }
98 listen(chart, event, cb) {
99 this._getAnims(chart).listeners[event].push(cb);
100 }
101 add(chart, items) {
102 if (!items || !items.length) {
103 return;
104 }
105 this._getAnims(chart).items.push(...items);
106 }
107 has(chart) {
108 return this._getAnims(chart).items.length > 0;
109 }
110 start(chart) {
111 const anims = this._charts.get(chart);
112 if (!anims) {
113 return;
114 }
115 anims.running = true;
116 anims.start = Date.now();
117 anims.duration = anims.items.reduce((acc, cur) => Math.max(acc, cur._duration), 0);
118 this._refresh();
119 }
120 running(chart) {
121 if (!this._running) {
122 return false;
123 }
124 const anims = this._charts.get(chart);
125 if (!anims || !anims.running || !anims.items.length) {
126 return false;
127 }
128 return true;
129 }
130 stop(chart) {
131 const anims = this._charts.get(chart);
132 if (!anims || !anims.items.length) {
133 return;
134 }
135 const items = anims.items;
136 let i = items.length - 1;
137 for (; i >= 0; --i) {
138 items[i].cancel();
139 }
140 anims.items = [];
141 this._notify(chart, anims, Date.now(), 'complete');
142 }
143 remove(chart) {
144 return this._charts.delete(chart);
145 }
146 }
147 var animator = new Animator();
148
149 const transparent = 'transparent';
150 const interpolators = {
151 boolean(from, to, factor) {
152 return factor > 0.5 ? to : from;
153 },
154 color(from, to, factor) {
155 const c0 = color(from || transparent);
156 const c1 = c0.valid && color(to || transparent);
157 return c1 && c1.valid
158 ? c1.mix(c0, factor).hexString()
159 : to;
160 },
161 number(from, to, factor) {
162 return from + (to - from) * factor;
163 }
164 };
165 class Animation {
166 constructor(cfg, target, prop, to) {
167 const currentValue = target[prop];
168 to = resolve([cfg.to, to, currentValue, cfg.from]);
169 const from = resolve([cfg.from, currentValue, to]);
170 this._active = true;
171 this._fn = cfg.fn || interpolators[cfg.type || typeof from];
172 this._easing = effects[cfg.easing] || effects.linear;
173 this._start = Math.floor(Date.now() + (cfg.delay || 0));
174 this._duration = this._total = Math.floor(cfg.duration);
175 this._loop = !!cfg.loop;
176 this._target = target;
177 this._prop = prop;
178 this._from = from;
179 this._to = to;
180 this._promises = undefined;
181 }
182 active() {
183 return this._active;
184 }
185 update(cfg, to, date) {
186 const me = this;
187 if (me._active) {
188 me._notify(false);
189 const currentValue = me._target[me._prop];
190 const elapsed = date - me._start;
191 const remain = me._duration - elapsed;
192 me._start = date;
193 me._duration = Math.floor(Math.max(remain, cfg.duration));
194 me._total += elapsed;
195 me._loop = !!cfg.loop;
196 me._to = resolve([cfg.to, to, currentValue, cfg.from]);
197 me._from = resolve([cfg.from, currentValue, to]);
198 }
199 }
200 cancel() {
201 const me = this;
202 if (me._active) {
203 me.tick(Date.now());
204 me._active = false;
205 me._notify(false);
206 }
207 }
208 tick(date) {
209 const me = this;
210 const elapsed = date - me._start;
211 const duration = me._duration;
212 const prop = me._prop;
213 const from = me._from;
214 const loop = me._loop;
215 const to = me._to;
216 let factor;
217 me._active = from !== to && (loop || (elapsed < duration));
218 if (!me._active) {
219 me._target[prop] = to;
220 me._notify(true);
221 return;
222 }
223 if (elapsed < 0) {
224 me._target[prop] = from;
225 return;
226 }
227 factor = (elapsed / duration) % 2;
228 factor = loop && factor > 1 ? 2 - factor : factor;
229 factor = me._easing(Math.min(1, Math.max(0, factor)));
230 me._target[prop] = me._fn(from, to, factor);
231 }
232 wait() {
233 const promises = this._promises || (this._promises = []);
234 return new Promise((res, rej) => {
235 promises.push({res, rej});
236 });
237 }
238 _notify(resolved) {
239 const method = resolved ? 'res' : 'rej';
240 const promises = this._promises || [];
241 for (let i = 0; i < promises.length; i++) {
242 promises[i][method]();
243 }
244 }
245 }
246
247 const numbers = ['x', 'y', 'borderWidth', 'radius', 'tension'];
248 const colors = ['color', 'borderColor', 'backgroundColor'];
249 defaults.set('animation', {
250 delay: undefined,
251 duration: 1000,
252 easing: 'easeOutQuart',
253 fn: undefined,
254 from: undefined,
255 loop: undefined,
256 to: undefined,
257 type: undefined,
258 });
259 const animationOptions = Object.keys(defaults.animation);
260 defaults.describe('animation', {
261 _fallback: false,
262 _indexable: false,
263 _scriptable: (name) => name !== 'onProgress' && name !== 'onComplete' && name !== 'fn',
264 });
265 defaults.set('animations', {
266 colors: {
267 type: 'color',
268 properties: colors
269 },
270 numbers: {
271 type: 'number',
272 properties: numbers
273 },
274 });
275 defaults.describe('animations', {
276 _fallback: 'animation',
277 });
278 defaults.set('transitions', {
279 active: {
280 animation: {
281 duration: 400
282 }
283 },
284 resize: {
285 animation: {
286 duration: 0
287 }
288 },
289 show: {
290 animations: {
291 colors: {
292 from: 'transparent'
293 },
294 visible: {
295 type: 'boolean',
296 duration: 0
297 },
298 }
299 },
300 hide: {
301 animations: {
302 colors: {
303 to: 'transparent'
304 },
305 visible: {
306 type: 'boolean',
307 easing: 'linear',
308 fn: v => v | 0
309 },
310 }
311 }
312 });
313 class Animations {
314 constructor(chart, config) {
315 this._chart = chart;
316 this._properties = new Map();
317 this.configure(config);
318 }
319 configure(config) {
320 if (!isObject(config)) {
321 return;
322 }
323 const animatedProps = this._properties;
324 Object.getOwnPropertyNames(config).forEach(key => {
325 const cfg = config[key];
326 if (!isObject(cfg)) {
327 return;
328 }
329 const resolved = {};
330 for (const option of animationOptions) {
331 resolved[option] = cfg[option];
332 }
333 (isArray(cfg.properties) && cfg.properties || [key]).forEach((prop) => {
334 if (prop === key || !animatedProps.has(prop)) {
335 animatedProps.set(prop, resolved);
336 }
337 });
338 });
339 }
340 _animateOptions(target, values) {
341 const newOptions = values.options;
342 const options = resolveTargetOptions(target, newOptions);
343 if (!options) {
344 return [];
345 }
346 const animations = this._createAnimations(options, newOptions);
347 if (newOptions.$shared) {
348 awaitAll(target.options.$animations, newOptions).then(() => {
349 target.options = newOptions;
350 }, () => {
351 });
352 }
353 return animations;
354 }
355 _createAnimations(target, values) {
356 const animatedProps = this._properties;
357 const animations = [];
358 const running = target.$animations || (target.$animations = {});
359 const props = Object.keys(values);
360 const date = Date.now();
361 let i;
362 for (i = props.length - 1; i >= 0; --i) {
363 const prop = props[i];
364 if (prop.charAt(0) === '$') {
365 continue;
366 }
367 if (prop === 'options') {
368 animations.push(...this._animateOptions(target, values));
369 continue;
370 }
371 const value = values[prop];
372 let animation = running[prop];
373 const cfg = animatedProps.get(prop);
374 if (animation) {
375 if (cfg && animation.active()) {
376 animation.update(cfg, value, date);
377 continue;
378 } else {
379 animation.cancel();
380 }
381 }
382 if (!cfg || !cfg.duration) {
383 target[prop] = value;
384 continue;
385 }
386 running[prop] = animation = new Animation(cfg, target, prop, value);
387 animations.push(animation);
388 }
389 return animations;
390 }
391 update(target, values) {
392 if (this._properties.size === 0) {
393 Object.assign(target, values);
394 return;
395 }
396 const animations = this._createAnimations(target, values);
397 if (animations.length) {
398 animator.add(this._chart, animations);
399 return true;
400 }
401 }
402 }
403 function awaitAll(animations, properties) {
404 const running = [];
405 const keys = Object.keys(properties);
406 for (let i = 0; i < keys.length; i++) {
407 const anim = animations[keys[i]];
408 if (anim && anim.active()) {
409 running.push(anim.wait());
410 }
411 }
412 return Promise.all(running);
413 }
414 function resolveTargetOptions(target, newOptions) {
415 if (!newOptions) {
416 return;
417 }
418 let options = target.options;
419 if (!options) {
420 target.options = newOptions;
421 return;
422 }
423 if (options.$shared) {
424 target.options = options = Object.assign({}, options, {$shared: false, $animations: {}});
425 }
426 return options;
427 }
428
429 function scaleClip(scale, allowedOverflow) {
430 const opts = scale && scale.options || {};
431 const reverse = opts.reverse;
432 const min = opts.min === undefined ? allowedOverflow : 0;
433 const max = opts.max === undefined ? allowedOverflow : 0;
434 return {
435 start: reverse ? max : min,
436 end: reverse ? min : max
437 };
438 }
439 function defaultClip(xScale, yScale, allowedOverflow) {
440 if (allowedOverflow === false) {
441 return false;
442 }
443 const x = scaleClip(xScale, allowedOverflow);
444 const y = scaleClip(yScale, allowedOverflow);
445 return {
446 top: y.end,
447 right: x.end,
448 bottom: y.start,
449 left: x.start
450 };
451 }
452 function toClip(value) {
453 let t, r, b, l;
454 if (isObject(value)) {
455 t = value.top;
456 r = value.right;
457 b = value.bottom;
458 l = value.left;
459 } else {
460 t = r = b = l = value;
461 }
462 return {
463 top: t,
464 right: r,
465 bottom: b,
466 left: l
467 };
468 }
469 function getSortedDatasetIndices(chart, filterVisible) {
470 const keys = [];
471 const metasets = chart._getSortedDatasetMetas(filterVisible);
472 let i, ilen;
473 for (i = 0, ilen = metasets.length; i < ilen; ++i) {
474 keys.push(metasets[i].index);
475 }
476 return keys;
477 }
478 function applyStack(stack, value, dsIndex, options) {
479 const keys = stack.keys;
480 const singleMode = options.mode === 'single';
481 let i, ilen, datasetIndex, otherValue;
482 if (value === null) {
483 return;
484 }
485 for (i = 0, ilen = keys.length; i < ilen; ++i) {
486 datasetIndex = +keys[i];
487 if (datasetIndex === dsIndex) {
488 if (options.all) {
489 continue;
490 }
491 break;
492 }
493 otherValue = stack.values[datasetIndex];
494 if (isNumberFinite(otherValue) && (singleMode || (value === 0 || sign(value) === sign(otherValue)))) {
495 value += otherValue;
496 }
497 }
498 return value;
499 }
500 function convertObjectDataToArray(data) {
501 const keys = Object.keys(data);
502 const adata = new Array(keys.length);
503 let i, ilen, key;
504 for (i = 0, ilen = keys.length; i < ilen; ++i) {
505 key = keys[i];
506 adata[i] = {
507 x: key,
508 y: data[key]
509 };
510 }
511 return adata;
512 }
513 function isStacked(scale, meta) {
514 const stacked = scale && scale.options.stacked;
515 return stacked || (stacked === undefined && meta.stack !== undefined);
516 }
517 function getStackKey(indexScale, valueScale, meta) {
518 return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;
519 }
520 function getUserBounds(scale) {
521 const {min, max, minDefined, maxDefined} = scale.getUserBounds();
522 return {
523 min: minDefined ? min : Number.NEGATIVE_INFINITY,
524 max: maxDefined ? max : Number.POSITIVE_INFINITY
525 };
526 }
527 function getOrCreateStack(stacks, stackKey, indexValue) {
528 const subStack = stacks[stackKey] || (stacks[stackKey] = {});
529 return subStack[indexValue] || (subStack[indexValue] = {});
530 }
531 function getLastIndexInStack(stack, vScale, positive) {
532 for (const meta of vScale.getMatchingVisibleMetas('bar').reverse()) {
533 const value = stack[meta.index];
534 if ((positive && value > 0) || (!positive && value < 0)) {
535 return meta.index;
536 }
537 }
538 return null;
539 }
540 function updateStacks(controller, parsed) {
541 const {chart, _cachedMeta: meta} = controller;
542 const stacks = chart._stacks || (chart._stacks = {});
543 const {iScale, vScale, index: datasetIndex} = meta;
544 const iAxis = iScale.axis;
545 const vAxis = vScale.axis;
546 const key = getStackKey(iScale, vScale, meta);
547 const ilen = parsed.length;
548 let stack;
549 for (let i = 0; i < ilen; ++i) {
550 const item = parsed[i];
551 const {[iAxis]: index, [vAxis]: value} = item;
552 const itemStacks = item._stacks || (item._stacks = {});
553 stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index);
554 stack[datasetIndex] = value;
555 stack._top = getLastIndexInStack(stack, vScale, true);
556 stack._bottom = getLastIndexInStack(stack, vScale, false);
557 }
558 }
559 function getFirstScaleId(chart, axis) {
560 const scales = chart.scales;
561 return Object.keys(scales).filter(key => scales[key].axis === axis).shift();
562 }
563 function createDatasetContext(parent, index) {
564 return Object.assign(Object.create(parent),
565 {
566 active: false,
567 dataset: undefined,
568 datasetIndex: index,
569 index,
570 mode: 'default',
571 type: 'dataset'
572 }
573 );
574 }
575 function createDataContext(parent, index, element) {
576 return Object.assign(Object.create(parent), {
577 active: false,
578 dataIndex: index,
579 parsed: undefined,
580 raw: undefined,
581 element,
582 index,
583 mode: 'default',
584 type: 'data'
585 });
586 }
587 function clearStacks(meta, items) {
588 items = items || meta._parsed;
589 for (const parsed of items) {
590 const stacks = parsed._stacks;
591 if (!stacks || stacks[meta.vScale.id] === undefined || stacks[meta.vScale.id][meta.index] === undefined) {
592 return;
593 }
594 delete stacks[meta.vScale.id][meta.index];
595 }
596 }
597 const isDirectUpdateMode = (mode) => mode === 'reset' || mode === 'none';
598 const cloneIfNotShared = (cached, shared) => shared ? cached : Object.assign({}, cached);
599 class DatasetController {
600 constructor(chart, datasetIndex) {
601 this.chart = chart;
602 this._ctx = chart.ctx;
603 this.index = datasetIndex;
604 this._cachedDataOpts = {};
605 this._cachedMeta = this.getMeta();
606 this._type = this._cachedMeta.type;
607 this.options = undefined;
608 this._parsing = false;
609 this._data = undefined;
610 this._objectData = undefined;
611 this._sharedOptions = undefined;
612 this._drawStart = undefined;
613 this._drawCount = undefined;
614 this.enableOptionSharing = false;
615 this.$context = undefined;
616 this.initialize();
617 }
618 initialize() {
619 const me = this;
620 const meta = me._cachedMeta;
621 me.configure();
622 me.linkScales();
623 meta._stacked = isStacked(meta.vScale, meta);
624 me.addElements();
625 }
626 updateIndex(datasetIndex) {
627 this.index = datasetIndex;
628 }
629 linkScales() {
630 const me = this;
631 const chart = me.chart;
632 const meta = me._cachedMeta;
633 const dataset = me.getDataset();
634 const chooseId = (axis, x, y, r) => axis === 'x' ? x : axis === 'r' ? r : y;
635 const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));
636 const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));
637 const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));
638 const indexAxis = meta.indexAxis;
639 const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);
640 const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);
641 meta.xScale = me.getScaleForId(xid);
642 meta.yScale = me.getScaleForId(yid);
643 meta.rScale = me.getScaleForId(rid);
644 meta.iScale = me.getScaleForId(iid);
645 meta.vScale = me.getScaleForId(vid);
646 }
647 getDataset() {
648 return this.chart.data.datasets[this.index];
649 }
650 getMeta() {
651 return this.chart.getDatasetMeta(this.index);
652 }
653 getScaleForId(scaleID) {
654 return this.chart.scales[scaleID];
655 }
656 _getOtherScale(scale) {
657 const meta = this._cachedMeta;
658 return scale === meta.iScale
659 ? meta.vScale
660 : meta.iScale;
661 }
662 reset() {
663 this._update('reset');
664 }
665 _destroy() {
666 const meta = this._cachedMeta;
667 if (this._data) {
668 unlistenArrayEvents(this._data, this);
669 }
670 if (meta._stacked) {
671 clearStacks(meta);
672 }
673 }
674 _dataCheck() {
675 const me = this;
676 const dataset = me.getDataset();
677 const data = dataset.data || (dataset.data = []);
678 if (isObject(data)) {
679 me._data = convertObjectDataToArray(data);
680 } else if (me._data !== data) {
681 if (me._data) {
682 unlistenArrayEvents(me._data, me);
683 clearStacks(me._cachedMeta);
684 }
685 if (data && Object.isExtensible(data)) {
686 listenArrayEvents(data, me);
687 }
688 me._data = data;
689 }
690 }
691 addElements() {
692 const me = this;
693 const meta = me._cachedMeta;
694 me._dataCheck();
695 if (me.datasetElementType) {
696 meta.dataset = new me.datasetElementType();
697 }
698 }
699 buildOrUpdateElements(resetNewElements) {
700 const me = this;
701 const meta = me._cachedMeta;
702 const dataset = me.getDataset();
703 let stackChanged = false;
704 me._dataCheck();
705 meta._stacked = isStacked(meta.vScale, meta);
706 if (meta.stack !== dataset.stack) {
707 stackChanged = true;
708 clearStacks(meta);
709 meta.stack = dataset.stack;
710 }
711 me._resyncElements(resetNewElements);
712 if (stackChanged) {
713 updateStacks(me, meta._parsed);
714 }
715 }
716 configure() {
717 const me = this;
718 const config = me.chart.config;
719 const scopeKeys = config.datasetScopeKeys(me._type);
720 const scopes = config.getOptionScopes(me.getDataset(), scopeKeys, true);
721 me.options = config.createResolver(scopes, me.getContext());
722 me._parsing = me.options.parsing;
723 }
724 parse(start, count) {
725 const me = this;
726 const {_cachedMeta: meta, _data: data} = me;
727 const {iScale, _stacked} = meta;
728 const iAxis = iScale.axis;
729 let sorted = start === 0 && count === data.length ? true : meta._sorted;
730 let prev = start > 0 && meta._parsed[start - 1];
731 let i, cur, parsed;
732 if (me._parsing === false) {
733 meta._parsed = data;
734 meta._sorted = true;
735 parsed = data;
736 } else {
737 if (isArray(data[start])) {
738 parsed = me.parseArrayData(meta, data, start, count);
739 } else if (isObject(data[start])) {
740 parsed = me.parseObjectData(meta, data, start, count);
741 } else {
742 parsed = me.parsePrimitiveData(meta, data, start, count);
743 }
744 const isNotInOrderComparedToPrev = () => cur[iAxis] === null || (prev && cur[iAxis] < prev[iAxis]);
745 for (i = 0; i < count; ++i) {
746 meta._parsed[i + start] = cur = parsed[i];
747 if (sorted) {
748 if (isNotInOrderComparedToPrev()) {
749 sorted = false;
750 }
751 prev = cur;
752 }
753 }
754 meta._sorted = sorted;
755 }
756 if (_stacked) {
757 updateStacks(me, parsed);
758 }
759 }
760 parsePrimitiveData(meta, data, start, count) {
761 const {iScale, vScale} = meta;
762 const iAxis = iScale.axis;
763 const vAxis = vScale.axis;
764 const labels = iScale.getLabels();
765 const singleScale = iScale === vScale;
766 const parsed = new Array(count);
767 let i, ilen, index;
768 for (i = 0, ilen = count; i < ilen; ++i) {
769 index = i + start;
770 parsed[i] = {
771 [iAxis]: singleScale || iScale.parse(labels[index], index),
772 [vAxis]: vScale.parse(data[index], index)
773 };
774 }
775 return parsed;
776 }
777 parseArrayData(meta, data, start, count) {
778 const {xScale, yScale} = meta;
779 const parsed = new Array(count);
780 let i, ilen, index, item;
781 for (i = 0, ilen = count; i < ilen; ++i) {
782 index = i + start;
783 item = data[index];
784 parsed[i] = {
785 x: xScale.parse(item[0], index),
786 y: yScale.parse(item[1], index)
787 };
788 }
789 return parsed;
790 }
791 parseObjectData(meta, data, start, count) {
792 const {xScale, yScale} = meta;
793 const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
794 const parsed = new Array(count);
795 let i, ilen, index, item;
796 for (i = 0, ilen = count; i < ilen; ++i) {
797 index = i + start;
798 item = data[index];
799 parsed[i] = {
800 x: xScale.parse(resolveObjectKey(item, xAxisKey), index),
801 y: yScale.parse(resolveObjectKey(item, yAxisKey), index)
802 };
803 }
804 return parsed;
805 }
806 getParsed(index) {
807 return this._cachedMeta._parsed[index];
808 }
809 getDataElement(index) {
810 return this._cachedMeta.data[index];
811 }
812 applyStack(scale, parsed, mode) {
813 const chart = this.chart;
814 const meta = this._cachedMeta;
815 const value = parsed[scale.axis];
816 const stack = {
817 keys: getSortedDatasetIndices(chart, true),
818 values: parsed._stacks[scale.axis]
819 };
820 return applyStack(stack, value, meta.index, {mode});
821 }
822 updateRangeFromParsed(range, scale, parsed, stack) {
823 const parsedValue = parsed[scale.axis];
824 let value = parsedValue === null ? NaN : parsedValue;
825 const values = stack && parsed._stacks[scale.axis];
826 if (stack && values) {
827 stack.values = values;
828 range.min = Math.min(range.min, value);
829 range.max = Math.max(range.max, value);
830 value = applyStack(stack, parsedValue, this._cachedMeta.index, {all: true});
831 }
832 range.min = Math.min(range.min, value);
833 range.max = Math.max(range.max, value);
834 }
835 getMinMax(scale, canStack) {
836 const me = this;
837 const meta = me._cachedMeta;
838 const _parsed = meta._parsed;
839 const sorted = meta._sorted && scale === meta.iScale;
840 const ilen = _parsed.length;
841 const otherScale = me._getOtherScale(scale);
842 const stack = canStack && meta._stacked && {keys: getSortedDatasetIndices(me.chart, true), values: null};
843 const range = {min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY};
844 const {min: otherMin, max: otherMax} = getUserBounds(otherScale);
845 let i, value, parsed, otherValue;
846 function _skip() {
847 parsed = _parsed[i];
848 value = parsed[scale.axis];
849 otherValue = parsed[otherScale.axis];
850 return !isNumberFinite(value) || otherMin > otherValue || otherMax < otherValue;
851 }
852 for (i = 0; i < ilen; ++i) {
853 if (_skip()) {
854 continue;
855 }
856 me.updateRangeFromParsed(range, scale, parsed, stack);
857 if (sorted) {
858 break;
859 }
860 }
861 if (sorted) {
862 for (i = ilen - 1; i >= 0; --i) {
863 if (_skip()) {
864 continue;
865 }
866 me.updateRangeFromParsed(range, scale, parsed, stack);
867 break;
868 }
869 }
870 return range;
871 }
872 getAllParsedValues(scale) {
873 const parsed = this._cachedMeta._parsed;
874 const values = [];
875 let i, ilen, value;
876 for (i = 0, ilen = parsed.length; i < ilen; ++i) {
877 value = parsed[i][scale.axis];
878 if (isNumberFinite(value)) {
879 values.push(value);
880 }
881 }
882 return values;
883 }
884 getMaxOverflow() {
885 return false;
886 }
887 getLabelAndValue(index) {
888 const me = this;
889 const meta = me._cachedMeta;
890 const iScale = meta.iScale;
891 const vScale = meta.vScale;
892 const parsed = me.getParsed(index);
893 return {
894 label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',
895 value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''
896 };
897 }
898 _update(mode) {
899 const me = this;
900 const meta = me._cachedMeta;
901 me.configure();
902 me._cachedDataOpts = {};
903 me.update(mode || 'default');
904 meta._clip = toClip(valueOrDefault(me.options.clip, defaultClip(meta.xScale, meta.yScale, me.getMaxOverflow())));
905 }
906 update(mode) {}
907 draw() {
908 const me = this;
909 const ctx = me._ctx;
910 const chart = me.chart;
911 const meta = me._cachedMeta;
912 const elements = meta.data || [];
913 const area = chart.chartArea;
914 const active = [];
915 const start = me._drawStart || 0;
916 const count = me._drawCount || (elements.length - start);
917 let i;
918 if (meta.dataset) {
919 meta.dataset.draw(ctx, area, start, count);
920 }
921 for (i = start; i < start + count; ++i) {
922 const element = elements[i];
923 if (element.active) {
924 active.push(element);
925 } else {
926 element.draw(ctx, area);
927 }
928 }
929 for (i = 0; i < active.length; ++i) {
930 active[i].draw(ctx, area);
931 }
932 }
933 getStyle(index, active) {
934 const mode = active ? 'active' : 'default';
935 return index === undefined && this._cachedMeta.dataset
936 ? this.resolveDatasetElementOptions(mode)
937 : this.resolveDataElementOptions(index || 0, mode);
938 }
939 getContext(index, active, mode) {
940 const me = this;
941 const dataset = me.getDataset();
942 let context;
943 if (index >= 0 && index < me._cachedMeta.data.length) {
944 const element = me._cachedMeta.data[index];
945 context = element.$context ||
946 (element.$context = createDataContext(me.getContext(), index, element));
947 context.parsed = me.getParsed(index);
948 context.raw = dataset.data[index];
949 } else {
950 context = me.$context ||
951 (me.$context = createDatasetContext(me.chart.getContext(), me.index));
952 context.dataset = dataset;
953 }
954 context.active = !!active;
955 context.mode = mode;
956 return context;
957 }
958 resolveDatasetElementOptions(mode) {
959 return this._resolveElementOptions(this.datasetElementType.id, mode);
960 }
961 resolveDataElementOptions(index, mode) {
962 return this._resolveElementOptions(this.dataElementType.id, mode, index);
963 }
964 _resolveElementOptions(elementType, mode = 'default', index) {
965 const me = this;
966 const active = mode === 'active';
967 const cache = me._cachedDataOpts;
968 const cacheKey = elementType + '-' + mode;
969 const cached = cache[cacheKey];
970 const sharing = me.enableOptionSharing && defined(index);
971 if (cached) {
972 return cloneIfNotShared(cached, sharing);
973 }
974 const config = me.chart.config;
975 const scopeKeys = config.datasetElementScopeKeys(me._type, elementType);
976 const prefixes = active ? [`${elementType}Hover`, 'hover', elementType, ''] : [elementType, ''];
977 const scopes = config.getOptionScopes(me.getDataset(), scopeKeys);
978 const names = Object.keys(defaults.elements[elementType]);
979 const context = () => me.getContext(index, active);
980 const values = config.resolveNamedOptions(scopes, names, context, prefixes);
981 if (values.$shared) {
982 values.$shared = sharing;
983 cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));
984 }
985 return values;
986 }
987 _resolveAnimations(index, transition, active) {
988 const me = this;
989 const chart = me.chart;
990 const cache = me._cachedDataOpts;
991 const cacheKey = `animation-${transition}`;
992 const cached = cache[cacheKey];
993 if (cached) {
994 return cached;
995 }
996 let options;
997 if (chart.options.animation !== false) {
998 const config = me.chart.config;
999 const scopeKeys = config.datasetAnimationScopeKeys(me._type, transition);
1000 const scopes = config.getOptionScopes(me.getDataset(), scopeKeys);
1001 options = config.createResolver(scopes, me.getContext(index, active, transition));
1002 }
1003 const animations = new Animations(chart, options && options.animations);
1004 if (options && options._cacheable) {
1005 cache[cacheKey] = Object.freeze(animations);
1006 }
1007 return animations;
1008 }
1009 getSharedOptions(options) {
1010 if (!options.$shared) {
1011 return;
1012 }
1013 return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
1014 }
1015 includeOptions(mode, sharedOptions) {
1016 return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;
1017 }
1018 updateElement(element, index, properties, mode) {
1019 if (isDirectUpdateMode(mode)) {
1020 Object.assign(element, properties);
1021 } else {
1022 this._resolveAnimations(index, mode).update(element, properties);
1023 }
1024 }
1025 updateSharedOptions(sharedOptions, mode, newOptions) {
1026 if (sharedOptions && !isDirectUpdateMode(mode)) {
1027 this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);
1028 }
1029 }
1030 _setStyle(element, index, mode, active) {
1031 element.active = active;
1032 const options = this.getStyle(index, active);
1033 this._resolveAnimations(index, mode, active).update(element, {
1034 options: (!active && this.getSharedOptions(options)) || options
1035 });
1036 }
1037 removeHoverStyle(element, datasetIndex, index) {
1038 this._setStyle(element, index, 'active', false);
1039 }
1040 setHoverStyle(element, datasetIndex, index) {
1041 this._setStyle(element, index, 'active', true);
1042 }
1043 _removeDatasetHoverStyle() {
1044 const element = this._cachedMeta.dataset;
1045 if (element) {
1046 this._setStyle(element, undefined, 'active', false);
1047 }
1048 }
1049 _setDatasetHoverStyle() {
1050 const element = this._cachedMeta.dataset;
1051 if (element) {
1052 this._setStyle(element, undefined, 'active', true);
1053 }
1054 }
1055 _resyncElements(resetNewElements) {
1056 const me = this;
1057 const numMeta = me._cachedMeta.data.length;
1058 const numData = me._data.length;
1059 if (numData > numMeta) {
1060 me._insertElements(numMeta, numData - numMeta, resetNewElements);
1061 } else if (numData < numMeta) {
1062 me._removeElements(numData, numMeta - numData);
1063 }
1064 const count = Math.min(numData, numMeta);
1065 if (count) {
1066 me.parse(0, count);
1067 }
1068 }
1069 _insertElements(start, count, resetNewElements = true) {
1070 const me = this;
1071 const meta = me._cachedMeta;
1072 const data = meta.data;
1073 const end = start + count;
1074 let i;
1075 const move = (arr) => {
1076 arr.length += count;
1077 for (i = arr.length - 1; i >= end; i--) {
1078 arr[i] = arr[i - count];
1079 }
1080 };
1081 move(data);
1082 for (i = start; i < end; ++i) {
1083 data[i] = new me.dataElementType();
1084 }
1085 if (me._parsing) {
1086 move(meta._parsed);
1087 }
1088 me.parse(start, count);
1089 if (resetNewElements) {
1090 me.updateElements(data, start, count, 'reset');
1091 }
1092 }
1093 updateElements(element, start, count, mode) {}
1094 _removeElements(start, count) {
1095 const me = this;
1096 const meta = me._cachedMeta;
1097 if (me._parsing) {
1098 const removed = meta._parsed.splice(start, count);
1099 if (meta._stacked) {
1100 clearStacks(meta, removed);
1101 }
1102 }
1103 meta.data.splice(start, count);
1104 }
1105 _onDataPush() {
1106 const count = arguments.length;
1107 this._insertElements(this.getDataset().data.length - count, count);
1108 }
1109 _onDataPop() {
1110 this._removeElements(this._cachedMeta.data.length - 1, 1);
1111 }
1112 _onDataShift() {
1113 this._removeElements(0, 1);
1114 }
1115 _onDataSplice(start, count) {
1116 this._removeElements(start, count);
1117 this._insertElements(start, arguments.length - 2);
1118 }
1119 _onDataUnshift() {
1120 this._insertElements(0, arguments.length);
1121 }
1122 }
1123 DatasetController.defaults = {};
1124 DatasetController.prototype.datasetElementType = null;
1125 DatasetController.prototype.dataElementType = null;
1126
1127 function getAllScaleValues(scale) {
1128 if (!scale._cache.$bar) {
1129 const metas = scale.getMatchingVisibleMetas('bar');
1130 let values = [];
1131 for (let i = 0, ilen = metas.length; i < ilen; i++) {
1132 values = values.concat(metas[i].controller.getAllParsedValues(scale));
1133 }
1134 scale._cache.$bar = _arrayUnique(values.sort((a, b) => a - b));
1135 }
1136 return scale._cache.$bar;
1137 }
1138 function computeMinSampleSize(scale) {
1139 const values = getAllScaleValues(scale);
1140 let min = scale._length;
1141 let i, ilen, curr, prev;
1142 const updateMinAndPrev = () => {
1143 min = Math.min(min, i && Math.abs(curr - prev) || min);
1144 prev = curr;
1145 };
1146 for (i = 0, ilen = values.length; i < ilen; ++i) {
1147 curr = scale.getPixelForValue(values[i]);
1148 updateMinAndPrev();
1149 }
1150 for (i = 0, ilen = scale.ticks.length; i < ilen; ++i) {
1151 curr = scale.getPixelForTick(i);
1152 updateMinAndPrev();
1153 }
1154 return min;
1155 }
1156 function computeFitCategoryTraits(index, ruler, options, stackCount) {
1157 const thickness = options.barThickness;
1158 let size, ratio;
1159 if (isNullOrUndef(thickness)) {
1160 size = ruler.min * options.categoryPercentage;
1161 ratio = options.barPercentage;
1162 } else {
1163 size = thickness * stackCount;
1164 ratio = 1;
1165 }
1166 return {
1167 chunk: size / stackCount,
1168 ratio,
1169 start: ruler.pixels[index] - (size / 2)
1170 };
1171 }
1172 function computeFlexCategoryTraits(index, ruler, options, stackCount) {
1173 const pixels = ruler.pixels;
1174 const curr = pixels[index];
1175 let prev = index > 0 ? pixels[index - 1] : null;
1176 let next = index < pixels.length - 1 ? pixels[index + 1] : null;
1177 const percent = options.categoryPercentage;
1178 if (prev === null) {
1179 prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
1180 }
1181 if (next === null) {
1182 next = curr + curr - prev;
1183 }
1184 const start = curr - (curr - Math.min(prev, next)) / 2 * percent;
1185 const size = Math.abs(next - prev) / 2 * percent;
1186 return {
1187 chunk: size / stackCount,
1188 ratio: options.barPercentage,
1189 start
1190 };
1191 }
1192 function parseFloatBar(entry, item, vScale, i) {
1193 const startValue = vScale.parse(entry[0], i);
1194 const endValue = vScale.parse(entry[1], i);
1195 const min = Math.min(startValue, endValue);
1196 const max = Math.max(startValue, endValue);
1197 let barStart = min;
1198 let barEnd = max;
1199 if (Math.abs(min) > Math.abs(max)) {
1200 barStart = max;
1201 barEnd = min;
1202 }
1203 item[vScale.axis] = barEnd;
1204 item._custom = {
1205 barStart,
1206 barEnd,
1207 start: startValue,
1208 end: endValue,
1209 min,
1210 max
1211 };
1212 }
1213 function parseValue(entry, item, vScale, i) {
1214 if (isArray(entry)) {
1215 parseFloatBar(entry, item, vScale, i);
1216 } else {
1217 item[vScale.axis] = vScale.parse(entry, i);
1218 }
1219 return item;
1220 }
1221 function parseArrayOrPrimitive(meta, data, start, count) {
1222 const iScale = meta.iScale;
1223 const vScale = meta.vScale;
1224 const labels = iScale.getLabels();
1225 const singleScale = iScale === vScale;
1226 const parsed = [];
1227 let i, ilen, item, entry;
1228 for (i = start, ilen = start + count; i < ilen; ++i) {
1229 entry = data[i];
1230 item = {};
1231 item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
1232 parsed.push(parseValue(entry, item, vScale, i));
1233 }
1234 return parsed;
1235 }
1236 function isFloatBar(custom) {
1237 return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
1238 }
1239 class BarController extends DatasetController {
1240 parsePrimitiveData(meta, data, start, count) {
1241 return parseArrayOrPrimitive(meta, data, start, count);
1242 }
1243 parseArrayData(meta, data, start, count) {
1244 return parseArrayOrPrimitive(meta, data, start, count);
1245 }
1246 parseObjectData(meta, data, start, count) {
1247 const {iScale, vScale} = meta;
1248 const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
1249 const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
1250 const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
1251 const parsed = [];
1252 let i, ilen, item, obj;
1253 for (i = start, ilen = start + count; i < ilen; ++i) {
1254 obj = data[i];
1255 item = {};
1256 item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i);
1257 parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i));
1258 }
1259 return parsed;
1260 }
1261 updateRangeFromParsed(range, scale, parsed, stack) {
1262 super.updateRangeFromParsed(range, scale, parsed, stack);
1263 const custom = parsed._custom;
1264 if (custom && scale === this._cachedMeta.vScale) {
1265 range.min = Math.min(range.min, custom.min);
1266 range.max = Math.max(range.max, custom.max);
1267 }
1268 }
1269 getLabelAndValue(index) {
1270 const me = this;
1271 const meta = me._cachedMeta;
1272 const {iScale, vScale} = meta;
1273 const parsed = me.getParsed(index);
1274 const custom = parsed._custom;
1275 const value = isFloatBar(custom)
1276 ? '[' + custom.start + ', ' + custom.end + ']'
1277 : '' + vScale.getLabelForValue(parsed[vScale.axis]);
1278 return {
1279 label: '' + iScale.getLabelForValue(parsed[iScale.axis]),
1280 value
1281 };
1282 }
1283 initialize() {
1284 const me = this;
1285 me.enableOptionSharing = true;
1286 super.initialize();
1287 const meta = me._cachedMeta;
1288 meta.stack = me.getDataset().stack;
1289 }
1290 update(mode) {
1291 const me = this;
1292 const meta = me._cachedMeta;
1293 me.updateElements(meta.data, 0, meta.data.length, mode);
1294 }
1295 updateElements(bars, start, count, mode) {
1296 const me = this;
1297 const reset = mode === 'reset';
1298 const vScale = me._cachedMeta.vScale;
1299 const base = vScale.getBasePixel();
1300 const horizontal = vScale.isHorizontal();
1301 const ruler = me._getRuler();
1302 const firstOpts = me.resolveDataElementOptions(start, mode);
1303 const sharedOptions = me.getSharedOptions(firstOpts);
1304 const includeOptions = me.includeOptions(mode, sharedOptions);
1305 me.updateSharedOptions(sharedOptions, mode, firstOpts);
1306 for (let i = start; i < start + count; i++) {
1307 const parsed = me.getParsed(i);
1308 const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {base, head: base} : me._calculateBarValuePixels(i);
1309 const ipixels = me._calculateBarIndexPixels(i, ruler);
1310 const stack = (parsed._stacks || {})[vScale.axis];
1311 const properties = {
1312 horizontal,
1313 base: vpixels.base,
1314 enableBorderRadius: !stack || isFloatBar(parsed._custom) || (me.index === stack._top || me.index === stack._bottom),
1315 x: horizontal ? vpixels.head : ipixels.center,
1316 y: horizontal ? ipixels.center : vpixels.head,
1317 height: horizontal ? ipixels.size : undefined,
1318 width: horizontal ? undefined : ipixels.size
1319 };
1320 if (includeOptions) {
1321 properties.options = sharedOptions || me.resolveDataElementOptions(i, mode);
1322 }
1323 me.updateElement(bars[i], i, properties, mode);
1324 }
1325 }
1326 _getStacks(last, dataIndex) {
1327 const me = this;
1328 const meta = me._cachedMeta;
1329 const iScale = meta.iScale;
1330 const metasets = iScale.getMatchingVisibleMetas(me._type);
1331 const stacked = iScale.options.stacked;
1332 const ilen = metasets.length;
1333 const stacks = [];
1334 let i, item;
1335 for (i = 0; i < ilen; ++i) {
1336 item = metasets[i];
1337 if (typeof dataIndex !== 'undefined') {
1338 const val = item.controller.getParsed(dataIndex)[
1339 item.controller._cachedMeta.vScale.axis
1340 ];
1341 if (isNullOrUndef(val) || isNaN(val)) {
1342 continue;
1343 }
1344 }
1345 if (stacked === false || stacks.indexOf(item.stack) === -1 ||
1346 (stacked === undefined && item.stack === undefined)) {
1347 stacks.push(item.stack);
1348 }
1349 if (item.index === last) {
1350 break;
1351 }
1352 }
1353 if (!stacks.length) {
1354 stacks.push(undefined);
1355 }
1356 return stacks;
1357 }
1358 _getStackCount(index) {
1359 return this._getStacks(undefined, index).length;
1360 }
1361 _getStackIndex(datasetIndex, name, dataIndex) {
1362 const stacks = this._getStacks(datasetIndex, dataIndex);
1363 const index = (name !== undefined)
1364 ? stacks.indexOf(name)
1365 : -1;
1366 return (index === -1)
1367 ? stacks.length - 1
1368 : index;
1369 }
1370 _getRuler() {
1371 const me = this;
1372 const opts = me.options;
1373 const meta = me._cachedMeta;
1374 const iScale = meta.iScale;
1375 const pixels = [];
1376 let i, ilen;
1377 for (i = 0, ilen = meta.data.length; i < ilen; ++i) {
1378 pixels.push(iScale.getPixelForValue(me.getParsed(i)[iScale.axis], i));
1379 }
1380 const barThickness = opts.barThickness;
1381 const min = barThickness || computeMinSampleSize(iScale);
1382 return {
1383 min,
1384 pixels,
1385 start: iScale._startPixel,
1386 end: iScale._endPixel,
1387 stackCount: me._getStackCount(),
1388 scale: iScale,
1389 grouped: opts.grouped,
1390 ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage
1391 };
1392 }
1393 _calculateBarValuePixels(index) {
1394 const me = this;
1395 const {vScale, _stacked} = me._cachedMeta;
1396 const {base: baseValue, minBarLength} = me.options;
1397 const parsed = me.getParsed(index);
1398 const custom = parsed._custom;
1399 const floating = isFloatBar(custom);
1400 let value = parsed[vScale.axis];
1401 let start = 0;
1402 let length = _stacked ? me.applyStack(vScale, parsed, _stacked) : value;
1403 let head, size;
1404 if (length !== value) {
1405 start = length - value;
1406 length = value;
1407 }
1408 if (floating) {
1409 value = custom.barStart;
1410 length = custom.barEnd - custom.barStart;
1411 if (value !== 0 && sign(value) !== sign(custom.barEnd)) {
1412 start = 0;
1413 }
1414 start += value;
1415 }
1416 const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start;
1417 let base = vScale.getPixelForValue(startValue);
1418 if (this.chart.getDataVisibility(index)) {
1419 head = vScale.getPixelForValue(start + length);
1420 } else {
1421 head = base;
1422 }
1423 size = head - base;
1424 if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
1425 size = size < 0 ? -minBarLength : minBarLength;
1426 if (value === 0) {
1427 base -= size / 2;
1428 }
1429 head = base + size;
1430 }
1431 const actualBase = baseValue || 0;
1432 if (base === vScale.getPixelForValue(actualBase)) {
1433 const halfGrid = vScale.getLineWidthForValue(actualBase) / 2;
1434 if (size > 0) {
1435 base += halfGrid;
1436 size -= halfGrid;
1437 } else if (size < 0) {
1438 base -= halfGrid;
1439 size += halfGrid;
1440 }
1441 }
1442 return {
1443 size,
1444 base,
1445 head,
1446 center: head + size / 2
1447 };
1448 }
1449 _calculateBarIndexPixels(index, ruler) {
1450 const me = this;
1451 const scale = ruler.scale;
1452 const options = me.options;
1453 const skipNull = options.skipNull;
1454 const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity);
1455 let center, size;
1456 if (ruler.grouped) {
1457 const stackCount = skipNull ? me._getStackCount(index) : ruler.stackCount;
1458 const range = options.barThickness === 'flex'
1459 ? computeFlexCategoryTraits(index, ruler, options, stackCount)
1460 : computeFitCategoryTraits(index, ruler, options, stackCount);
1461 const stackIndex = me._getStackIndex(me.index, me._cachedMeta.stack, skipNull ? index : undefined);
1462 center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
1463 size = Math.min(maxBarThickness, range.chunk * range.ratio);
1464 } else {
1465 center = scale.getPixelForValue(me.getParsed(index)[scale.axis], index);
1466 size = Math.min(maxBarThickness, ruler.min * ruler.ratio);
1467 }
1468 return {
1469 base: center - size / 2,
1470 head: center + size / 2,
1471 center,
1472 size
1473 };
1474 }
1475 draw() {
1476 const me = this;
1477 const chart = me.chart;
1478 const meta = me._cachedMeta;
1479 const vScale = meta.vScale;
1480 const rects = meta.data;
1481 const ilen = rects.length;
1482 let i = 0;
1483 clipArea(chart.ctx, chart.chartArea);
1484 for (; i < ilen; ++i) {
1485 if (me.getParsed(i)[vScale.axis] !== null) {
1486 rects[i].draw(me._ctx);
1487 }
1488 }
1489 unclipArea(chart.ctx);
1490 }
1491 }
1492 BarController.id = 'bar';
1493 BarController.defaults = {
1494 datasetElementType: false,
1495 dataElementType: 'bar',
1496 categoryPercentage: 0.8,
1497 barPercentage: 0.9,
1498 grouped: true,
1499 animations: {
1500 numbers: {
1501 type: 'number',
1502 properties: ['x', 'y', 'base', 'width', 'height']
1503 }
1504 }
1505 };
1506 BarController.overrides = {
1507 interaction: {
1508 mode: 'index'
1509 },
1510 scales: {
1511 _index_: {
1512 type: 'category',
1513 offset: true,
1514 grid: {
1515 offset: true
1516 }
1517 },
1518 _value_: {
1519 type: 'linear',
1520 beginAtZero: true,
1521 }
1522 }
1523 };
1524
1525 class BubbleController extends DatasetController {
1526 initialize() {
1527 this.enableOptionSharing = true;
1528 super.initialize();
1529 }
1530 parseObjectData(meta, data, start, count) {
1531 const {xScale, yScale} = meta;
1532 const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
1533 const parsed = [];
1534 let i, ilen, item;
1535 for (i = start, ilen = start + count; i < ilen; ++i) {
1536 item = data[i];
1537 parsed.push({
1538 x: xScale.parse(resolveObjectKey(item, xAxisKey), i),
1539 y: yScale.parse(resolveObjectKey(item, yAxisKey), i),
1540 _custom: item && item.r && +item.r
1541 });
1542 }
1543 return parsed;
1544 }
1545 getMaxOverflow() {
1546 const {data, _parsed} = this._cachedMeta;
1547 let max = 0;
1548 for (let i = data.length - 1; i >= 0; --i) {
1549 max = Math.max(max, data[i].size() / 2, _parsed[i]._custom);
1550 }
1551 return max > 0 && max;
1552 }
1553 getLabelAndValue(index) {
1554 const me = this;
1555 const meta = me._cachedMeta;
1556 const {xScale, yScale} = meta;
1557 const parsed = me.getParsed(index);
1558 const x = xScale.getLabelForValue(parsed.x);
1559 const y = yScale.getLabelForValue(parsed.y);
1560 const r = parsed._custom;
1561 return {
1562 label: meta.label,
1563 value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'
1564 };
1565 }
1566 update(mode) {
1567 const me = this;
1568 const points = me._cachedMeta.data;
1569 me.updateElements(points, 0, points.length, mode);
1570 }
1571 updateElements(points, start, count, mode) {
1572 const me = this;
1573 const reset = mode === 'reset';
1574 const {xScale, yScale} = me._cachedMeta;
1575 const firstOpts = me.resolveDataElementOptions(start, mode);
1576 const sharedOptions = me.getSharedOptions(firstOpts);
1577 const includeOptions = me.includeOptions(mode, sharedOptions);
1578 for (let i = start; i < start + count; i++) {
1579 const point = points[i];
1580 const parsed = !reset && me.getParsed(i);
1581 const x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(parsed.x);
1582 const y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(parsed.y);
1583 const properties = {
1584 x,
1585 y,
1586 skip: isNaN(x) || isNaN(y)
1587 };
1588 if (includeOptions) {
1589 properties.options = me.resolveDataElementOptions(i, mode);
1590 if (reset) {
1591 properties.options.radius = 0;
1592 }
1593 }
1594 me.updateElement(point, i, properties, mode);
1595 }
1596 me.updateSharedOptions(sharedOptions, mode, firstOpts);
1597 }
1598 resolveDataElementOptions(index, mode) {
1599 const parsed = this.getParsed(index);
1600 let values = super.resolveDataElementOptions(index, mode);
1601 if (values.$shared) {
1602 values = Object.assign({}, values, {$shared: false});
1603 }
1604 const radius = values.radius;
1605 if (mode !== 'active') {
1606 values.radius = 0;
1607 }
1608 values.radius += valueOrDefault(parsed && parsed._custom, radius);
1609 return values;
1610 }
1611 }
1612 BubbleController.id = 'bubble';
1613 BubbleController.defaults = {
1614 datasetElementType: false,
1615 dataElementType: 'point',
1616 animations: {
1617 numbers: {
1618 type: 'number',
1619 properties: ['x', 'y', 'borderWidth', 'radius']
1620 }
1621 }
1622 };
1623 BubbleController.overrides = {
1624 scales: {
1625 x: {
1626 type: 'linear'
1627 },
1628 y: {
1629 type: 'linear'
1630 }
1631 },
1632 plugins: {
1633 tooltip: {
1634 callbacks: {
1635 title() {
1636 return '';
1637 }
1638 }
1639 }
1640 }
1641 };
1642
1643 function getRatioAndOffset(rotation, circumference, cutout) {
1644 let ratioX = 1;
1645 let ratioY = 1;
1646 let offsetX = 0;
1647 let offsetY = 0;
1648 if (circumference < TAU) {
1649 const startAngle = rotation;
1650 const endAngle = startAngle + circumference;
1651 const startX = Math.cos(startAngle);
1652 const startY = Math.sin(startAngle);
1653 const endX = Math.cos(endAngle);
1654 const endY = Math.sin(endAngle);
1655 const calcMax = (angle, a, b) => _angleBetween(angle, startAngle, endAngle) ? 1 : Math.max(a, a * cutout, b, b * cutout);
1656 const calcMin = (angle, a, b) => _angleBetween(angle, startAngle, endAngle) ? -1 : Math.min(a, a * cutout, b, b * cutout);
1657 const maxX = calcMax(0, startX, endX);
1658 const maxY = calcMax(HALF_PI, startY, endY);
1659 const minX = calcMin(PI, startX, endX);
1660 const minY = calcMin(PI + HALF_PI, startY, endY);
1661 ratioX = (maxX - minX) / 2;
1662 ratioY = (maxY - minY) / 2;
1663 offsetX = -(maxX + minX) / 2;
1664 offsetY = -(maxY + minY) / 2;
1665 }
1666 return {ratioX, ratioY, offsetX, offsetY};
1667 }
1668 class DoughnutController extends DatasetController {
1669 constructor(chart, datasetIndex) {
1670 super(chart, datasetIndex);
1671 this.enableOptionSharing = true;
1672 this.innerRadius = undefined;
1673 this.outerRadius = undefined;
1674 this.offsetX = undefined;
1675 this.offsetY = undefined;
1676 }
1677 linkScales() {}
1678 parse(start, count) {
1679 const data = this.getDataset().data;
1680 const meta = this._cachedMeta;
1681 let i, ilen;
1682 for (i = start, ilen = start + count; i < ilen; ++i) {
1683 meta._parsed[i] = +data[i];
1684 }
1685 }
1686 _getRotation() {
1687 return toRadians(this.options.rotation - 90);
1688 }
1689 _getCircumference() {
1690 return toRadians(this.options.circumference);
1691 }
1692 _getRotationExtents() {
1693 let min = TAU;
1694 let max = -TAU;
1695 const me = this;
1696 for (let i = 0; i < me.chart.data.datasets.length; ++i) {
1697 if (me.chart.isDatasetVisible(i)) {
1698 const controller = me.chart.getDatasetMeta(i).controller;
1699 const rotation = controller._getRotation();
1700 const circumference = controller._getCircumference();
1701 min = Math.min(min, rotation);
1702 max = Math.max(max, rotation + circumference);
1703 }
1704 }
1705 return {
1706 rotation: min,
1707 circumference: max - min,
1708 };
1709 }
1710 update(mode) {
1711 const me = this;
1712 const chart = me.chart;
1713 const {chartArea} = chart;
1714 const meta = me._cachedMeta;
1715 const arcs = meta.data;
1716 const spacing = me.getMaxBorderWidth() + me.getMaxOffset(arcs);
1717 const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
1718 const cutout = Math.min(toPercentage(me.options.cutout, maxSize), 1);
1719 const chartWeight = me._getRingWeight(me.index);
1720 const {circumference, rotation} = me._getRotationExtents();
1721 const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(rotation, circumference, cutout);
1722 const maxWidth = (chartArea.width - spacing) / ratioX;
1723 const maxHeight = (chartArea.height - spacing) / ratioY;
1724 const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
1725 const outerRadius = toDimension(me.options.radius, maxRadius);
1726 const innerRadius = Math.max(outerRadius * cutout, 0);
1727 const radiusLength = (outerRadius - innerRadius) / me._getVisibleDatasetWeightTotal();
1728 me.offsetX = offsetX * outerRadius;
1729 me.offsetY = offsetY * outerRadius;
1730 meta.total = me.calculateTotal();
1731 me.outerRadius = outerRadius - radiusLength * me._getRingWeightOffset(me.index);
1732 me.innerRadius = Math.max(me.outerRadius - radiusLength * chartWeight, 0);
1733 me.updateElements(arcs, 0, arcs.length, mode);
1734 }
1735 _circumference(i, reset) {
1736 const me = this;
1737 const opts = me.options;
1738 const meta = me._cachedMeta;
1739 const circumference = me._getCircumference();
1740 if ((reset && opts.animation.animateRotate) || !this.chart.getDataVisibility(i) || meta._parsed[i] === null) {
1741 return 0;
1742 }
1743 return me.calculateCircumference(meta._parsed[i] * circumference / TAU);
1744 }
1745 updateElements(arcs, start, count, mode) {
1746 const me = this;
1747 const reset = mode === 'reset';
1748 const chart = me.chart;
1749 const chartArea = chart.chartArea;
1750 const opts = chart.options;
1751 const animationOpts = opts.animation;
1752 const centerX = (chartArea.left + chartArea.right) / 2;
1753 const centerY = (chartArea.top + chartArea.bottom) / 2;
1754 const animateScale = reset && animationOpts.animateScale;
1755 const innerRadius = animateScale ? 0 : me.innerRadius;
1756 const outerRadius = animateScale ? 0 : me.outerRadius;
1757 const firstOpts = me.resolveDataElementOptions(start, mode);
1758 const sharedOptions = me.getSharedOptions(firstOpts);
1759 const includeOptions = me.includeOptions(mode, sharedOptions);
1760 let startAngle = me._getRotation();
1761 let i;
1762 for (i = 0; i < start; ++i) {
1763 startAngle += me._circumference(i, reset);
1764 }
1765 for (i = start; i < start + count; ++i) {
1766 const circumference = me._circumference(i, reset);
1767 const arc = arcs[i];
1768 const properties = {
1769 x: centerX + me.offsetX,
1770 y: centerY + me.offsetY,
1771 startAngle,
1772 endAngle: startAngle + circumference,
1773 circumference,
1774 outerRadius,
1775 innerRadius
1776 };
1777 if (includeOptions) {
1778 properties.options = sharedOptions || me.resolveDataElementOptions(i, mode);
1779 }
1780 startAngle += circumference;
1781 me.updateElement(arc, i, properties, mode);
1782 }
1783 me.updateSharedOptions(sharedOptions, mode, firstOpts);
1784 }
1785 calculateTotal() {
1786 const meta = this._cachedMeta;
1787 const metaData = meta.data;
1788 let total = 0;
1789 let i;
1790 for (i = 0; i < metaData.length; i++) {
1791 const value = meta._parsed[i];
1792 if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i)) {
1793 total += Math.abs(value);
1794 }
1795 }
1796 return total;
1797 }
1798 calculateCircumference(value) {
1799 const total = this._cachedMeta.total;
1800 if (total > 0 && !isNaN(value)) {
1801 return TAU * (Math.abs(value) / total);
1802 }
1803 return 0;
1804 }
1805 getLabelAndValue(index) {
1806 const me = this;
1807 const meta = me._cachedMeta;
1808 const chart = me.chart;
1809 const labels = chart.data.labels || [];
1810 const value = formatNumber(meta._parsed[index], chart.options.locale);
1811 return {
1812 label: labels[index] || '',
1813 value,
1814 };
1815 }
1816 getMaxBorderWidth(arcs) {
1817 const me = this;
1818 let max = 0;
1819 const chart = me.chart;
1820 let i, ilen, meta, controller, options;
1821 if (!arcs) {
1822 for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
1823 if (chart.isDatasetVisible(i)) {
1824 meta = chart.getDatasetMeta(i);
1825 arcs = meta.data;
1826 controller = meta.controller;
1827 if (controller !== me) {
1828 controller.configure();
1829 }
1830 break;
1831 }
1832 }
1833 }
1834 if (!arcs) {
1835 return 0;
1836 }
1837 for (i = 0, ilen = arcs.length; i < ilen; ++i) {
1838 options = controller.resolveDataElementOptions(i);
1839 if (options.borderAlign !== 'inner') {
1840 max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
1841 }
1842 }
1843 return max;
1844 }
1845 getMaxOffset(arcs) {
1846 let max = 0;
1847 for (let i = 0, ilen = arcs.length; i < ilen; ++i) {
1848 const options = this.resolveDataElementOptions(i);
1849 max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
1850 }
1851 return max;
1852 }
1853 _getRingWeightOffset(datasetIndex) {
1854 let ringWeightOffset = 0;
1855 for (let i = 0; i < datasetIndex; ++i) {
1856 if (this.chart.isDatasetVisible(i)) {
1857 ringWeightOffset += this._getRingWeight(i);
1858 }
1859 }
1860 return ringWeightOffset;
1861 }
1862 _getRingWeight(datasetIndex) {
1863 return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
1864 }
1865 _getVisibleDatasetWeightTotal() {
1866 return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
1867 }
1868 }
1869 DoughnutController.id = 'doughnut';
1870 DoughnutController.defaults = {
1871 datasetElementType: false,
1872 dataElementType: 'arc',
1873 animation: {
1874 animateRotate: true,
1875 animateScale: false
1876 },
1877 animations: {
1878 numbers: {
1879 type: 'number',
1880 properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth']
1881 },
1882 },
1883 cutout: '50%',
1884 rotation: 0,
1885 circumference: 360,
1886 radius: '100%',
1887 indexAxis: 'r',
1888 };
1889 DoughnutController.overrides = {
1890 aspectRatio: 1,
1891 plugins: {
1892 legend: {
1893 labels: {
1894 generateLabels(chart) {
1895 const data = chart.data;
1896 if (data.labels.length && data.datasets.length) {
1897 return data.labels.map((label, i) => {
1898 const meta = chart.getDatasetMeta(0);
1899 const style = meta.controller.getStyle(i);
1900 return {
1901 text: label,
1902 fillStyle: style.backgroundColor,
1903 strokeStyle: style.borderColor,
1904 lineWidth: style.borderWidth,
1905 hidden: !chart.getDataVisibility(i),
1906 index: i
1907 };
1908 });
1909 }
1910 return [];
1911 }
1912 },
1913 onClick(e, legendItem, legend) {
1914 legend.chart.toggleDataVisibility(legendItem.index);
1915 legend.chart.update();
1916 }
1917 },
1918 tooltip: {
1919 callbacks: {
1920 title() {
1921 return '';
1922 },
1923 label(tooltipItem) {
1924 let dataLabel = tooltipItem.label;
1925 const value = ': ' + tooltipItem.formattedValue;
1926 if (isArray(dataLabel)) {
1927 dataLabel = dataLabel.slice();
1928 dataLabel[0] += value;
1929 } else {
1930 dataLabel += value;
1931 }
1932 return dataLabel;
1933 }
1934 }
1935 }
1936 }
1937 };
1938
1939 class LineController extends DatasetController {
1940 initialize() {
1941 this.enableOptionSharing = true;
1942 super.initialize();
1943 }
1944 update(mode) {
1945 const me = this;
1946 const meta = me._cachedMeta;
1947 const {dataset: line, data: points = [], _dataset} = meta;
1948 const animationsDisabled = me.chart._animationsDisabled;
1949 let {start, count} = getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
1950 me._drawStart = start;
1951 me._drawCount = count;
1952 if (scaleRangesChanged(meta)) {
1953 start = 0;
1954 count = points.length;
1955 }
1956 line._decimated = !!_dataset._decimated;
1957 line.points = points;
1958 const options = me.resolveDatasetElementOptions(mode);
1959 if (!me.options.showLine) {
1960 options.borderWidth = 0;
1961 }
1962 options.segment = me.options.segment;
1963 me.updateElement(line, undefined, {
1964 animated: !animationsDisabled,
1965 options
1966 }, mode);
1967 me.updateElements(points, start, count, mode);
1968 }
1969 updateElements(points, start, count, mode) {
1970 const me = this;
1971 const reset = mode === 'reset';
1972 const {xScale, yScale, _stacked} = me._cachedMeta;
1973 const firstOpts = me.resolveDataElementOptions(start, mode);
1974 const sharedOptions = me.getSharedOptions(firstOpts);
1975 const includeOptions = me.includeOptions(mode, sharedOptions);
1976 const spanGaps = me.options.spanGaps;
1977 const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
1978 const directUpdate = me.chart._animationsDisabled || reset || mode === 'none';
1979 let prevParsed = start > 0 && me.getParsed(start - 1);
1980 for (let i = start; i < start + count; ++i) {
1981 const point = points[i];
1982 const parsed = me.getParsed(i);
1983 const properties = directUpdate ? point : {};
1984 const nullData = isNullOrUndef(parsed.y);
1985 const x = properties.x = xScale.getPixelForValue(parsed.x, i);
1986 const y = properties.y = reset || nullData ? yScale.getBasePixel() : yScale.getPixelForValue(_stacked ? me.applyStack(yScale, parsed, _stacked) : parsed.y, i);
1987 properties.skip = isNaN(x) || isNaN(y) || nullData;
1988 properties.stop = i > 0 && (parsed.x - prevParsed.x) > maxGapLength;
1989 properties.parsed = parsed;
1990 if (includeOptions) {
1991 properties.options = sharedOptions || me.resolveDataElementOptions(i, mode);
1992 }
1993 if (!directUpdate) {
1994 me.updateElement(point, i, properties, mode);
1995 }
1996 prevParsed = parsed;
1997 }
1998 me.updateSharedOptions(sharedOptions, mode, firstOpts);
1999 }
2000 getMaxOverflow() {
2001 const me = this;
2002 const meta = me._cachedMeta;
2003 const dataset = meta.dataset;
2004 const border = dataset.options && dataset.options.borderWidth || 0;
2005 const data = meta.data || [];
2006 if (!data.length) {
2007 return border;
2008 }
2009 const firstPoint = data[0].size(me.resolveDataElementOptions(0));
2010 const lastPoint = data[data.length - 1].size(me.resolveDataElementOptions(data.length - 1));
2011 return Math.max(border, firstPoint, lastPoint) / 2;
2012 }
2013 draw() {
2014 this._cachedMeta.dataset.updateControlPoints(this.chart.chartArea);
2015 super.draw();
2016 }
2017 }
2018 LineController.id = 'line';
2019 LineController.defaults = {
2020 datasetElementType: 'line',
2021 dataElementType: 'point',
2022 showLine: true,
2023 spanGaps: false,
2024 };
2025 LineController.overrides = {
2026 scales: {
2027 _index_: {
2028 type: 'category',
2029 },
2030 _value_: {
2031 type: 'linear',
2032 },
2033 }
2034 };
2035 function getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) {
2036 const pointCount = points.length;
2037 let start = 0;
2038 let count = pointCount;
2039 if (meta._sorted) {
2040 const {iScale, _parsed} = meta;
2041 const axis = iScale.axis;
2042 const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
2043 if (minDefined) {
2044 start = _limitValue(Math.min(
2045 _lookupByKey(_parsed, iScale.axis, min).lo,
2046 animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo),
2047 0, pointCount - 1);
2048 }
2049 if (maxDefined) {
2050 count = _limitValue(Math.max(
2051 _lookupByKey(_parsed, iScale.axis, max).hi + 1,
2052 animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max)).hi + 1),
2053 start, pointCount) - start;
2054 } else {
2055 count = pointCount - start;
2056 }
2057 }
2058 return {start, count};
2059 }
2060 function scaleRangesChanged(meta) {
2061 const {xScale, yScale, _scaleRanges} = meta;
2062 const newRanges = {
2063 xmin: xScale.min,
2064 xmax: xScale.max,
2065 ymin: yScale.min,
2066 ymax: yScale.max
2067 };
2068 if (!_scaleRanges) {
2069 meta._scaleRanges = newRanges;
2070 return true;
2071 }
2072 const changed = _scaleRanges.xmin !== xScale.min
2073 || _scaleRanges.xmax !== xScale.max
2074 || _scaleRanges.ymin !== yScale.min
2075 || _scaleRanges.ymax !== yScale.max;
2076 Object.assign(_scaleRanges, newRanges);
2077 return changed;
2078 }
2079
2080 class PolarAreaController extends DatasetController {
2081 constructor(chart, datasetIndex) {
2082 super(chart, datasetIndex);
2083 this.innerRadius = undefined;
2084 this.outerRadius = undefined;
2085 }
2086 update(mode) {
2087 const arcs = this._cachedMeta.data;
2088 this._updateRadius();
2089 this.updateElements(arcs, 0, arcs.length, mode);
2090 }
2091 _updateRadius() {
2092 const me = this;
2093 const chart = me.chart;
2094 const chartArea = chart.chartArea;
2095 const opts = chart.options;
2096 const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
2097 const outerRadius = Math.max(minSize / 2, 0);
2098 const innerRadius = Math.max(opts.cutoutPercentage ? (outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
2099 const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
2100 me.outerRadius = outerRadius - (radiusLength * me.index);
2101 me.innerRadius = me.outerRadius - radiusLength;
2102 }
2103 updateElements(arcs, start, count, mode) {
2104 const me = this;
2105 const reset = mode === 'reset';
2106 const chart = me.chart;
2107 const dataset = me.getDataset();
2108 const opts = chart.options;
2109 const animationOpts = opts.animation;
2110 const scale = me._cachedMeta.rScale;
2111 const centerX = scale.xCenter;
2112 const centerY = scale.yCenter;
2113 const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI;
2114 let angle = datasetStartAngle;
2115 let i;
2116 const defaultAngle = 360 / me.countVisibleElements();
2117 for (i = 0; i < start; ++i) {
2118 angle += me._computeAngle(i, mode, defaultAngle);
2119 }
2120 for (i = start; i < start + count; i++) {
2121 const arc = arcs[i];
2122 let startAngle = angle;
2123 let endAngle = angle + me._computeAngle(i, mode, defaultAngle);
2124 let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(dataset.data[i]) : 0;
2125 angle = endAngle;
2126 if (reset) {
2127 if (animationOpts.animateScale) {
2128 outerRadius = 0;
2129 }
2130 if (animationOpts.animateRotate) {
2131 startAngle = endAngle = datasetStartAngle;
2132 }
2133 }
2134 const properties = {
2135 x: centerX,
2136 y: centerY,
2137 innerRadius: 0,
2138 outerRadius,
2139 startAngle,
2140 endAngle,
2141 options: me.resolveDataElementOptions(i, mode)
2142 };
2143 me.updateElement(arc, i, properties, mode);
2144 }
2145 }
2146 countVisibleElements() {
2147 const dataset = this.getDataset();
2148 const meta = this._cachedMeta;
2149 let count = 0;
2150 meta.data.forEach((element, index) => {
2151 if (!isNaN(dataset.data[index]) && this.chart.getDataVisibility(index)) {
2152 count++;
2153 }
2154 });
2155 return count;
2156 }
2157 _computeAngle(index, mode, defaultAngle) {
2158 return this.chart.getDataVisibility(index)
2159 ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle)
2160 : 0;
2161 }
2162 }
2163 PolarAreaController.id = 'polarArea';
2164 PolarAreaController.defaults = {
2165 dataElementType: 'arc',
2166 animation: {
2167 animateRotate: true,
2168 animateScale: true
2169 },
2170 animations: {
2171 numbers: {
2172 type: 'number',
2173 properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
2174 },
2175 },
2176 indexAxis: 'r',
2177 startAngle: 0,
2178 };
2179 PolarAreaController.overrides = {
2180 aspectRatio: 1,
2181 plugins: {
2182 legend: {
2183 labels: {
2184 generateLabels(chart) {
2185 const data = chart.data;
2186 if (data.labels.length && data.datasets.length) {
2187 return data.labels.map((label, i) => {
2188 const meta = chart.getDatasetMeta(0);
2189 const style = meta.controller.getStyle(i);
2190 return {
2191 text: label,
2192 fillStyle: style.backgroundColor,
2193 strokeStyle: style.borderColor,
2194 lineWidth: style.borderWidth,
2195 hidden: !chart.getDataVisibility(i),
2196 index: i
2197 };
2198 });
2199 }
2200 return [];
2201 }
2202 },
2203 onClick(e, legendItem, legend) {
2204 legend.chart.toggleDataVisibility(legendItem.index);
2205 legend.chart.update();
2206 }
2207 },
2208 tooltip: {
2209 callbacks: {
2210 title() {
2211 return '';
2212 },
2213 label(context) {
2214 return context.chart.data.labels[context.dataIndex] + ': ' + context.formattedValue;
2215 }
2216 }
2217 }
2218 },
2219 scales: {
2220 r: {
2221 type: 'radialLinear',
2222 angleLines: {
2223 display: false
2224 },
2225 beginAtZero: true,
2226 grid: {
2227 circular: true
2228 },
2229 pointLabels: {
2230 display: false
2231 },
2232 startAngle: 0
2233 }
2234 }
2235 };
2236
2237 class PieController extends DoughnutController {
2238 }
2239 PieController.id = 'pie';
2240 PieController.defaults = {
2241 cutout: 0,
2242 rotation: 0,
2243 circumference: 360,
2244 radius: '100%'
2245 };
2246
2247 class RadarController extends DatasetController {
2248 getLabelAndValue(index) {
2249 const me = this;
2250 const vScale = me._cachedMeta.vScale;
2251 const parsed = me.getParsed(index);
2252 return {
2253 label: vScale.getLabels()[index],
2254 value: '' + vScale.getLabelForValue(parsed[vScale.axis])
2255 };
2256 }
2257 update(mode) {
2258 const me = this;
2259 const meta = me._cachedMeta;
2260 const line = meta.dataset;
2261 const points = meta.data || [];
2262 const labels = meta.iScale.getLabels();
2263 line.points = points;
2264 if (mode !== 'resize') {
2265 const options = me.resolveDatasetElementOptions(mode);
2266 if (!me.options.showLine) {
2267 options.borderWidth = 0;
2268 }
2269 const properties = {
2270 _loop: true,
2271 _fullLoop: labels.length === points.length,
2272 options
2273 };
2274 me.updateElement(line, undefined, properties, mode);
2275 }
2276 me.updateElements(points, 0, points.length, mode);
2277 }
2278 updateElements(points, start, count, mode) {
2279 const me = this;
2280 const dataset = me.getDataset();
2281 const scale = me._cachedMeta.rScale;
2282 const reset = mode === 'reset';
2283 for (let i = start; i < start + count; i++) {
2284 const point = points[i];
2285 const options = me.resolveDataElementOptions(i, mode);
2286 const pointPosition = scale.getPointPositionForValue(i, dataset.data[i]);
2287 const x = reset ? scale.xCenter : pointPosition.x;
2288 const y = reset ? scale.yCenter : pointPosition.y;
2289 const properties = {
2290 x,
2291 y,
2292 angle: pointPosition.angle,
2293 skip: isNaN(x) || isNaN(y),
2294 options
2295 };
2296 me.updateElement(point, i, properties, mode);
2297 }
2298 }
2299 }
2300 RadarController.id = 'radar';
2301 RadarController.defaults = {
2302 datasetElementType: 'line',
2303 dataElementType: 'point',
2304 indexAxis: 'r',
2305 showLine: true,
2306 elements: {
2307 line: {
2308 fill: 'start'
2309 }
2310 },
2311 };
2312 RadarController.overrides = {
2313 aspectRatio: 1,
2314 scales: {
2315 r: {
2316 type: 'radialLinear',
2317 }
2318 }
2319 };
2320
2321 class ScatterController extends LineController {
2322 }
2323 ScatterController.id = 'scatter';
2324 ScatterController.defaults = {
2325 showLine: false,
2326 fill: false
2327 };
2328 ScatterController.overrides = {
2329 interaction: {
2330 mode: 'point'
2331 },
2332 plugins: {
2333 tooltip: {
2334 callbacks: {
2335 title() {
2336 return '';
2337 },
2338 label(item) {
2339 return '(' + item.label + ', ' + item.formattedValue + ')';
2340 }
2341 }
2342 }
2343 },
2344 scales: {
2345 x: {
2346 type: 'linear'
2347 },
2348 y: {
2349 type: 'linear'
2350 }
2351 }
2352 };
2353
2354 var controllers = /*#__PURE__*/Object.freeze({
2355 __proto__: null,
2356 BarController: BarController,
2357 BubbleController: BubbleController,
2358 DoughnutController: DoughnutController,
2359 LineController: LineController,
2360 PolarAreaController: PolarAreaController,
2361 PieController: PieController,
2362 RadarController: RadarController,
2363 ScatterController: ScatterController
2364 });
2365
2366 function abstract() {
2367 throw new Error('This method is not implemented: either no adapter can be found or an incomplete integration was provided.');
2368 }
2369 class DateAdapter {
2370 constructor(options) {
2371 this.options = options || {};
2372 }
2373 formats() {
2374 return abstract();
2375 }
2376 parse(value, format) {
2377 return abstract();
2378 }
2379 format(timestamp, format) {
2380 return abstract();
2381 }
2382 add(timestamp, amount, unit) {
2383 return abstract();
2384 }
2385 diff(a, b, unit) {
2386 return abstract();
2387 }
2388 startOf(timestamp, unit, weekday) {
2389 return abstract();
2390 }
2391 endOf(timestamp, unit) {
2392 return abstract();
2393 }
2394 }
2395 DateAdapter.override = function(members) {
2396 Object.assign(DateAdapter.prototype, members);
2397 };
2398 var adapters = {
2399 _date: DateAdapter
2400 };
2401
2402 function getRelativePosition(e, chart) {
2403 if ('native' in e) {
2404 return {
2405 x: e.x,
2406 y: e.y
2407 };
2408 }
2409 return getRelativePosition$1(e, chart);
2410 }
2411 function evaluateAllVisibleItems(chart, handler) {
2412 const metasets = chart.getSortedVisibleDatasetMetas();
2413 let index, data, element;
2414 for (let i = 0, ilen = metasets.length; i < ilen; ++i) {
2415 ({index, data} = metasets[i]);
2416 for (let j = 0, jlen = data.length; j < jlen; ++j) {
2417 element = data[j];
2418 if (!element.skip) {
2419 handler(element, index, j);
2420 }
2421 }
2422 }
2423 }
2424 function binarySearch(metaset, axis, value, intersect) {
2425 const {controller, data, _sorted} = metaset;
2426 const iScale = controller._cachedMeta.iScale;
2427 if (iScale && axis === iScale.axis && _sorted && data.length) {
2428 const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey;
2429 if (!intersect) {
2430 return lookupMethod(data, axis, value);
2431 } else if (controller._sharedOptions) {
2432 const el = data[0];
2433 const range = typeof el.getRange === 'function' && el.getRange(axis);
2434 if (range) {
2435 const start = lookupMethod(data, axis, value - range);
2436 const end = lookupMethod(data, axis, value + range);
2437 return {lo: start.lo, hi: end.hi};
2438 }
2439 }
2440 }
2441 return {lo: 0, hi: data.length - 1};
2442 }
2443 function optimizedEvaluateItems(chart, axis, position, handler, intersect) {
2444 const metasets = chart.getSortedVisibleDatasetMetas();
2445 const value = position[axis];
2446 for (let i = 0, ilen = metasets.length; i < ilen; ++i) {
2447 const {index, data} = metasets[i];
2448 const {lo, hi} = binarySearch(metasets[i], axis, value, intersect);
2449 for (let j = lo; j <= hi; ++j) {
2450 const element = data[j];
2451 if (!element.skip) {
2452 handler(element, index, j);
2453 }
2454 }
2455 }
2456 }
2457 function getDistanceMetricForAxis(axis) {
2458 const useX = axis.indexOf('x') !== -1;
2459 const useY = axis.indexOf('y') !== -1;
2460 return function(pt1, pt2) {
2461 const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
2462 const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
2463 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
2464 };
2465 }
2466 function getIntersectItems(chart, position, axis, useFinalPosition) {
2467 const items = [];
2468 if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) {
2469 return items;
2470 }
2471 const evaluationFunc = function(element, datasetIndex, index) {
2472 if (element.inRange(position.x, position.y, useFinalPosition)) {
2473 items.push({element, datasetIndex, index});
2474 }
2475 };
2476 optimizedEvaluateItems(chart, axis, position, evaluationFunc, true);
2477 return items;
2478 }
2479 function getNearestItems(chart, position, axis, intersect, useFinalPosition) {
2480 const distanceMetric = getDistanceMetricForAxis(axis);
2481 let minDistance = Number.POSITIVE_INFINITY;
2482 let items = [];
2483 if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) {
2484 return items;
2485 }
2486 const evaluationFunc = function(element, datasetIndex, index) {
2487 if (intersect && !element.inRange(position.x, position.y, useFinalPosition)) {
2488 return;
2489 }
2490 const center = element.getCenterPoint(useFinalPosition);
2491 const distance = distanceMetric(position, center);
2492 if (distance < minDistance) {
2493 items = [{element, datasetIndex, index}];
2494 minDistance = distance;
2495 } else if (distance === minDistance) {
2496 items.push({element, datasetIndex, index});
2497 }
2498 };
2499 optimizedEvaluateItems(chart, axis, position, evaluationFunc);
2500 return items;
2501 }
2502 function getAxisItems(chart, e, options, useFinalPosition) {
2503 const position = getRelativePosition(e, chart);
2504 const items = [];
2505 const axis = options.axis;
2506 const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
2507 let intersectsItem = false;
2508 evaluateAllVisibleItems(chart, (element, datasetIndex, index) => {
2509 if (element[rangeMethod](position[axis], useFinalPosition)) {
2510 items.push({element, datasetIndex, index});
2511 }
2512 if (element.inRange(position.x, position.y, useFinalPosition)) {
2513 intersectsItem = true;
2514 }
2515 });
2516 if (options.intersect && !intersectsItem) {
2517 return [];
2518 }
2519 return items;
2520 }
2521 var Interaction = {
2522 modes: {
2523 index(chart, e, options, useFinalPosition) {
2524 const position = getRelativePosition(e, chart);
2525 const axis = options.axis || 'x';
2526 const items = options.intersect
2527 ? getIntersectItems(chart, position, axis, useFinalPosition)
2528 : getNearestItems(chart, position, axis, false, useFinalPosition);
2529 const elements = [];
2530 if (!items.length) {
2531 return [];
2532 }
2533 chart.getSortedVisibleDatasetMetas().forEach((meta) => {
2534 const index = items[0].index;
2535 const element = meta.data[index];
2536 if (element && !element.skip) {
2537 elements.push({element, datasetIndex: meta.index, index});
2538 }
2539 });
2540 return elements;
2541 },
2542 dataset(chart, e, options, useFinalPosition) {
2543 const position = getRelativePosition(e, chart);
2544 const axis = options.axis || 'xy';
2545 let items = options.intersect
2546 ? getIntersectItems(chart, position, axis, useFinalPosition) :
2547 getNearestItems(chart, position, axis, false, useFinalPosition);
2548 if (items.length > 0) {
2549 const datasetIndex = items[0].datasetIndex;
2550 const data = chart.getDatasetMeta(datasetIndex).data;
2551 items = [];
2552 for (let i = 0; i < data.length; ++i) {
2553 items.push({element: data[i], datasetIndex, index: i});
2554 }
2555 }
2556 return items;
2557 },
2558 point(chart, e, options, useFinalPosition) {
2559 const position = getRelativePosition(e, chart);
2560 const axis = options.axis || 'xy';
2561 return getIntersectItems(chart, position, axis, useFinalPosition);
2562 },
2563 nearest(chart, e, options, useFinalPosition) {
2564 const position = getRelativePosition(e, chart);
2565 const axis = options.axis || 'xy';
2566 return getNearestItems(chart, position, axis, options.intersect, useFinalPosition);
2567 },
2568 x(chart, e, options, useFinalPosition) {
2569 options.axis = 'x';
2570 return getAxisItems(chart, e, options, useFinalPosition);
2571 },
2572 y(chart, e, options, useFinalPosition) {
2573 options.axis = 'y';
2574 return getAxisItems(chart, e, options, useFinalPosition);
2575 }
2576 }
2577 };
2578
2579 const STATIC_POSITIONS = ['left', 'top', 'right', 'bottom'];
2580 function filterByPosition(array, position) {
2581 return array.filter(v => v.pos === position);
2582 }
2583 function filterDynamicPositionByAxis(array, axis) {
2584 return array.filter(v => STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);
2585 }
2586 function sortByWeight(array, reverse) {
2587 return array.sort((a, b) => {
2588 const v0 = reverse ? b : a;
2589 const v1 = reverse ? a : b;
2590 return v0.weight === v1.weight ?
2591 v0.index - v1.index :
2592 v0.weight - v1.weight;
2593 });
2594 }
2595 function wrapBoxes(boxes) {
2596 const layoutBoxes = [];
2597 let i, ilen, box;
2598 for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) {
2599 box = boxes[i];
2600 layoutBoxes.push({
2601 index: i,
2602 box,
2603 pos: box.position,
2604 horizontal: box.isHorizontal(),
2605 weight: box.weight
2606 });
2607 }
2608 return layoutBoxes;
2609 }
2610 function setLayoutDims(layouts, params) {
2611 let i, ilen, layout;
2612 for (i = 0, ilen = layouts.length; i < ilen; ++i) {
2613 layout = layouts[i];
2614 if (layout.horizontal) {
2615 layout.width = layout.box.fullSize && params.availableWidth;
2616 layout.height = params.hBoxMaxHeight;
2617 } else {
2618 layout.width = params.vBoxMaxWidth;
2619 layout.height = layout.box.fullSize && params.availableHeight;
2620 }
2621 }
2622 }
2623 function buildLayoutBoxes(boxes) {
2624 const layoutBoxes = wrapBoxes(boxes);
2625 const fullSize = sortByWeight(layoutBoxes.filter(wrap => wrap.box.fullSize), true);
2626 const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
2627 const right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
2628 const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
2629 const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
2630 const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');
2631 const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');
2632 return {
2633 fullSize,
2634 leftAndTop: left.concat(top),
2635 rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),
2636 chartArea: filterByPosition(layoutBoxes, 'chartArea'),
2637 vertical: left.concat(right).concat(centerVertical),
2638 horizontal: top.concat(bottom).concat(centerHorizontal)
2639 };
2640 }
2641 function getCombinedMax(maxPadding, chartArea, a, b) {
2642 return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
2643 }
2644 function updateMaxPadding(maxPadding, boxPadding) {
2645 maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
2646 maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
2647 maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
2648 maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
2649 }
2650 function updateDims(chartArea, params, layout) {
2651 const box = layout.box;
2652 const maxPadding = chartArea.maxPadding;
2653 if (!isObject(layout.pos)) {
2654 if (layout.size) {
2655 chartArea[layout.pos] -= layout.size;
2656 }
2657 layout.size = layout.horizontal ? box.height : box.width;
2658 chartArea[layout.pos] += layout.size;
2659 }
2660 if (box.getPadding) {
2661 updateMaxPadding(maxPadding, box.getPadding());
2662 }
2663 const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));
2664 const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));
2665 const widthChanged = newWidth !== chartArea.w;
2666 const heightChanged = newHeight !== chartArea.h;
2667 chartArea.w = newWidth;
2668 chartArea.h = newHeight;
2669 return layout.horizontal
2670 ? {same: widthChanged, other: heightChanged}
2671 : {same: heightChanged, other: widthChanged};
2672 }
2673 function handleMaxPadding(chartArea) {
2674 const maxPadding = chartArea.maxPadding;
2675 function updatePos(pos) {
2676 const change = Math.max(maxPadding[pos] - chartArea[pos], 0);
2677 chartArea[pos] += change;
2678 return change;
2679 }
2680 chartArea.y += updatePos('top');
2681 chartArea.x += updatePos('left');
2682 updatePos('right');
2683 updatePos('bottom');
2684 }
2685 function getMargins(horizontal, chartArea) {
2686 const maxPadding = chartArea.maxPadding;
2687 function marginForPositions(positions) {
2688 const margin = {left: 0, top: 0, right: 0, bottom: 0};
2689 positions.forEach((pos) => {
2690 margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
2691 });
2692 return margin;
2693 }
2694 return horizontal
2695 ? marginForPositions(['left', 'right'])
2696 : marginForPositions(['top', 'bottom']);
2697 }
2698 function fitBoxes(boxes, chartArea, params) {
2699 const refitBoxes = [];
2700 let i, ilen, layout, box, refit, changed;
2701 for (i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i) {
2702 layout = boxes[i];
2703 box = layout.box;
2704 box.update(
2705 layout.width || chartArea.w,
2706 layout.height || chartArea.h,
2707 getMargins(layout.horizontal, chartArea)
2708 );
2709 const {same, other} = updateDims(chartArea, params, layout);
2710 refit |= same && refitBoxes.length;
2711 changed = changed || other;
2712 if (!box.fullSize) {
2713 refitBoxes.push(layout);
2714 }
2715 }
2716 return refit && fitBoxes(refitBoxes, chartArea, params) || changed;
2717 }
2718 function placeBoxes(boxes, chartArea, params) {
2719 const userPadding = params.padding;
2720 let x = chartArea.x;
2721 let y = chartArea.y;
2722 let i, ilen, layout, box;
2723 for (i = 0, ilen = boxes.length; i < ilen; ++i) {
2724 layout = boxes[i];
2725 box = layout.box;
2726 if (layout.horizontal) {
2727 box.left = box.fullSize ? userPadding.left : chartArea.left;
2728 box.right = box.fullSize ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w;
2729 box.top = y;
2730 box.bottom = y + box.height;
2731 box.width = box.right - box.left;
2732 y = box.bottom;
2733 } else {
2734 box.left = x;
2735 box.right = x + box.width;
2736 box.top = box.fullSize ? userPadding.top : chartArea.top;
2737 box.bottom = box.fullSize ? params.outerHeight - userPadding.right : chartArea.top + chartArea.h;
2738 box.height = box.bottom - box.top;
2739 x = box.right;
2740 }
2741 }
2742 chartArea.x = x;
2743 chartArea.y = y;
2744 }
2745 defaults.set('layout', {
2746 padding: {
2747 top: 0,
2748 right: 0,
2749 bottom: 0,
2750 left: 0
2751 }
2752 });
2753 var layouts = {
2754 addBox(chart, item) {
2755 if (!chart.boxes) {
2756 chart.boxes = [];
2757 }
2758 item.fullSize = item.fullSize || false;
2759 item.position = item.position || 'top';
2760 item.weight = item.weight || 0;
2761 item._layers = item._layers || function() {
2762 return [{
2763 z: 0,
2764 draw(chartArea) {
2765 item.draw(chartArea);
2766 }
2767 }];
2768 };
2769 chart.boxes.push(item);
2770 },
2771 removeBox(chart, layoutItem) {
2772 const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
2773 if (index !== -1) {
2774 chart.boxes.splice(index, 1);
2775 }
2776 },
2777 configure(chart, item, options) {
2778 item.fullSize = options.fullSize;
2779 item.position = options.position;
2780 item.weight = options.weight;
2781 },
2782 update(chart, width, height, minPadding) {
2783 if (!chart) {
2784 return;
2785 }
2786 const padding = toPadding(chart.options.layout.padding);
2787 const availableWidth = width - padding.width;
2788 const availableHeight = height - padding.height;
2789 const boxes = buildLayoutBoxes(chart.boxes);
2790 const verticalBoxes = boxes.vertical;
2791 const horizontalBoxes = boxes.horizontal;
2792 each(chart.boxes, box => {
2793 if (typeof box.beforeLayout === 'function') {
2794 box.beforeLayout();
2795 }
2796 });
2797 const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap) =>
2798 wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1;
2799 const params = Object.freeze({
2800 outerWidth: width,
2801 outerHeight: height,
2802 padding,
2803 availableWidth,
2804 availableHeight,
2805 vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
2806 hBoxMaxHeight: availableHeight / 2
2807 });
2808 const maxPadding = Object.assign({}, padding);
2809 updateMaxPadding(maxPadding, toPadding(minPadding));
2810 const chartArea = Object.assign({
2811 maxPadding,
2812 w: availableWidth,
2813 h: availableHeight,
2814 x: padding.left,
2815 y: padding.top
2816 }, padding);
2817 setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
2818 fitBoxes(boxes.fullSize, chartArea, params);
2819 fitBoxes(verticalBoxes, chartArea, params);
2820 if (fitBoxes(horizontalBoxes, chartArea, params)) {
2821 fitBoxes(verticalBoxes, chartArea, params);
2822 }
2823 handleMaxPadding(chartArea);
2824 placeBoxes(boxes.leftAndTop, chartArea, params);
2825 chartArea.x += chartArea.w;
2826 chartArea.y += chartArea.h;
2827 placeBoxes(boxes.rightAndBottom, chartArea, params);
2828 chart.chartArea = {
2829 left: chartArea.left,
2830 top: chartArea.top,
2831 right: chartArea.left + chartArea.w,
2832 bottom: chartArea.top + chartArea.h,
2833 height: chartArea.h,
2834 width: chartArea.w,
2835 };
2836 each(boxes.chartArea, (layout) => {
2837 const box = layout.box;
2838 Object.assign(box, chart.chartArea);
2839 box.update(chartArea.w, chartArea.h);
2840 });
2841 }
2842 };
2843
2844 class BasePlatform {
2845 acquireContext(canvas, aspectRatio) {}
2846 releaseContext(context) {
2847 return false;
2848 }
2849 addEventListener(chart, type, listener) {}
2850 removeEventListener(chart, type, listener) {}
2851 getDevicePixelRatio() {
2852 return 1;
2853 }
2854 getMaximumSize(element, width, height, aspectRatio) {
2855 width = Math.max(0, width || element.width);
2856 height = height || element.height;
2857 return {
2858 width,
2859 height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)
2860 };
2861 }
2862 isAttached(canvas) {
2863 return true;
2864 }
2865 }
2866
2867 class BasicPlatform extends BasePlatform {
2868 acquireContext(item) {
2869 return item && item.getContext && item.getContext('2d') || null;
2870 }
2871 }
2872
2873 const EXPANDO_KEY = '$chartjs';
2874 const EVENT_TYPES = {
2875 touchstart: 'mousedown',
2876 touchmove: 'mousemove',
2877 touchend: 'mouseup',
2878 pointerenter: 'mouseenter',
2879 pointerdown: 'mousedown',
2880 pointermove: 'mousemove',
2881 pointerup: 'mouseup',
2882 pointerleave: 'mouseout',
2883 pointerout: 'mouseout'
2884 };
2885 const isNullOrEmpty = value => value === null || value === '';
2886 function initCanvas(canvas, aspectRatio) {
2887 const style = canvas.style;
2888 const renderHeight = canvas.getAttribute('height');
2889 const renderWidth = canvas.getAttribute('width');
2890 canvas[EXPANDO_KEY] = {
2891 initial: {
2892 height: renderHeight,
2893 width: renderWidth,
2894 style: {
2895 display: style.display,
2896 height: style.height,
2897 width: style.width
2898 }
2899 }
2900 };
2901 style.display = style.display || 'block';
2902 style.boxSizing = style.boxSizing || 'border-box';
2903 if (isNullOrEmpty(renderWidth)) {
2904 const displayWidth = readUsedSize(canvas, 'width');
2905 if (displayWidth !== undefined) {
2906 canvas.width = displayWidth;
2907 }
2908 }
2909 if (isNullOrEmpty(renderHeight)) {
2910 if (canvas.style.height === '') {
2911 canvas.height = canvas.width / (aspectRatio || 2);
2912 } else {
2913 const displayHeight = readUsedSize(canvas, 'height');
2914 if (displayHeight !== undefined) {
2915 canvas.height = displayHeight;
2916 }
2917 }
2918 }
2919 return canvas;
2920 }
2921 const eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
2922 function addListener(node, type, listener) {
2923 node.addEventListener(type, listener, eventListenerOptions);
2924 }
2925 function removeListener(chart, type, listener) {
2926 chart.canvas.removeEventListener(type, listener, eventListenerOptions);
2927 }
2928 function fromNativeEvent(event, chart) {
2929 const type = EVENT_TYPES[event.type] || event.type;
2930 const {x, y} = getRelativePosition$1(event, chart);
2931 return {
2932 type,
2933 chart,
2934 native: event,
2935 x: x !== undefined ? x : null,
2936 y: y !== undefined ? y : null,
2937 };
2938 }
2939 function createAttachObserver(chart, type, listener) {
2940 const canvas = chart.canvas;
2941 const container = canvas && _getParentNode(canvas);
2942 const element = container || canvas;
2943 const observer = new MutationObserver(entries => {
2944 const parent = _getParentNode(element);
2945 entries.forEach(entry => {
2946 for (let i = 0; i < entry.addedNodes.length; i++) {
2947 const added = entry.addedNodes[i];
2948 if (added === element || added === parent) {
2949 listener(entry.target);
2950 }
2951 }
2952 });
2953 });
2954 observer.observe(document, {childList: true, subtree: true});
2955 return observer;
2956 }
2957 function createDetachObserver(chart, type, listener) {
2958 const canvas = chart.canvas;
2959 const container = canvas && _getParentNode(canvas);
2960 if (!container) {
2961 return;
2962 }
2963 const observer = new MutationObserver(entries => {
2964 entries.forEach(entry => {
2965 for (let i = 0; i < entry.removedNodes.length; i++) {
2966 if (entry.removedNodes[i] === canvas) {
2967 listener();
2968 break;
2969 }
2970 }
2971 });
2972 });
2973 observer.observe(container, {childList: true});
2974 return observer;
2975 }
2976 const drpListeningCharts = new Map();
2977 let oldDevicePixelRatio = 0;
2978 function onWindowResize() {
2979 const dpr = window.devicePixelRatio;
2980 if (dpr === oldDevicePixelRatio) {
2981 return;
2982 }
2983 oldDevicePixelRatio = dpr;
2984 drpListeningCharts.forEach((resize, chart) => {
2985 if (chart.currentDevicePixelRatio !== dpr) {
2986 resize();
2987 }
2988 });
2989 }
2990 function listenDevicePixelRatioChanges(chart, resize) {
2991 if (!drpListeningCharts.size) {
2992 window.addEventListener('resize', onWindowResize);
2993 }
2994 drpListeningCharts.set(chart, resize);
2995 }
2996 function unlistenDevicePixelRatioChanges(chart) {
2997 drpListeningCharts.delete(chart);
2998 if (!drpListeningCharts.size) {
2999 window.removeEventListener('resize', onWindowResize);
3000 }
3001 }
3002 function createResizeObserver(chart, type, listener) {
3003 const canvas = chart.canvas;
3004 const container = canvas && _getParentNode(canvas);
3005 if (!container) {
3006 return;
3007 }
3008 const resize = throttled((width, height) => {
3009 const w = container.clientWidth;
3010 listener(width, height);
3011 if (w < container.clientWidth) {
3012 listener();
3013 }
3014 }, window);
3015 const observer = new ResizeObserver(entries => {
3016 const entry = entries[0];
3017 const width = entry.contentRect.width;
3018 const height = entry.contentRect.height;
3019 if (width === 0 && height === 0) {
3020 return;
3021 }
3022 resize(width, height);
3023 });
3024 observer.observe(container);
3025 listenDevicePixelRatioChanges(chart, resize);
3026 return observer;
3027 }
3028 function releaseObserver(chart, type, observer) {
3029 if (observer) {
3030 observer.disconnect();
3031 }
3032 if (type === 'resize') {
3033 unlistenDevicePixelRatioChanges(chart);
3034 }
3035 }
3036 function createProxyAndListen(chart, type, listener) {
3037 const canvas = chart.canvas;
3038 const proxy = throttled((event) => {
3039 if (chart.ctx !== null) {
3040 listener(fromNativeEvent(event, chart));
3041 }
3042 }, chart, (args) => {
3043 const event = args[0];
3044 return [event, event.offsetX, event.offsetY];
3045 });
3046 addListener(canvas, type, proxy);
3047 return proxy;
3048 }
3049 class DomPlatform extends BasePlatform {
3050 acquireContext(canvas, aspectRatio) {
3051 const context = canvas && canvas.getContext && canvas.getContext('2d');
3052 if (context && context.canvas === canvas) {
3053 initCanvas(canvas, aspectRatio);
3054 return context;
3055 }
3056 return null;
3057 }
3058 releaseContext(context) {
3059 const canvas = context.canvas;
3060 if (!canvas[EXPANDO_KEY]) {
3061 return false;
3062 }
3063 const initial = canvas[EXPANDO_KEY].initial;
3064 ['height', 'width'].forEach((prop) => {
3065 const value = initial[prop];
3066 if (isNullOrUndef(value)) {
3067 canvas.removeAttribute(prop);
3068 } else {
3069 canvas.setAttribute(prop, value);
3070 }
3071 });
3072 const style = initial.style || {};
3073 Object.keys(style).forEach((key) => {
3074 canvas.style[key] = style[key];
3075 });
3076 canvas.width = canvas.width;
3077 delete canvas[EXPANDO_KEY];
3078 return true;
3079 }
3080 addEventListener(chart, type, listener) {
3081 this.removeEventListener(chart, type);
3082 const proxies = chart.$proxies || (chart.$proxies = {});
3083 const handlers = {
3084 attach: createAttachObserver,
3085 detach: createDetachObserver,
3086 resize: createResizeObserver
3087 };
3088 const handler = handlers[type] || createProxyAndListen;
3089 proxies[type] = handler(chart, type, listener);
3090 }
3091 removeEventListener(chart, type) {
3092 const proxies = chart.$proxies || (chart.$proxies = {});
3093 const proxy = proxies[type];
3094 if (!proxy) {
3095 return;
3096 }
3097 const handlers = {
3098 attach: releaseObserver,
3099 detach: releaseObserver,
3100 resize: releaseObserver
3101 };
3102 const handler = handlers[type] || removeListener;
3103 handler(chart, type, proxy);
3104 proxies[type] = undefined;
3105 }
3106 getDevicePixelRatio() {
3107 return window.devicePixelRatio;
3108 }
3109 getMaximumSize(canvas, width, height, aspectRatio) {
3110 return getMaximumSize(canvas, width, height, aspectRatio);
3111 }
3112 isAttached(canvas) {
3113 const container = _getParentNode(canvas);
3114 return !!(container && _getParentNode(container));
3115 }
3116 }
3117
3118 class Element {
3119 constructor() {
3120 this.x = undefined;
3121 this.y = undefined;
3122 this.active = false;
3123 this.options = undefined;
3124 this.$animations = undefined;
3125 }
3126 tooltipPosition(useFinalPosition) {
3127 const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
3128 return {x, y};
3129 }
3130 hasValue() {
3131 return isNumber(this.x) && isNumber(this.y);
3132 }
3133 getProps(props, final) {
3134 const me = this;
3135 const anims = this.$animations;
3136 if (!final || !anims) {
3137 return me;
3138 }
3139 const ret = {};
3140 props.forEach(prop => {
3141 ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : me[prop];
3142 });
3143 return ret;
3144 }
3145 }
3146 Element.defaults = {};
3147 Element.defaultRoutes = undefined;
3148
3149 const formatters = {
3150 values(value) {
3151 return isArray(value) ? value : '' + value;
3152 },
3153 numeric(tickValue, index, ticks) {
3154 if (tickValue === 0) {
3155 return '0';
3156 }
3157 const locale = this.chart.options.locale;
3158 let notation;
3159 let delta = tickValue;
3160 if (ticks.length > 1) {
3161 const maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value));
3162 if (maxTick < 1e-4 || maxTick > 1e+15) {
3163 notation = 'scientific';
3164 }
3165 delta = calculateDelta(tickValue, ticks);
3166 }
3167 const logDelta = log10(Math.abs(delta));
3168 const numDecimal = Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0);
3169 const options = {notation, minimumFractionDigits: numDecimal, maximumFractionDigits: numDecimal};
3170 Object.assign(options, this.options.ticks.format);
3171 return formatNumber(tickValue, locale, options);
3172 },
3173 logarithmic(tickValue, index, ticks) {
3174 if (tickValue === 0) {
3175 return '0';
3176 }
3177 const remain = tickValue / (Math.pow(10, Math.floor(log10(tickValue))));
3178 if (remain === 1 || remain === 2 || remain === 5) {
3179 return formatters.numeric.call(this, tickValue, index, ticks);
3180 }
3181 return '';
3182 }
3183 };
3184 function calculateDelta(tickValue, ticks) {
3185 let delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value;
3186 if (Math.abs(delta) > 1 && tickValue !== Math.floor(tickValue)) {
3187 delta = tickValue - Math.floor(tickValue);
3188 }
3189 return delta;
3190 }
3191 var Ticks = {formatters};
3192
3193 defaults.set('scale', {
3194 display: true,
3195 offset: false,
3196 reverse: false,
3197 beginAtZero: false,
3198 bounds: 'ticks',
3199 grace: 0,
3200 grid: {
3201 display: true,
3202 lineWidth: 1,
3203 drawBorder: true,
3204 drawOnChartArea: true,
3205 drawTicks: true,
3206 tickLength: 8,
3207 tickWidth: (_ctx, options) => options.lineWidth,
3208 tickColor: (_ctx, options) => options.color,
3209 offset: false,
3210 borderDash: [],
3211 borderDashOffset: 0.0,
3212 borderWidth: 1
3213 },
3214 title: {
3215 display: false,
3216 text: '',
3217 padding: {
3218 top: 4,
3219 bottom: 4
3220 }
3221 },
3222 ticks: {
3223 minRotation: 0,
3224 maxRotation: 50,
3225 mirror: false,
3226 textStrokeWidth: 0,
3227 textStrokeColor: '',
3228 padding: 3,
3229 display: true,
3230 autoSkip: true,
3231 autoSkipPadding: 3,
3232 labelOffset: 0,
3233 callback: Ticks.formatters.values,
3234 minor: {},
3235 major: {},
3236 align: 'center',
3237 crossAlign: 'near',
3238 showLabelBackdrop: false,
3239 backdropColor: 'rgba(255, 255, 255, 0.75)',
3240 backdropPadding: 2,
3241 }
3242 });
3243 defaults.route('scale.ticks', 'color', '', 'color');
3244 defaults.route('scale.grid', 'color', '', 'borderColor');
3245 defaults.route('scale.grid', 'borderColor', '', 'borderColor');
3246 defaults.route('scale.title', 'color', '', 'color');
3247 defaults.describe('scale', {
3248 _fallback: false,
3249 _scriptable: (name) => !name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser',
3250 _indexable: (name) => name !== 'borderDash' && name !== 'tickBorderDash',
3251 });
3252 defaults.describe('scales', {
3253 _fallback: 'scale',
3254 });
3255
3256 function autoSkip(scale, ticks) {
3257 const tickOpts = scale.options.ticks;
3258 const ticksLimit = tickOpts.maxTicksLimit || determineMaxTicks(scale);
3259 const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
3260 const numMajorIndices = majorIndices.length;
3261 const first = majorIndices[0];
3262 const last = majorIndices[numMajorIndices - 1];
3263 const newTicks = [];
3264 if (numMajorIndices > ticksLimit) {
3265 skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);
3266 return newTicks;
3267 }
3268 const spacing = calculateSpacing(majorIndices, ticks, ticksLimit);
3269 if (numMajorIndices > 0) {
3270 let i, ilen;
3271 const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;
3272 skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
3273 for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
3274 skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);
3275 }
3276 skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
3277 return newTicks;
3278 }
3279 skip(ticks, newTicks, spacing);
3280 return newTicks;
3281 }
3282 function determineMaxTicks(scale) {
3283 const offset = scale.options.offset;
3284 const tickLength = scale._tickSize();
3285 const maxScale = scale._length / tickLength + (offset ? 0 : 1);
3286 const maxChart = scale._maxLength / tickLength;
3287 return Math.floor(Math.min(maxScale, maxChart));
3288 }
3289 function calculateSpacing(majorIndices, ticks, ticksLimit) {
3290 const evenMajorSpacing = getEvenSpacing(majorIndices);
3291 const spacing = ticks.length / ticksLimit;
3292 if (!evenMajorSpacing) {
3293 return Math.max(spacing, 1);
3294 }
3295 const factors = _factorize(evenMajorSpacing);
3296 for (let i = 0, ilen = factors.length - 1; i < ilen; i++) {
3297 const factor = factors[i];
3298 if (factor > spacing) {
3299 return factor;
3300 }
3301 }
3302 return Math.max(spacing, 1);
3303 }
3304 function getMajorIndices(ticks) {
3305 const result = [];
3306 let i, ilen;
3307 for (i = 0, ilen = ticks.length; i < ilen; i++) {
3308 if (ticks[i].major) {
3309 result.push(i);
3310 }
3311 }
3312 return result;
3313 }
3314 function skipMajors(ticks, newTicks, majorIndices, spacing) {
3315 let count = 0;
3316 let next = majorIndices[0];
3317 let i;
3318 spacing = Math.ceil(spacing);
3319 for (i = 0; i < ticks.length; i++) {
3320 if (i === next) {
3321 newTicks.push(ticks[i]);
3322 count++;
3323 next = majorIndices[count * spacing];
3324 }
3325 }
3326 }
3327 function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
3328 const start = valueOrDefault(majorStart, 0);
3329 const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
3330 let count = 0;
3331 let length, i, next;
3332 spacing = Math.ceil(spacing);
3333 if (majorEnd) {
3334 length = majorEnd - majorStart;
3335 spacing = length / Math.floor(length / spacing);
3336 }
3337 next = start;
3338 while (next < 0) {
3339 count++;
3340 next = Math.round(start + count * spacing);
3341 }
3342 for (i = Math.max(start, 0); i < end; i++) {
3343 if (i === next) {
3344 newTicks.push(ticks[i]);
3345 count++;
3346 next = Math.round(start + count * spacing);
3347 }
3348 }
3349 }
3350 function getEvenSpacing(arr) {
3351 const len = arr.length;
3352 let i, diff;
3353 if (len < 2) {
3354 return false;
3355 }
3356 for (diff = arr[0], i = 1; i < len; ++i) {
3357 if (arr[i] - arr[i - 1] !== diff) {
3358 return false;
3359 }
3360 }
3361 return diff;
3362 }
3363
3364 const reverseAlign = (align) => align === 'left' ? 'right' : align === 'right' ? 'left' : align;
3365 const offsetFromEdge = (scale, edge, offset) => edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
3366 function sample(arr, numItems) {
3367 const result = [];
3368 const increment = arr.length / numItems;
3369 const len = arr.length;
3370 let i = 0;
3371 for (; i < len; i += increment) {
3372 result.push(arr[Math.floor(i)]);
3373 }
3374 return result;
3375 }
3376 function getPixelForGridLine(scale, index, offsetGridLines) {
3377 const length = scale.ticks.length;
3378 const validIndex = Math.min(index, length - 1);
3379 const start = scale._startPixel;
3380 const end = scale._endPixel;
3381 const epsilon = 1e-6;
3382 let lineValue = scale.getPixelForTick(validIndex);
3383 let offset;
3384 if (offsetGridLines) {
3385 if (length === 1) {
3386 offset = Math.max(lineValue - start, end - lineValue);
3387 } else if (index === 0) {
3388 offset = (scale.getPixelForTick(1) - lineValue) / 2;
3389 } else {
3390 offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
3391 }
3392 lineValue += validIndex < index ? offset : -offset;
3393 if (lineValue < start - epsilon || lineValue > end + epsilon) {
3394 return;
3395 }
3396 }
3397 return lineValue;
3398 }
3399 function garbageCollect(caches, length) {
3400 each(caches, (cache) => {
3401 const gc = cache.gc;
3402 const gcLen = gc.length / 2;
3403 let i;
3404 if (gcLen > length) {
3405 for (i = 0; i < gcLen; ++i) {
3406 delete cache.data[gc[i]];
3407 }
3408 gc.splice(0, gcLen);
3409 }
3410 });
3411 }
3412 function getTickMarkLength(options) {
3413 return options.drawTicks ? options.tickLength : 0;
3414 }
3415 function getTitleHeight(options, fallback) {
3416 if (!options.display) {
3417 return 0;
3418 }
3419 const font = toFont(options.font, fallback);
3420 const padding = toPadding(options.padding);
3421 const lines = isArray(options.text) ? options.text.length : 1;
3422 return (lines * font.lineHeight) + padding.height;
3423 }
3424 function createScaleContext(parent, scale) {
3425 return Object.assign(Object.create(parent), {
3426 scale,
3427 type: 'scale'
3428 });
3429 }
3430 function createTickContext(parent, index, tick) {
3431 return Object.assign(Object.create(parent), {
3432 tick,
3433 index,
3434 type: 'tick'
3435 });
3436 }
3437 function titleAlign(align, position, reverse) {
3438 let ret = _toLeftRightCenter(align);
3439 if ((reverse && position !== 'right') || (!reverse && position === 'right')) {
3440 ret = reverseAlign(ret);
3441 }
3442 return ret;
3443 }
3444 function titleArgs(scale, offset, position, align) {
3445 const {top, left, bottom, right} = scale;
3446 let rotation = 0;
3447 let maxWidth, titleX, titleY;
3448 if (scale.isHorizontal()) {
3449 titleX = _alignStartEnd(align, left, right);
3450 titleY = offsetFromEdge(scale, position, offset);
3451 maxWidth = right - left;
3452 } else {
3453 titleX = offsetFromEdge(scale, position, offset);
3454 titleY = _alignStartEnd(align, bottom, top);
3455 rotation = position === 'left' ? -HALF_PI : HALF_PI;
3456 }
3457 return {titleX, titleY, maxWidth, rotation};
3458 }
3459 class Scale extends Element {
3460 constructor(cfg) {
3461 super();
3462 this.id = cfg.id;
3463 this.type = cfg.type;
3464 this.options = undefined;
3465 this.ctx = cfg.ctx;
3466 this.chart = cfg.chart;
3467 this.top = undefined;
3468 this.bottom = undefined;
3469 this.left = undefined;
3470 this.right = undefined;
3471 this.width = undefined;
3472 this.height = undefined;
3473 this._margins = {
3474 left: 0,
3475 right: 0,
3476 top: 0,
3477 bottom: 0
3478 };
3479 this.maxWidth = undefined;
3480 this.maxHeight = undefined;
3481 this.paddingTop = undefined;
3482 this.paddingBottom = undefined;
3483 this.paddingLeft = undefined;
3484 this.paddingRight = undefined;
3485 this.axis = undefined;
3486 this.labelRotation = undefined;
3487 this.min = undefined;
3488 this.max = undefined;
3489 this._range = undefined;
3490 this.ticks = [];
3491 this._gridLineItems = null;
3492 this._labelItems = null;
3493 this._labelSizes = null;
3494 this._length = 0;
3495 this._maxLength = 0;
3496 this._longestTextCache = {};
3497 this._startPixel = undefined;
3498 this._endPixel = undefined;
3499 this._reversePixels = false;
3500 this._userMax = undefined;
3501 this._userMin = undefined;
3502 this._suggestedMax = undefined;
3503 this._suggestedMin = undefined;
3504 this._ticksLength = 0;
3505 this._borderValue = 0;
3506 this._cache = {};
3507 this._dataLimitsCached = false;
3508 this.$context = undefined;
3509 }
3510 init(options) {
3511 const me = this;
3512 me.options = options.setContext(me.getContext());
3513 me.axis = options.axis;
3514 me._userMin = me.parse(options.min);
3515 me._userMax = me.parse(options.max);
3516 me._suggestedMin = me.parse(options.suggestedMin);
3517 me._suggestedMax = me.parse(options.suggestedMax);
3518 }
3519 parse(raw, index) {
3520 return raw;
3521 }
3522 getUserBounds() {
3523 let {_userMin, _userMax, _suggestedMin, _suggestedMax} = this;
3524 _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);
3525 _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);
3526 _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);
3527 _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);
3528 return {
3529 min: finiteOrDefault(_userMin, _suggestedMin),
3530 max: finiteOrDefault(_userMax, _suggestedMax),
3531 minDefined: isNumberFinite(_userMin),
3532 maxDefined: isNumberFinite(_userMax)
3533 };
3534 }
3535 getMinMax(canStack) {
3536 const me = this;
3537 let {min, max, minDefined, maxDefined} = me.getUserBounds();
3538 let range;
3539 if (minDefined && maxDefined) {
3540 return {min, max};
3541 }
3542 const metas = me.getMatchingVisibleMetas();
3543 for (let i = 0, ilen = metas.length; i < ilen; ++i) {
3544 range = metas[i].controller.getMinMax(me, canStack);
3545 if (!minDefined) {
3546 min = Math.min(min, range.min);
3547 }
3548 if (!maxDefined) {
3549 max = Math.max(max, range.max);
3550 }
3551 }
3552 return {
3553 min: finiteOrDefault(min, finiteOrDefault(max, min)),
3554 max: finiteOrDefault(max, finiteOrDefault(min, max))
3555 };
3556 }
3557 getPadding() {
3558 const me = this;
3559 return {
3560 left: me.paddingLeft || 0,
3561 top: me.paddingTop || 0,
3562 right: me.paddingRight || 0,
3563 bottom: me.paddingBottom || 0
3564 };
3565 }
3566 getTicks() {
3567 return this.ticks;
3568 }
3569 getLabels() {
3570 const data = this.chart.data;
3571 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
3572 }
3573 beforeLayout() {
3574 this._cache = {};
3575 this._dataLimitsCached = false;
3576 }
3577 beforeUpdate() {
3578 callback(this.options.beforeUpdate, [this]);
3579 }
3580 update(maxWidth, maxHeight, margins) {
3581 const me = this;
3582 const tickOpts = me.options.ticks;
3583 const sampleSize = tickOpts.sampleSize;
3584 me.beforeUpdate();
3585 me.maxWidth = maxWidth;
3586 me.maxHeight = maxHeight;
3587 me._margins = margins = Object.assign({
3588 left: 0,
3589 right: 0,
3590 top: 0,
3591 bottom: 0
3592 }, margins);
3593 me.ticks = null;
3594 me._labelSizes = null;
3595 me._gridLineItems = null;
3596 me._labelItems = null;
3597 me.beforeSetDimensions();
3598 me.setDimensions();
3599 me.afterSetDimensions();
3600 me._maxLength = me.isHorizontal()
3601 ? me.width + margins.left + margins.right
3602 : me.height + margins.top + margins.bottom;
3603 if (!me._dataLimitsCached) {
3604 me.beforeDataLimits();
3605 me.determineDataLimits();
3606 me.afterDataLimits();
3607 me._range = _addGrace(me, me.options.grace);
3608 me._dataLimitsCached = true;
3609 }
3610 me.beforeBuildTicks();
3611 me.ticks = me.buildTicks() || [];
3612 me.afterBuildTicks();
3613 const samplingEnabled = sampleSize < me.ticks.length;
3614 me._convertTicksToLabels(samplingEnabled ? sample(me.ticks, sampleSize) : me.ticks);
3615 me.configure();
3616 me.beforeCalculateLabelRotation();
3617 me.calculateLabelRotation();
3618 me.afterCalculateLabelRotation();
3619 if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
3620 me.ticks = autoSkip(me, me.ticks);
3621 me._labelSizes = null;
3622 }
3623 if (samplingEnabled) {
3624 me._convertTicksToLabels(me.ticks);
3625 }
3626 me.beforeFit();
3627 me.fit();
3628 me.afterFit();
3629 me.afterUpdate();
3630 }
3631 configure() {
3632 const me = this;
3633 let reversePixels = me.options.reverse;
3634 let startPixel, endPixel;
3635 if (me.isHorizontal()) {
3636 startPixel = me.left;
3637 endPixel = me.right;
3638 } else {
3639 startPixel = me.top;
3640 endPixel = me.bottom;
3641 reversePixels = !reversePixels;
3642 }
3643 me._startPixel = startPixel;
3644 me._endPixel = endPixel;
3645 me._reversePixels = reversePixels;
3646 me._length = endPixel - startPixel;
3647 me._alignToPixels = me.options.alignToPixels;
3648 }
3649 afterUpdate() {
3650 callback(this.options.afterUpdate, [this]);
3651 }
3652 beforeSetDimensions() {
3653 callback(this.options.beforeSetDimensions, [this]);
3654 }
3655 setDimensions() {
3656 const me = this;
3657 if (me.isHorizontal()) {
3658 me.width = me.maxWidth;
3659 me.left = 0;
3660 me.right = me.width;
3661 } else {
3662 me.height = me.maxHeight;
3663 me.top = 0;
3664 me.bottom = me.height;
3665 }
3666 me.paddingLeft = 0;
3667 me.paddingTop = 0;
3668 me.paddingRight = 0;
3669 me.paddingBottom = 0;
3670 }
3671 afterSetDimensions() {
3672 callback(this.options.afterSetDimensions, [this]);
3673 }
3674 _callHooks(name) {
3675 const me = this;
3676 me.chart.notifyPlugins(name, me.getContext());
3677 callback(me.options[name], [me]);
3678 }
3679 beforeDataLimits() {
3680 this._callHooks('beforeDataLimits');
3681 }
3682 determineDataLimits() {}
3683 afterDataLimits() {
3684 this._callHooks('afterDataLimits');
3685 }
3686 beforeBuildTicks() {
3687 this._callHooks('beforeBuildTicks');
3688 }
3689 buildTicks() {
3690 return [];
3691 }
3692 afterBuildTicks() {
3693 this._callHooks('afterBuildTicks');
3694 }
3695 beforeTickToLabelConversion() {
3696 callback(this.options.beforeTickToLabelConversion, [this]);
3697 }
3698 generateTickLabels(ticks) {
3699 const me = this;
3700 const tickOpts = me.options.ticks;
3701 let i, ilen, tick;
3702 for (i = 0, ilen = ticks.length; i < ilen; i++) {
3703 tick = ticks[i];
3704 tick.label = callback(tickOpts.callback, [tick.value, i, ticks], me);
3705 }
3706 for (i = 0; i < ilen; i++) {
3707 if (isNullOrUndef(ticks[i].label)) {
3708 ticks.splice(i, 1);
3709 ilen--;
3710 i--;
3711 }
3712 }
3713 }
3714 afterTickToLabelConversion() {
3715 callback(this.options.afterTickToLabelConversion, [this]);
3716 }
3717 beforeCalculateLabelRotation() {
3718 callback(this.options.beforeCalculateLabelRotation, [this]);
3719 }
3720 calculateLabelRotation() {
3721 const me = this;
3722 const options = me.options;
3723 const tickOpts = options.ticks;
3724 const numTicks = me.ticks.length;
3725 const minRotation = tickOpts.minRotation || 0;
3726 const maxRotation = tickOpts.maxRotation;
3727 let labelRotation = minRotation;
3728 let tickWidth, maxHeight, maxLabelDiagonal;
3729 if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) {
3730 me.labelRotation = minRotation;
3731 return;
3732 }
3733 const labelSizes = me._getLabelSizes();
3734 const maxLabelWidth = labelSizes.widest.width;
3735 const maxLabelHeight = labelSizes.highest.height;
3736 const maxWidth = _limitValue(me.chart.width - maxLabelWidth, 0, me.maxWidth);
3737 tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1);
3738 if (maxLabelWidth + 6 > tickWidth) {
3739 tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
3740 maxHeight = me.maxHeight - getTickMarkLength(options.grid)
3741 - tickOpts.padding - getTitleHeight(options.title, me.chart.options.font);
3742 maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
3743 labelRotation = toDegrees(Math.min(
3744 Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)),
3745 Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal)
3746 ));
3747 labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
3748 }
3749 me.labelRotation = labelRotation;
3750 }
3751 afterCalculateLabelRotation() {
3752 callback(this.options.afterCalculateLabelRotation, [this]);
3753 }
3754 beforeFit() {
3755 callback(this.options.beforeFit, [this]);
3756 }
3757 fit() {
3758 const me = this;
3759 const minSize = {
3760 width: 0,
3761 height: 0
3762 };
3763 const {chart, options: {ticks: tickOpts, title: titleOpts, grid: gridOpts}} = me;
3764 const display = me._isVisible();
3765 const isHorizontal = me.isHorizontal();
3766 if (display) {
3767 const titleHeight = getTitleHeight(titleOpts, chart.options.font);
3768 if (isHorizontal) {
3769 minSize.width = me.maxWidth;
3770 minSize.height = getTickMarkLength(gridOpts) + titleHeight;
3771 } else {
3772 minSize.height = me.maxHeight;
3773 minSize.width = getTickMarkLength(gridOpts) + titleHeight;
3774 }
3775 if (tickOpts.display && me.ticks.length) {
3776 const {first, last, widest, highest} = me._getLabelSizes();
3777 const tickPadding = tickOpts.padding * 2;
3778 const angleRadians = toRadians(me.labelRotation);
3779 const cos = Math.cos(angleRadians);
3780 const sin = Math.sin(angleRadians);
3781 if (isHorizontal) {
3782 const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;
3783 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
3784 } else {
3785 const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
3786 minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth + tickPadding);
3787 }
3788 me._calculatePadding(first, last, sin, cos);
3789 }
3790 }
3791 me._handleMargins();
3792 if (isHorizontal) {
3793 me.width = me._length = chart.width - me._margins.left - me._margins.right;
3794 me.height = minSize.height;
3795 } else {
3796 me.width = minSize.width;
3797 me.height = me._length = chart.height - me._margins.top - me._margins.bottom;
3798 }
3799 }
3800 _calculatePadding(first, last, sin, cos) {
3801 const me = this;
3802 const {ticks: {align, padding}, position} = me.options;
3803 const isRotated = me.labelRotation !== 0;
3804 const labelsBelowTicks = position !== 'top' && me.axis === 'x';
3805 if (me.isHorizontal()) {
3806 const offsetLeft = me.getPixelForTick(0) - me.left;
3807 const offsetRight = me.right - me.getPixelForTick(me.ticks.length - 1);
3808 let paddingLeft = 0;
3809 let paddingRight = 0;
3810 if (isRotated) {
3811 if (labelsBelowTicks) {
3812 paddingLeft = cos * first.width;
3813 paddingRight = sin * last.height;
3814 } else {
3815 paddingLeft = sin * first.height;
3816 paddingRight = cos * last.width;
3817 }
3818 } else if (align === 'start') {
3819 paddingRight = last.width;
3820 } else if (align === 'end') {
3821 paddingLeft = first.width;
3822 } else {
3823 paddingLeft = first.width / 2;
3824 paddingRight = last.width / 2;
3825 }
3826 me.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * me.width / (me.width - offsetLeft), 0);
3827 me.paddingRight = Math.max((paddingRight - offsetRight + padding) * me.width / (me.width - offsetRight), 0);
3828 } else {
3829 let paddingTop = last.height / 2;
3830 let paddingBottom = first.height / 2;
3831 if (align === 'start') {
3832 paddingTop = 0;
3833 paddingBottom = first.height;
3834 } else if (align === 'end') {
3835 paddingTop = last.height;
3836 paddingBottom = 0;
3837 }
3838 me.paddingTop = paddingTop + padding;
3839 me.paddingBottom = paddingBottom + padding;
3840 }
3841 }
3842 _handleMargins() {
3843 const me = this;
3844 if (me._margins) {
3845 me._margins.left = Math.max(me.paddingLeft, me._margins.left);
3846 me._margins.top = Math.max(me.paddingTop, me._margins.top);
3847 me._margins.right = Math.max(me.paddingRight, me._margins.right);
3848 me._margins.bottom = Math.max(me.paddingBottom, me._margins.bottom);
3849 }
3850 }
3851 afterFit() {
3852 callback(this.options.afterFit, [this]);
3853 }
3854 isHorizontal() {
3855 const {axis, position} = this.options;
3856 return position === 'top' || position === 'bottom' || axis === 'x';
3857 }
3858 isFullSize() {
3859 return this.options.fullSize;
3860 }
3861 _convertTicksToLabels(ticks) {
3862 const me = this;
3863 me.beforeTickToLabelConversion();
3864 me.generateTickLabels(ticks);
3865 me.afterTickToLabelConversion();
3866 }
3867 _getLabelSizes() {
3868 const me = this;
3869 let labelSizes = me._labelSizes;
3870 if (!labelSizes) {
3871 const sampleSize = me.options.ticks.sampleSize;
3872 let ticks = me.ticks;
3873 if (sampleSize < ticks.length) {
3874 ticks = sample(ticks, sampleSize);
3875 }
3876 me._labelSizes = labelSizes = me._computeLabelSizes(ticks, ticks.length);
3877 }
3878 return labelSizes;
3879 }
3880 _computeLabelSizes(ticks, length) {
3881 const {ctx, _longestTextCache: caches} = this;
3882 const widths = [];
3883 const heights = [];
3884 let widestLabelSize = 0;
3885 let highestLabelSize = 0;
3886 let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
3887 for (i = 0; i < length; ++i) {
3888 label = ticks[i].label;
3889 tickFont = this._resolveTickFontOptions(i);
3890 ctx.font = fontString = tickFont.string;
3891 cache = caches[fontString] = caches[fontString] || {data: {}, gc: []};
3892 lineHeight = tickFont.lineHeight;
3893 width = height = 0;
3894 if (!isNullOrUndef(label) && !isArray(label)) {
3895 width = _measureText(ctx, cache.data, cache.gc, width, label);
3896 height = lineHeight;
3897 } else if (isArray(label)) {
3898 for (j = 0, jlen = label.length; j < jlen; ++j) {
3899 nestedLabel = label[j];
3900 if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
3901 width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel);
3902 height += lineHeight;
3903 }
3904 }
3905 }
3906 widths.push(width);
3907 heights.push(height);
3908 widestLabelSize = Math.max(width, widestLabelSize);
3909 highestLabelSize = Math.max(height, highestLabelSize);
3910 }
3911 garbageCollect(caches, length);
3912 const widest = widths.indexOf(widestLabelSize);
3913 const highest = heights.indexOf(highestLabelSize);
3914 const valueAt = (idx) => ({width: widths[idx] || 0, height: heights[idx] || 0});
3915 return {
3916 first: valueAt(0),
3917 last: valueAt(length - 1),
3918 widest: valueAt(widest),
3919 highest: valueAt(highest),
3920 widths,
3921 heights,
3922 };
3923 }
3924 getLabelForValue(value) {
3925 return value;
3926 }
3927 getPixelForValue(value, index) {
3928 return NaN;
3929 }
3930 getValueForPixel(pixel) {}
3931 getPixelForTick(index) {
3932 const ticks = this.ticks;
3933 if (index < 0 || index > ticks.length - 1) {
3934 return null;
3935 }
3936 return this.getPixelForValue(ticks[index].value);
3937 }
3938 getPixelForDecimal(decimal) {
3939 const me = this;
3940 if (me._reversePixels) {
3941 decimal = 1 - decimal;
3942 }
3943 const pixel = me._startPixel + decimal * me._length;
3944 return _int16Range(me._alignToPixels ? _alignPixel(me.chart, pixel, 0) : pixel);
3945 }
3946 getDecimalForPixel(pixel) {
3947 const decimal = (pixel - this._startPixel) / this._length;
3948 return this._reversePixels ? 1 - decimal : decimal;
3949 }
3950 getBasePixel() {
3951 return this.getPixelForValue(this.getBaseValue());
3952 }
3953 getBaseValue() {
3954 const {min, max} = this;
3955 return min < 0 && max < 0 ? max :
3956 min > 0 && max > 0 ? min :
3957 0;
3958 }
3959 getContext(index) {
3960 const me = this;
3961 const ticks = me.ticks || [];
3962 if (index >= 0 && index < ticks.length) {
3963 const tick = ticks[index];
3964 return tick.$context ||
3965 (tick.$context = createTickContext(me.getContext(), index, tick));
3966 }
3967 return me.$context ||
3968 (me.$context = createScaleContext(me.chart.getContext(), me));
3969 }
3970 _tickSize() {
3971 const me = this;
3972 const optionTicks = me.options.ticks;
3973 const rot = toRadians(me.labelRotation);
3974 const cos = Math.abs(Math.cos(rot));
3975 const sin = Math.abs(Math.sin(rot));
3976 const labelSizes = me._getLabelSizes();
3977 const padding = optionTicks.autoSkipPadding || 0;
3978 const w = labelSizes ? labelSizes.widest.width + padding : 0;
3979 const h = labelSizes ? labelSizes.highest.height + padding : 0;
3980 return me.isHorizontal()
3981 ? h * cos > w * sin ? w / cos : h / sin
3982 : h * sin < w * cos ? h / cos : w / sin;
3983 }
3984 _isVisible() {
3985 const display = this.options.display;
3986 if (display !== 'auto') {
3987 return !!display;
3988 }
3989 return this.getMatchingVisibleMetas().length > 0;
3990 }
3991 _computeGridLineItems(chartArea) {
3992 const me = this;
3993 const axis = me.axis;
3994 const chart = me.chart;
3995 const options = me.options;
3996 const {grid, position} = options;
3997 const offset = grid.offset;
3998 const isHorizontal = me.isHorizontal();
3999 const ticks = me.ticks;
4000 const ticksLength = ticks.length + (offset ? 1 : 0);
4001 const tl = getTickMarkLength(grid);
4002 const items = [];
4003 const borderOpts = grid.setContext(me.getContext());
4004 const axisWidth = borderOpts.drawBorder ? borderOpts.borderWidth : 0;
4005 const axisHalfWidth = axisWidth / 2;
4006 const alignBorderValue = function(pixel) {
4007 return _alignPixel(chart, pixel, axisWidth);
4008 };
4009 let borderValue, i, lineValue, alignedLineValue;
4010 let tx1, ty1, tx2, ty2, x1, y1, x2, y2;
4011 if (position === 'top') {
4012 borderValue = alignBorderValue(me.bottom);
4013 ty1 = me.bottom - tl;
4014 ty2 = borderValue - axisHalfWidth;
4015 y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
4016 y2 = chartArea.bottom;
4017 } else if (position === 'bottom') {
4018 borderValue = alignBorderValue(me.top);
4019 y1 = chartArea.top;
4020 y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
4021 ty1 = borderValue + axisHalfWidth;
4022 ty2 = me.top + tl;
4023 } else if (position === 'left') {
4024 borderValue = alignBorderValue(me.right);
4025 tx1 = me.right - tl;
4026 tx2 = borderValue - axisHalfWidth;
4027 x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
4028 x2 = chartArea.right;
4029 } else if (position === 'right') {
4030 borderValue = alignBorderValue(me.left);
4031 x1 = chartArea.left;
4032 x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
4033 tx1 = borderValue + axisHalfWidth;
4034 tx2 = me.left + tl;
4035 } else if (axis === 'x') {
4036 if (position === 'center') {
4037 borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
4038 } else if (isObject(position)) {
4039 const positionAxisID = Object.keys(position)[0];
4040 const value = position[positionAxisID];
4041 borderValue = alignBorderValue(me.chart.scales[positionAxisID].getPixelForValue(value));
4042 }
4043 y1 = chartArea.top;
4044 y2 = chartArea.bottom;
4045 ty1 = borderValue + axisHalfWidth;
4046 ty2 = ty1 + tl;
4047 } else if (axis === 'y') {
4048 if (position === 'center') {
4049 borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);
4050 } else if (isObject(position)) {
4051 const positionAxisID = Object.keys(position)[0];
4052 const value = position[positionAxisID];
4053 borderValue = alignBorderValue(me.chart.scales[positionAxisID].getPixelForValue(value));
4054 }
4055 tx1 = borderValue - axisHalfWidth;
4056 tx2 = tx1 - tl;
4057 x1 = chartArea.left;
4058 x2 = chartArea.right;
4059 }
4060 for (i = 0; i < ticksLength; ++i) {
4061 const optsAtIndex = grid.setContext(me.getContext(i));
4062 const lineWidth = optsAtIndex.lineWidth;
4063 const lineColor = optsAtIndex.color;
4064 const borderDash = grid.borderDash || [];
4065 const borderDashOffset = optsAtIndex.borderDashOffset;
4066 const tickWidth = optsAtIndex.tickWidth;
4067 const tickColor = optsAtIndex.tickColor;
4068 const tickBorderDash = optsAtIndex.tickBorderDash || [];
4069 const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;
4070 lineValue = getPixelForGridLine(me, i, offset);
4071 if (lineValue === undefined) {
4072 continue;
4073 }
4074 alignedLineValue = _alignPixel(chart, lineValue, lineWidth);
4075 if (isHorizontal) {
4076 tx1 = tx2 = x1 = x2 = alignedLineValue;
4077 } else {
4078 ty1 = ty2 = y1 = y2 = alignedLineValue;
4079 }
4080 items.push({
4081 tx1,
4082 ty1,
4083 tx2,
4084 ty2,
4085 x1,
4086 y1,
4087 x2,
4088 y2,
4089 width: lineWidth,
4090 color: lineColor,
4091 borderDash,
4092 borderDashOffset,
4093 tickWidth,
4094 tickColor,
4095 tickBorderDash,
4096 tickBorderDashOffset,
4097 });
4098 }
4099 me._ticksLength = ticksLength;
4100 me._borderValue = borderValue;
4101 return items;
4102 }
4103 _computeLabelItems(chartArea) {
4104 const me = this;
4105 const axis = me.axis;
4106 const options = me.options;
4107 const {position, ticks: optionTicks} = options;
4108 const isHorizontal = me.isHorizontal();
4109 const ticks = me.ticks;
4110 const {align, crossAlign, padding, mirror} = optionTicks;
4111 const tl = getTickMarkLength(options.grid);
4112 const tickAndPadding = tl + padding;
4113 const hTickAndPadding = mirror ? -padding : tickAndPadding;
4114 const rotation = -toRadians(me.labelRotation);
4115 const items = [];
4116 let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
4117 let textBaseline = 'middle';
4118 if (position === 'top') {
4119 y = me.bottom - hTickAndPadding;
4120 textAlign = me._getXAxisLabelAlignment();
4121 } else if (position === 'bottom') {
4122 y = me.top + hTickAndPadding;
4123 textAlign = me._getXAxisLabelAlignment();
4124 } else if (position === 'left') {
4125 const ret = me._getYAxisLabelAlignment(tl);
4126 textAlign = ret.textAlign;
4127 x = ret.x;
4128 } else if (position === 'right') {
4129 const ret = me._getYAxisLabelAlignment(tl);
4130 textAlign = ret.textAlign;
4131 x = ret.x;
4132 } else if (axis === 'x') {
4133 if (position === 'center') {
4134 y = ((chartArea.top + chartArea.bottom) / 2) + tickAndPadding;
4135 } else if (isObject(position)) {
4136 const positionAxisID = Object.keys(position)[0];
4137 const value = position[positionAxisID];
4138 y = me.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
4139 }
4140 textAlign = me._getXAxisLabelAlignment();
4141 } else if (axis === 'y') {
4142 if (position === 'center') {
4143 x = ((chartArea.left + chartArea.right) / 2) - tickAndPadding;
4144 } else if (isObject(position)) {
4145 const positionAxisID = Object.keys(position)[0];
4146 const value = position[positionAxisID];
4147 x = me.chart.scales[positionAxisID].getPixelForValue(value);
4148 }
4149 textAlign = me._getYAxisLabelAlignment(tl).textAlign;
4150 }
4151 if (axis === 'y') {
4152 if (align === 'start') {
4153 textBaseline = 'top';
4154 } else if (align === 'end') {
4155 textBaseline = 'bottom';
4156 }
4157 }
4158 const labelSizes = me._getLabelSizes();
4159 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
4160 tick = ticks[i];
4161 label = tick.label;
4162 const optsAtIndex = optionTicks.setContext(me.getContext(i));
4163 pixel = me.getPixelForTick(i) + optionTicks.labelOffset;
4164 font = me._resolveTickFontOptions(i);
4165 lineHeight = font.lineHeight;
4166 lineCount = isArray(label) ? label.length : 1;
4167 const halfCount = lineCount / 2;
4168 const color = optsAtIndex.color;
4169 const strokeColor = optsAtIndex.textStrokeColor;
4170 const strokeWidth = optsAtIndex.textStrokeWidth;
4171 if (isHorizontal) {
4172 x = pixel;
4173 if (position === 'top') {
4174 if (crossAlign === 'near' || rotation !== 0) {
4175 textOffset = -lineCount * lineHeight + lineHeight / 2;
4176 } else if (crossAlign === 'center') {
4177 textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
4178 } else {
4179 textOffset = -labelSizes.highest.height + lineHeight / 2;
4180 }
4181 } else {
4182 if (crossAlign === 'near' || rotation !== 0) {
4183 textOffset = lineHeight / 2;
4184 } else if (crossAlign === 'center') {
4185 textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
4186 } else {
4187 textOffset = labelSizes.highest.height - lineCount * lineHeight;
4188 }
4189 }
4190 if (mirror) {
4191 textOffset *= -1;
4192 }
4193 } else {
4194 y = pixel;
4195 textOffset = (1 - lineCount) * lineHeight / 2;
4196 }
4197 let backdrop;
4198 if (optsAtIndex.showLabelBackdrop) {
4199 const labelPadding = toPadding(optsAtIndex.backdropPadding);
4200 const height = labelSizes.heights[i];
4201 const width = labelSizes.widths[i];
4202 let top = y + textOffset - labelPadding.top;
4203 let left = x - labelPadding.left;
4204 switch (textBaseline) {
4205 case 'middle':
4206 top -= height / 2;
4207 break;
4208 case 'bottom':
4209 top -= height;
4210 break;
4211 }
4212 switch (textAlign) {
4213 case 'center':
4214 left -= width / 2;
4215 break;
4216 case 'right':
4217 left -= width;
4218 break;
4219 }
4220 backdrop = {
4221 left,
4222 top,
4223 width: width + labelPadding.width,
4224 height: height + labelPadding.height,
4225 color: optsAtIndex.backdropColor,
4226 };
4227 }
4228 items.push({
4229 rotation,
4230 label,
4231 font,
4232 color,
4233 strokeColor,
4234 strokeWidth,
4235 textOffset,
4236 textAlign,
4237 textBaseline,
4238 translation: [x, y],
4239 backdrop,
4240 });
4241 }
4242 return items;
4243 }
4244 _getXAxisLabelAlignment() {
4245 const me = this;
4246 const {position, ticks} = me.options;
4247 const rotation = -toRadians(me.labelRotation);
4248 if (rotation) {
4249 return position === 'top' ? 'left' : 'right';
4250 }
4251 let align = 'center';
4252 if (ticks.align === 'start') {
4253 align = 'left';
4254 } else if (ticks.align === 'end') {
4255 align = 'right';
4256 }
4257 return align;
4258 }
4259 _getYAxisLabelAlignment(tl) {
4260 const me = this;
4261 const {position, ticks: {crossAlign, mirror, padding}} = me.options;
4262 const labelSizes = me._getLabelSizes();
4263 const tickAndPadding = tl + padding;
4264 const widest = labelSizes.widest.width;
4265 let textAlign;
4266 let x;
4267 if (position === 'left') {
4268 if (mirror) {
4269 textAlign = 'left';
4270 x = me.right + padding;
4271 } else {
4272 x = me.right - tickAndPadding;
4273 if (crossAlign === 'near') {
4274 textAlign = 'right';
4275 } else if (crossAlign === 'center') {
4276 textAlign = 'center';
4277 x -= (widest / 2);
4278 } else {
4279 textAlign = 'left';
4280 x = me.left;
4281 }
4282 }
4283 } else if (position === 'right') {
4284 if (mirror) {
4285 textAlign = 'right';
4286 x = me.left + padding;
4287 } else {
4288 x = me.left + tickAndPadding;
4289 if (crossAlign === 'near') {
4290 textAlign = 'left';
4291 } else if (crossAlign === 'center') {
4292 textAlign = 'center';
4293 x += widest / 2;
4294 } else {
4295 textAlign = 'right';
4296 x = me.right;
4297 }
4298 }
4299 } else {
4300 textAlign = 'right';
4301 }
4302 return {textAlign, x};
4303 }
4304 _computeLabelArea() {
4305 const me = this;
4306 if (me.options.ticks.mirror) {
4307 return;
4308 }
4309 const chart = me.chart;
4310 const position = me.options.position;
4311 if (position === 'left' || position === 'right') {
4312 return {top: 0, left: me.left, bottom: chart.height, right: me.right};
4313 } if (position === 'top' || position === 'bottom') {
4314 return {top: me.top, left: 0, bottom: me.bottom, right: chart.width};
4315 }
4316 }
4317 drawBackground() {
4318 const {ctx, options: {backgroundColor}, left, top, width, height} = this;
4319 if (backgroundColor) {
4320 ctx.save();
4321 ctx.fillStyle = backgroundColor;
4322 ctx.fillRect(left, top, width, height);
4323 ctx.restore();
4324 }
4325 }
4326 getLineWidthForValue(value) {
4327 const me = this;
4328 const grid = me.options.grid;
4329 if (!me._isVisible() || !grid.display) {
4330 return 0;
4331 }
4332 const ticks = me.ticks;
4333 const index = ticks.findIndex(t => t.value === value);
4334 if (index >= 0) {
4335 const opts = grid.setContext(me.getContext(index));
4336 return opts.lineWidth;
4337 }
4338 return 0;
4339 }
4340 drawGrid(chartArea) {
4341 const me = this;
4342 const grid = me.options.grid;
4343 const ctx = me.ctx;
4344 const items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea));
4345 let i, ilen;
4346 const drawLine = (p1, p2, style) => {
4347 if (!style.width || !style.color) {
4348 return;
4349 }
4350 ctx.save();
4351 ctx.lineWidth = style.width;
4352 ctx.strokeStyle = style.color;
4353 ctx.setLineDash(style.borderDash || []);
4354 ctx.lineDashOffset = style.borderDashOffset;
4355 ctx.beginPath();
4356 ctx.moveTo(p1.x, p1.y);
4357 ctx.lineTo(p2.x, p2.y);
4358 ctx.stroke();
4359 ctx.restore();
4360 };
4361 if (grid.display) {
4362 for (i = 0, ilen = items.length; i < ilen; ++i) {
4363 const item = items[i];
4364 if (grid.drawOnChartArea) {
4365 drawLine(
4366 {x: item.x1, y: item.y1},
4367 {x: item.x2, y: item.y2},
4368 item
4369 );
4370 }
4371 if (grid.drawTicks) {
4372 drawLine(
4373 {x: item.tx1, y: item.ty1},
4374 {x: item.tx2, y: item.ty2},
4375 {
4376 color: item.tickColor,
4377 width: item.tickWidth,
4378 borderDash: item.tickBorderDash,
4379 borderDashOffset: item.tickBorderDashOffset
4380 }
4381 );
4382 }
4383 }
4384 }
4385 }
4386 drawBorder() {
4387 const me = this;
4388 const {chart, ctx, options: {grid}} = me;
4389 const borderOpts = grid.setContext(me.getContext());
4390 const axisWidth = grid.drawBorder ? borderOpts.borderWidth : 0;
4391 if (!axisWidth) {
4392 return;
4393 }
4394 const lastLineWidth = grid.setContext(me.getContext(0)).lineWidth;
4395 const borderValue = me._borderValue;
4396 let x1, x2, y1, y2;
4397 if (me.isHorizontal()) {
4398 x1 = _alignPixel(chart, me.left, axisWidth) - axisWidth / 2;
4399 x2 = _alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2;
4400 y1 = y2 = borderValue;
4401 } else {
4402 y1 = _alignPixel(chart, me.top, axisWidth) - axisWidth / 2;
4403 y2 = _alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2;
4404 x1 = x2 = borderValue;
4405 }
4406 ctx.save();
4407 ctx.lineWidth = borderOpts.borderWidth;
4408 ctx.strokeStyle = borderOpts.borderColor;
4409 ctx.beginPath();
4410 ctx.moveTo(x1, y1);
4411 ctx.lineTo(x2, y2);
4412 ctx.stroke();
4413 ctx.restore();
4414 }
4415 drawLabels(chartArea) {
4416 const me = this;
4417 const optionTicks = me.options.ticks;
4418 if (!optionTicks.display) {
4419 return;
4420 }
4421 const ctx = me.ctx;
4422 const area = me._computeLabelArea();
4423 if (area) {
4424 clipArea(ctx, area);
4425 }
4426 const items = me._labelItems || (me._labelItems = me._computeLabelItems(chartArea));
4427 let i, ilen;
4428 for (i = 0, ilen = items.length; i < ilen; ++i) {
4429 const item = items[i];
4430 const tickFont = item.font;
4431 const label = item.label;
4432 if (item.backdrop) {
4433 ctx.fillStyle = item.backdrop.color;
4434 ctx.fillRect(item.backdrop.left, item.backdrop.top, item.backdrop.width, item.backdrop.height);
4435 }
4436 let y = item.textOffset;
4437 renderText(ctx, label, 0, y, tickFont, item);
4438 }
4439 if (area) {
4440 unclipArea(ctx);
4441 }
4442 }
4443 drawTitle() {
4444 const {ctx, options: {position, title, reverse}} = this;
4445 if (!title.display) {
4446 return;
4447 }
4448 const font = toFont(title.font);
4449 const padding = toPadding(title.padding);
4450 const align = title.align;
4451 let offset = font.lineHeight / 2;
4452 if (position === 'bottom') {
4453 offset += padding.bottom;
4454 if (isArray(title.text)) {
4455 offset += font.lineHeight * (title.text.length - 1);
4456 }
4457 } else {
4458 offset += padding.top;
4459 }
4460 const {titleX, titleY, maxWidth, rotation} = titleArgs(this, offset, position, align);
4461 renderText(ctx, title.text, 0, 0, font, {
4462 color: title.color,
4463 maxWidth,
4464 rotation,
4465 textAlign: titleAlign(align, position, reverse),
4466 textBaseline: 'middle',
4467 translation: [titleX, titleY],
4468 });
4469 }
4470 draw(chartArea) {
4471 const me = this;
4472 if (!me._isVisible()) {
4473 return;
4474 }
4475 me.drawBackground();
4476 me.drawGrid(chartArea);
4477 me.drawBorder();
4478 me.drawTitle();
4479 me.drawLabels(chartArea);
4480 }
4481 _layers() {
4482 const me = this;
4483 const opts = me.options;
4484 const tz = opts.ticks && opts.ticks.z || 0;
4485 const gz = opts.grid && opts.grid.z || 0;
4486 if (!me._isVisible() || me.draw !== Scale.prototype.draw) {
4487 return [{
4488 z: tz,
4489 draw(chartArea) {
4490 me.draw(chartArea);
4491 }
4492 }];
4493 }
4494 return [{
4495 z: gz,
4496 draw(chartArea) {
4497 me.drawBackground();
4498 me.drawGrid(chartArea);
4499 me.drawTitle();
4500 }
4501 }, {
4502 z: gz + 1,
4503 draw() {
4504 me.drawBorder();
4505 }
4506 }, {
4507 z: tz,
4508 draw(chartArea) {
4509 me.drawLabels(chartArea);
4510 }
4511 }];
4512 }
4513 getMatchingVisibleMetas(type) {
4514 const me = this;
4515 const metas = me.chart.getSortedVisibleDatasetMetas();
4516 const axisID = me.axis + 'AxisID';
4517 const result = [];
4518 let i, ilen;
4519 for (i = 0, ilen = metas.length; i < ilen; ++i) {
4520 const meta = metas[i];
4521 if (meta[axisID] === me.id && (!type || meta.type === type)) {
4522 result.push(meta);
4523 }
4524 }
4525 return result;
4526 }
4527 _resolveTickFontOptions(index) {
4528 const opts = this.options.ticks.setContext(this.getContext(index));
4529 return toFont(opts.font);
4530 }
4531 }
4532
4533 class TypedRegistry {
4534 constructor(type, scope, override) {
4535 this.type = type;
4536 this.scope = scope;
4537 this.override = override;
4538 this.items = Object.create(null);
4539 }
4540 isForType(type) {
4541 return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);
4542 }
4543 register(item) {
4544 const me = this;
4545 const proto = Object.getPrototypeOf(item);
4546 let parentScope;
4547 if (isIChartComponent(proto)) {
4548 parentScope = me.register(proto);
4549 }
4550 const items = me.items;
4551 const id = item.id;
4552 const scope = me.scope + '.' + id;
4553 if (!id) {
4554 throw new Error('class does not have id: ' + item);
4555 }
4556 if (id in items) {
4557 return scope;
4558 }
4559 items[id] = item;
4560 registerDefaults(item, scope, parentScope);
4561 if (me.override) {
4562 defaults.override(item.id, item.overrides);
4563 }
4564 return scope;
4565 }
4566 get(id) {
4567 return this.items[id];
4568 }
4569 unregister(item) {
4570 const items = this.items;
4571 const id = item.id;
4572 const scope = this.scope;
4573 if (id in items) {
4574 delete items[id];
4575 }
4576 if (scope && id in defaults[scope]) {
4577 delete defaults[scope][id];
4578 if (this.override) {
4579 delete overrides[id];
4580 }
4581 }
4582 }
4583 }
4584 function registerDefaults(item, scope, parentScope) {
4585 const itemDefaults = merge(Object.create(null), [
4586 parentScope ? defaults.get(parentScope) : {},
4587 defaults.get(scope),
4588 item.defaults
4589 ]);
4590 defaults.set(scope, itemDefaults);
4591 if (item.defaultRoutes) {
4592 routeDefaults(scope, item.defaultRoutes);
4593 }
4594 if (item.descriptors) {
4595 defaults.describe(scope, item.descriptors);
4596 }
4597 }
4598 function routeDefaults(scope, routes) {
4599 Object.keys(routes).forEach(property => {
4600 const propertyParts = property.split('.');
4601 const sourceName = propertyParts.pop();
4602 const sourceScope = [scope].concat(propertyParts).join('.');
4603 const parts = routes[property].split('.');
4604 const targetName = parts.pop();
4605 const targetScope = parts.join('.');
4606 defaults.route(sourceScope, sourceName, targetScope, targetName);
4607 });
4608 }
4609 function isIChartComponent(proto) {
4610 return 'id' in proto && 'defaults' in proto;
4611 }
4612
4613 class Registry {
4614 constructor() {
4615 this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
4616 this.elements = new TypedRegistry(Element, 'elements');
4617 this.plugins = new TypedRegistry(Object, 'plugins');
4618 this.scales = new TypedRegistry(Scale, 'scales');
4619 this._typedRegistries = [this.controllers, this.scales, this.elements];
4620 }
4621 add(...args) {
4622 this._each('register', args);
4623 }
4624 remove(...args) {
4625 this._each('unregister', args);
4626 }
4627 addControllers(...args) {
4628 this._each('register', args, this.controllers);
4629 }
4630 addElements(...args) {
4631 this._each('register', args, this.elements);
4632 }
4633 addPlugins(...args) {
4634 this._each('register', args, this.plugins);
4635 }
4636 addScales(...args) {
4637 this._each('register', args, this.scales);
4638 }
4639 getController(id) {
4640 return this._get(id, this.controllers, 'controller');
4641 }
4642 getElement(id) {
4643 return this._get(id, this.elements, 'element');
4644 }
4645 getPlugin(id) {
4646 return this._get(id, this.plugins, 'plugin');
4647 }
4648 getScale(id) {
4649 return this._get(id, this.scales, 'scale');
4650 }
4651 removeControllers(...args) {
4652 this._each('unregister', args, this.controllers);
4653 }
4654 removeElements(...args) {
4655 this._each('unregister', args, this.elements);
4656 }
4657 removePlugins(...args) {
4658 this._each('unregister', args, this.plugins);
4659 }
4660 removeScales(...args) {
4661 this._each('unregister', args, this.scales);
4662 }
4663 _each(method, args, typedRegistry) {
4664 const me = this;
4665 [...args].forEach(arg => {
4666 const reg = typedRegistry || me._getRegistryForType(arg);
4667 if (typedRegistry || reg.isForType(arg) || (reg === me.plugins && arg.id)) {
4668 me._exec(method, reg, arg);
4669 } else {
4670 each(arg, item => {
4671 const itemReg = typedRegistry || me._getRegistryForType(item);
4672 me._exec(method, itemReg, item);
4673 });
4674 }
4675 });
4676 }
4677 _exec(method, registry, component) {
4678 const camelMethod = _capitalize(method);
4679 callback(component['before' + camelMethod], [], component);
4680 registry[method](component);
4681 callback(component['after' + camelMethod], [], component);
4682 }
4683 _getRegistryForType(type) {
4684 for (let i = 0; i < this._typedRegistries.length; i++) {
4685 const reg = this._typedRegistries[i];
4686 if (reg.isForType(type)) {
4687 return reg;
4688 }
4689 }
4690 return this.plugins;
4691 }
4692 _get(id, typedRegistry, type) {
4693 const item = typedRegistry.get(id);
4694 if (item === undefined) {
4695 throw new Error('"' + id + '" is not a registered ' + type + '.');
4696 }
4697 return item;
4698 }
4699 }
4700 var registry = new Registry();
4701
4702 class PluginService {
4703 constructor() {
4704 this._init = [];
4705 }
4706 notify(chart, hook, args, filter) {
4707 const me = this;
4708 if (hook === 'beforeInit') {
4709 me._init = me._createDescriptors(chart, true);
4710 me._notify(me._init, chart, 'install');
4711 }
4712 const descriptors = filter ? me._descriptors(chart).filter(filter) : me._descriptors(chart);
4713 const result = me._notify(descriptors, chart, hook, args);
4714 if (hook === 'destroy') {
4715 me._notify(descriptors, chart, 'stop');
4716 me._notify(me._init, chart, 'uninstall');
4717 }
4718 return result;
4719 }
4720 _notify(descriptors, chart, hook, args) {
4721 args = args || {};
4722 for (const descriptor of descriptors) {
4723 const plugin = descriptor.plugin;
4724 const method = plugin[hook];
4725 const params = [chart, args, descriptor.options];
4726 if (callback(method, params, plugin) === false && args.cancelable) {
4727 return false;
4728 }
4729 }
4730 return true;
4731 }
4732 invalidate() {
4733 if (!isNullOrUndef(this._cache)) {
4734 this._oldCache = this._cache;
4735 this._cache = undefined;
4736 }
4737 }
4738 _descriptors(chart) {
4739 if (this._cache) {
4740 return this._cache;
4741 }
4742 const descriptors = this._cache = this._createDescriptors(chart);
4743 this._notifyStateChanges(chart);
4744 return descriptors;
4745 }
4746 _createDescriptors(chart, all) {
4747 const config = chart && chart.config;
4748 const options = valueOrDefault(config.options && config.options.plugins, {});
4749 const plugins = allPlugins(config);
4750 return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);
4751 }
4752 _notifyStateChanges(chart) {
4753 const previousDescriptors = this._oldCache || [];
4754 const descriptors = this._cache;
4755 const diff = (a, b) => a.filter(x => !b.some(y => x.plugin.id === y.plugin.id));
4756 this._notify(diff(previousDescriptors, descriptors), chart, 'stop');
4757 this._notify(diff(descriptors, previousDescriptors), chart, 'start');
4758 }
4759 }
4760 function allPlugins(config) {
4761 const plugins = [];
4762 const keys = Object.keys(registry.plugins.items);
4763 for (let i = 0; i < keys.length; i++) {
4764 plugins.push(registry.getPlugin(keys[i]));
4765 }
4766 const local = config.plugins || [];
4767 for (let i = 0; i < local.length; i++) {
4768 const plugin = local[i];
4769 if (plugins.indexOf(plugin) === -1) {
4770 plugins.push(plugin);
4771 }
4772 }
4773 return plugins;
4774 }
4775 function getOpts(options, all) {
4776 if (!all && options === false) {
4777 return null;
4778 }
4779 if (options === true) {
4780 return {};
4781 }
4782 return options;
4783 }
4784 function createDescriptors(chart, plugins, options, all) {
4785 const result = [];
4786 const context = chart.getContext();
4787 for (let i = 0; i < plugins.length; i++) {
4788 const plugin = plugins[i];
4789 const id = plugin.id;
4790 const opts = getOpts(options[id], all);
4791 if (opts === null) {
4792 continue;
4793 }
4794 result.push({
4795 plugin,
4796 options: pluginOpts(chart.config, plugin, opts, context)
4797 });
4798 }
4799 return result;
4800 }
4801 function pluginOpts(config, plugin, opts, context) {
4802 const keys = config.pluginScopeKeys(plugin);
4803 const scopes = config.getOptionScopes(opts, keys);
4804 return config.createResolver(scopes, context, [''], {scriptable: false, indexable: false, allKeys: true});
4805 }
4806
4807 function getIndexAxis(type, options) {
4808 const datasetDefaults = defaults.datasets[type] || {};
4809 const datasetOptions = (options.datasets || {})[type] || {};
4810 return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
4811 }
4812 function getAxisFromDefaultScaleID(id, indexAxis) {
4813 let axis = id;
4814 if (id === '_index_') {
4815 axis = indexAxis;
4816 } else if (id === '_value_') {
4817 axis = indexAxis === 'x' ? 'y' : 'x';
4818 }
4819 return axis;
4820 }
4821 function getDefaultScaleIDFromAxis(axis, indexAxis) {
4822 return axis === indexAxis ? '_index_' : '_value_';
4823 }
4824 function axisFromPosition(position) {
4825 if (position === 'top' || position === 'bottom') {
4826 return 'x';
4827 }
4828 if (position === 'left' || position === 'right') {
4829 return 'y';
4830 }
4831 }
4832 function determineAxis(id, scaleOptions) {
4833 if (id === 'x' || id === 'y') {
4834 return id;
4835 }
4836 return scaleOptions.axis || axisFromPosition(scaleOptions.position) || id.charAt(0).toLowerCase();
4837 }
4838 function mergeScaleConfig(config, options) {
4839 const chartDefaults = overrides[config.type] || {scales: {}};
4840 const configScales = options.scales || {};
4841 const chartIndexAxis = getIndexAxis(config.type, options);
4842 const firstIDs = Object.create(null);
4843 const scales = Object.create(null);
4844 Object.keys(configScales).forEach(id => {
4845 const scaleConf = configScales[id];
4846 const axis = determineAxis(id, scaleConf);
4847 const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
4848 const defaultScaleOptions = chartDefaults.scales || {};
4849 firstIDs[axis] = firstIDs[axis] || id;
4850 scales[id] = mergeIf(Object.create(null), [{axis}, scaleConf, defaultScaleOptions[axis], defaultScaleOptions[defaultId]]);
4851 });
4852 config.data.datasets.forEach(dataset => {
4853 const type = dataset.type || config.type;
4854 const indexAxis = dataset.indexAxis || getIndexAxis(type, options);
4855 const datasetDefaults = overrides[type] || {};
4856 const defaultScaleOptions = datasetDefaults.scales || {};
4857 Object.keys(defaultScaleOptions).forEach(defaultID => {
4858 const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
4859 const id = dataset[axis + 'AxisID'] || firstIDs[axis] || axis;
4860 scales[id] = scales[id] || Object.create(null);
4861 mergeIf(scales[id], [{axis}, configScales[id], defaultScaleOptions[defaultID]]);
4862 });
4863 });
4864 Object.keys(scales).forEach(key => {
4865 const scale = scales[key];
4866 mergeIf(scale, [defaults.scales[scale.type], defaults.scale]);
4867 });
4868 return scales;
4869 }
4870 function initOptions(config) {
4871 const options = config.options || (config.options = {});
4872 options.plugins = valueOrDefault(options.plugins, {});
4873 options.scales = mergeScaleConfig(config, options);
4874 }
4875 function initData(data) {
4876 data = data || {};
4877 data.datasets = data.datasets || [];
4878 data.labels = data.labels || [];
4879 return data;
4880 }
4881 function initConfig(config) {
4882 config = config || {};
4883 config.data = initData(config.data);
4884 initOptions(config);
4885 return config;
4886 }
4887 const keyCache = new Map();
4888 const keysCached = new Set();
4889 function cachedKeys(cacheKey, generate) {
4890 let keys = keyCache.get(cacheKey);
4891 if (!keys) {
4892 keys = generate();
4893 keyCache.set(cacheKey, keys);
4894 keysCached.add(keys);
4895 }
4896 return keys;
4897 }
4898 const addIfFound = (set, obj, key) => {
4899 const opts = resolveObjectKey(obj, key);
4900 if (opts !== undefined) {
4901 set.add(opts);
4902 }
4903 };
4904 class Config {
4905 constructor(config) {
4906 this._config = initConfig(config);
4907 this._scopeCache = new Map();
4908 this._resolverCache = new Map();
4909 }
4910 get type() {
4911 return this._config.type;
4912 }
4913 set type(type) {
4914 this._config.type = type;
4915 }
4916 get data() {
4917 return this._config.data;
4918 }
4919 set data(data) {
4920 this._config.data = initData(data);
4921 }
4922 get options() {
4923 return this._config.options;
4924 }
4925 set options(options) {
4926 this._config.options = options;
4927 }
4928 get plugins() {
4929 return this._config.plugins;
4930 }
4931 update() {
4932 const config = this._config;
4933 this.clearCache();
4934 initOptions(config);
4935 }
4936 clearCache() {
4937 this._scopeCache.clear();
4938 this._resolverCache.clear();
4939 }
4940 datasetScopeKeys(datasetType) {
4941 return cachedKeys(datasetType,
4942 () => [[
4943 `datasets.${datasetType}`,
4944 ''
4945 ]]);
4946 }
4947 datasetAnimationScopeKeys(datasetType, transition) {
4948 return cachedKeys(`${datasetType}.transition.${transition}`,
4949 () => [
4950 [
4951 `datasets.${datasetType}.transitions.${transition}`,
4952 `transitions.${transition}`,
4953 ],
4954 [
4955 `datasets.${datasetType}`,
4956 ''
4957 ]
4958 ]);
4959 }
4960 datasetElementScopeKeys(datasetType, elementType) {
4961 return cachedKeys(`${datasetType}-${elementType}`,
4962 () => [[
4963 `datasets.${datasetType}.elements.${elementType}`,
4964 `datasets.${datasetType}`,
4965 `elements.${elementType}`,
4966 ''
4967 ]]);
4968 }
4969 pluginScopeKeys(plugin) {
4970 const id = plugin.id;
4971 const type = this.type;
4972 return cachedKeys(`${type}-plugin-${id}`,
4973 () => [[
4974 `plugins.${id}`,
4975 ...plugin.additionalOptionScopes || [],
4976 ]]);
4977 }
4978 _cachedScopes(mainScope, resetCache) {
4979 const _scopeCache = this._scopeCache;
4980 let cache = _scopeCache.get(mainScope);
4981 if (!cache || resetCache) {
4982 cache = new Map();
4983 _scopeCache.set(mainScope, cache);
4984 }
4985 return cache;
4986 }
4987 getOptionScopes(mainScope, keyLists, resetCache) {
4988 const {options, type} = this;
4989 const cache = this._cachedScopes(mainScope, resetCache);
4990 const cached = cache.get(keyLists);
4991 if (cached) {
4992 return cached;
4993 }
4994 const scopes = new Set();
4995 keyLists.forEach(keys => {
4996 if (mainScope) {
4997 scopes.add(mainScope);
4998 keys.forEach(key => addIfFound(scopes, mainScope, key));
4999 }
5000 keys.forEach(key => addIfFound(scopes, options, key));
5001 keys.forEach(key => addIfFound(scopes, overrides[type] || {}, key));
5002 keys.forEach(key => addIfFound(scopes, defaults, key));
5003 keys.forEach(key => addIfFound(scopes, descriptors, key));
5004 });
5005 const array = [...scopes];
5006 if (keysCached.has(keyLists)) {
5007 cache.set(keyLists, array);
5008 }
5009 return array;
5010 }
5011 chartOptionScopes() {
5012 const {options, type} = this;
5013 return [
5014 options,
5015 overrides[type] || {},
5016 defaults.datasets[type] || {},
5017 {type},
5018 defaults,
5019 descriptors
5020 ];
5021 }
5022 resolveNamedOptions(scopes, names, context, prefixes = ['']) {
5023 const result = {$shared: true};
5024 const {resolver, subPrefixes} = getResolver(this._resolverCache, scopes, prefixes);
5025 let options = resolver;
5026 if (needContext(resolver, names)) {
5027 result.$shared = false;
5028 context = isFunction(context) ? context() : context;
5029 const subResolver = this.createResolver(scopes, context, subPrefixes);
5030 options = _attachContext(resolver, context, subResolver);
5031 }
5032 for (const prop of names) {
5033 result[prop] = options[prop];
5034 }
5035 return result;
5036 }
5037 createResolver(scopes, context, prefixes = [''], descriptorDefaults) {
5038 const {resolver} = getResolver(this._resolverCache, scopes, prefixes);
5039 return isObject(context)
5040 ? _attachContext(resolver, context, undefined, descriptorDefaults)
5041 : resolver;
5042 }
5043 }
5044 function getResolver(resolverCache, scopes, prefixes) {
5045 let cache = resolverCache.get(scopes);
5046 if (!cache) {
5047 cache = new Map();
5048 resolverCache.set(scopes, cache);
5049 }
5050 const cacheKey = prefixes.join();
5051 let cached = cache.get(cacheKey);
5052 if (!cached) {
5053 const resolver = _createResolver(scopes, prefixes);
5054 cached = {
5055 resolver,
5056 subPrefixes: prefixes.filter(p => !p.toLowerCase().includes('hover'))
5057 };
5058 cache.set(cacheKey, cached);
5059 }
5060 return cached;
5061 }
5062 function needContext(proxy, names) {
5063 const {isScriptable, isIndexable} = _descriptors(proxy);
5064 for (const prop of names) {
5065 if ((isScriptable(prop) && isFunction(proxy[prop]))
5066 || (isIndexable(prop) && isArray(proxy[prop]))) {
5067 return true;
5068 }
5069 }
5070 return false;
5071 }
5072
5073 var version = "3.2.0";
5074
5075 const KNOWN_POSITIONS = ['top', 'bottom', 'left', 'right', 'chartArea'];
5076 function positionIsHorizontal(position, axis) {
5077 return position === 'top' || position === 'bottom' || (KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x');
5078 }
5079 function compare2Level(l1, l2) {
5080 return function(a, b) {
5081 return a[l1] === b[l1]
5082 ? a[l2] - b[l2]
5083 : a[l1] - b[l1];
5084 };
5085 }
5086 function onAnimationsComplete(context) {
5087 const chart = context.chart;
5088 const animationOptions = chart.options.animation;
5089 chart.notifyPlugins('afterRender');
5090 callback(animationOptions && animationOptions.onComplete, [context], chart);
5091 }
5092 function onAnimationProgress(context) {
5093 const chart = context.chart;
5094 const animationOptions = chart.options.animation;
5095 callback(animationOptions && animationOptions.onProgress, [context], chart);
5096 }
5097 function isDomSupported() {
5098 return typeof window !== 'undefined' && typeof document !== 'undefined';
5099 }
5100 function getCanvas(item) {
5101 if (isDomSupported() && typeof item === 'string') {
5102 item = document.getElementById(item);
5103 } else if (item && item.length) {
5104 item = item[0];
5105 }
5106 if (item && item.canvas) {
5107 item = item.canvas;
5108 }
5109 return item;
5110 }
5111 const instances = {};
5112 const getChart = (key) => {
5113 const canvas = getCanvas(key);
5114 return Object.values(instances).filter((c) => c.canvas === canvas).pop();
5115 };
5116 class Chart {
5117 constructor(item, config) {
5118 const me = this;
5119 this.config = config = new Config(config);
5120 const initialCanvas = getCanvas(item);
5121 const existingChart = getChart(initialCanvas);
5122 if (existingChart) {
5123 throw new Error(
5124 'Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' +
5125 ' must be destroyed before the canvas can be reused.'
5126 );
5127 }
5128 const options = config.createResolver(config.chartOptionScopes(), me.getContext());
5129 this.platform = me._initializePlatform(initialCanvas, config);
5130 const context = me.platform.acquireContext(initialCanvas, options.aspectRatio);
5131 const canvas = context && context.canvas;
5132 const height = canvas && canvas.height;
5133 const width = canvas && canvas.width;
5134 this.id = uid();
5135 this.ctx = context;
5136 this.canvas = canvas;
5137 this.width = width;
5138 this.height = height;
5139 this._options = options;
5140 this._aspectRatio = this.aspectRatio;
5141 this._layers = [];
5142 this._metasets = [];
5143 this._stacks = undefined;
5144 this.boxes = [];
5145 this.currentDevicePixelRatio = undefined;
5146 this.chartArea = undefined;
5147 this._active = [];
5148 this._lastEvent = undefined;
5149 this._listeners = {};
5150 this._sortedMetasets = [];
5151 this.scales = {};
5152 this.scale = undefined;
5153 this._plugins = new PluginService();
5154 this.$proxies = {};
5155 this._hiddenIndices = {};
5156 this.attached = false;
5157 this._animationsDisabled = undefined;
5158 this.$context = undefined;
5159 this._doResize = debounce(() => this.update('resize'), options.resizeDelay || 0);
5160 instances[me.id] = me;
5161 if (!context || !canvas) {
5162 console.error("Failed to create chart: can't acquire context from the given item");
5163 return;
5164 }
5165 animator.listen(me, 'complete', onAnimationsComplete);
5166 animator.listen(me, 'progress', onAnimationProgress);
5167 me._initialize();
5168 if (me.attached) {
5169 me.update();
5170 }
5171 }
5172 get aspectRatio() {
5173 const {options: {aspectRatio, maintainAspectRatio}, width, height, _aspectRatio} = this;
5174 if (!isNullOrUndef(aspectRatio)) {
5175 return aspectRatio;
5176 }
5177 if (maintainAspectRatio && _aspectRatio) {
5178 return _aspectRatio;
5179 }
5180 return height ? width / height : null;
5181 }
5182 get data() {
5183 return this.config.data;
5184 }
5185 set data(data) {
5186 this.config.data = data;
5187 }
5188 get options() {
5189 return this._options;
5190 }
5191 set options(options) {
5192 this.config.options = options;
5193 }
5194 _initialize() {
5195 const me = this;
5196 me.notifyPlugins('beforeInit');
5197 if (me.options.responsive) {
5198 me.resize();
5199 } else {
5200 retinaScale(me, me.options.devicePixelRatio);
5201 }
5202 me.bindEvents();
5203 me.notifyPlugins('afterInit');
5204 return me;
5205 }
5206 _initializePlatform(canvas, config) {
5207 if (config.platform) {
5208 return new config.platform();
5209 } else if (!isDomSupported() || (typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)) {
5210 return new BasicPlatform();
5211 }
5212 return new DomPlatform();
5213 }
5214 clear() {
5215 clearCanvas(this.canvas, this.ctx);
5216 return this;
5217 }
5218 stop() {
5219 animator.stop(this);
5220 return this;
5221 }
5222 resize(width, height) {
5223 if (!animator.running(this)) {
5224 this._resize(width, height);
5225 } else {
5226 this._resizeBeforeDraw = {width, height};
5227 }
5228 }
5229 _resize(width, height) {
5230 const me = this;
5231 const options = me.options;
5232 const canvas = me.canvas;
5233 const aspectRatio = options.maintainAspectRatio && me.aspectRatio;
5234 const newSize = me.platform.getMaximumSize(canvas, width, height, aspectRatio);
5235 const oldRatio = me.currentDevicePixelRatio;
5236 const newRatio = options.devicePixelRatio || me.platform.getDevicePixelRatio();
5237 if (me.width === newSize.width && me.height === newSize.height && oldRatio === newRatio) {
5238 return;
5239 }
5240 me.width = newSize.width;
5241 me.height = newSize.height;
5242 me._aspectRatio = me.aspectRatio;
5243 retinaScale(me, newRatio, true);
5244 me.notifyPlugins('resize', {size: newSize});
5245 callback(options.onResize, [me, newSize], me);
5246 if (me.attached) {
5247 if (me._doResize()) {
5248 me.render();
5249 }
5250 }
5251 }
5252 ensureScalesHaveIDs() {
5253 const options = this.options;
5254 const scalesOptions = options.scales || {};
5255 each(scalesOptions, (axisOptions, axisID) => {
5256 axisOptions.id = axisID;
5257 });
5258 }
5259 buildOrUpdateScales() {
5260 const me = this;
5261 const options = me.options;
5262 const scaleOpts = options.scales;
5263 const scales = me.scales;
5264 const updated = Object.keys(scales).reduce((obj, id) => {
5265 obj[id] = false;
5266 return obj;
5267 }, {});
5268 let items = [];
5269 if (scaleOpts) {
5270 items = items.concat(
5271 Object.keys(scaleOpts).map((id) => {
5272 const scaleOptions = scaleOpts[id];
5273 const axis = determineAxis(id, scaleOptions);
5274 const isRadial = axis === 'r';
5275 const isHorizontal = axis === 'x';
5276 return {
5277 options: scaleOptions,
5278 dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',
5279 dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'
5280 };
5281 })
5282 );
5283 }
5284 each(items, (item) => {
5285 const scaleOptions = item.options;
5286 const id = scaleOptions.id;
5287 const axis = determineAxis(id, scaleOptions);
5288 const scaleType = valueOrDefault(scaleOptions.type, item.dtype);
5289 if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {
5290 scaleOptions.position = item.dposition;
5291 }
5292 updated[id] = true;
5293 let scale = null;
5294 if (id in scales && scales[id].type === scaleType) {
5295 scale = scales[id];
5296 } else {
5297 const scaleClass = registry.getScale(scaleType);
5298 scale = new scaleClass({
5299 id,
5300 type: scaleType,
5301 ctx: me.ctx,
5302 chart: me
5303 });
5304 scales[scale.id] = scale;
5305 }
5306 scale.init(scaleOptions, options);
5307 });
5308 each(updated, (hasUpdated, id) => {
5309 if (!hasUpdated) {
5310 delete scales[id];
5311 }
5312 });
5313 each(scales, (scale) => {
5314 layouts.configure(me, scale, scale.options);
5315 layouts.addBox(me, scale);
5316 });
5317 }
5318 _updateMetasetIndex(meta, index) {
5319 const metasets = this._metasets;
5320 const oldIndex = meta.index;
5321 if (oldIndex !== index) {
5322 metasets[oldIndex] = metasets[index];
5323 metasets[index] = meta;
5324 meta.index = index;
5325 }
5326 }
5327 _updateMetasets() {
5328 const me = this;
5329 const metasets = me._metasets;
5330 const numData = me.data.datasets.length;
5331 const numMeta = metasets.length;
5332 if (numMeta > numData) {
5333 for (let i = numData; i < numMeta; ++i) {
5334 me._destroyDatasetMeta(i);
5335 }
5336 metasets.splice(numData, numMeta - numData);
5337 }
5338 me._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
5339 }
5340 _removeUnreferencedMetasets() {
5341 const me = this;
5342 const {_metasets: metasets, data: {datasets}} = me;
5343 if (metasets.length > datasets.length) {
5344 delete me._stacks;
5345 }
5346 metasets.forEach((meta, index) => {
5347 if (datasets.filter(x => x === meta._dataset).length === 0) {
5348 me._destroyDatasetMeta(index);
5349 }
5350 });
5351 }
5352 buildOrUpdateControllers() {
5353 const me = this;
5354 const newControllers = [];
5355 const datasets = me.data.datasets;
5356 let i, ilen;
5357 me._removeUnreferencedMetasets();
5358 for (i = 0, ilen = datasets.length; i < ilen; i++) {
5359 const dataset = datasets[i];
5360 let meta = me.getDatasetMeta(i);
5361 const type = dataset.type || me.config.type;
5362 if (meta.type && meta.type !== type) {
5363 me._destroyDatasetMeta(i);
5364 meta = me.getDatasetMeta(i);
5365 }
5366 meta.type = type;
5367 meta.indexAxis = dataset.indexAxis || getIndexAxis(type, me.options);
5368 meta.order = dataset.order || 0;
5369 me._updateMetasetIndex(meta, i);
5370 meta.label = '' + dataset.label;
5371 meta.visible = me.isDatasetVisible(i);
5372 if (meta.controller) {
5373 meta.controller.updateIndex(i);
5374 meta.controller.linkScales();
5375 } else {
5376 const ControllerClass = registry.getController(type);
5377 const {datasetElementType, dataElementType} = defaults.datasets[type];
5378 Object.assign(ControllerClass.prototype, {
5379 dataElementType: registry.getElement(dataElementType),
5380 datasetElementType: datasetElementType && registry.getElement(datasetElementType)
5381 });
5382 meta.controller = new ControllerClass(me, i);
5383 newControllers.push(meta.controller);
5384 }
5385 }
5386 me._updateMetasets();
5387 return newControllers;
5388 }
5389 _resetElements() {
5390 const me = this;
5391 each(me.data.datasets, (dataset, datasetIndex) => {
5392 me.getDatasetMeta(datasetIndex).controller.reset();
5393 }, me);
5394 }
5395 reset() {
5396 this._resetElements();
5397 this.notifyPlugins('reset');
5398 }
5399 update(mode) {
5400 const me = this;
5401 const config = me.config;
5402 config.update();
5403 me._options = config.createResolver(config.chartOptionScopes(), me.getContext());
5404 each(me.scales, (scale) => {
5405 layouts.removeBox(me, scale);
5406 });
5407 const animsDisabled = me._animationsDisabled = !me.options.animation;
5408 me.ensureScalesHaveIDs();
5409 me.buildOrUpdateScales();
5410 const existingEvents = new Set(Object.keys(me._listeners));
5411 const newEvents = new Set(me.options.events);
5412 if (!setsEqual(existingEvents, newEvents)) {
5413 me.unbindEvents();
5414 me.bindEvents();
5415 }
5416 me._plugins.invalidate();
5417 if (me.notifyPlugins('beforeUpdate', {mode, cancelable: true}) === false) {
5418 return;
5419 }
5420 const newControllers = me.buildOrUpdateControllers();
5421 me.notifyPlugins('beforeElementsUpdate');
5422 let minPadding = 0;
5423 for (let i = 0, ilen = me.data.datasets.length; i < ilen; i++) {
5424 const {controller} = me.getDatasetMeta(i);
5425 const reset = !animsDisabled && newControllers.indexOf(controller) === -1;
5426 controller.buildOrUpdateElements(reset);
5427 minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
5428 }
5429 me._minPadding = minPadding;
5430 me._updateLayout(minPadding);
5431 if (!animsDisabled) {
5432 each(newControllers, (controller) => {
5433 controller.reset();
5434 });
5435 }
5436 me._updateDatasets(mode);
5437 me.notifyPlugins('afterUpdate', {mode});
5438 me._layers.sort(compare2Level('z', '_idx'));
5439 if (me._lastEvent) {
5440 me._eventHandler(me._lastEvent, true);
5441 }
5442 me.render();
5443 }
5444 _updateLayout(minPadding) {
5445 const me = this;
5446 if (me.notifyPlugins('beforeLayout', {cancelable: true}) === false) {
5447 return;
5448 }
5449 layouts.update(me, me.width, me.height, minPadding);
5450 const area = me.chartArea;
5451 const noArea = area.width <= 0 || area.height <= 0;
5452 me._layers = [];
5453 each(me.boxes, (box) => {
5454 if (noArea && box.position === 'chartArea') {
5455 return;
5456 }
5457 if (box.configure) {
5458 box.configure();
5459 }
5460 me._layers.push(...box._layers());
5461 }, me);
5462 me._layers.forEach((item, index) => {
5463 item._idx = index;
5464 });
5465 me.notifyPlugins('afterLayout');
5466 }
5467 _updateDatasets(mode) {
5468 const me = this;
5469 const isFunction = typeof mode === 'function';
5470 if (me.notifyPlugins('beforeDatasetsUpdate', {mode, cancelable: true}) === false) {
5471 return;
5472 }
5473 for (let i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
5474 me._updateDataset(i, isFunction ? mode({datasetIndex: i}) : mode);
5475 }
5476 me.notifyPlugins('afterDatasetsUpdate', {mode});
5477 }
5478 _updateDataset(index, mode) {
5479 const me = this;
5480 const meta = me.getDatasetMeta(index);
5481 const args = {meta, index, mode, cancelable: true};
5482 if (me.notifyPlugins('beforeDatasetUpdate', args) === false) {
5483 return;
5484 }
5485 meta.controller._update(mode);
5486 args.cancelable = false;
5487 me.notifyPlugins('afterDatasetUpdate', args);
5488 }
5489 render() {
5490 const me = this;
5491 if (me.notifyPlugins('beforeRender', {cancelable: true}) === false) {
5492 return;
5493 }
5494 if (animator.has(me)) {
5495 if (me.attached && !animator.running(me)) {
5496 animator.start(me);
5497 }
5498 } else {
5499 me.draw();
5500 onAnimationsComplete({chart: me});
5501 }
5502 }
5503 draw() {
5504 const me = this;
5505 let i;
5506 if (me._resizeBeforeDraw) {
5507 const {width, height} = me._resizeBeforeDraw;
5508 me._resize(width, height);
5509 me._resizeBeforeDraw = null;
5510 }
5511 me.clear();
5512 if (me.width <= 0 || me.height <= 0) {
5513 return;
5514 }
5515 if (me.notifyPlugins('beforeDraw', {cancelable: true}) === false) {
5516 return;
5517 }
5518 const layers = me._layers;
5519 for (i = 0; i < layers.length && layers[i].z <= 0; ++i) {
5520 layers[i].draw(me.chartArea);
5521 }
5522 me._drawDatasets();
5523 for (; i < layers.length; ++i) {
5524 layers[i].draw(me.chartArea);
5525 }
5526 me.notifyPlugins('afterDraw');
5527 }
5528 _getSortedDatasetMetas(filterVisible) {
5529 const me = this;
5530 const metasets = me._sortedMetasets;
5531 const result = [];
5532 let i, ilen;
5533 for (i = 0, ilen = metasets.length; i < ilen; ++i) {
5534 const meta = metasets[i];
5535 if (!filterVisible || meta.visible) {
5536 result.push(meta);
5537 }
5538 }
5539 return result;
5540 }
5541 getSortedVisibleDatasetMetas() {
5542 return this._getSortedDatasetMetas(true);
5543 }
5544 _drawDatasets() {
5545 const me = this;
5546 if (me.notifyPlugins('beforeDatasetsDraw', {cancelable: true}) === false) {
5547 return;
5548 }
5549 const metasets = me.getSortedVisibleDatasetMetas();
5550 for (let i = metasets.length - 1; i >= 0; --i) {
5551 me._drawDataset(metasets[i]);
5552 }
5553 me.notifyPlugins('afterDatasetsDraw');
5554 }
5555 _drawDataset(meta) {
5556 const me = this;
5557 const ctx = me.ctx;
5558 const clip = meta._clip;
5559 const area = me.chartArea;
5560 const args = {
5561 meta,
5562 index: meta.index,
5563 cancelable: true
5564 };
5565 if (me.notifyPlugins('beforeDatasetDraw', args) === false) {
5566 return;
5567 }
5568 clipArea(ctx, {
5569 left: clip.left === false ? 0 : area.left - clip.left,
5570 right: clip.right === false ? me.width : area.right + clip.right,
5571 top: clip.top === false ? 0 : area.top - clip.top,
5572 bottom: clip.bottom === false ? me.height : area.bottom + clip.bottom
5573 });
5574 meta.controller.draw();
5575 unclipArea(ctx);
5576 args.cancelable = false;
5577 me.notifyPlugins('afterDatasetDraw', args);
5578 }
5579 getElementsAtEventForMode(e, mode, options, useFinalPosition) {
5580 const method = Interaction.modes[mode];
5581 if (typeof method === 'function') {
5582 return method(this, e, options, useFinalPosition);
5583 }
5584 return [];
5585 }
5586 getDatasetMeta(datasetIndex) {
5587 const me = this;
5588 const dataset = me.data.datasets[datasetIndex];
5589 const metasets = me._metasets;
5590 let meta = metasets.filter(x => x && x._dataset === dataset).pop();
5591 if (!meta) {
5592 meta = metasets[datasetIndex] = {
5593 type: null,
5594 data: [],
5595 dataset: null,
5596 controller: null,
5597 hidden: null,
5598 xAxisID: null,
5599 yAxisID: null,
5600 order: dataset && dataset.order || 0,
5601 index: datasetIndex,
5602 _dataset: dataset,
5603 _parsed: [],
5604 _sorted: false
5605 };
5606 }
5607 return meta;
5608 }
5609 getContext() {
5610 return this.$context || (this.$context = {chart: this, type: 'chart'});
5611 }
5612 getVisibleDatasetCount() {
5613 return this.getSortedVisibleDatasetMetas().length;
5614 }
5615 isDatasetVisible(datasetIndex) {
5616 const dataset = this.data.datasets[datasetIndex];
5617 if (!dataset) {
5618 return false;
5619 }
5620 const meta = this.getDatasetMeta(datasetIndex);
5621 return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;
5622 }
5623 setDatasetVisibility(datasetIndex, visible) {
5624 const meta = this.getDatasetMeta(datasetIndex);
5625 meta.hidden = !visible;
5626 }
5627 toggleDataVisibility(index) {
5628 this._hiddenIndices[index] = !this._hiddenIndices[index];
5629 }
5630 getDataVisibility(index) {
5631 return !this._hiddenIndices[index];
5632 }
5633 _updateDatasetVisibility(datasetIndex, visible) {
5634 const me = this;
5635 const mode = visible ? 'show' : 'hide';
5636 const meta = me.getDatasetMeta(datasetIndex);
5637 const anims = meta.controller._resolveAnimations(undefined, mode);
5638 me.setDatasetVisibility(datasetIndex, visible);
5639 anims.update(meta, {visible});
5640 me.update((ctx) => ctx.datasetIndex === datasetIndex ? mode : undefined);
5641 }
5642 hide(datasetIndex) {
5643 this._updateDatasetVisibility(datasetIndex, false);
5644 }
5645 show(datasetIndex) {
5646 this._updateDatasetVisibility(datasetIndex, true);
5647 }
5648 _destroyDatasetMeta(datasetIndex) {
5649 const me = this;
5650 const meta = me._metasets && me._metasets[datasetIndex];
5651 if (meta && meta.controller) {
5652 meta.controller._destroy();
5653 delete me._metasets[datasetIndex];
5654 }
5655 }
5656 destroy() {
5657 const me = this;
5658 const {canvas, ctx} = me;
5659 let i, ilen;
5660 me.stop();
5661 animator.remove(me);
5662 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
5663 me._destroyDatasetMeta(i);
5664 }
5665 me.config.clearCache();
5666 if (canvas) {
5667 me.unbindEvents();
5668 clearCanvas(canvas, ctx);
5669 me.platform.releaseContext(ctx);
5670 me.canvas = null;
5671 me.ctx = null;
5672 }
5673 me.notifyPlugins('destroy');
5674 delete instances[me.id];
5675 }
5676 toBase64Image(...args) {
5677 return this.canvas.toDataURL(...args);
5678 }
5679 bindEvents() {
5680 const me = this;
5681 const listeners = me._listeners;
5682 const platform = me.platform;
5683 const _add = (type, listener) => {
5684 platform.addEventListener(me, type, listener);
5685 listeners[type] = listener;
5686 };
5687 const _remove = (type, listener) => {
5688 if (listeners[type]) {
5689 platform.removeEventListener(me, type, listener);
5690 delete listeners[type];
5691 }
5692 };
5693 let listener = function(e, x, y) {
5694 e.offsetX = x;
5695 e.offsetY = y;
5696 me._eventHandler(e);
5697 };
5698 each(me.options.events, (type) => _add(type, listener));
5699 if (me.options.responsive) {
5700 listener = (width, height) => {
5701 if (me.canvas) {
5702 me.resize(width, height);
5703 }
5704 };
5705 let detached;
5706 const attached = () => {
5707 _remove('attach', attached);
5708 me.attached = true;
5709 me.resize();
5710 _add('resize', listener);
5711 _add('detach', detached);
5712 };
5713 detached = () => {
5714 me.attached = false;
5715 _remove('resize', listener);
5716 _add('attach', attached);
5717 };
5718 if (platform.isAttached(me.canvas)) {
5719 attached();
5720 } else {
5721 detached();
5722 }
5723 } else {
5724 me.attached = true;
5725 }
5726 }
5727 unbindEvents() {
5728 const me = this;
5729 const listeners = me._listeners;
5730 if (!listeners) {
5731 return;
5732 }
5733 me._listeners = {};
5734 each(listeners, (listener, type) => {
5735 me.platform.removeEventListener(me, type, listener);
5736 });
5737 }
5738 updateHoverStyle(items, mode, enabled) {
5739 const prefix = enabled ? 'set' : 'remove';
5740 let meta, item, i, ilen;
5741 if (mode === 'dataset') {
5742 meta = this.getDatasetMeta(items[0].datasetIndex);
5743 meta.controller['_' + prefix + 'DatasetHoverStyle']();
5744 }
5745 for (i = 0, ilen = items.length; i < ilen; ++i) {
5746 item = items[i];
5747 const controller = item && this.getDatasetMeta(item.datasetIndex).controller;
5748 if (controller) {
5749 controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);
5750 }
5751 }
5752 }
5753 getActiveElements() {
5754 return this._active || [];
5755 }
5756 setActiveElements(activeElements) {
5757 const me = this;
5758 const lastActive = me._active || [];
5759 const active = activeElements.map(({datasetIndex, index}) => {
5760 const meta = me.getDatasetMeta(datasetIndex);
5761 if (!meta) {
5762 throw new Error('No dataset found at index ' + datasetIndex);
5763 }
5764 return {
5765 datasetIndex,
5766 element: meta.data[index],
5767 index,
5768 };
5769 });
5770 const changed = !_elementsEqual(active, lastActive);
5771 if (changed) {
5772 me._active = active;
5773 me._updateHoverStyles(active, lastActive);
5774 }
5775 }
5776 notifyPlugins(hook, args, filter) {
5777 return this._plugins.notify(this, hook, args, filter);
5778 }
5779 _updateHoverStyles(active, lastActive, replay) {
5780 const me = this;
5781 const hoverOptions = me.options.hover;
5782 const diff = (a, b) => a.filter(x => !b.some(y => x.datasetIndex === y.datasetIndex && x.index === y.index));
5783 const deactivated = diff(lastActive, active);
5784 const activated = replay ? active : diff(active, lastActive);
5785 if (deactivated.length) {
5786 me.updateHoverStyle(deactivated, hoverOptions.mode, false);
5787 }
5788 if (activated.length && hoverOptions.mode) {
5789 me.updateHoverStyle(activated, hoverOptions.mode, true);
5790 }
5791 }
5792 _eventHandler(e, replay) {
5793 const me = this;
5794 const args = {event: e, replay, cancelable: true};
5795 const eventFilter = (plugin) => (plugin.options.events || this.options.events).includes(e.type);
5796 if (me.notifyPlugins('beforeEvent', args, eventFilter) === false) {
5797 return;
5798 }
5799 const changed = me._handleEvent(e, replay);
5800 args.cancelable = false;
5801 me.notifyPlugins('afterEvent', args, eventFilter);
5802 if (changed || args.changed) {
5803 me.render();
5804 }
5805 return me;
5806 }
5807 _handleEvent(e, replay) {
5808 const me = this;
5809 const {_active: lastActive = [], options} = me;
5810 const hoverOptions = options.hover;
5811 const useFinalPosition = replay;
5812 let active = [];
5813 let changed = false;
5814 let lastEvent = null;
5815 if (e.type !== 'mouseout') {
5816 active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
5817 lastEvent = e.type === 'click' ? me._lastEvent : e;
5818 }
5819 me._lastEvent = null;
5820 if (_isPointInArea(e, me.chartArea, me._minPadding)) {
5821 callback(options.onHover, [e, active, me], me);
5822 if (e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu') {
5823 callback(options.onClick, [e, active, me], me);
5824 }
5825 }
5826 changed = !_elementsEqual(active, lastActive);
5827 if (changed || replay) {
5828 me._active = active;
5829 me._updateHoverStyles(active, lastActive, replay);
5830 }
5831 me._lastEvent = lastEvent;
5832 return changed;
5833 }
5834 }
5835 const invalidatePlugins = () => each(Chart.instances, (chart) => chart._plugins.invalidate());
5836 const enumerable = true;
5837 Object.defineProperties(Chart, {
5838 defaults: {
5839 enumerable,
5840 value: defaults
5841 },
5842 instances: {
5843 enumerable,
5844 value: instances
5845 },
5846 overrides: {
5847 enumerable,
5848 value: overrides
5849 },
5850 registry: {
5851 enumerable,
5852 value: registry
5853 },
5854 version: {
5855 enumerable,
5856 value: version
5857 },
5858 getChart: {
5859 enumerable,
5860 value: getChart
5861 },
5862 register: {
5863 enumerable,
5864 value: (...items) => {
5865 registry.add(...items);
5866 invalidatePlugins();
5867 }
5868 },
5869 unregister: {
5870 enumerable,
5871 value: (...items) => {
5872 registry.remove(...items);
5873 invalidatePlugins();
5874 }
5875 }
5876 });
5877
5878 function clipArc(ctx, element) {
5879 const {startAngle, endAngle, pixelMargin, x, y, outerRadius, innerRadius} = element;
5880 let angleMargin = pixelMargin / outerRadius;
5881 ctx.beginPath();
5882 ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
5883 if (innerRadius > pixelMargin) {
5884 angleMargin = pixelMargin / innerRadius;
5885 ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
5886 } else {
5887 ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI);
5888 }
5889 ctx.closePath();
5890 ctx.clip();
5891 }
5892 function toRadiusCorners(value) {
5893 return _readValueToProps(value, ['outerStart', 'outerEnd', 'innerStart', 'innerEnd']);
5894 }
5895 function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) {
5896 const o = toRadiusCorners(arc.options.borderRadius);
5897 const halfThickness = (outerRadius - innerRadius) / 2;
5898 const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);
5899 const computeOuterLimit = (val) => {
5900 const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;
5901 return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit));
5902 };
5903 return {
5904 outerStart: computeOuterLimit(o.outerStart),
5905 outerEnd: computeOuterLimit(o.outerEnd),
5906 innerStart: _limitValue(o.innerStart, 0, innerLimit),
5907 innerEnd: _limitValue(o.innerEnd, 0, innerLimit),
5908 };
5909 }
5910 function rThetaToXY(r, theta, x, y) {
5911 return {
5912 x: x + r * Math.cos(theta),
5913 y: y + r * Math.sin(theta),
5914 };
5915 }
5916 function pathArc(ctx, element) {
5917 const {x, y, startAngle, endAngle, pixelMargin} = element;
5918 const outerRadius = Math.max(element.outerRadius - pixelMargin, 0);
5919 const innerRadius = element.innerRadius + pixelMargin;
5920 const {outerStart, outerEnd, innerStart, innerEnd} = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle);
5921 const outerStartAdjustedRadius = outerRadius - outerStart;
5922 const outerEndAdjustedRadius = outerRadius - outerEnd;
5923 const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;
5924 const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;
5925 const innerStartAdjustedRadius = innerRadius + innerStart;
5926 const innerEndAdjustedRadius = innerRadius + innerEnd;
5927 const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;
5928 const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;
5929 ctx.beginPath();
5930 ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerEndAdjustedAngle);
5931 if (outerEnd > 0) {
5932 const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
5933 ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);
5934 }
5935 const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
5936 ctx.lineTo(p4.x, p4.y);
5937 if (innerEnd > 0) {
5938 const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
5939 ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);
5940 }
5941 ctx.arc(x, y, innerRadius, endAngle - (innerEnd / innerRadius), startAngle + (innerStart / innerRadius), true);
5942 if (innerStart > 0) {
5943 const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
5944 ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);
5945 }
5946 const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
5947 ctx.lineTo(p8.x, p8.y);
5948 if (outerStart > 0) {
5949 const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
5950 ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);
5951 }
5952 ctx.closePath();
5953 }
5954 function drawArc(ctx, element) {
5955 if (element.fullCircles) {
5956 element.endAngle = element.startAngle + TAU;
5957 pathArc(ctx, element);
5958 for (let i = 0; i < element.fullCircles; ++i) {
5959 ctx.fill();
5960 }
5961 }
5962 if (!isNaN(element.circumference)) {
5963 element.endAngle = element.startAngle + element.circumference % TAU;
5964 }
5965 pathArc(ctx, element);
5966 ctx.fill();
5967 }
5968 function drawFullCircleBorders(ctx, element, inner) {
5969 const {x, y, startAngle, endAngle, pixelMargin} = element;
5970 const outerRadius = Math.max(element.outerRadius - pixelMargin, 0);
5971 const innerRadius = element.innerRadius + pixelMargin;
5972 let i;
5973 if (inner) {
5974 element.endAngle = element.startAngle + TAU;
5975 clipArc(ctx, element);
5976 element.endAngle = endAngle;
5977 if (element.endAngle === element.startAngle) {
5978 element.endAngle += TAU;
5979 element.fullCircles--;
5980 }
5981 }
5982 ctx.beginPath();
5983 ctx.arc(x, y, innerRadius, startAngle + TAU, startAngle, true);
5984 for (i = 0; i < element.fullCircles; ++i) {
5985 ctx.stroke();
5986 }
5987 ctx.beginPath();
5988 ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU);
5989 for (i = 0; i < element.fullCircles; ++i) {
5990 ctx.stroke();
5991 }
5992 }
5993 function drawBorder(ctx, element) {
5994 const {options} = element;
5995 const inner = options.borderAlign === 'inner';
5996 if (!options.borderWidth) {
5997 return;
5998 }
5999 if (inner) {
6000 ctx.lineWidth = options.borderWidth * 2;
6001 ctx.lineJoin = 'round';
6002 } else {
6003 ctx.lineWidth = options.borderWidth;
6004 ctx.lineJoin = 'bevel';
6005 }
6006 if (element.fullCircles) {
6007 drawFullCircleBorders(ctx, element, inner);
6008 }
6009 if (inner) {
6010 clipArc(ctx, element);
6011 }
6012 pathArc(ctx, element);
6013 ctx.stroke();
6014 }
6015 class ArcElement extends Element {
6016 constructor(cfg) {
6017 super();
6018 this.options = undefined;
6019 this.circumference = undefined;
6020 this.startAngle = undefined;
6021 this.endAngle = undefined;
6022 this.innerRadius = undefined;
6023 this.outerRadius = undefined;
6024 this.pixelMargin = 0;
6025 this.fullCircles = 0;
6026 if (cfg) {
6027 Object.assign(this, cfg);
6028 }
6029 }
6030 inRange(chartX, chartY, useFinalPosition) {
6031 const point = this.getProps(['x', 'y'], useFinalPosition);
6032 const {angle, distance} = getAngleFromPoint(point, {x: chartX, y: chartY});
6033 const {startAngle, endAngle, innerRadius, outerRadius, circumference} = this.getProps([
6034 'startAngle',
6035 'endAngle',
6036 'innerRadius',
6037 'outerRadius',
6038 'circumference'
6039 ], useFinalPosition);
6040 const betweenAngles = circumference >= TAU || _angleBetween(angle, startAngle, endAngle);
6041 const withinRadius = (distance >= innerRadius && distance <= outerRadius);
6042 return (betweenAngles && withinRadius);
6043 }
6044 getCenterPoint(useFinalPosition) {
6045 const {x, y, startAngle, endAngle, innerRadius, outerRadius} = this.getProps([
6046 'x',
6047 'y',
6048 'startAngle',
6049 'endAngle',
6050 'innerRadius',
6051 'outerRadius'
6052 ], useFinalPosition);
6053 const halfAngle = (startAngle + endAngle) / 2;
6054 const halfRadius = (innerRadius + outerRadius) / 2;
6055 return {
6056 x: x + Math.cos(halfAngle) * halfRadius,
6057 y: y + Math.sin(halfAngle) * halfRadius
6058 };
6059 }
6060 tooltipPosition(useFinalPosition) {
6061 return this.getCenterPoint(useFinalPosition);
6062 }
6063 draw(ctx) {
6064 const me = this;
6065 const options = me.options;
6066 const offset = options.offset || 0;
6067 me.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0;
6068 me.fullCircles = Math.floor(me.circumference / TAU);
6069 if (me.circumference === 0 || me.innerRadius < 0 || me.outerRadius < 0) {
6070 return;
6071 }
6072 ctx.save();
6073 if (offset && me.circumference < TAU) {
6074 const halfAngle = (me.startAngle + me.endAngle) / 2;
6075 ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset);
6076 }
6077 ctx.fillStyle = options.backgroundColor;
6078 ctx.strokeStyle = options.borderColor;
6079 drawArc(ctx, me);
6080 drawBorder(ctx, me);
6081 ctx.restore();
6082 }
6083 }
6084 ArcElement.id = 'arc';
6085 ArcElement.defaults = {
6086 borderAlign: 'center',
6087 borderColor: '#fff',
6088 borderRadius: 0,
6089 borderWidth: 2,
6090 offset: 0,
6091 angle: undefined,
6092 };
6093 ArcElement.defaultRoutes = {
6094 backgroundColor: 'backgroundColor'
6095 };
6096
6097 function setStyle(ctx, options, style = options) {
6098 ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle);
6099 ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash));
6100 ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset);
6101 ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);
6102 ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth);
6103 ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor);
6104 }
6105 function lineTo(ctx, previous, target) {
6106 ctx.lineTo(target.x, target.y);
6107 }
6108 function getLineMethod(options) {
6109 if (options.stepped) {
6110 return _steppedLineTo;
6111 }
6112 if (options.tension) {
6113 return _bezierCurveTo;
6114 }
6115 return lineTo;
6116 }
6117 function pathVars(points, segment, params = {}) {
6118 const count = points.length;
6119 const {start: paramsStart = 0, end: paramsEnd = count - 1} = params;
6120 const {start: segmentStart, end: segmentEnd} = segment;
6121 const start = Math.max(paramsStart, segmentStart);
6122 const end = Math.min(paramsEnd, segmentEnd);
6123 const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;
6124 return {
6125 count,
6126 start,
6127 loop: segment.loop,
6128 ilen: end < start && !outside ? count + end - start : end - start
6129 };
6130 }
6131 function pathSegment(ctx, line, segment, params) {
6132 const {points, options} = line;
6133 const {count, start, loop, ilen} = pathVars(points, segment, params);
6134 const lineMethod = getLineMethod(options);
6135 let {move = true, reverse} = params || {};
6136 let i, point, prev;
6137 for (i = 0; i <= ilen; ++i) {
6138 point = points[(start + (reverse ? ilen - i : i)) % count];
6139 if (point.skip) {
6140 continue;
6141 } else if (move) {
6142 ctx.moveTo(point.x, point.y);
6143 move = false;
6144 } else {
6145 lineMethod(ctx, prev, point, reverse, options.stepped);
6146 }
6147 prev = point;
6148 }
6149 if (loop) {
6150 point = points[(start + (reverse ? ilen : 0)) % count];
6151 lineMethod(ctx, prev, point, reverse, options.stepped);
6152 }
6153 return !!loop;
6154 }
6155 function fastPathSegment(ctx, line, segment, params) {
6156 const points = line.points;
6157 const {count, start, ilen} = pathVars(points, segment, params);
6158 const {move = true, reverse} = params || {};
6159 let avgX = 0;
6160 let countX = 0;
6161 let i, point, prevX, minY, maxY, lastY;
6162 const pointIndex = (index) => (start + (reverse ? ilen - index : index)) % count;
6163 const drawX = () => {
6164 if (minY !== maxY) {
6165 ctx.lineTo(avgX, maxY);
6166 ctx.lineTo(avgX, minY);
6167 ctx.lineTo(avgX, lastY);
6168 }
6169 };
6170 if (move) {
6171 point = points[pointIndex(0)];
6172 ctx.moveTo(point.x, point.y);
6173 }
6174 for (i = 0; i <= ilen; ++i) {
6175 point = points[pointIndex(i)];
6176 if (point.skip) {
6177 continue;
6178 }
6179 const x = point.x;
6180 const y = point.y;
6181 const truncX = x | 0;
6182 if (truncX === prevX) {
6183 if (y < minY) {
6184 minY = y;
6185 } else if (y > maxY) {
6186 maxY = y;
6187 }
6188 avgX = (countX * avgX + x) / ++countX;
6189 } else {
6190 drawX();
6191 ctx.lineTo(x, y);
6192 prevX = truncX;
6193 countX = 0;
6194 minY = maxY = y;
6195 }
6196 lastY = y;
6197 }
6198 drawX();
6199 }
6200 function _getSegmentMethod(line) {
6201 const opts = line.options;
6202 const borderDash = opts.borderDash && opts.borderDash.length;
6203 const useFastPath = !line._decimated && !line._loop && !opts.tension && !opts.stepped && !borderDash;
6204 return useFastPath ? fastPathSegment : pathSegment;
6205 }
6206 function _getInterpolationMethod(options) {
6207 if (options.stepped) {
6208 return _steppedInterpolation;
6209 }
6210 if (options.tension) {
6211 return _bezierInterpolation;
6212 }
6213 return _pointInLine;
6214 }
6215 function strokePathWithCache(ctx, line, start, count) {
6216 let path = line._path;
6217 if (!path) {
6218 path = line._path = new Path2D();
6219 if (line.path(path, start, count)) {
6220 path.closePath();
6221 }
6222 }
6223 setStyle(ctx, line.options);
6224 ctx.stroke(path);
6225 }
6226 function strokePathDirect(ctx, line, start, count) {
6227 const {segments, options} = line;
6228 const segmentMethod = _getSegmentMethod(line);
6229 for (const segment of segments) {
6230 setStyle(ctx, options, segment.style);
6231 ctx.beginPath();
6232 if (segmentMethod(ctx, line, segment, {start, end: start + count - 1})) {
6233 ctx.closePath();
6234 }
6235 ctx.stroke();
6236 }
6237 }
6238 const usePath2D = typeof Path2D === 'function';
6239 function draw(ctx, line, start, count) {
6240 if (usePath2D && line.segments.length === 1) {
6241 strokePathWithCache(ctx, line, start, count);
6242 } else {
6243 strokePathDirect(ctx, line, start, count);
6244 }
6245 }
6246 class LineElement extends Element {
6247 constructor(cfg) {
6248 super();
6249 this.animated = true;
6250 this.options = undefined;
6251 this._loop = undefined;
6252 this._fullLoop = undefined;
6253 this._path = undefined;
6254 this._points = undefined;
6255 this._segments = undefined;
6256 this._decimated = false;
6257 this._pointsUpdated = false;
6258 if (cfg) {
6259 Object.assign(this, cfg);
6260 }
6261 }
6262 updateControlPoints(chartArea) {
6263 const me = this;
6264 const options = me.options;
6265 if (options.tension && !options.stepped && !me._pointsUpdated) {
6266 const loop = options.spanGaps ? me._loop : me._fullLoop;
6267 _updateBezierControlPoints(me._points, options, chartArea, loop);
6268 me._pointsUpdated = true;
6269 }
6270 }
6271 set points(points) {
6272 const me = this;
6273 me._points = points;
6274 delete me._segments;
6275 delete me._path;
6276 me._pointsUpdated = false;
6277 }
6278 get points() {
6279 return this._points;
6280 }
6281 get segments() {
6282 return this._segments || (this._segments = _computeSegments(this, this.options.segment));
6283 }
6284 first() {
6285 const segments = this.segments;
6286 const points = this.points;
6287 return segments.length && points[segments[0].start];
6288 }
6289 last() {
6290 const segments = this.segments;
6291 const points = this.points;
6292 const count = segments.length;
6293 return count && points[segments[count - 1].end];
6294 }
6295 interpolate(point, property) {
6296 const me = this;
6297 const options = me.options;
6298 const value = point[property];
6299 const points = me.points;
6300 const segments = _boundSegments(me, {property, start: value, end: value});
6301 if (!segments.length) {
6302 return;
6303 }
6304 const result = [];
6305 const _interpolate = _getInterpolationMethod(options);
6306 let i, ilen;
6307 for (i = 0, ilen = segments.length; i < ilen; ++i) {
6308 const {start, end} = segments[i];
6309 const p1 = points[start];
6310 const p2 = points[end];
6311 if (p1 === p2) {
6312 result.push(p1);
6313 continue;
6314 }
6315 const t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));
6316 const interpolated = _interpolate(p1, p2, t, options.stepped);
6317 interpolated[property] = point[property];
6318 result.push(interpolated);
6319 }
6320 return result.length === 1 ? result[0] : result;
6321 }
6322 pathSegment(ctx, segment, params) {
6323 const segmentMethod = _getSegmentMethod(this);
6324 return segmentMethod(ctx, this, segment, params);
6325 }
6326 path(ctx, start, count) {
6327 const me = this;
6328 const segments = me.segments;
6329 const segmentMethod = _getSegmentMethod(me);
6330 let loop = me._loop;
6331 start = start || 0;
6332 count = count || (me.points.length - start);
6333 for (const segment of segments) {
6334 loop &= segmentMethod(ctx, me, segment, {start, end: start + count - 1});
6335 }
6336 return !!loop;
6337 }
6338 draw(ctx, chartArea, start, count) {
6339 const me = this;
6340 const options = me.options || {};
6341 const points = me.points || [];
6342 if (!points.length || !options.borderWidth) {
6343 return;
6344 }
6345 ctx.save();
6346 draw(ctx, me, start, count);
6347 ctx.restore();
6348 if (me.animated) {
6349 me._pointsUpdated = false;
6350 me._path = undefined;
6351 }
6352 }
6353 }
6354 LineElement.id = 'line';
6355 LineElement.defaults = {
6356 borderCapStyle: 'butt',
6357 borderDash: [],
6358 borderDashOffset: 0,
6359 borderJoinStyle: 'miter',
6360 borderWidth: 3,
6361 capBezierPoints: true,
6362 cubicInterpolationMode: 'default',
6363 fill: false,
6364 spanGaps: false,
6365 stepped: false,
6366 tension: 0,
6367 };
6368 LineElement.defaultRoutes = {
6369 backgroundColor: 'backgroundColor',
6370 borderColor: 'borderColor'
6371 };
6372 LineElement.descriptors = {
6373 _scriptable: true,
6374 _indexable: (name) => name !== 'borderDash' && name !== 'fill',
6375 };
6376
6377 function inRange$1(el, pos, axis, useFinalPosition) {
6378 const options = el.options;
6379 const {[axis]: value} = el.getProps([axis], useFinalPosition);
6380 return (Math.abs(pos - value) < options.radius + options.hitRadius);
6381 }
6382 class PointElement extends Element {
6383 constructor(cfg) {
6384 super();
6385 this.options = undefined;
6386 this.parsed = undefined;
6387 this.skip = undefined;
6388 this.stop = undefined;
6389 if (cfg) {
6390 Object.assign(this, cfg);
6391 }
6392 }
6393 inRange(mouseX, mouseY, useFinalPosition) {
6394 const options = this.options;
6395 const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
6396 return ((Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2)) < Math.pow(options.hitRadius + options.radius, 2));
6397 }
6398 inXRange(mouseX, useFinalPosition) {
6399 return inRange$1(this, mouseX, 'x', useFinalPosition);
6400 }
6401 inYRange(mouseY, useFinalPosition) {
6402 return inRange$1(this, mouseY, 'y', useFinalPosition);
6403 }
6404 getCenterPoint(useFinalPosition) {
6405 const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
6406 return {x, y};
6407 }
6408 size(options) {
6409 options = options || this.options || {};
6410 let radius = options.radius || 0;
6411 radius = Math.max(radius, radius && options.hoverRadius || 0);
6412 const borderWidth = radius && options.borderWidth || 0;
6413 return (radius + borderWidth) * 2;
6414 }
6415 draw(ctx) {
6416 const me = this;
6417 const options = me.options;
6418 if (me.skip || options.radius < 0.1) {
6419 return;
6420 }
6421 ctx.strokeStyle = options.borderColor;
6422 ctx.lineWidth = options.borderWidth;
6423 ctx.fillStyle = options.backgroundColor;
6424 drawPoint(ctx, options, me.x, me.y);
6425 }
6426 getRange() {
6427 const options = this.options || {};
6428 return options.radius + options.hitRadius;
6429 }
6430 }
6431 PointElement.id = 'point';
6432 PointElement.defaults = {
6433 borderWidth: 1,
6434 hitRadius: 1,
6435 hoverBorderWidth: 1,
6436 hoverRadius: 4,
6437 pointStyle: 'circle',
6438 radius: 3,
6439 rotation: 0
6440 };
6441 PointElement.defaultRoutes = {
6442 backgroundColor: 'backgroundColor',
6443 borderColor: 'borderColor'
6444 };
6445
6446 function getBarBounds(bar, useFinalPosition) {
6447 const {x, y, base, width, height} = bar.getProps(['x', 'y', 'base', 'width', 'height'], useFinalPosition);
6448 let left, right, top, bottom, half;
6449 if (bar.horizontal) {
6450 half = height / 2;
6451 left = Math.min(x, base);
6452 right = Math.max(x, base);
6453 top = y - half;
6454 bottom = y + half;
6455 } else {
6456 half = width / 2;
6457 left = x - half;
6458 right = x + half;
6459 top = Math.min(y, base);
6460 bottom = Math.max(y, base);
6461 }
6462 return {left, top, right, bottom};
6463 }
6464 function parseBorderSkipped(bar) {
6465 let edge = bar.options.borderSkipped;
6466 const res = {};
6467 if (!edge) {
6468 return res;
6469 }
6470 edge = bar.horizontal
6471 ? parseEdge(edge, 'left', 'right', bar.base > bar.x)
6472 : parseEdge(edge, 'bottom', 'top', bar.base < bar.y);
6473 res[edge] = true;
6474 return res;
6475 }
6476 function parseEdge(edge, a, b, reverse) {
6477 if (reverse) {
6478 edge = swap(edge, a, b);
6479 edge = startEnd(edge, b, a);
6480 } else {
6481 edge = startEnd(edge, a, b);
6482 }
6483 return edge;
6484 }
6485 function swap(orig, v1, v2) {
6486 return orig === v1 ? v2 : orig === v2 ? v1 : orig;
6487 }
6488 function startEnd(v, start, end) {
6489 return v === 'start' ? start : v === 'end' ? end : v;
6490 }
6491 function skipOrLimit(skip, value, min, max) {
6492 return skip ? 0 : Math.max(Math.min(value, max), min);
6493 }
6494 function parseBorderWidth(bar, maxW, maxH) {
6495 const value = bar.options.borderWidth;
6496 const skip = parseBorderSkipped(bar);
6497 const o = toTRBL(value);
6498 return {
6499 t: skipOrLimit(skip.top, o.top, 0, maxH),
6500 r: skipOrLimit(skip.right, o.right, 0, maxW),
6501 b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
6502 l: skipOrLimit(skip.left, o.left, 0, maxW)
6503 };
6504 }
6505 function parseBorderRadius(bar, maxW, maxH) {
6506 const {enableBorderRadius} = bar.getProps(['enableBorderRadius']);
6507 const value = bar.options.borderRadius;
6508 const o = toTRBLCorners(value);
6509 const maxR = Math.min(maxW, maxH);
6510 const skip = parseBorderSkipped(bar);
6511 const enableBorder = enableBorderRadius || isObject(value);
6512 return {
6513 topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
6514 topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
6515 bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
6516 bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
6517 };
6518 }
6519 function boundingRects(bar) {
6520 const bounds = getBarBounds(bar);
6521 const width = bounds.right - bounds.left;
6522 const height = bounds.bottom - bounds.top;
6523 const border = parseBorderWidth(bar, width / 2, height / 2);
6524 const radius = parseBorderRadius(bar, width / 2, height / 2);
6525 return {
6526 outer: {
6527 x: bounds.left,
6528 y: bounds.top,
6529 w: width,
6530 h: height,
6531 radius
6532 },
6533 inner: {
6534 x: bounds.left + border.l,
6535 y: bounds.top + border.t,
6536 w: width - border.l - border.r,
6537 h: height - border.t - border.b,
6538 radius: {
6539 topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
6540 topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
6541 bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
6542 bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
6543 }
6544 }
6545 };
6546 }
6547 function inRange(bar, x, y, useFinalPosition) {
6548 const skipX = x === null;
6549 const skipY = y === null;
6550 const skipBoth = skipX && skipY;
6551 const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
6552 return bounds
6553 && (skipX || x >= bounds.left && x <= bounds.right)
6554 && (skipY || y >= bounds.top && y <= bounds.bottom);
6555 }
6556 function hasRadius(radius) {
6557 return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
6558 }
6559 function addNormalRectPath(ctx, rect) {
6560 ctx.rect(rect.x, rect.y, rect.w, rect.h);
6561 }
6562 class BarElement extends Element {
6563 constructor(cfg) {
6564 super();
6565 this.options = undefined;
6566 this.horizontal = undefined;
6567 this.base = undefined;
6568 this.width = undefined;
6569 this.height = undefined;
6570 if (cfg) {
6571 Object.assign(this, cfg);
6572 }
6573 }
6574 draw(ctx) {
6575 const options = this.options;
6576 const {inner, outer} = boundingRects(this);
6577 const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
6578 ctx.save();
6579 if (outer.w !== inner.w || outer.h !== inner.h) {
6580 ctx.beginPath();
6581 addRectPath(ctx, outer);
6582 ctx.clip();
6583 addRectPath(ctx, inner);
6584 ctx.fillStyle = options.borderColor;
6585 ctx.fill('evenodd');
6586 }
6587 ctx.beginPath();
6588 addRectPath(ctx, inner);
6589 ctx.fillStyle = options.backgroundColor;
6590 ctx.fill();
6591 ctx.restore();
6592 }
6593 inRange(mouseX, mouseY, useFinalPosition) {
6594 return inRange(this, mouseX, mouseY, useFinalPosition);
6595 }
6596 inXRange(mouseX, useFinalPosition) {
6597 return inRange(this, mouseX, null, useFinalPosition);
6598 }
6599 inYRange(mouseY, useFinalPosition) {
6600 return inRange(this, null, mouseY, useFinalPosition);
6601 }
6602 getCenterPoint(useFinalPosition) {
6603 const {x, y, base, horizontal} = this.getProps(['x', 'y', 'base', 'horizontal'], useFinalPosition);
6604 return {
6605 x: horizontal ? (x + base) / 2 : x,
6606 y: horizontal ? y : (y + base) / 2
6607 };
6608 }
6609 getRange(axis) {
6610 return axis === 'x' ? this.width / 2 : this.height / 2;
6611 }
6612 }
6613 BarElement.id = 'bar';
6614 BarElement.defaults = {
6615 borderSkipped: 'start',
6616 borderWidth: 0,
6617 borderRadius: 0,
6618 enableBorderRadius: true,
6619 pointStyle: undefined
6620 };
6621 BarElement.defaultRoutes = {
6622 backgroundColor: 'backgroundColor',
6623 borderColor: 'borderColor'
6624 };
6625
6626 var elements = /*#__PURE__*/Object.freeze({
6627 __proto__: null,
6628 ArcElement: ArcElement,
6629 LineElement: LineElement,
6630 PointElement: PointElement,
6631 BarElement: BarElement
6632 });
6633
6634 function lttbDecimation(data, start, count, availableWidth, options) {
6635 const samples = options.samples || availableWidth;
6636 if (samples >= count) {
6637 return data.slice(start, start + count);
6638 }
6639 const decimated = [];
6640 const bucketWidth = (count - 2) / (samples - 2);
6641 let sampledIndex = 0;
6642 const endIndex = start + count - 1;
6643 let a = start;
6644 let i, maxAreaPoint, maxArea, area, nextA;
6645 decimated[sampledIndex++] = data[a];
6646 for (i = 0; i < samples - 2; i++) {
6647 let avgX = 0;
6648 let avgY = 0;
6649 let j;
6650 const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
6651 const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
6652 const avgRangeLength = avgRangeEnd - avgRangeStart;
6653 for (j = avgRangeStart; j < avgRangeEnd; j++) {
6654 avgX += data[j].x;
6655 avgY += data[j].y;
6656 }
6657 avgX /= avgRangeLength;
6658 avgY /= avgRangeLength;
6659 const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
6660 const rangeTo = Math.floor((i + 1) * bucketWidth) + 1 + start;
6661 const {x: pointAx, y: pointAy} = data[a];
6662 maxArea = area = -1;
6663 for (j = rangeOffs; j < rangeTo; j++) {
6664 area = 0.5 * Math.abs(
6665 (pointAx - avgX) * (data[j].y - pointAy) -
6666 (pointAx - data[j].x) * (avgY - pointAy)
6667 );
6668 if (area > maxArea) {
6669 maxArea = area;
6670 maxAreaPoint = data[j];
6671 nextA = j;
6672 }
6673 }
6674 decimated[sampledIndex++] = maxAreaPoint;
6675 a = nextA;
6676 }
6677 decimated[sampledIndex++] = data[endIndex];
6678 return decimated;
6679 }
6680 function minMaxDecimation(data, start, count, availableWidth) {
6681 let avgX = 0;
6682 let countX = 0;
6683 let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
6684 const decimated = [];
6685 const endIndex = start + count - 1;
6686 const xMin = data[start].x;
6687 const xMax = data[endIndex].x;
6688 const dx = xMax - xMin;
6689 for (i = start; i < start + count; ++i) {
6690 point = data[i];
6691 x = (point.x - xMin) / dx * availableWidth;
6692 y = point.y;
6693 const truncX = x | 0;
6694 if (truncX === prevX) {
6695 if (y < minY) {
6696 minY = y;
6697 minIndex = i;
6698 } else if (y > maxY) {
6699 maxY = y;
6700 maxIndex = i;
6701 }
6702 avgX = (countX * avgX + point.x) / ++countX;
6703 } else {
6704 const lastIndex = i - 1;
6705 if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) {
6706 const intermediateIndex1 = Math.min(minIndex, maxIndex);
6707 const intermediateIndex2 = Math.max(minIndex, maxIndex);
6708 if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {
6709 decimated.push({
6710 ...data[intermediateIndex1],
6711 x: avgX,
6712 });
6713 }
6714 if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {
6715 decimated.push({
6716 ...data[intermediateIndex2],
6717 x: avgX
6718 });
6719 }
6720 }
6721 if (i > 0 && lastIndex !== startIndex) {
6722 decimated.push(data[lastIndex]);
6723 }
6724 decimated.push(point);
6725 prevX = truncX;
6726 countX = 0;
6727 minY = maxY = y;
6728 minIndex = maxIndex = startIndex = i;
6729 }
6730 }
6731 return decimated;
6732 }
6733 function cleanDecimatedDataset(dataset) {
6734 if (dataset._decimated) {
6735 const data = dataset._data;
6736 delete dataset._decimated;
6737 delete dataset._data;
6738 Object.defineProperty(dataset, 'data', {value: data});
6739 }
6740 }
6741 function cleanDecimatedData(chart) {
6742 chart.data.datasets.forEach((dataset) => {
6743 cleanDecimatedDataset(dataset);
6744 });
6745 }
6746 function getStartAndCountOfVisiblePointsSimplified(meta, points) {
6747 const pointCount = points.length;
6748 let start = 0;
6749 let count;
6750 const {iScale} = meta;
6751 const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
6752 if (minDefined) {
6753 start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
6754 }
6755 if (maxDefined) {
6756 count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
6757 } else {
6758 count = pointCount - start;
6759 }
6760 return {start, count};
6761 }
6762 var plugin_decimation = {
6763 id: 'decimation',
6764 defaults: {
6765 algorithm: 'min-max',
6766 enabled: false,
6767 },
6768 beforeElementsUpdate: (chart, args, options) => {
6769 if (!options.enabled) {
6770 cleanDecimatedData(chart);
6771 return;
6772 }
6773 const availableWidth = chart.width;
6774 chart.data.datasets.forEach((dataset, datasetIndex) => {
6775 const {_data, indexAxis} = dataset;
6776 const meta = chart.getDatasetMeta(datasetIndex);
6777 const data = _data || dataset.data;
6778 if (resolve([indexAxis, chart.options.indexAxis]) === 'y') {
6779 return;
6780 }
6781 if (meta.type !== 'line') {
6782 return;
6783 }
6784 const xAxis = chart.scales[meta.xAxisID];
6785 if (xAxis.type !== 'linear' && xAxis.type !== 'time') {
6786 return;
6787 }
6788 if (chart.options.parsing) {
6789 return;
6790 }
6791 let {start, count} = getStartAndCountOfVisiblePointsSimplified(meta, data);
6792 if (count <= 4 * availableWidth) {
6793 cleanDecimatedDataset(dataset);
6794 return;
6795 }
6796 if (isNullOrUndef(_data)) {
6797 dataset._data = data;
6798 delete dataset.data;
6799 Object.defineProperty(dataset, 'data', {
6800 configurable: true,
6801 enumerable: true,
6802 get: function() {
6803 return this._decimated;
6804 },
6805 set: function(d) {
6806 this._data = d;
6807 }
6808 });
6809 }
6810 let decimated;
6811 switch (options.algorithm) {
6812 case 'lttb':
6813 decimated = lttbDecimation(data, start, count, availableWidth, options);
6814 break;
6815 case 'min-max':
6816 decimated = minMaxDecimation(data, start, count, availableWidth);
6817 break;
6818 default:
6819 throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);
6820 }
6821 dataset._decimated = decimated;
6822 });
6823 },
6824 destroy(chart) {
6825 cleanDecimatedData(chart);
6826 }
6827 };
6828
6829 function getLineByIndex(chart, index) {
6830 const meta = chart.getDatasetMeta(index);
6831 const visible = meta && chart.isDatasetVisible(index);
6832 return visible ? meta.dataset : null;
6833 }
6834 function parseFillOption(line) {
6835 const options = line.options;
6836 const fillOption = options.fill;
6837 let fill = valueOrDefault(fillOption && fillOption.target, fillOption);
6838 if (fill === undefined) {
6839 fill = !!options.backgroundColor;
6840 }
6841 if (fill === false || fill === null) {
6842 return false;
6843 }
6844 if (fill === true) {
6845 return 'origin';
6846 }
6847 return fill;
6848 }
6849 function decodeFill(line, index, count) {
6850 const fill = parseFillOption(line);
6851 if (isObject(fill)) {
6852 return isNaN(fill.value) ? false : fill;
6853 }
6854 let target = parseFloat(fill);
6855 if (isNumberFinite(target) && Math.floor(target) === target) {
6856 if (fill[0] === '-' || fill[0] === '+') {
6857 target = index + target;
6858 }
6859 if (target === index || target < 0 || target >= count) {
6860 return false;
6861 }
6862 return target;
6863 }
6864 return ['origin', 'start', 'end', 'stack'].indexOf(fill) >= 0 && fill;
6865 }
6866 function computeLinearBoundary(source) {
6867 const {scale = {}, fill} = source;
6868 let target = null;
6869 let horizontal;
6870 if (fill === 'start') {
6871 target = scale.bottom;
6872 } else if (fill === 'end') {
6873 target = scale.top;
6874 } else if (isObject(fill)) {
6875 target = scale.getPixelForValue(fill.value);
6876 } else if (scale.getBasePixel) {
6877 target = scale.getBasePixel();
6878 }
6879 if (isNumberFinite(target)) {
6880 horizontal = scale.isHorizontal();
6881 return {
6882 x: horizontal ? target : null,
6883 y: horizontal ? null : target
6884 };
6885 }
6886 return null;
6887 }
6888 class simpleArc {
6889 constructor(opts) {
6890 this.x = opts.x;
6891 this.y = opts.y;
6892 this.radius = opts.radius;
6893 }
6894 pathSegment(ctx, bounds, opts) {
6895 const {x, y, radius} = this;
6896 bounds = bounds || {start: 0, end: TAU};
6897 ctx.arc(x, y, radius, bounds.end, bounds.start, true);
6898 return !opts.bounds;
6899 }
6900 interpolate(point) {
6901 const {x, y, radius} = this;
6902 const angle = point.angle;
6903 return {
6904 x: x + Math.cos(angle) * radius,
6905 y: y + Math.sin(angle) * radius,
6906 angle
6907 };
6908 }
6909 }
6910 function computeCircularBoundary(source) {
6911 const {scale, fill} = source;
6912 const options = scale.options;
6913 const length = scale.getLabels().length;
6914 const target = [];
6915 const start = options.reverse ? scale.max : scale.min;
6916 const end = options.reverse ? scale.min : scale.max;
6917 let i, center, value;
6918 if (fill === 'start') {
6919 value = start;
6920 } else if (fill === 'end') {
6921 value = end;
6922 } else if (isObject(fill)) {
6923 value = fill.value;
6924 } else {
6925 value = scale.getBaseValue();
6926 }
6927 if (options.grid.circular) {
6928 center = scale.getPointPositionForValue(0, start);
6929 return new simpleArc({
6930 x: center.x,
6931 y: center.y,
6932 radius: scale.getDistanceFromCenterForValue(value)
6933 });
6934 }
6935 for (i = 0; i < length; ++i) {
6936 target.push(scale.getPointPositionForValue(i, value));
6937 }
6938 return target;
6939 }
6940 function computeBoundary(source) {
6941 const scale = source.scale || {};
6942 if (scale.getPointPositionForValue) {
6943 return computeCircularBoundary(source);
6944 }
6945 return computeLinearBoundary(source);
6946 }
6947 function pointsFromSegments(boundary, line) {
6948 const {x = null, y = null} = boundary || {};
6949 const linePoints = line.points;
6950 const points = [];
6951 line.segments.forEach((segment) => {
6952 const first = linePoints[segment.start];
6953 const last = linePoints[segment.end];
6954 if (y !== null) {
6955 points.push({x: first.x, y});
6956 points.push({x: last.x, y});
6957 } else if (x !== null) {
6958 points.push({x, y: first.y});
6959 points.push({x, y: last.y});
6960 }
6961 });
6962 return points;
6963 }
6964 function buildStackLine(source) {
6965 const {chart, scale, index, line} = source;
6966 const points = [];
6967 const segments = line.segments;
6968 const sourcePoints = line.points;
6969 const linesBelow = getLinesBelow(chart, index);
6970 linesBelow.push(createBoundaryLine({x: null, y: scale.bottom}, line));
6971 for (let i = 0; i < segments.length; i++) {
6972 const segment = segments[i];
6973 for (let j = segment.start; j <= segment.end; j++) {
6974 addPointsBelow(points, sourcePoints[j], linesBelow);
6975 }
6976 }
6977 return new LineElement({points, options: {}});
6978 }
6979 const isLineAndNotInHideAnimation = (meta) => meta.type === 'line' && !meta.hidden;
6980 function getLinesBelow(chart, index) {
6981 const below = [];
6982 const metas = chart.getSortedVisibleDatasetMetas();
6983 for (let i = 0; i < metas.length; i++) {
6984 const meta = metas[i];
6985 if (meta.index === index) {
6986 break;
6987 }
6988 if (isLineAndNotInHideAnimation(meta)) {
6989 below.unshift(meta.dataset);
6990 }
6991 }
6992 return below;
6993 }
6994 function addPointsBelow(points, sourcePoint, linesBelow) {
6995 const postponed = [];
6996 for (let j = 0; j < linesBelow.length; j++) {
6997 const line = linesBelow[j];
6998 const {first, last, point} = findPoint(line, sourcePoint, 'x');
6999 if (!point || (first && last)) {
7000 continue;
7001 }
7002 if (first) {
7003 postponed.unshift(point);
7004 } else {
7005 points.push(point);
7006 if (!last) {
7007 break;
7008 }
7009 }
7010 }
7011 points.push(...postponed);
7012 }
7013 function findPoint(line, sourcePoint, property) {
7014 const point = line.interpolate(sourcePoint, property);
7015 if (!point) {
7016 return {};
7017 }
7018 const pointValue = point[property];
7019 const segments = line.segments;
7020 const linePoints = line.points;
7021 let first = false;
7022 let last = false;
7023 for (let i = 0; i < segments.length; i++) {
7024 const segment = segments[i];
7025 const firstValue = linePoints[segment.start][property];
7026 const lastValue = linePoints[segment.end][property];
7027 if (pointValue >= firstValue && pointValue <= lastValue) {
7028 first = pointValue === firstValue;
7029 last = pointValue === lastValue;
7030 break;
7031 }
7032 }
7033 return {first, last, point};
7034 }
7035 function getTarget(source) {
7036 const {chart, fill, line} = source;
7037 if (isNumberFinite(fill)) {
7038 return getLineByIndex(chart, fill);
7039 }
7040 if (fill === 'stack') {
7041 return buildStackLine(source);
7042 }
7043 const boundary = computeBoundary(source);
7044 if (boundary instanceof simpleArc) {
7045 return boundary;
7046 }
7047 return createBoundaryLine(boundary, line);
7048 }
7049 function createBoundaryLine(boundary, line) {
7050 let points = [];
7051 let _loop = false;
7052 if (isArray(boundary)) {
7053 _loop = true;
7054 points = boundary;
7055 } else {
7056 points = pointsFromSegments(boundary, line);
7057 }
7058 return points.length ? new LineElement({
7059 points,
7060 options: {tension: 0},
7061 _loop,
7062 _fullLoop: _loop
7063 }) : null;
7064 }
7065 function resolveTarget(sources, index, propagate) {
7066 const source = sources[index];
7067 let fill = source.fill;
7068 const visited = [index];
7069 let target;
7070 if (!propagate) {
7071 return fill;
7072 }
7073 while (fill !== false && visited.indexOf(fill) === -1) {
7074 if (!isNumberFinite(fill)) {
7075 return fill;
7076 }
7077 target = sources[fill];
7078 if (!target) {
7079 return false;
7080 }
7081 if (target.visible) {
7082 return fill;
7083 }
7084 visited.push(fill);
7085 fill = target.fill;
7086 }
7087 return false;
7088 }
7089 function _clip(ctx, target, clipY) {
7090 ctx.beginPath();
7091 target.path(ctx);
7092 ctx.lineTo(target.last().x, clipY);
7093 ctx.lineTo(target.first().x, clipY);
7094 ctx.closePath();
7095 ctx.clip();
7096 }
7097 function getBounds(property, first, last, loop) {
7098 if (loop) {
7099 return;
7100 }
7101 let start = first[property];
7102 let end = last[property];
7103 if (property === 'angle') {
7104 start = _normalizeAngle(start);
7105 end = _normalizeAngle(end);
7106 }
7107 return {property, start, end};
7108 }
7109 function _getEdge(a, b, prop, fn) {
7110 if (a && b) {
7111 return fn(a[prop], b[prop]);
7112 }
7113 return a ? a[prop] : b ? b[prop] : 0;
7114 }
7115 function _segments(line, target, property) {
7116 const segments = line.segments;
7117 const points = line.points;
7118 const tpoints = target.points;
7119 const parts = [];
7120 for (const segment of segments) {
7121 const bounds = getBounds(property, points[segment.start], points[segment.end], segment.loop);
7122 if (!target.segments) {
7123 parts.push({
7124 source: segment,
7125 target: bounds,
7126 start: points[segment.start],
7127 end: points[segment.end]
7128 });
7129 continue;
7130 }
7131 const targetSegments = _boundSegments(target, bounds);
7132 for (const tgt of targetSegments) {
7133 const subBounds = getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
7134 const fillSources = _boundSegment(segment, points, subBounds);
7135 for (const fillSource of fillSources) {
7136 parts.push({
7137 source: fillSource,
7138 target: tgt,
7139 start: {
7140 [property]: _getEdge(bounds, subBounds, 'start', Math.max)
7141 },
7142 end: {
7143 [property]: _getEdge(bounds, subBounds, 'end', Math.min)
7144 }
7145 });
7146 }
7147 }
7148 }
7149 return parts;
7150 }
7151 function clipBounds(ctx, scale, bounds) {
7152 const {top, bottom} = scale.chart.chartArea;
7153 const {property, start, end} = bounds || {};
7154 if (property === 'x') {
7155 ctx.beginPath();
7156 ctx.rect(start, top, end - start, bottom - top);
7157 ctx.clip();
7158 }
7159 }
7160 function interpolatedLineTo(ctx, target, point, property) {
7161 const interpolatedPoint = target.interpolate(point, property);
7162 if (interpolatedPoint) {
7163 ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
7164 }
7165 }
7166 function _fill(ctx, cfg) {
7167 const {line, target, property, color, scale} = cfg;
7168 const segments = _segments(line, target, property);
7169 for (const {source: src, target: tgt, start, end} of segments) {
7170 const {style: {backgroundColor = color} = {}} = src;
7171 ctx.save();
7172 ctx.fillStyle = backgroundColor;
7173 clipBounds(ctx, scale, getBounds(property, start, end));
7174 ctx.beginPath();
7175 const lineLoop = !!line.pathSegment(ctx, src);
7176 if (lineLoop) {
7177 ctx.closePath();
7178 } else {
7179 interpolatedLineTo(ctx, target, end, property);
7180 }
7181 const targetLoop = !!target.pathSegment(ctx, tgt, {move: lineLoop, reverse: true});
7182 const loop = lineLoop && targetLoop;
7183 if (!loop) {
7184 interpolatedLineTo(ctx, target, start, property);
7185 }
7186 ctx.closePath();
7187 ctx.fill(loop ? 'evenodd' : 'nonzero');
7188 ctx.restore();
7189 }
7190 }
7191 function doFill(ctx, cfg) {
7192 const {line, target, above, below, area, scale} = cfg;
7193 const property = line._loop ? 'angle' : cfg.axis;
7194 ctx.save();
7195 if (property === 'x' && below !== above) {
7196 _clip(ctx, target, area.top);
7197 _fill(ctx, {line, target, color: above, scale, property});
7198 ctx.restore();
7199 ctx.save();
7200 _clip(ctx, target, area.bottom);
7201 }
7202 _fill(ctx, {line, target, color: below, scale, property});
7203 ctx.restore();
7204 }
7205 function drawfill(ctx, source, area) {
7206 const target = getTarget(source);
7207 const {line, scale, axis} = source;
7208 const lineOpts = line.options;
7209 const fillOption = lineOpts.fill;
7210 const color = lineOpts.backgroundColor;
7211 const {above = color, below = color} = fillOption || {};
7212 if (target && line.points.length) {
7213 clipArea(ctx, area);
7214 doFill(ctx, {line, target, above, below, area, scale, axis});
7215 unclipArea(ctx);
7216 }
7217 }
7218 var plugin_filler = {
7219 id: 'filler',
7220 afterDatasetsUpdate(chart, _args, options) {
7221 const count = (chart.data.datasets || []).length;
7222 const sources = [];
7223 let meta, i, line, source;
7224 for (i = 0; i < count; ++i) {
7225 meta = chart.getDatasetMeta(i);
7226 line = meta.dataset;
7227 source = null;
7228 if (line && line.options && line instanceof LineElement) {
7229 source = {
7230 visible: chart.isDatasetVisible(i),
7231 index: i,
7232 fill: decodeFill(line, i, count),
7233 chart,
7234 axis: meta.controller.options.indexAxis,
7235 scale: meta.vScale,
7236 line,
7237 };
7238 }
7239 meta.$filler = source;
7240 sources.push(source);
7241 }
7242 for (i = 0; i < count; ++i) {
7243 source = sources[i];
7244 if (!source || source.fill === false) {
7245 continue;
7246 }
7247 source.fill = resolveTarget(sources, i, options.propagate);
7248 }
7249 },
7250 beforeDraw(chart, _args, options) {
7251 const draw = options.drawTime === 'beforeDraw';
7252 const metasets = chart.getSortedVisibleDatasetMetas();
7253 const area = chart.chartArea;
7254 for (let i = metasets.length - 1; i >= 0; --i) {
7255 const source = metasets[i].$filler;
7256 if (!source) {
7257 continue;
7258 }
7259 source.line.updateControlPoints(area);
7260 if (draw) {
7261 drawfill(chart.ctx, source, area);
7262 }
7263 }
7264 },
7265 beforeDatasetsDraw(chart, _args, options) {
7266 if (options.drawTime !== 'beforeDatasetsDraw') {
7267 return;
7268 }
7269 const metasets = chart.getSortedVisibleDatasetMetas();
7270 for (let i = metasets.length - 1; i >= 0; --i) {
7271 const source = metasets[i].$filler;
7272 if (source) {
7273 drawfill(chart.ctx, source, chart.chartArea);
7274 }
7275 }
7276 },
7277 beforeDatasetDraw(chart, args, options) {
7278 const source = args.meta.$filler;
7279 if (!source || source.fill === false || options.drawTime !== 'beforeDatasetDraw') {
7280 return;
7281 }
7282 drawfill(chart.ctx, source, chart.chartArea);
7283 },
7284 defaults: {
7285 propagate: true,
7286 drawTime: 'beforeDatasetDraw'
7287 }
7288 };
7289
7290 const getBoxSize = (labelOpts, fontSize) => {
7291 let {boxHeight = fontSize, boxWidth = fontSize} = labelOpts;
7292 if (labelOpts.usePointStyle) {
7293 boxHeight = Math.min(boxHeight, fontSize);
7294 boxWidth = Math.min(boxWidth, fontSize);
7295 }
7296 return {
7297 boxWidth,
7298 boxHeight,
7299 itemHeight: Math.max(fontSize, boxHeight)
7300 };
7301 };
7302 const itemsEqual = (a, b) => a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
7303 class Legend extends Element {
7304 constructor(config) {
7305 super();
7306 this._added = false;
7307 this.legendHitBoxes = [];
7308 this._hoveredItem = null;
7309 this.doughnutMode = false;
7310 this.chart = config.chart;
7311 this.options = config.options;
7312 this.ctx = config.ctx;
7313 this.legendItems = undefined;
7314 this.columnSizes = undefined;
7315 this.lineWidths = undefined;
7316 this.maxHeight = undefined;
7317 this.maxWidth = undefined;
7318 this.top = undefined;
7319 this.bottom = undefined;
7320 this.left = undefined;
7321 this.right = undefined;
7322 this.height = undefined;
7323 this.width = undefined;
7324 this._margins = undefined;
7325 this.position = undefined;
7326 this.weight = undefined;
7327 this.fullSize = undefined;
7328 }
7329 update(maxWidth, maxHeight, margins) {
7330 const me = this;
7331 me.maxWidth = maxWidth;
7332 me.maxHeight = maxHeight;
7333 me._margins = margins;
7334 me.setDimensions();
7335 me.buildLabels();
7336 me.fit();
7337 }
7338 setDimensions() {
7339 const me = this;
7340 if (me.isHorizontal()) {
7341 me.width = me.maxWidth;
7342 me.left = 0;
7343 me.right = me.width;
7344 } else {
7345 me.height = me.maxHeight;
7346 me.top = 0;
7347 me.bottom = me.height;
7348 }
7349 }
7350 buildLabels() {
7351 const me = this;
7352 const labelOpts = me.options.labels || {};
7353 let legendItems = callback(labelOpts.generateLabels, [me.chart], me) || [];
7354 if (labelOpts.filter) {
7355 legendItems = legendItems.filter((item) => labelOpts.filter(item, me.chart.data));
7356 }
7357 if (labelOpts.sort) {
7358 legendItems = legendItems.sort((a, b) => labelOpts.sort(a, b, me.chart.data));
7359 }
7360 if (me.options.reverse) {
7361 legendItems.reverse();
7362 }
7363 me.legendItems = legendItems;
7364 }
7365 fit() {
7366 const me = this;
7367 const {options, ctx} = me;
7368 if (!options.display) {
7369 me.width = me.height = 0;
7370 return;
7371 }
7372 const labelOpts = options.labels;
7373 const labelFont = toFont(labelOpts.font);
7374 const fontSize = labelFont.size;
7375 const titleHeight = me._computeTitleHeight();
7376 const {boxWidth, itemHeight} = getBoxSize(labelOpts, fontSize);
7377 let width, height;
7378 ctx.font = labelFont.string;
7379 if (me.isHorizontal()) {
7380 width = me.maxWidth;
7381 height = me._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
7382 } else {
7383 height = me.maxHeight;
7384 width = me._fitCols(titleHeight, fontSize, boxWidth, itemHeight) + 10;
7385 }
7386 me.width = Math.min(width, options.maxWidth || me.maxWidth);
7387 me.height = Math.min(height, options.maxHeight || me.maxHeight);
7388 }
7389 _fitRows(titleHeight, fontSize, boxWidth, itemHeight) {
7390 const me = this;
7391 const {ctx, maxWidth, options: {labels: {padding}}} = me;
7392 const hitboxes = me.legendHitBoxes = [];
7393 const lineWidths = me.lineWidths = [0];
7394 const lineHeight = itemHeight + padding;
7395 let totalHeight = titleHeight;
7396 ctx.textAlign = 'left';
7397 ctx.textBaseline = 'middle';
7398 let row = -1;
7399 let top = -lineHeight;
7400 me.legendItems.forEach((legendItem, i) => {
7401 const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
7402 if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {
7403 totalHeight += lineHeight;
7404 lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
7405 top += lineHeight;
7406 row++;
7407 }
7408 hitboxes[i] = {left: 0, top, row, width: itemWidth, height: itemHeight};
7409 lineWidths[lineWidths.length - 1] += itemWidth + padding;
7410 });
7411 return totalHeight;
7412 }
7413 _fitCols(titleHeight, fontSize, boxWidth, itemHeight) {
7414 const me = this;
7415 const {ctx, maxHeight, options: {labels: {padding}}} = me;
7416 const hitboxes = me.legendHitBoxes = [];
7417 const columnSizes = me.columnSizes = [];
7418 const heightLimit = maxHeight - titleHeight;
7419 let totalWidth = padding;
7420 let currentColWidth = 0;
7421 let currentColHeight = 0;
7422 let left = 0;
7423 let top = 0;
7424 let col = 0;
7425 me.legendItems.forEach((legendItem, i) => {
7426 const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
7427 if (i > 0 && currentColHeight + fontSize + 2 * padding > heightLimit) {
7428 totalWidth += currentColWidth + padding;
7429 columnSizes.push({width: currentColWidth, height: currentColHeight});
7430 left += currentColWidth + padding;
7431 col++;
7432 top = 0;
7433 currentColWidth = currentColHeight = 0;
7434 }
7435 currentColWidth = Math.max(currentColWidth, itemWidth);
7436 currentColHeight += fontSize + padding;
7437 hitboxes[i] = {left, top, col, width: itemWidth, height: itemHeight};
7438 top += itemHeight + padding;
7439 });
7440 totalWidth += currentColWidth;
7441 columnSizes.push({width: currentColWidth, height: currentColHeight});
7442 return totalWidth;
7443 }
7444 adjustHitBoxes() {
7445 const me = this;
7446 if (!me.options.display) {
7447 return;
7448 }
7449 const titleHeight = me._computeTitleHeight();
7450 const {legendHitBoxes: hitboxes, options: {align, labels: {padding}}} = me;
7451 if (this.isHorizontal()) {
7452 let row = 0;
7453 let left = _alignStartEnd(align, me.left + padding, me.right - me.lineWidths[row]);
7454 for (const hitbox of hitboxes) {
7455 if (row !== hitbox.row) {
7456 row = hitbox.row;
7457 left = _alignStartEnd(align, me.left + padding, me.right - me.lineWidths[row]);
7458 }
7459 hitbox.top += me.top + titleHeight + padding;
7460 hitbox.left = left;
7461 left += hitbox.width + padding;
7462 }
7463 } else {
7464 let col = 0;
7465 let top = _alignStartEnd(align, me.top + titleHeight + padding, me.bottom - me.columnSizes[col].height);
7466 for (const hitbox of hitboxes) {
7467 if (hitbox.col !== col) {
7468 col = hitbox.col;
7469 top = _alignStartEnd(align, me.top + titleHeight + padding, me.bottom - me.columnSizes[col].height);
7470 }
7471 hitbox.top = top;
7472 hitbox.left += me.left + padding;
7473 top += hitbox.height + padding;
7474 }
7475 }
7476 }
7477 isHorizontal() {
7478 return this.options.position === 'top' || this.options.position === 'bottom';
7479 }
7480 draw() {
7481 const me = this;
7482 if (me.options.display) {
7483 const ctx = me.ctx;
7484 clipArea(ctx, me);
7485 me._draw();
7486 unclipArea(ctx);
7487 }
7488 }
7489 _draw() {
7490 const me = this;
7491 const {options: opts, columnSizes, lineWidths, ctx} = me;
7492 const {align, labels: labelOpts} = opts;
7493 const defaultColor = defaults.color;
7494 const rtlHelper = getRtlAdapter(opts.rtl, me.left, me.width);
7495 const labelFont = toFont(labelOpts.font);
7496 const {color: fontColor, padding} = labelOpts;
7497 const fontSize = labelFont.size;
7498 const halfFontSize = fontSize / 2;
7499 let cursor;
7500 me.drawTitle();
7501 ctx.textAlign = rtlHelper.textAlign('left');
7502 ctx.textBaseline = 'middle';
7503 ctx.lineWidth = 0.5;
7504 ctx.font = labelFont.string;
7505 const {boxWidth, boxHeight, itemHeight} = getBoxSize(labelOpts, fontSize);
7506 const drawLegendBox = function(x, y, legendItem) {
7507 if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {
7508 return;
7509 }
7510 ctx.save();
7511 const lineWidth = valueOrDefault(legendItem.lineWidth, 1);
7512 ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor);
7513 ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt');
7514 ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0);
7515 ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter');
7516 ctx.lineWidth = lineWidth;
7517 ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor);
7518 ctx.setLineDash(valueOrDefault(legendItem.lineDash, []));
7519 if (labelOpts.usePointStyle) {
7520 const drawOptions = {
7521 radius: boxWidth * Math.SQRT2 / 2,
7522 pointStyle: legendItem.pointStyle,
7523 rotation: legendItem.rotation,
7524 borderWidth: lineWidth
7525 };
7526 const centerX = rtlHelper.xPlus(x, boxWidth / 2);
7527 const centerY = y + halfFontSize;
7528 drawPoint(ctx, drawOptions, centerX, centerY);
7529 } else {
7530 const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);
7531 const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);
7532 const borderRadius = toTRBLCorners(legendItem.borderRadius);
7533 ctx.beginPath();
7534 if (Object.values(borderRadius).some(v => v !== 0)) {
7535 addRoundedRectPath(ctx, {
7536 x: xBoxLeft,
7537 y: yBoxTop,
7538 w: boxWidth,
7539 h: boxHeight,
7540 radius: borderRadius,
7541 });
7542 } else {
7543 ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);
7544 }
7545 ctx.fill();
7546 if (lineWidth !== 0) {
7547 ctx.stroke();
7548 }
7549 }
7550 ctx.restore();
7551 };
7552 const fillText = function(x, y, legendItem) {
7553 renderText(ctx, legendItem.text, x, y + (itemHeight / 2), labelFont, {
7554 strikethrough: legendItem.hidden,
7555 textAlign: legendItem.textAlign
7556 });
7557 };
7558 const isHorizontal = me.isHorizontal();
7559 const titleHeight = this._computeTitleHeight();
7560 if (isHorizontal) {
7561 cursor = {
7562 x: _alignStartEnd(align, me.left + padding, me.right - lineWidths[0]),
7563 y: me.top + padding + titleHeight,
7564 line: 0
7565 };
7566 } else {
7567 cursor = {
7568 x: me.left + padding,
7569 y: _alignStartEnd(align, me.top + titleHeight + padding, me.bottom - columnSizes[0].height),
7570 line: 0
7571 };
7572 }
7573 overrideTextDirection(me.ctx, opts.textDirection);
7574 const lineHeight = itemHeight + padding;
7575 me.legendItems.forEach((legendItem, i) => {
7576 ctx.strokeStyle = legendItem.fontColor || fontColor;
7577 ctx.fillStyle = legendItem.fontColor || fontColor;
7578 const textWidth = ctx.measureText(legendItem.text).width;
7579 const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
7580 const width = boxWidth + (fontSize / 2) + textWidth;
7581 let x = cursor.x;
7582 let y = cursor.y;
7583 rtlHelper.setWidth(me.width);
7584 if (isHorizontal) {
7585 if (i > 0 && x + width + padding > me.right) {
7586 y = cursor.y += lineHeight;
7587 cursor.line++;
7588 x = cursor.x = _alignStartEnd(align, me.left + padding, me.right - lineWidths[cursor.line]);
7589 }
7590 } else if (i > 0 && y + lineHeight > me.bottom) {
7591 x = cursor.x = x + columnSizes[cursor.line].width + padding;
7592 cursor.line++;
7593 y = cursor.y = _alignStartEnd(align, me.top + titleHeight + padding, me.bottom - columnSizes[cursor.line].height);
7594 }
7595 const realX = rtlHelper.x(x);
7596 drawLegendBox(realX, y, legendItem);
7597 x = _textX(textAlign, x + boxWidth + halfFontSize, me.right);
7598 fillText(rtlHelper.x(x), y, legendItem);
7599 if (isHorizontal) {
7600 cursor.x += width + padding;
7601 } else {
7602 cursor.y += lineHeight;
7603 }
7604 });
7605 restoreTextDirection(me.ctx, opts.textDirection);
7606 }
7607 drawTitle() {
7608 const me = this;
7609 const opts = me.options;
7610 const titleOpts = opts.title;
7611 const titleFont = toFont(titleOpts.font);
7612 const titlePadding = toPadding(titleOpts.padding);
7613 if (!titleOpts.display) {
7614 return;
7615 }
7616 const rtlHelper = getRtlAdapter(opts.rtl, me.left, me.width);
7617 const ctx = me.ctx;
7618 const position = titleOpts.position;
7619 const halfFontSize = titleFont.size / 2;
7620 const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;
7621 let y;
7622 let left = me.left;
7623 let maxWidth = me.width;
7624 if (this.isHorizontal()) {
7625 maxWidth = Math.max(...me.lineWidths);
7626 y = me.top + topPaddingPlusHalfFontSize;
7627 left = _alignStartEnd(opts.align, left, me.right - maxWidth);
7628 } else {
7629 const maxHeight = me.columnSizes.reduce((acc, size) => Math.max(acc, size.height), 0);
7630 y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, me.top, me.bottom - maxHeight - opts.labels.padding - me._computeTitleHeight());
7631 }
7632 const x = _alignStartEnd(position, left, left + maxWidth);
7633 ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position));
7634 ctx.textBaseline = 'middle';
7635 ctx.strokeStyle = titleOpts.color;
7636 ctx.fillStyle = titleOpts.color;
7637 ctx.font = titleFont.string;
7638 renderText(ctx, titleOpts.text, x, y, titleFont);
7639 }
7640 _computeTitleHeight() {
7641 const titleOpts = this.options.title;
7642 const titleFont = toFont(titleOpts.font);
7643 const titlePadding = toPadding(titleOpts.padding);
7644 return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;
7645 }
7646 _getLegendItemAt(x, y) {
7647 const me = this;
7648 let i, hitBox, lh;
7649 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
7650 lh = me.legendHitBoxes;
7651 for (i = 0; i < lh.length; ++i) {
7652 hitBox = lh[i];
7653 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
7654 return me.legendItems[i];
7655 }
7656 }
7657 }
7658 return null;
7659 }
7660 handleEvent(e) {
7661 const me = this;
7662 const opts = me.options;
7663 if (!isListened(e.type, opts)) {
7664 return;
7665 }
7666 const hoveredItem = me._getLegendItemAt(e.x, e.y);
7667 if (e.type === 'mousemove') {
7668 const previous = me._hoveredItem;
7669 const sameItem = itemsEqual(previous, hoveredItem);
7670 if (previous && !sameItem) {
7671 callback(opts.onLeave, [e, previous, me], me);
7672 }
7673 me._hoveredItem = hoveredItem;
7674 if (hoveredItem && !sameItem) {
7675 callback(opts.onHover, [e, hoveredItem, me], me);
7676 }
7677 } else if (hoveredItem) {
7678 callback(opts.onClick, [e, hoveredItem, me], me);
7679 }
7680 }
7681 }
7682 function isListened(type, opts) {
7683 if (type === 'mousemove' && (opts.onHover || opts.onLeave)) {
7684 return true;
7685 }
7686 if (opts.onClick && (type === 'click' || type === 'mouseup')) {
7687 return true;
7688 }
7689 return false;
7690 }
7691 var plugin_legend = {
7692 id: 'legend',
7693 _element: Legend,
7694 start(chart, _args, options) {
7695 const legend = chart.legend = new Legend({ctx: chart.ctx, options, chart});
7696 layouts.configure(chart, legend, options);
7697 layouts.addBox(chart, legend);
7698 },
7699 stop(chart) {
7700 layouts.removeBox(chart, chart.legend);
7701 delete chart.legend;
7702 },
7703 beforeUpdate(chart, _args, options) {
7704 const legend = chart.legend;
7705 layouts.configure(chart, legend, options);
7706 legend.options = options;
7707 },
7708 afterUpdate(chart) {
7709 const legend = chart.legend;
7710 legend.buildLabels();
7711 legend.adjustHitBoxes();
7712 },
7713 afterEvent(chart, args) {
7714 if (!args.replay) {
7715 chart.legend.handleEvent(args.event);
7716 }
7717 },
7718 defaults: {
7719 display: true,
7720 position: 'top',
7721 align: 'center',
7722 fullSize: true,
7723 reverse: false,
7724 weight: 1000,
7725 onClick(e, legendItem, legend) {
7726 const index = legendItem.datasetIndex;
7727 const ci = legend.chart;
7728 if (ci.isDatasetVisible(index)) {
7729 ci.hide(index);
7730 legendItem.hidden = true;
7731 } else {
7732 ci.show(index);
7733 legendItem.hidden = false;
7734 }
7735 },
7736 onHover: null,
7737 onLeave: null,
7738 labels: {
7739 color: (ctx) => ctx.chart.options.color,
7740 boxWidth: 40,
7741 padding: 10,
7742 generateLabels(chart) {
7743 const datasets = chart.data.datasets;
7744 const {labels: {usePointStyle, pointStyle, textAlign, color}} = chart.legend.options;
7745 return chart._getSortedDatasetMetas().map((meta) => {
7746 const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
7747 const borderWidth = toPadding(style.borderWidth);
7748 return {
7749 text: datasets[meta.index].label,
7750 fillStyle: style.backgroundColor,
7751 fontColor: color,
7752 hidden: !meta.visible,
7753 lineCap: style.borderCapStyle,
7754 lineDash: style.borderDash,
7755 lineDashOffset: style.borderDashOffset,
7756 lineJoin: style.borderJoinStyle,
7757 lineWidth: (borderWidth.width + borderWidth.height) / 4,
7758 strokeStyle: style.borderColor,
7759 pointStyle: pointStyle || style.pointStyle,
7760 rotation: style.rotation,
7761 textAlign: textAlign || style.textAlign,
7762 borderRadius: 0,
7763 datasetIndex: meta.index
7764 };
7765 }, this);
7766 }
7767 },
7768 title: {
7769 color: (ctx) => ctx.chart.options.color,
7770 display: false,
7771 position: 'center',
7772 text: '',
7773 }
7774 },
7775 descriptors: {
7776 _scriptable: (name) => !name.startsWith('on'),
7777 labels: {
7778 _scriptable: (name) => !['generateLabels', 'filter', 'sort'].includes(name),
7779 }
7780 },
7781 };
7782
7783 class Title extends Element {
7784 constructor(config) {
7785 super();
7786 this.chart = config.chart;
7787 this.options = config.options;
7788 this.ctx = config.ctx;
7789 this._padding = undefined;
7790 this.top = undefined;
7791 this.bottom = undefined;
7792 this.left = undefined;
7793 this.right = undefined;
7794 this.width = undefined;
7795 this.height = undefined;
7796 this.position = undefined;
7797 this.weight = undefined;
7798 this.fullSize = undefined;
7799 }
7800 update(maxWidth, maxHeight) {
7801 const me = this;
7802 const opts = me.options;
7803 me.left = 0;
7804 me.top = 0;
7805 if (!opts.display) {
7806 me.width = me.height = me.right = me.bottom = 0;
7807 return;
7808 }
7809 me.width = me.right = maxWidth;
7810 me.height = me.bottom = maxHeight;
7811 const lineCount = isArray(opts.text) ? opts.text.length : 1;
7812 me._padding = toPadding(opts.padding);
7813 const textSize = lineCount * toFont(opts.font).lineHeight + me._padding.height;
7814 if (me.isHorizontal()) {
7815 me.height = textSize;
7816 } else {
7817 me.width = textSize;
7818 }
7819 }
7820 isHorizontal() {
7821 const pos = this.options.position;
7822 return pos === 'top' || pos === 'bottom';
7823 }
7824 _drawArgs(offset) {
7825 const {top, left, bottom, right, options} = this;
7826 const align = options.align;
7827 let rotation = 0;
7828 let maxWidth, titleX, titleY;
7829 if (this.isHorizontal()) {
7830 titleX = _alignStartEnd(align, left, right);
7831 titleY = top + offset;
7832 maxWidth = right - left;
7833 } else {
7834 if (options.position === 'left') {
7835 titleX = left + offset;
7836 titleY = _alignStartEnd(align, bottom, top);
7837 rotation = PI * -0.5;
7838 } else {
7839 titleX = right - offset;
7840 titleY = _alignStartEnd(align, top, bottom);
7841 rotation = PI * 0.5;
7842 }
7843 maxWidth = bottom - top;
7844 }
7845 return {titleX, titleY, maxWidth, rotation};
7846 }
7847 draw() {
7848 const me = this;
7849 const ctx = me.ctx;
7850 const opts = me.options;
7851 if (!opts.display) {
7852 return;
7853 }
7854 const fontOpts = toFont(opts.font);
7855 const lineHeight = fontOpts.lineHeight;
7856 const offset = lineHeight / 2 + me._padding.top;
7857 const {titleX, titleY, maxWidth, rotation} = me._drawArgs(offset);
7858 renderText(ctx, opts.text, 0, 0, fontOpts, {
7859 color: opts.color,
7860 maxWidth,
7861 rotation,
7862 textAlign: _toLeftRightCenter(opts.align),
7863 textBaseline: 'middle',
7864 translation: [titleX, titleY],
7865 });
7866 }
7867 }
7868 function createTitle(chart, titleOpts) {
7869 const title = new Title({
7870 ctx: chart.ctx,
7871 options: titleOpts,
7872 chart
7873 });
7874 layouts.configure(chart, title, titleOpts);
7875 layouts.addBox(chart, title);
7876 chart.titleBlock = title;
7877 }
7878 var plugin_title = {
7879 id: 'title',
7880 _element: Title,
7881 start(chart, _args, options) {
7882 createTitle(chart, options);
7883 },
7884 stop(chart) {
7885 const titleBlock = chart.titleBlock;
7886 layouts.removeBox(chart, titleBlock);
7887 delete chart.titleBlock;
7888 },
7889 beforeUpdate(chart, _args, options) {
7890 const title = chart.titleBlock;
7891 layouts.configure(chart, title, options);
7892 title.options = options;
7893 },
7894 defaults: {
7895 align: 'center',
7896 display: false,
7897 font: {
7898 weight: 'bold',
7899 },
7900 fullSize: true,
7901 padding: 10,
7902 position: 'top',
7903 text: '',
7904 weight: 2000
7905 },
7906 defaultRoutes: {
7907 color: 'color'
7908 },
7909 descriptors: {
7910 _scriptable: true,
7911 _indexable: false,
7912 },
7913 };
7914
7915 const positioners = {
7916 average(items) {
7917 if (!items.length) {
7918 return false;
7919 }
7920 let i, len;
7921 let x = 0;
7922 let y = 0;
7923 let count = 0;
7924 for (i = 0, len = items.length; i < len; ++i) {
7925 const el = items[i].element;
7926 if (el && el.hasValue()) {
7927 const pos = el.tooltipPosition();
7928 x += pos.x;
7929 y += pos.y;
7930 ++count;
7931 }
7932 }
7933 return {
7934 x: x / count,
7935 y: y / count
7936 };
7937 },
7938 nearest(items, eventPosition) {
7939 if (!items.length) {
7940 return false;
7941 }
7942 let x = eventPosition.x;
7943 let y = eventPosition.y;
7944 let minDistance = Number.POSITIVE_INFINITY;
7945 let i, len, nearestElement;
7946 for (i = 0, len = items.length; i < len; ++i) {
7947 const el = items[i].element;
7948 if (el && el.hasValue()) {
7949 const center = el.getCenterPoint();
7950 const d = distanceBetweenPoints(eventPosition, center);
7951 if (d < minDistance) {
7952 minDistance = d;
7953 nearestElement = el;
7954 }
7955 }
7956 }
7957 if (nearestElement) {
7958 const tp = nearestElement.tooltipPosition();
7959 x = tp.x;
7960 y = tp.y;
7961 }
7962 return {
7963 x,
7964 y
7965 };
7966 }
7967 };
7968 function pushOrConcat(base, toPush) {
7969 if (toPush) {
7970 if (isArray(toPush)) {
7971 Array.prototype.push.apply(base, toPush);
7972 } else {
7973 base.push(toPush);
7974 }
7975 }
7976 return base;
7977 }
7978 function splitNewlines(str) {
7979 if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
7980 return str.split('\n');
7981 }
7982 return str;
7983 }
7984 function createTooltipItem(chart, item) {
7985 const {element, datasetIndex, index} = item;
7986 const controller = chart.getDatasetMeta(datasetIndex).controller;
7987 const {label, value} = controller.getLabelAndValue(index);
7988 return {
7989 chart,
7990 label,
7991 parsed: controller.getParsed(index),
7992 raw: chart.data.datasets[datasetIndex].data[index],
7993 formattedValue: value,
7994 dataset: controller.getDataset(),
7995 dataIndex: index,
7996 datasetIndex,
7997 element
7998 };
7999 }
8000 function getTooltipSize(tooltip, options) {
8001 const ctx = tooltip._chart.ctx;
8002 const {body, footer, title} = tooltip;
8003 const {boxWidth, boxHeight} = options;
8004 const bodyFont = toFont(options.bodyFont);
8005 const titleFont = toFont(options.titleFont);
8006 const footerFont = toFont(options.footerFont);
8007 const titleLineCount = title.length;
8008 const footerLineCount = footer.length;
8009 const bodyLineItemCount = body.length;
8010 const padding = toPadding(options.padding);
8011 let height = padding.height;
8012 let width = 0;
8013 let combinedBodyLength = body.reduce((count, bodyItem) => count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0);
8014 combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
8015 if (titleLineCount) {
8016 height += titleLineCount * titleFont.lineHeight
8017 + (titleLineCount - 1) * options.titleSpacing
8018 + options.titleMarginBottom;
8019 }
8020 if (combinedBodyLength) {
8021 const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;
8022 height += bodyLineItemCount * bodyLineHeight
8023 + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight
8024 + (combinedBodyLength - 1) * options.bodySpacing;
8025 }
8026 if (footerLineCount) {
8027 height += options.footerMarginTop
8028 + footerLineCount * footerFont.lineHeight
8029 + (footerLineCount - 1) * options.footerSpacing;
8030 }
8031 let widthPadding = 0;
8032 const maxLineWidth = function(line) {
8033 width = Math.max(width, ctx.measureText(line).width + widthPadding);
8034 };
8035 ctx.save();
8036 ctx.font = titleFont.string;
8037 each(tooltip.title, maxLineWidth);
8038 ctx.font = bodyFont.string;
8039 each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
8040 widthPadding = options.displayColors ? (boxWidth + 2) : 0;
8041 each(body, (bodyItem) => {
8042 each(bodyItem.before, maxLineWidth);
8043 each(bodyItem.lines, maxLineWidth);
8044 each(bodyItem.after, maxLineWidth);
8045 });
8046 widthPadding = 0;
8047 ctx.font = footerFont.string;
8048 each(tooltip.footer, maxLineWidth);
8049 ctx.restore();
8050 width += padding.width;
8051 return {width, height};
8052 }
8053 function determineYAlign(chart, size) {
8054 const {y, height} = size;
8055 if (y < height / 2) {
8056 return 'top';
8057 } else if (y > (chart.height - height / 2)) {
8058 return 'bottom';
8059 }
8060 return 'center';
8061 }
8062 function doesNotFitWithAlign(xAlign, chart, options, size) {
8063 const {x, width} = size;
8064 const caret = options.caretSize + options.caretPadding;
8065 if (xAlign === 'left' && x + width + caret > chart.width) {
8066 return true;
8067 }
8068 if (xAlign === 'right' && x - width - caret < 0) {
8069 return true;
8070 }
8071 }
8072 function determineXAlign(chart, options, size, yAlign) {
8073 const {x, width} = size;
8074 const {width: chartWidth, chartArea: {left, right}} = chart;
8075 let xAlign = 'center';
8076 if (yAlign === 'center') {
8077 xAlign = x <= (left + right) / 2 ? 'left' : 'right';
8078 } else if (x <= width / 2) {
8079 xAlign = 'left';
8080 } else if (x >= chartWidth - width / 2) {
8081 xAlign = 'right';
8082 }
8083 if (doesNotFitWithAlign(xAlign, chart, options, size)) {
8084 xAlign = 'center';
8085 }
8086 return xAlign;
8087 }
8088 function determineAlignment(chart, options, size) {
8089 const yAlign = options.yAlign || determineYAlign(chart, size);
8090 return {
8091 xAlign: options.xAlign || determineXAlign(chart, options, size, yAlign),
8092 yAlign
8093 };
8094 }
8095 function alignX(size, xAlign) {
8096 let {x, width} = size;
8097 if (xAlign === 'right') {
8098 x -= width;
8099 } else if (xAlign === 'center') {
8100 x -= (width / 2);
8101 }
8102 return x;
8103 }
8104 function alignY(size, yAlign, paddingAndSize) {
8105 let {y, height} = size;
8106 if (yAlign === 'top') {
8107 y += paddingAndSize;
8108 } else if (yAlign === 'bottom') {
8109 y -= height + paddingAndSize;
8110 } else {
8111 y -= (height / 2);
8112 }
8113 return y;
8114 }
8115 function getBackgroundPoint(options, size, alignment, chart) {
8116 const {caretSize, caretPadding, cornerRadius} = options;
8117 const {xAlign, yAlign} = alignment;
8118 const paddingAndSize = caretSize + caretPadding;
8119 const radiusAndPadding = cornerRadius + caretPadding;
8120 let x = alignX(size, xAlign);
8121 const y = alignY(size, yAlign, paddingAndSize);
8122 if (yAlign === 'center') {
8123 if (xAlign === 'left') {
8124 x += paddingAndSize;
8125 } else if (xAlign === 'right') {
8126 x -= paddingAndSize;
8127 }
8128 } else if (xAlign === 'left') {
8129 x -= radiusAndPadding;
8130 } else if (xAlign === 'right') {
8131 x += radiusAndPadding;
8132 }
8133 return {
8134 x: _limitValue(x, 0, chart.width - size.width),
8135 y: _limitValue(y, 0, chart.height - size.height)
8136 };
8137 }
8138 function getAlignedX(tooltip, align, options) {
8139 const padding = toPadding(options.padding);
8140 return align === 'center'
8141 ? tooltip.x + tooltip.width / 2
8142 : align === 'right'
8143 ? tooltip.x + tooltip.width - padding.right
8144 : tooltip.x + padding.left;
8145 }
8146 function getBeforeAfterBodyLines(callback) {
8147 return pushOrConcat([], splitNewlines(callback));
8148 }
8149 function createTooltipContext(parent, tooltip, tooltipItems) {
8150 return Object.assign(Object.create(parent), {
8151 tooltip,
8152 tooltipItems,
8153 type: 'tooltip'
8154 });
8155 }
8156 function overrideCallbacks(callbacks, context) {
8157 const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
8158 return override ? callbacks.override(override) : callbacks;
8159 }
8160 class Tooltip extends Element {
8161 constructor(config) {
8162 super();
8163 this.opacity = 0;
8164 this._active = [];
8165 this._chart = config._chart;
8166 this._eventPosition = undefined;
8167 this._size = undefined;
8168 this._cachedAnimations = undefined;
8169 this._tooltipItems = [];
8170 this.$animations = undefined;
8171 this.$context = undefined;
8172 this.options = config.options;
8173 this.dataPoints = undefined;
8174 this.title = undefined;
8175 this.beforeBody = undefined;
8176 this.body = undefined;
8177 this.afterBody = undefined;
8178 this.footer = undefined;
8179 this.xAlign = undefined;
8180 this.yAlign = undefined;
8181 this.x = undefined;
8182 this.y = undefined;
8183 this.height = undefined;
8184 this.width = undefined;
8185 this.caretX = undefined;
8186 this.caretY = undefined;
8187 this.labelColors = undefined;
8188 this.labelPointStyles = undefined;
8189 this.labelTextColors = undefined;
8190 }
8191 initialize(options) {
8192 this.options = options;
8193 this._cachedAnimations = undefined;
8194 this.$context = undefined;
8195 }
8196 _resolveAnimations() {
8197 const me = this;
8198 const cached = me._cachedAnimations;
8199 if (cached) {
8200 return cached;
8201 }
8202 const chart = me._chart;
8203 const options = me.options.setContext(me.getContext());
8204 const opts = options.enabled && chart.options.animation && options.animations;
8205 const animations = new Animations(me._chart, opts);
8206 if (opts._cacheable) {
8207 me._cachedAnimations = Object.freeze(animations);
8208 }
8209 return animations;
8210 }
8211 getContext() {
8212 const me = this;
8213 return me.$context ||
8214 (me.$context = createTooltipContext(me._chart.getContext(), me, me._tooltipItems));
8215 }
8216 getTitle(context, options) {
8217 const me = this;
8218 const {callbacks} = options;
8219 const beforeTitle = callbacks.beforeTitle.apply(me, [context]);
8220 const title = callbacks.title.apply(me, [context]);
8221 const afterTitle = callbacks.afterTitle.apply(me, [context]);
8222 let lines = [];
8223 lines = pushOrConcat(lines, splitNewlines(beforeTitle));
8224 lines = pushOrConcat(lines, splitNewlines(title));
8225 lines = pushOrConcat(lines, splitNewlines(afterTitle));
8226 return lines;
8227 }
8228 getBeforeBody(tooltipItems, options) {
8229 return getBeforeAfterBodyLines(options.callbacks.beforeBody.apply(this, [tooltipItems]));
8230 }
8231 getBody(tooltipItems, options) {
8232 const me = this;
8233 const {callbacks} = options;
8234 const bodyItems = [];
8235 each(tooltipItems, (context) => {
8236 const bodyItem = {
8237 before: [],
8238 lines: [],
8239 after: []
8240 };
8241 const scoped = overrideCallbacks(callbacks, context);
8242 pushOrConcat(bodyItem.before, splitNewlines(scoped.beforeLabel.call(me, context)));
8243 pushOrConcat(bodyItem.lines, scoped.label.call(me, context));
8244 pushOrConcat(bodyItem.after, splitNewlines(scoped.afterLabel.call(me, context)));
8245 bodyItems.push(bodyItem);
8246 });
8247 return bodyItems;
8248 }
8249 getAfterBody(tooltipItems, options) {
8250 return getBeforeAfterBodyLines(options.callbacks.afterBody.apply(this, [tooltipItems]));
8251 }
8252 getFooter(tooltipItems, options) {
8253 const me = this;
8254 const {callbacks} = options;
8255 const beforeFooter = callbacks.beforeFooter.apply(me, [tooltipItems]);
8256 const footer = callbacks.footer.apply(me, [tooltipItems]);
8257 const afterFooter = callbacks.afterFooter.apply(me, [tooltipItems]);
8258 let lines = [];
8259 lines = pushOrConcat(lines, splitNewlines(beforeFooter));
8260 lines = pushOrConcat(lines, splitNewlines(footer));
8261 lines = pushOrConcat(lines, splitNewlines(afterFooter));
8262 return lines;
8263 }
8264 _createItems(options) {
8265 const me = this;
8266 const active = me._active;
8267 const data = me._chart.data;
8268 const labelColors = [];
8269 const labelPointStyles = [];
8270 const labelTextColors = [];
8271 let tooltipItems = [];
8272 let i, len;
8273 for (i = 0, len = active.length; i < len; ++i) {
8274 tooltipItems.push(createTooltipItem(me._chart, active[i]));
8275 }
8276 if (options.filter) {
8277 tooltipItems = tooltipItems.filter((element, index, array) => options.filter(element, index, array, data));
8278 }
8279 if (options.itemSort) {
8280 tooltipItems = tooltipItems.sort((a, b) => options.itemSort(a, b, data));
8281 }
8282 each(tooltipItems, (context) => {
8283 const scoped = overrideCallbacks(options.callbacks, context);
8284 labelColors.push(scoped.labelColor.call(me, context));
8285 labelPointStyles.push(scoped.labelPointStyle.call(me, context));
8286 labelTextColors.push(scoped.labelTextColor.call(me, context));
8287 });
8288 me.labelColors = labelColors;
8289 me.labelPointStyles = labelPointStyles;
8290 me.labelTextColors = labelTextColors;
8291 me.dataPoints = tooltipItems;
8292 return tooltipItems;
8293 }
8294 update(changed, replay) {
8295 const me = this;
8296 const options = me.options.setContext(me.getContext());
8297 const active = me._active;
8298 let properties;
8299 let tooltipItems = [];
8300 if (!active.length) {
8301 if (me.opacity !== 0) {
8302 properties = {
8303 opacity: 0
8304 };
8305 }
8306 } else {
8307 const position = positioners[options.position].call(me, active, me._eventPosition);
8308 tooltipItems = me._createItems(options);
8309 me.title = me.getTitle(tooltipItems, options);
8310 me.beforeBody = me.getBeforeBody(tooltipItems, options);
8311 me.body = me.getBody(tooltipItems, options);
8312 me.afterBody = me.getAfterBody(tooltipItems, options);
8313 me.footer = me.getFooter(tooltipItems, options);
8314 const size = me._size = getTooltipSize(me, options);
8315 const positionAndSize = Object.assign({}, position, size);
8316 const alignment = determineAlignment(me._chart, options, positionAndSize);
8317 const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, me._chart);
8318 me.xAlign = alignment.xAlign;
8319 me.yAlign = alignment.yAlign;
8320 properties = {
8321 opacity: 1,
8322 x: backgroundPoint.x,
8323 y: backgroundPoint.y,
8324 width: size.width,
8325 height: size.height,
8326 caretX: position.x,
8327 caretY: position.y
8328 };
8329 }
8330 me._tooltipItems = tooltipItems;
8331 me.$context = undefined;
8332 if (properties) {
8333 me._resolveAnimations().update(me, properties);
8334 }
8335 if (changed && options.external) {
8336 options.external.call(me, {chart: me._chart, tooltip: me, replay});
8337 }
8338 }
8339 drawCaret(tooltipPoint, ctx, size, options) {
8340 const caretPosition = this.getCaretPosition(tooltipPoint, size, options);
8341 ctx.lineTo(caretPosition.x1, caretPosition.y1);
8342 ctx.lineTo(caretPosition.x2, caretPosition.y2);
8343 ctx.lineTo(caretPosition.x3, caretPosition.y3);
8344 }
8345 getCaretPosition(tooltipPoint, size, options) {
8346 const {xAlign, yAlign} = this;
8347 const {cornerRadius, caretSize} = options;
8348 const {x: ptX, y: ptY} = tooltipPoint;
8349 const {width, height} = size;
8350 let x1, x2, x3, y1, y2, y3;
8351 if (yAlign === 'center') {
8352 y2 = ptY + (height / 2);
8353 if (xAlign === 'left') {
8354 x1 = ptX;
8355 x2 = x1 - caretSize;
8356 y1 = y2 + caretSize;
8357 y3 = y2 - caretSize;
8358 } else {
8359 x1 = ptX + width;
8360 x2 = x1 + caretSize;
8361 y1 = y2 - caretSize;
8362 y3 = y2 + caretSize;
8363 }
8364 x3 = x1;
8365 } else {
8366 if (xAlign === 'left') {
8367 x2 = ptX + cornerRadius + (caretSize);
8368 } else if (xAlign === 'right') {
8369 x2 = ptX + width - cornerRadius - caretSize;
8370 } else {
8371 x2 = this.caretX;
8372 }
8373 if (yAlign === 'top') {
8374 y1 = ptY;
8375 y2 = y1 - caretSize;
8376 x1 = x2 - caretSize;
8377 x3 = x2 + caretSize;
8378 } else {
8379 y1 = ptY + height;
8380 y2 = y1 + caretSize;
8381 x1 = x2 + caretSize;
8382 x3 = x2 - caretSize;
8383 }
8384 y3 = y1;
8385 }
8386 return {x1, x2, x3, y1, y2, y3};
8387 }
8388 drawTitle(pt, ctx, options) {
8389 const me = this;
8390 const title = me.title;
8391 const length = title.length;
8392 let titleFont, titleSpacing, i;
8393 if (length) {
8394 const rtlHelper = getRtlAdapter(options.rtl, me.x, me.width);
8395 pt.x = getAlignedX(me, options.titleAlign, options);
8396 ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
8397 ctx.textBaseline = 'middle';
8398 titleFont = toFont(options.titleFont);
8399 titleSpacing = options.titleSpacing;
8400 ctx.fillStyle = options.titleColor;
8401 ctx.font = titleFont.string;
8402 for (i = 0; i < length; ++i) {
8403 ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);
8404 pt.y += titleFont.lineHeight + titleSpacing;
8405 if (i + 1 === length) {
8406 pt.y += options.titleMarginBottom - titleSpacing;
8407 }
8408 }
8409 }
8410 }
8411 _drawColorBox(ctx, pt, i, rtlHelper, options) {
8412 const me = this;
8413 const labelColors = me.labelColors[i];
8414 const labelPointStyle = me.labelPointStyles[i];
8415 const {boxHeight, boxWidth} = options;
8416 const bodyFont = toFont(options.bodyFont);
8417 const colorX = getAlignedX(me, 'left', options);
8418 const rtlColorX = rtlHelper.x(colorX);
8419 const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;
8420 const colorY = pt.y + yOffSet;
8421 if (options.usePointStyle) {
8422 const drawOptions = {
8423 radius: Math.min(boxWidth, boxHeight) / 2,
8424 pointStyle: labelPointStyle.pointStyle,
8425 rotation: labelPointStyle.rotation,
8426 borderWidth: 1
8427 };
8428 const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;
8429 const centerY = colorY + boxHeight / 2;
8430 ctx.strokeStyle = options.multiKeyBackground;
8431 ctx.fillStyle = options.multiKeyBackground;
8432 drawPoint(ctx, drawOptions, centerX, centerY);
8433 ctx.strokeStyle = labelColors.borderColor;
8434 ctx.fillStyle = labelColors.backgroundColor;
8435 drawPoint(ctx, drawOptions, centerX, centerY);
8436 } else {
8437 ctx.lineWidth = labelColors.borderWidth || 1;
8438 ctx.strokeStyle = labelColors.borderColor;
8439 ctx.setLineDash(labelColors.borderDash || []);
8440 ctx.lineDashOffset = labelColors.borderDashOffset || 0;
8441 const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth);
8442 const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2);
8443 const borderRadius = toTRBLCorners(labelColors.borderRadius);
8444 if (Object.values(borderRadius).some(v => v !== 0)) {
8445 ctx.beginPath();
8446 ctx.fillStyle = options.multiKeyBackground;
8447 addRoundedRectPath(ctx, {
8448 x: outerX,
8449 y: colorY,
8450 w: boxWidth,
8451 h: boxHeight,
8452 radius: borderRadius,
8453 });
8454 ctx.fill();
8455 ctx.stroke();
8456 ctx.fillStyle = labelColors.backgroundColor;
8457 ctx.beginPath();
8458 addRoundedRectPath(ctx, {
8459 x: innerX,
8460 y: colorY + 1,
8461 w: boxWidth - 2,
8462 h: boxHeight - 2,
8463 radius: borderRadius,
8464 });
8465 ctx.fill();
8466 } else {
8467 ctx.fillStyle = options.multiKeyBackground;
8468 ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
8469 ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
8470 ctx.fillStyle = labelColors.backgroundColor;
8471 ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
8472 }
8473 }
8474 ctx.fillStyle = me.labelTextColors[i];
8475 }
8476 drawBody(pt, ctx, options) {
8477 const me = this;
8478 const {body} = me;
8479 const {bodySpacing, bodyAlign, displayColors, boxHeight, boxWidth} = options;
8480 const bodyFont = toFont(options.bodyFont);
8481 let bodyLineHeight = bodyFont.lineHeight;
8482 let xLinePadding = 0;
8483 const rtlHelper = getRtlAdapter(options.rtl, me.x, me.width);
8484 const fillLineOfText = function(line) {
8485 ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);
8486 pt.y += bodyLineHeight + bodySpacing;
8487 };
8488 const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
8489 let bodyItem, textColor, lines, i, j, ilen, jlen;
8490 ctx.textAlign = bodyAlign;
8491 ctx.textBaseline = 'middle';
8492 ctx.font = bodyFont.string;
8493 pt.x = getAlignedX(me, bodyAlignForCalculation, options);
8494 ctx.fillStyle = options.bodyColor;
8495 each(me.beforeBody, fillLineOfText);
8496 xLinePadding = displayColors && bodyAlignForCalculation !== 'right'
8497 ? bodyAlign === 'center' ? (boxWidth / 2 + 1) : (boxWidth + 2)
8498 : 0;
8499 for (i = 0, ilen = body.length; i < ilen; ++i) {
8500 bodyItem = body[i];
8501 textColor = me.labelTextColors[i];
8502 ctx.fillStyle = textColor;
8503 each(bodyItem.before, fillLineOfText);
8504 lines = bodyItem.lines;
8505 if (displayColors && lines.length) {
8506 me._drawColorBox(ctx, pt, i, rtlHelper, options);
8507 bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);
8508 }
8509 for (j = 0, jlen = lines.length; j < jlen; ++j) {
8510 fillLineOfText(lines[j]);
8511 bodyLineHeight = bodyFont.lineHeight;
8512 }
8513 each(bodyItem.after, fillLineOfText);
8514 }
8515 xLinePadding = 0;
8516 bodyLineHeight = bodyFont.lineHeight;
8517 each(me.afterBody, fillLineOfText);
8518 pt.y -= bodySpacing;
8519 }
8520 drawFooter(pt, ctx, options) {
8521 const me = this;
8522 const footer = me.footer;
8523 const length = footer.length;
8524 let footerFont, i;
8525 if (length) {
8526 const rtlHelper = getRtlAdapter(options.rtl, me.x, me.width);
8527 pt.x = getAlignedX(me, options.footerAlign, options);
8528 pt.y += options.footerMarginTop;
8529 ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
8530 ctx.textBaseline = 'middle';
8531 footerFont = toFont(options.footerFont);
8532 ctx.fillStyle = options.footerColor;
8533 ctx.font = footerFont.string;
8534 for (i = 0; i < length; ++i) {
8535 ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);
8536 pt.y += footerFont.lineHeight + options.footerSpacing;
8537 }
8538 }
8539 }
8540 drawBackground(pt, ctx, tooltipSize, options) {
8541 const {xAlign, yAlign} = this;
8542 const {x, y} = pt;
8543 const {width, height} = tooltipSize;
8544 const radius = options.cornerRadius;
8545 ctx.fillStyle = options.backgroundColor;
8546 ctx.strokeStyle = options.borderColor;
8547 ctx.lineWidth = options.borderWidth;
8548 ctx.beginPath();
8549 ctx.moveTo(x + radius, y);
8550 if (yAlign === 'top') {
8551 this.drawCaret(pt, ctx, tooltipSize, options);
8552 }
8553 ctx.lineTo(x + width - radius, y);
8554 ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
8555 if (yAlign === 'center' && xAlign === 'right') {
8556 this.drawCaret(pt, ctx, tooltipSize, options);
8557 }
8558 ctx.lineTo(x + width, y + height - radius);
8559 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
8560 if (yAlign === 'bottom') {
8561 this.drawCaret(pt, ctx, tooltipSize, options);
8562 }
8563 ctx.lineTo(x + radius, y + height);
8564 ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
8565 if (yAlign === 'center' && xAlign === 'left') {
8566 this.drawCaret(pt, ctx, tooltipSize, options);
8567 }
8568 ctx.lineTo(x, y + radius);
8569 ctx.quadraticCurveTo(x, y, x + radius, y);
8570 ctx.closePath();
8571 ctx.fill();
8572 if (options.borderWidth > 0) {
8573 ctx.stroke();
8574 }
8575 }
8576 _updateAnimationTarget(options) {
8577 const me = this;
8578 const chart = me._chart;
8579 const anims = me.$animations;
8580 const animX = anims && anims.x;
8581 const animY = anims && anims.y;
8582 if (animX || animY) {
8583 const position = positioners[options.position].call(me, me._active, me._eventPosition);
8584 if (!position) {
8585 return;
8586 }
8587 const size = me._size = getTooltipSize(me, options);
8588 const positionAndSize = Object.assign({}, position, me._size);
8589 const alignment = determineAlignment(chart, options, positionAndSize);
8590 const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
8591 if (animX._to !== point.x || animY._to !== point.y) {
8592 me.xAlign = alignment.xAlign;
8593 me.yAlign = alignment.yAlign;
8594 me.width = size.width;
8595 me.height = size.height;
8596 me.caretX = position.x;
8597 me.caretY = position.y;
8598 me._resolveAnimations().update(me, point);
8599 }
8600 }
8601 }
8602 draw(ctx) {
8603 const me = this;
8604 const options = me.options.setContext(me.getContext());
8605 let opacity = me.opacity;
8606 if (!opacity) {
8607 return;
8608 }
8609 me._updateAnimationTarget(options);
8610 const tooltipSize = {
8611 width: me.width,
8612 height: me.height
8613 };
8614 const pt = {
8615 x: me.x,
8616 y: me.y
8617 };
8618 opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;
8619 const padding = toPadding(options.padding);
8620 const hasTooltipContent = me.title.length || me.beforeBody.length || me.body.length || me.afterBody.length || me.footer.length;
8621 if (options.enabled && hasTooltipContent) {
8622 ctx.save();
8623 ctx.globalAlpha = opacity;
8624 me.drawBackground(pt, ctx, tooltipSize, options);
8625 overrideTextDirection(ctx, options.textDirection);
8626 pt.y += padding.top;
8627 me.drawTitle(pt, ctx, options);
8628 me.drawBody(pt, ctx, options);
8629 me.drawFooter(pt, ctx, options);
8630 restoreTextDirection(ctx, options.textDirection);
8631 ctx.restore();
8632 }
8633 }
8634 getActiveElements() {
8635 return this._active || [];
8636 }
8637 setActiveElements(activeElements, eventPosition) {
8638 const me = this;
8639 const lastActive = me._active;
8640 const active = activeElements.map(({datasetIndex, index}) => {
8641 const meta = me._chart.getDatasetMeta(datasetIndex);
8642 if (!meta) {
8643 throw new Error('Cannot find a dataset at index ' + datasetIndex);
8644 }
8645 return {
8646 datasetIndex,
8647 element: meta.data[index],
8648 index,
8649 };
8650 });
8651 const changed = !_elementsEqual(lastActive, active);
8652 const positionChanged = me._positionChanged(active, eventPosition);
8653 if (changed || positionChanged) {
8654 me._active = active;
8655 me._eventPosition = eventPosition;
8656 me.update(true);
8657 }
8658 }
8659 handleEvent(e, replay) {
8660 const me = this;
8661 const options = me.options;
8662 const lastActive = me._active || [];
8663 let changed = false;
8664 let active = [];
8665 if (e.type !== 'mouseout') {
8666 active = me._chart.getElementsAtEventForMode(e, options.mode, options, replay);
8667 if (options.reverse) {
8668 active.reverse();
8669 }
8670 }
8671 const positionChanged = me._positionChanged(active, e);
8672 changed = replay || !_elementsEqual(active, lastActive) || positionChanged;
8673 if (changed) {
8674 me._active = active;
8675 if (options.enabled || options.external) {
8676 me._eventPosition = {
8677 x: e.x,
8678 y: e.y
8679 };
8680 me.update(true, replay);
8681 }
8682 }
8683 return changed;
8684 }
8685 _positionChanged(active, e) {
8686 const {caretX, caretY, options} = this;
8687 const position = positioners[options.position].call(this, active, e);
8688 return position !== false && (caretX !== position.x || caretY !== position.y);
8689 }
8690 }
8691 Tooltip.positioners = positioners;
8692 var plugin_tooltip = {
8693 id: 'tooltip',
8694 _element: Tooltip,
8695 positioners,
8696 afterInit(chart, _args, options) {
8697 if (options) {
8698 chart.tooltip = new Tooltip({_chart: chart, options});
8699 }
8700 },
8701 beforeUpdate(chart, _args, options) {
8702 if (chart.tooltip) {
8703 chart.tooltip.initialize(options);
8704 }
8705 },
8706 reset(chart, _args, options) {
8707 if (chart.tooltip) {
8708 chart.tooltip.initialize(options);
8709 }
8710 },
8711 afterDraw(chart) {
8712 const tooltip = chart.tooltip;
8713 const args = {
8714 tooltip
8715 };
8716 if (chart.notifyPlugins('beforeTooltipDraw', args) === false) {
8717 return;
8718 }
8719 if (tooltip) {
8720 tooltip.draw(chart.ctx);
8721 }
8722 chart.notifyPlugins('afterTooltipDraw', args);
8723 },
8724 afterEvent(chart, args) {
8725 if (chart.tooltip) {
8726 const useFinalPosition = args.replay;
8727 if (chart.tooltip.handleEvent(args.event, useFinalPosition)) {
8728 args.changed = true;
8729 }
8730 }
8731 },
8732 defaults: {
8733 enabled: true,
8734 external: null,
8735 position: 'average',
8736 backgroundColor: 'rgba(0,0,0,0.8)',
8737 titleColor: '#fff',
8738 titleFont: {
8739 weight: 'bold',
8740 },
8741 titleSpacing: 2,
8742 titleMarginBottom: 6,
8743 titleAlign: 'left',
8744 bodyColor: '#fff',
8745 bodySpacing: 2,
8746 bodyFont: {
8747 },
8748 bodyAlign: 'left',
8749 footerColor: '#fff',
8750 footerSpacing: 2,
8751 footerMarginTop: 6,
8752 footerFont: {
8753 weight: 'bold',
8754 },
8755 footerAlign: 'left',
8756 padding: 6,
8757 caretPadding: 2,
8758 caretSize: 5,
8759 cornerRadius: 6,
8760 boxHeight: (ctx, opts) => opts.bodyFont.size,
8761 boxWidth: (ctx, opts) => opts.bodyFont.size,
8762 multiKeyBackground: '#fff',
8763 displayColors: true,
8764 borderColor: 'rgba(0,0,0,0)',
8765 borderWidth: 0,
8766 animation: {
8767 duration: 400,
8768 easing: 'easeOutQuart',
8769 },
8770 animations: {
8771 numbers: {
8772 type: 'number',
8773 properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'],
8774 },
8775 opacity: {
8776 easing: 'linear',
8777 duration: 200
8778 }
8779 },
8780 callbacks: {
8781 beforeTitle: noop,
8782 title(tooltipItems) {
8783 if (tooltipItems.length > 0) {
8784 const item = tooltipItems[0];
8785 const labels = item.chart.data.labels;
8786 const labelCount = labels ? labels.length : 0;
8787 if (this && this.options && this.options.mode === 'dataset') {
8788 return item.dataset.label || '';
8789 } else if (item.label) {
8790 return item.label;
8791 } else if (labelCount > 0 && item.dataIndex < labelCount) {
8792 return labels[item.dataIndex];
8793 }
8794 }
8795 return '';
8796 },
8797 afterTitle: noop,
8798 beforeBody: noop,
8799 beforeLabel: noop,
8800 label(tooltipItem) {
8801 if (this && this.options && this.options.mode === 'dataset') {
8802 return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;
8803 }
8804 let label = tooltipItem.dataset.label || '';
8805 if (label) {
8806 label += ': ';
8807 }
8808 const value = tooltipItem.formattedValue;
8809 if (!isNullOrUndef(value)) {
8810 label += value;
8811 }
8812 return label;
8813 },
8814 labelColor(tooltipItem) {
8815 const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
8816 const options = meta.controller.getStyle(tooltipItem.dataIndex);
8817 return {
8818 borderColor: options.borderColor,
8819 backgroundColor: options.backgroundColor,
8820 borderWidth: options.borderWidth,
8821 borderDash: options.borderDash,
8822 borderDashOffset: options.borderDashOffset,
8823 borderRadius: 0,
8824 };
8825 },
8826 labelTextColor() {
8827 return this.options.bodyColor;
8828 },
8829 labelPointStyle(tooltipItem) {
8830 const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
8831 const options = meta.controller.getStyle(tooltipItem.dataIndex);
8832 return {
8833 pointStyle: options.pointStyle,
8834 rotation: options.rotation,
8835 };
8836 },
8837 afterLabel: noop,
8838 afterBody: noop,
8839 beforeFooter: noop,
8840 footer: noop,
8841 afterFooter: noop
8842 }
8843 },
8844 defaultRoutes: {
8845 bodyFont: 'font',
8846 footerFont: 'font',
8847 titleFont: 'font'
8848 },
8849 descriptors: {
8850 _scriptable: (name) => name !== 'filter' && name !== 'itemSort' && name !== 'external',
8851 _indexable: false,
8852 callbacks: {
8853 _scriptable: false,
8854 _indexable: false,
8855 },
8856 animation: {
8857 _fallback: false
8858 },
8859 animations: {
8860 _fallback: 'animation'
8861 }
8862 },
8863 additionalOptionScopes: ['interaction']
8864 };
8865
8866 var plugins = /*#__PURE__*/Object.freeze({
8867 __proto__: null,
8868 Decimation: plugin_decimation,
8869 Filler: plugin_filler,
8870 Legend: plugin_legend,
8871 Title: plugin_title,
8872 Tooltip: plugin_tooltip
8873 });
8874
8875 const addIfString = (labels, raw, index) => typeof raw === 'string'
8876 ? labels.push(raw) - 1
8877 : isNaN(raw) ? null : index;
8878 function findOrAddLabel(labels, raw, index) {
8879 const first = labels.indexOf(raw);
8880 if (first === -1) {
8881 return addIfString(labels, raw, index);
8882 }
8883 const last = labels.lastIndexOf(raw);
8884 return first !== last ? index : first;
8885 }
8886 const validIndex = (index, max) => index === null ? null : _limitValue(Math.round(index), 0, max);
8887 class CategoryScale extends Scale {
8888 constructor(cfg) {
8889 super(cfg);
8890 this._startValue = undefined;
8891 this._valueRange = 0;
8892 }
8893 parse(raw, index) {
8894 if (isNullOrUndef(raw)) {
8895 return null;
8896 }
8897 const labels = this.getLabels();
8898 index = isFinite(index) && labels[index] === raw ? index
8899 : findOrAddLabel(labels, raw, valueOrDefault(index, raw));
8900 return validIndex(index, labels.length - 1);
8901 }
8902 determineDataLimits() {
8903 const me = this;
8904 const {minDefined, maxDefined} = me.getUserBounds();
8905 let {min, max} = me.getMinMax(true);
8906 if (me.options.bounds === 'ticks') {
8907 if (!minDefined) {
8908 min = 0;
8909 }
8910 if (!maxDefined) {
8911 max = me.getLabels().length - 1;
8912 }
8913 }
8914 me.min = min;
8915 me.max = max;
8916 }
8917 buildTicks() {
8918 const me = this;
8919 const min = me.min;
8920 const max = me.max;
8921 const offset = me.options.offset;
8922 const ticks = [];
8923 let labels = me.getLabels();
8924 labels = (min === 0 && max === labels.length - 1) ? labels : labels.slice(min, max + 1);
8925 me._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);
8926 me._startValue = me.min - (offset ? 0.5 : 0);
8927 for (let value = min; value <= max; value++) {
8928 ticks.push({value});
8929 }
8930 return ticks;
8931 }
8932 getLabelForValue(value) {
8933 const me = this;
8934 const labels = me.getLabels();
8935 if (value >= 0 && value < labels.length) {
8936 return labels[value];
8937 }
8938 return value;
8939 }
8940 configure() {
8941 const me = this;
8942 super.configure();
8943 if (!me.isHorizontal()) {
8944 me._reversePixels = !me._reversePixels;
8945 }
8946 }
8947 getPixelForValue(value) {
8948 const me = this;
8949 if (typeof value !== 'number') {
8950 value = me.parse(value);
8951 }
8952 return value === null ? NaN : me.getPixelForDecimal((value - me._startValue) / me._valueRange);
8953 }
8954 getPixelForTick(index) {
8955 const me = this;
8956 const ticks = me.ticks;
8957 if (index < 0 || index > ticks.length - 1) {
8958 return null;
8959 }
8960 return me.getPixelForValue(ticks[index].value);
8961 }
8962 getValueForPixel(pixel) {
8963 const me = this;
8964 return Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange);
8965 }
8966 getBasePixel() {
8967 return this.bottom;
8968 }
8969 }
8970 CategoryScale.id = 'category';
8971 CategoryScale.defaults = {
8972 ticks: {
8973 callback: CategoryScale.prototype.getLabelForValue
8974 }
8975 };
8976
8977 function generateTicks$1(generationOptions, dataRange) {
8978 const ticks = [];
8979 const MIN_SPACING = 1e-14;
8980 const {step, min, max, precision, count, maxTicks} = generationOptions;
8981 const unit = step || 1;
8982 const maxSpaces = maxTicks - 1;
8983 const {min: rmin, max: rmax} = dataRange;
8984 const minDefined = !isNullOrUndef(min);
8985 const maxDefined = !isNullOrUndef(max);
8986 const countDefined = !isNullOrUndef(count);
8987 let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit;
8988 let factor, niceMin, niceMax, numSpaces;
8989 if (spacing < MIN_SPACING && !minDefined && !maxDefined) {
8990 return [{value: rmin}, {value: rmax}];
8991 }
8992 numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
8993 if (numSpaces > maxSpaces) {
8994 spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
8995 }
8996 if (!isNullOrUndef(precision)) {
8997 factor = Math.pow(10, precision);
8998 spacing = Math.ceil(spacing * factor) / factor;
8999 }
9000 niceMin = Math.floor(rmin / spacing) * spacing;
9001 niceMax = Math.ceil(rmax / spacing) * spacing;
9002 if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) {
9003 numSpaces = Math.min((max - min) / spacing, maxTicks);
9004 spacing = (max - min) / numSpaces;
9005 niceMin = min;
9006 niceMax = max;
9007 } else if (countDefined) {
9008 niceMin = minDefined ? min : niceMin;
9009 niceMax = maxDefined ? max : niceMax;
9010 numSpaces = count - 1;
9011 spacing = (niceMax - niceMin) / numSpaces;
9012 } else {
9013 numSpaces = (niceMax - niceMin) / spacing;
9014 if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
9015 numSpaces = Math.round(numSpaces);
9016 } else {
9017 numSpaces = Math.ceil(numSpaces);
9018 }
9019 }
9020 factor = Math.pow(10, isNullOrUndef(precision) ? _decimalPlaces(spacing) : precision);
9021 niceMin = Math.round(niceMin * factor) / factor;
9022 niceMax = Math.round(niceMax * factor) / factor;
9023 let j = 0;
9024 if (minDefined) {
9025 ticks.push({value: min});
9026 if (niceMin <= min) {
9027 j++;
9028 }
9029 if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, spacing / 10)) {
9030 j++;
9031 }
9032 }
9033 for (; j < numSpaces; ++j) {
9034 ticks.push({value: Math.round((niceMin + j * spacing) * factor) / factor});
9035 }
9036 if (maxDefined) {
9037 if (almostEquals(ticks[ticks.length - 1].value, max, spacing / 10)) {
9038 ticks[ticks.length - 1].value = max;
9039 } else {
9040 ticks.push({value: max});
9041 }
9042 } else {
9043 ticks.push({value: niceMax});
9044 }
9045 return ticks;
9046 }
9047 class LinearScaleBase extends Scale {
9048 constructor(cfg) {
9049 super(cfg);
9050 this.start = undefined;
9051 this.end = undefined;
9052 this._startValue = undefined;
9053 this._endValue = undefined;
9054 this._valueRange = 0;
9055 }
9056 parse(raw, index) {
9057 if (isNullOrUndef(raw)) {
9058 return null;
9059 }
9060 if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
9061 return null;
9062 }
9063 return +raw;
9064 }
9065 handleTickRangeOptions() {
9066 const me = this;
9067 const {beginAtZero, stacked} = me.options;
9068 const {minDefined, maxDefined} = me.getUserBounds();
9069 let {min, max} = me;
9070 const setMin = v => (min = minDefined ? min : v);
9071 const setMax = v => (max = maxDefined ? max : v);
9072 if (beginAtZero || stacked) {
9073 const minSign = sign(min);
9074 const maxSign = sign(max);
9075 if (minSign < 0 && maxSign < 0) {
9076 setMax(0);
9077 } else if (minSign > 0 && maxSign > 0) {
9078 setMin(0);
9079 }
9080 }
9081 if (min === max) {
9082 setMax(max + 1);
9083 if (!beginAtZero) {
9084 setMin(min - 1);
9085 }
9086 }
9087 me.min = min;
9088 me.max = max;
9089 }
9090 getTickLimit() {
9091 const me = this;
9092 const tickOpts = me.options.ticks;
9093 let {maxTicksLimit, stepSize} = tickOpts;
9094 let maxTicks;
9095 if (stepSize) {
9096 maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1;
9097 } else {
9098 maxTicks = me.computeTickLimit();
9099 maxTicksLimit = maxTicksLimit || 11;
9100 }
9101 if (maxTicksLimit) {
9102 maxTicks = Math.min(maxTicksLimit, maxTicks);
9103 }
9104 return maxTicks;
9105 }
9106 computeTickLimit() {
9107 return Number.POSITIVE_INFINITY;
9108 }
9109 buildTicks() {
9110 const me = this;
9111 const opts = me.options;
9112 const tickOpts = opts.ticks;
9113 let maxTicks = me.getTickLimit();
9114 maxTicks = Math.max(2, maxTicks);
9115 const numericGeneratorOptions = {
9116 maxTicks,
9117 min: opts.min,
9118 max: opts.max,
9119 precision: tickOpts.precision,
9120 step: tickOpts.stepSize,
9121 count: tickOpts.count,
9122 };
9123 const dataRange = me._range || me;
9124 const ticks = generateTicks$1(numericGeneratorOptions, dataRange);
9125 if (opts.bounds === 'ticks') {
9126 _setMinAndMaxByKey(ticks, me, 'value');
9127 }
9128 if (opts.reverse) {
9129 ticks.reverse();
9130 me.start = me.max;
9131 me.end = me.min;
9132 } else {
9133 me.start = me.min;
9134 me.end = me.max;
9135 }
9136 return ticks;
9137 }
9138 configure() {
9139 const me = this;
9140 const ticks = me.ticks;
9141 let start = me.min;
9142 let end = me.max;
9143 super.configure();
9144 if (me.options.offset && ticks.length) {
9145 const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
9146 start -= offset;
9147 end += offset;
9148 }
9149 me._startValue = start;
9150 me._endValue = end;
9151 me._valueRange = end - start;
9152 }
9153 getLabelForValue(value) {
9154 return formatNumber(value, this.chart.options.locale);
9155 }
9156 }
9157
9158 class LinearScale extends LinearScaleBase {
9159 determineDataLimits() {
9160 const me = this;
9161 const {min, max} = me.getMinMax(true);
9162 me.min = isNumberFinite(min) ? min : 0;
9163 me.max = isNumberFinite(max) ? max : 1;
9164 me.handleTickRangeOptions();
9165 }
9166 computeTickLimit() {
9167 const me = this;
9168 if (me.isHorizontal()) {
9169 return Math.ceil(me.width / 40);
9170 }
9171 const tickFont = me._resolveTickFontOptions(0);
9172 return Math.ceil(me.height / tickFont.lineHeight);
9173 }
9174 getPixelForValue(value) {
9175 return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
9176 }
9177 getValueForPixel(pixel) {
9178 return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
9179 }
9180 }
9181 LinearScale.id = 'linear';
9182 LinearScale.defaults = {
9183 ticks: {
9184 callback: Ticks.formatters.numeric
9185 }
9186 };
9187
9188 function isMajor(tickVal) {
9189 const remain = tickVal / (Math.pow(10, Math.floor(log10(tickVal))));
9190 return remain === 1;
9191 }
9192 function generateTicks(generationOptions, dataRange) {
9193 const endExp = Math.floor(log10(dataRange.max));
9194 const endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
9195 const ticks = [];
9196 let tickVal = finiteOrDefault(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min))));
9197 let exp = Math.floor(log10(tickVal));
9198 let significand = Math.floor(tickVal / Math.pow(10, exp));
9199 let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
9200 do {
9201 ticks.push({value: tickVal, major: isMajor(tickVal)});
9202 ++significand;
9203 if (significand === 10) {
9204 significand = 1;
9205 ++exp;
9206 precision = exp >= 0 ? 1 : precision;
9207 }
9208 tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
9209 } while (exp < endExp || (exp === endExp && significand < endSignificand));
9210 const lastTick = finiteOrDefault(generationOptions.max, tickVal);
9211 ticks.push({value: lastTick, major: isMajor(tickVal)});
9212 return ticks;
9213 }
9214 class LogarithmicScale extends Scale {
9215 constructor(cfg) {
9216 super(cfg);
9217 this.start = undefined;
9218 this.end = undefined;
9219 this._startValue = undefined;
9220 this._valueRange = 0;
9221 }
9222 parse(raw, index) {
9223 const value = LinearScaleBase.prototype.parse.apply(this, [raw, index]);
9224 if (value === 0) {
9225 this._zero = true;
9226 return undefined;
9227 }
9228 return isNumberFinite(value) && value > 0 ? value : null;
9229 }
9230 determineDataLimits() {
9231 const me = this;
9232 const {min, max} = me.getMinMax(true);
9233 me.min = isNumberFinite(min) ? Math.max(0, min) : null;
9234 me.max = isNumberFinite(max) ? Math.max(0, max) : null;
9235 if (me.options.beginAtZero) {
9236 me._zero = true;
9237 }
9238 me.handleTickRangeOptions();
9239 }
9240 handleTickRangeOptions() {
9241 const me = this;
9242 const {minDefined, maxDefined} = me.getUserBounds();
9243 let min = me.min;
9244 let max = me.max;
9245 const setMin = v => (min = minDefined ? min : v);
9246 const setMax = v => (max = maxDefined ? max : v);
9247 const exp = (v, m) => Math.pow(10, Math.floor(log10(v)) + m);
9248 if (min === max) {
9249 if (min <= 0) {
9250 setMin(1);
9251 setMax(10);
9252 } else {
9253 setMin(exp(min, -1));
9254 setMax(exp(max, +1));
9255 }
9256 }
9257 if (min <= 0) {
9258 setMin(exp(max, -1));
9259 }
9260 if (max <= 0) {
9261 setMax(exp(min, +1));
9262 }
9263 if (me._zero && me.min !== me._suggestedMin && min === exp(me.min, 0)) {
9264 setMin(exp(min, -1));
9265 }
9266 me.min = min;
9267 me.max = max;
9268 }
9269 buildTicks() {
9270 const me = this;
9271 const opts = me.options;
9272 const generationOptions = {
9273 min: me._userMin,
9274 max: me._userMax
9275 };
9276 const ticks = generateTicks(generationOptions, me);
9277 if (opts.bounds === 'ticks') {
9278 _setMinAndMaxByKey(ticks, me, 'value');
9279 }
9280 if (opts.reverse) {
9281 ticks.reverse();
9282 me.start = me.max;
9283 me.end = me.min;
9284 } else {
9285 me.start = me.min;
9286 me.end = me.max;
9287 }
9288 return ticks;
9289 }
9290 getLabelForValue(value) {
9291 return value === undefined ? '0' : formatNumber(value, this.chart.options.locale);
9292 }
9293 configure() {
9294 const me = this;
9295 const start = me.min;
9296 super.configure();
9297 me._startValue = log10(start);
9298 me._valueRange = log10(me.max) - log10(start);
9299 }
9300 getPixelForValue(value) {
9301 const me = this;
9302 if (value === undefined || value === 0) {
9303 value = me.min;
9304 }
9305 if (value === null || isNaN(value)) {
9306 return NaN;
9307 }
9308 return me.getPixelForDecimal(value === me.min
9309 ? 0
9310 : (log10(value) - me._startValue) / me._valueRange);
9311 }
9312 getValueForPixel(pixel) {
9313 const me = this;
9314 const decimal = me.getDecimalForPixel(pixel);
9315 return Math.pow(10, me._startValue + decimal * me._valueRange);
9316 }
9317 }
9318 LogarithmicScale.id = 'logarithmic';
9319 LogarithmicScale.defaults = {
9320 ticks: {
9321 callback: Ticks.formatters.logarithmic,
9322 major: {
9323 enabled: true
9324 }
9325 }
9326 };
9327
9328 function getTickBackdropHeight(opts) {
9329 const tickOpts = opts.ticks;
9330 if (tickOpts.display && opts.display) {
9331 const padding = toPadding(tickOpts.backdropPadding);
9332 return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height;
9333 }
9334 return 0;
9335 }
9336 function measureLabelSize(ctx, lineHeight, label) {
9337 if (isArray(label)) {
9338 return {
9339 w: _longestText(ctx, ctx.font, label),
9340 h: label.length * lineHeight
9341 };
9342 }
9343 return {
9344 w: ctx.measureText(label).width,
9345 h: lineHeight
9346 };
9347 }
9348 function determineLimits(angle, pos, size, min, max) {
9349 if (angle === min || angle === max) {
9350 return {
9351 start: pos - (size / 2),
9352 end: pos + (size / 2)
9353 };
9354 } else if (angle < min || angle > max) {
9355 return {
9356 start: pos - size,
9357 end: pos
9358 };
9359 }
9360 return {
9361 start: pos,
9362 end: pos + size
9363 };
9364 }
9365 function fitWithPointLabels(scale) {
9366 const furthestLimits = {
9367 l: 0,
9368 r: scale.width,
9369 t: 0,
9370 b: scale.height - scale.paddingTop
9371 };
9372 const furthestAngles = {};
9373 let i, textSize, pointPosition;
9374 const labelSizes = [];
9375 const padding = [];
9376 const valueCount = scale.getLabels().length;
9377 for (i = 0; i < valueCount; i++) {
9378 const opts = scale.options.pointLabels.setContext(scale.getContext(i));
9379 padding[i] = opts.padding;
9380 pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i]);
9381 const plFont = toFont(opts.font);
9382 scale.ctx.font = plFont.string;
9383 textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale._pointLabels[i]);
9384 labelSizes[i] = textSize;
9385 const angleRadians = scale.getIndexAngle(i);
9386 const angle = toDegrees(angleRadians);
9387 const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
9388 const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
9389 if (hLimits.start < furthestLimits.l) {
9390 furthestLimits.l = hLimits.start;
9391 furthestAngles.l = angleRadians;
9392 }
9393 if (hLimits.end > furthestLimits.r) {
9394 furthestLimits.r = hLimits.end;
9395 furthestAngles.r = angleRadians;
9396 }
9397 if (vLimits.start < furthestLimits.t) {
9398 furthestLimits.t = vLimits.start;
9399 furthestAngles.t = angleRadians;
9400 }
9401 if (vLimits.end > furthestLimits.b) {
9402 furthestLimits.b = vLimits.end;
9403 furthestAngles.b = angleRadians;
9404 }
9405 }
9406 scale._setReductions(scale.drawingArea, furthestLimits, furthestAngles);
9407 scale._pointLabelItems = [];
9408 const opts = scale.options;
9409 const tickBackdropHeight = getTickBackdropHeight(opts);
9410 const outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
9411 for (i = 0; i < valueCount; i++) {
9412 const extra = (i === 0 ? tickBackdropHeight / 2 : 0);
9413 const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + padding[i]);
9414 const angle = toDegrees(scale.getIndexAngle(i));
9415 const size = labelSizes[i];
9416 adjustPointPositionForLabelHeight(angle, size, pointLabelPosition);
9417 const textAlign = getTextAlignForAngle(angle);
9418 let left;
9419 if (textAlign === 'left') {
9420 left = pointLabelPosition.x;
9421 } else if (textAlign === 'center') {
9422 left = pointLabelPosition.x - (size.w / 2);
9423 } else {
9424 left = pointLabelPosition.x - size.w;
9425 }
9426 const right = left + size.w;
9427 scale._pointLabelItems[i] = {
9428 x: pointLabelPosition.x,
9429 y: pointLabelPosition.y,
9430 textAlign,
9431 left,
9432 top: pointLabelPosition.y,
9433 right,
9434 bottom: pointLabelPosition.y + size.h,
9435 };
9436 }
9437 }
9438 function getTextAlignForAngle(angle) {
9439 if (angle === 0 || angle === 180) {
9440 return 'center';
9441 } else if (angle < 180) {
9442 return 'left';
9443 }
9444 return 'right';
9445 }
9446 function adjustPointPositionForLabelHeight(angle, textSize, position) {
9447 if (angle === 90 || angle === 270) {
9448 position.y -= (textSize.h / 2);
9449 } else if (angle > 270 || angle < 90) {
9450 position.y -= textSize.h;
9451 }
9452 }
9453 function drawPointLabels(scale, labelCount) {
9454 const {ctx, options: {pointLabels}} = scale;
9455 for (let i = labelCount - 1; i >= 0; i--) {
9456 const optsAtIndex = pointLabels.setContext(scale.getContext(i));
9457 const plFont = toFont(optsAtIndex.font);
9458 const {x, y, textAlign, left, top, right, bottom} = scale._pointLabelItems[i];
9459 const {backdropColor} = optsAtIndex;
9460 if (!isNullOrUndef(backdropColor)) {
9461 const padding = toPadding(optsAtIndex.backdropPadding);
9462 ctx.fillStyle = backdropColor;
9463 ctx.fillRect(left - padding.left, top - padding.top, right - left + padding.width, bottom - top + padding.height);
9464 }
9465 renderText(
9466 ctx,
9467 scale._pointLabels[i],
9468 x,
9469 y + (plFont.lineHeight / 2),
9470 plFont,
9471 {
9472 color: optsAtIndex.color,
9473 textAlign: textAlign,
9474 textBaseline: 'middle'
9475 }
9476 );
9477 }
9478 }
9479 function pathRadiusLine(scale, radius, circular, labelCount) {
9480 const {ctx} = scale;
9481 if (circular) {
9482 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU);
9483 } else {
9484 let pointPosition = scale.getPointPosition(0, radius);
9485 ctx.moveTo(pointPosition.x, pointPosition.y);
9486 for (let i = 1; i < labelCount; i++) {
9487 pointPosition = scale.getPointPosition(i, radius);
9488 ctx.lineTo(pointPosition.x, pointPosition.y);
9489 }
9490 }
9491 }
9492 function drawRadiusLine(scale, gridLineOpts, radius, labelCount) {
9493 const ctx = scale.ctx;
9494 const circular = gridLineOpts.circular;
9495 const {color, lineWidth} = gridLineOpts;
9496 if ((!circular && !labelCount) || !color || !lineWidth || radius < 0) {
9497 return;
9498 }
9499 ctx.save();
9500 ctx.strokeStyle = color;
9501 ctx.lineWidth = lineWidth;
9502 ctx.setLineDash(gridLineOpts.borderDash);
9503 ctx.lineDashOffset = gridLineOpts.borderDashOffset;
9504 ctx.beginPath();
9505 pathRadiusLine(scale, radius, circular, labelCount);
9506 ctx.closePath();
9507 ctx.stroke();
9508 ctx.restore();
9509 }
9510 function numberOrZero(param) {
9511 return isNumber(param) ? param : 0;
9512 }
9513 class RadialLinearScale extends LinearScaleBase {
9514 constructor(cfg) {
9515 super(cfg);
9516 this.xCenter = undefined;
9517 this.yCenter = undefined;
9518 this.drawingArea = undefined;
9519 this._pointLabels = [];
9520 this._pointLabelItems = [];
9521 }
9522 setDimensions() {
9523 const me = this;
9524 me.width = me.maxWidth;
9525 me.height = me.maxHeight;
9526 me.paddingTop = getTickBackdropHeight(me.options) / 2;
9527 me.xCenter = Math.floor(me.width / 2);
9528 me.yCenter = Math.floor((me.height - me.paddingTop) / 2);
9529 me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2;
9530 }
9531 determineDataLimits() {
9532 const me = this;
9533 const {min, max} = me.getMinMax(false);
9534 me.min = isNumberFinite(min) && !isNaN(min) ? min : 0;
9535 me.max = isNumberFinite(max) && !isNaN(max) ? max : 0;
9536 me.handleTickRangeOptions();
9537 }
9538 computeTickLimit() {
9539 return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
9540 }
9541 generateTickLabels(ticks) {
9542 const me = this;
9543 LinearScaleBase.prototype.generateTickLabels.call(me, ticks);
9544 me._pointLabels = me.getLabels().map((value, index) => {
9545 const label = callback(me.options.pointLabels.callback, [value, index], me);
9546 return label || label === 0 ? label : '';
9547 });
9548 }
9549 fit() {
9550 const me = this;
9551 const opts = me.options;
9552 if (opts.display && opts.pointLabels.display) {
9553 fitWithPointLabels(me);
9554 } else {
9555 me.setCenterPoint(0, 0, 0, 0);
9556 }
9557 }
9558 _setReductions(largestPossibleRadius, furthestLimits, furthestAngles) {
9559 const me = this;
9560 let radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
9561 let radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
9562 let radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
9563 let radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b);
9564 radiusReductionLeft = numberOrZero(radiusReductionLeft);
9565 radiusReductionRight = numberOrZero(radiusReductionRight);
9566 radiusReductionTop = numberOrZero(radiusReductionTop);
9567 radiusReductionBottom = numberOrZero(radiusReductionBottom);
9568 me.drawingArea = Math.max(largestPossibleRadius / 2, Math.min(
9569 Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
9570 Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)));
9571 me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
9572 }
9573 setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {
9574 const me = this;
9575 const maxRight = me.width - rightMovement - me.drawingArea;
9576 const maxLeft = leftMovement + me.drawingArea;
9577 const maxTop = topMovement + me.drawingArea;
9578 const maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea;
9579 me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left);
9580 me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop);
9581 }
9582 getIndexAngle(index) {
9583 const angleMultiplier = TAU / this.getLabels().length;
9584 const startAngle = this.options.startAngle || 0;
9585 return _normalizeAngle(index * angleMultiplier + toRadians(startAngle));
9586 }
9587 getDistanceFromCenterForValue(value) {
9588 const me = this;
9589 if (isNullOrUndef(value)) {
9590 return NaN;
9591 }
9592 const scalingFactor = me.drawingArea / (me.max - me.min);
9593 if (me.options.reverse) {
9594 return (me.max - value) * scalingFactor;
9595 }
9596 return (value - me.min) * scalingFactor;
9597 }
9598 getValueForDistanceFromCenter(distance) {
9599 if (isNullOrUndef(distance)) {
9600 return NaN;
9601 }
9602 const me = this;
9603 const scaledDistance = distance / (me.drawingArea / (me.max - me.min));
9604 return me.options.reverse ? me.max - scaledDistance : me.min + scaledDistance;
9605 }
9606 getPointPosition(index, distanceFromCenter) {
9607 const me = this;
9608 const angle = me.getIndexAngle(index) - HALF_PI;
9609 return {
9610 x: Math.cos(angle) * distanceFromCenter + me.xCenter,
9611 y: Math.sin(angle) * distanceFromCenter + me.yCenter,
9612 angle
9613 };
9614 }
9615 getPointPositionForValue(index, value) {
9616 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
9617 }
9618 getBasePosition(index) {
9619 return this.getPointPositionForValue(index || 0, this.getBaseValue());
9620 }
9621 getPointLabelPosition(index) {
9622 const {left, top, right, bottom} = this._pointLabelItems[index];
9623 return {
9624 left,
9625 top,
9626 right,
9627 bottom,
9628 };
9629 }
9630 drawBackground() {
9631 const me = this;
9632 const {backgroundColor, grid: {circular}} = me.options;
9633 if (backgroundColor) {
9634 const ctx = me.ctx;
9635 ctx.save();
9636 ctx.beginPath();
9637 pathRadiusLine(me, me.getDistanceFromCenterForValue(me._endValue), circular, me.getLabels().length);
9638 ctx.closePath();
9639 ctx.fillStyle = backgroundColor;
9640 ctx.fill();
9641 ctx.restore();
9642 }
9643 }
9644 drawGrid() {
9645 const me = this;
9646 const ctx = me.ctx;
9647 const opts = me.options;
9648 const {angleLines, grid} = opts;
9649 const labelCount = me.getLabels().length;
9650 let i, offset, position;
9651 if (opts.pointLabels.display) {
9652 drawPointLabels(me, labelCount);
9653 }
9654 if (grid.display) {
9655 me.ticks.forEach((tick, index) => {
9656 if (index !== 0) {
9657 offset = me.getDistanceFromCenterForValue(tick.value);
9658 const optsAtIndex = grid.setContext(me.getContext(index - 1));
9659 drawRadiusLine(me, optsAtIndex, offset, labelCount);
9660 }
9661 });
9662 }
9663 if (angleLines.display) {
9664 ctx.save();
9665 for (i = me.getLabels().length - 1; i >= 0; i--) {
9666 const optsAtIndex = angleLines.setContext(me.getContext(i));
9667 const {color, lineWidth} = optsAtIndex;
9668 if (!lineWidth || !color) {
9669 continue;
9670 }
9671 ctx.lineWidth = lineWidth;
9672 ctx.strokeStyle = color;
9673 ctx.setLineDash(optsAtIndex.borderDash);
9674 ctx.lineDashOffset = optsAtIndex.borderDashOffset;
9675 offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max);
9676 position = me.getPointPosition(i, offset);
9677 ctx.beginPath();
9678 ctx.moveTo(me.xCenter, me.yCenter);
9679 ctx.lineTo(position.x, position.y);
9680 ctx.stroke();
9681 }
9682 ctx.restore();
9683 }
9684 }
9685 drawBorder() {}
9686 drawLabels() {
9687 const me = this;
9688 const ctx = me.ctx;
9689 const opts = me.options;
9690 const tickOpts = opts.ticks;
9691 if (!tickOpts.display) {
9692 return;
9693 }
9694 const startAngle = me.getIndexAngle(0);
9695 let offset, width;
9696 ctx.save();
9697 ctx.translate(me.xCenter, me.yCenter);
9698 ctx.rotate(startAngle);
9699 ctx.textAlign = 'center';
9700 ctx.textBaseline = 'middle';
9701 me.ticks.forEach((tick, index) => {
9702 if (index === 0 && !opts.reverse) {
9703 return;
9704 }
9705 const optsAtIndex = tickOpts.setContext(me.getContext(index));
9706 const tickFont = toFont(optsAtIndex.font);
9707 offset = me.getDistanceFromCenterForValue(me.ticks[index].value);
9708 if (optsAtIndex.showLabelBackdrop) {
9709 width = ctx.measureText(tick.label).width;
9710 ctx.fillStyle = optsAtIndex.backdropColor;
9711 const padding = toPadding(optsAtIndex.backdropPadding);
9712 ctx.fillRect(
9713 -width / 2 - padding.left,
9714 -offset - tickFont.size / 2 - padding.top,
9715 width + padding.width,
9716 tickFont.size + padding.height
9717 );
9718 }
9719 renderText(ctx, tick.label, 0, -offset, tickFont, {
9720 color: optsAtIndex.color,
9721 });
9722 });
9723 ctx.restore();
9724 }
9725 drawTitle() {}
9726 }
9727 RadialLinearScale.id = 'radialLinear';
9728 RadialLinearScale.defaults = {
9729 display: true,
9730 animate: true,
9731 position: 'chartArea',
9732 angleLines: {
9733 display: true,
9734 lineWidth: 1,
9735 borderDash: [],
9736 borderDashOffset: 0.0
9737 },
9738 grid: {
9739 circular: false
9740 },
9741 startAngle: 0,
9742 ticks: {
9743 showLabelBackdrop: true,
9744 callback: Ticks.formatters.numeric
9745 },
9746 pointLabels: {
9747 backdropColor: undefined,
9748 backdropPadding: 2,
9749 display: true,
9750 font: {
9751 size: 10
9752 },
9753 callback(label) {
9754 return label;
9755 },
9756 padding: 5
9757 }
9758 };
9759 RadialLinearScale.defaultRoutes = {
9760 'angleLines.color': 'borderColor',
9761 'pointLabels.color': 'color',
9762 'ticks.color': 'color'
9763 };
9764 RadialLinearScale.descriptors = {
9765 angleLines: {
9766 _fallback: 'grid'
9767 }
9768 };
9769
9770 const INTERVALS = {
9771 millisecond: {common: true, size: 1, steps: 1000},
9772 second: {common: true, size: 1000, steps: 60},
9773 minute: {common: true, size: 60000, steps: 60},
9774 hour: {common: true, size: 3600000, steps: 24},
9775 day: {common: true, size: 86400000, steps: 30},
9776 week: {common: false, size: 604800000, steps: 4},
9777 month: {common: true, size: 2.628e9, steps: 12},
9778 quarter: {common: false, size: 7.884e9, steps: 4},
9779 year: {common: true, size: 3.154e10}
9780 };
9781 const UNITS = (Object.keys(INTERVALS));
9782 function sorter(a, b) {
9783 return a - b;
9784 }
9785 function parse(scale, input) {
9786 if (isNullOrUndef(input)) {
9787 return null;
9788 }
9789 const adapter = scale._adapter;
9790 const {parser, round, isoWeekday} = scale._parseOpts;
9791 let value = input;
9792 if (typeof parser === 'function') {
9793 value = parser(value);
9794 }
9795 if (!isNumberFinite(value)) {
9796 value = typeof parser === 'string'
9797 ? adapter.parse(value, parser)
9798 : adapter.parse(value);
9799 }
9800 if (value === null) {
9801 return null;
9802 }
9803 if (round) {
9804 value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true)
9805 ? adapter.startOf(value, 'isoWeek', isoWeekday)
9806 : adapter.startOf(value, round);
9807 }
9808 return +value;
9809 }
9810 function determineUnitForAutoTicks(minUnit, min, max, capacity) {
9811 const ilen = UNITS.length;
9812 for (let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
9813 const interval = INTERVALS[UNITS[i]];
9814 const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;
9815 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
9816 return UNITS[i];
9817 }
9818 }
9819 return UNITS[ilen - 1];
9820 }
9821 function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
9822 for (let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) {
9823 const unit = UNITS[i];
9824 if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
9825 return unit;
9826 }
9827 }
9828 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
9829 }
9830 function determineMajorUnit(unit) {
9831 for (let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
9832 if (INTERVALS[UNITS[i]].common) {
9833 return UNITS[i];
9834 }
9835 }
9836 }
9837 function addTick(ticks, time, timestamps) {
9838 if (!timestamps) {
9839 ticks[time] = true;
9840 } else if (timestamps.length) {
9841 const {lo, hi} = _lookup(timestamps, time);
9842 const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
9843 ticks[timestamp] = true;
9844 }
9845 }
9846 function setMajorTicks(scale, ticks, map, majorUnit) {
9847 const adapter = scale._adapter;
9848 const first = +adapter.startOf(ticks[0].value, majorUnit);
9849 const last = ticks[ticks.length - 1].value;
9850 let major, index;
9851 for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) {
9852 index = map[major];
9853 if (index >= 0) {
9854 ticks[index].major = true;
9855 }
9856 }
9857 return ticks;
9858 }
9859 function ticksFromTimestamps(scale, values, majorUnit) {
9860 const ticks = [];
9861 const map = {};
9862 const ilen = values.length;
9863 let i, value;
9864 for (i = 0; i < ilen; ++i) {
9865 value = values[i];
9866 map[value] = i;
9867 ticks.push({
9868 value,
9869 major: false
9870 });
9871 }
9872 return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
9873 }
9874 class TimeScale extends Scale {
9875 constructor(props) {
9876 super(props);
9877 this._cache = {
9878 data: [],
9879 labels: [],
9880 all: []
9881 };
9882 this._unit = 'day';
9883 this._majorUnit = undefined;
9884 this._offsets = {};
9885 this._normalized = false;
9886 this._parseOpts = undefined;
9887 }
9888 init(scaleOpts, opts) {
9889 const time = scaleOpts.time || (scaleOpts.time = {});
9890 const adapter = this._adapter = new adapters._date(scaleOpts.adapters.date);
9891 mergeIf(time.displayFormats, adapter.formats());
9892 this._parseOpts = {
9893 parser: time.parser,
9894 round: time.round,
9895 isoWeekday: time.isoWeekday
9896 };
9897 super.init(scaleOpts);
9898 this._normalized = opts.normalized;
9899 }
9900 parse(raw, index) {
9901 if (raw === undefined) {
9902 return null;
9903 }
9904 return parse(this, raw);
9905 }
9906 beforeLayout() {
9907 super.beforeLayout();
9908 this._cache = {
9909 data: [],
9910 labels: [],
9911 all: []
9912 };
9913 }
9914 determineDataLimits() {
9915 const me = this;
9916 const options = me.options;
9917 const adapter = me._adapter;
9918 const unit = options.time.unit || 'day';
9919 let {min, max, minDefined, maxDefined} = me.getUserBounds();
9920 function _applyBounds(bounds) {
9921 if (!minDefined && !isNaN(bounds.min)) {
9922 min = Math.min(min, bounds.min);
9923 }
9924 if (!maxDefined && !isNaN(bounds.max)) {
9925 max = Math.max(max, bounds.max);
9926 }
9927 }
9928 if (!minDefined || !maxDefined) {
9929 _applyBounds(me._getLabelBounds());
9930 if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {
9931 _applyBounds(me.getMinMax(false));
9932 }
9933 }
9934 min = isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
9935 max = isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
9936 me.min = Math.min(min, max - 1);
9937 me.max = Math.max(min + 1, max);
9938 }
9939 _getLabelBounds() {
9940 const arr = this.getLabelTimestamps();
9941 let min = Number.POSITIVE_INFINITY;
9942 let max = Number.NEGATIVE_INFINITY;
9943 if (arr.length) {
9944 min = arr[0];
9945 max = arr[arr.length - 1];
9946 }
9947 return {min, max};
9948 }
9949 buildTicks() {
9950 const me = this;
9951 const options = me.options;
9952 const timeOpts = options.time;
9953 const tickOpts = options.ticks;
9954 const timestamps = tickOpts.source === 'labels' ? me.getLabelTimestamps() : me._generate();
9955 if (options.bounds === 'ticks' && timestamps.length) {
9956 me.min = me._userMin || timestamps[0];
9957 me.max = me._userMax || timestamps[timestamps.length - 1];
9958 }
9959 const min = me.min;
9960 const max = me.max;
9961 const ticks = _filterBetween(timestamps, min, max);
9962 me._unit = timeOpts.unit || (tickOpts.autoSkip
9963 ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, me._getLabelCapacity(min))
9964 : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max));
9965 me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined
9966 : determineMajorUnit(me._unit);
9967 me.initOffsets(timestamps);
9968 if (options.reverse) {
9969 ticks.reverse();
9970 }
9971 return ticksFromTimestamps(me, ticks, me._majorUnit);
9972 }
9973 initOffsets(timestamps) {
9974 const me = this;
9975 let start = 0;
9976 let end = 0;
9977 let first, last;
9978 if (me.options.offset && timestamps.length) {
9979 first = me.getDecimalForValue(timestamps[0]);
9980 if (timestamps.length === 1) {
9981 start = 1 - first;
9982 } else {
9983 start = (me.getDecimalForValue(timestamps[1]) - first) / 2;
9984 }
9985 last = me.getDecimalForValue(timestamps[timestamps.length - 1]);
9986 if (timestamps.length === 1) {
9987 end = last;
9988 } else {
9989 end = (last - me.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
9990 }
9991 }
9992 const limit = timestamps.length < 3 ? 0.5 : 0.25;
9993 start = _limitValue(start, 0, limit);
9994 end = _limitValue(end, 0, limit);
9995 me._offsets = {start, end, factor: 1 / (start + 1 + end)};
9996 }
9997 _generate() {
9998 const me = this;
9999 const adapter = me._adapter;
10000 const min = me.min;
10001 const max = me.max;
10002 const options = me.options;
10003 const timeOpts = options.time;
10004 const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, me._getLabelCapacity(min));
10005 const stepSize = valueOrDefault(timeOpts.stepSize, 1);
10006 const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
10007 const hasWeekday = isNumber(weekday) || weekday === true;
10008 const ticks = {};
10009 let first = min;
10010 let time, count;
10011 if (hasWeekday) {
10012 first = +adapter.startOf(first, 'isoWeek', weekday);
10013 }
10014 first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
10015 if (adapter.diff(max, min, minor) > 100000 * stepSize) {
10016 throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
10017 }
10018 const timestamps = options.ticks.source === 'data' && me.getDataTimestamps();
10019 for (time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++) {
10020 addTick(ticks, time, timestamps);
10021 }
10022 if (time === max || options.bounds === 'ticks' || count === 1) {
10023 addTick(ticks, time, timestamps);
10024 }
10025 return Object.keys(ticks).sort((a, b) => a - b).map(x => +x);
10026 }
10027 getLabelForValue(value) {
10028 const me = this;
10029 const adapter = me._adapter;
10030 const timeOpts = me.options.time;
10031 if (timeOpts.tooltipFormat) {
10032 return adapter.format(value, timeOpts.tooltipFormat);
10033 }
10034 return adapter.format(value, timeOpts.displayFormats.datetime);
10035 }
10036 _tickFormatFunction(time, index, ticks, format) {
10037 const me = this;
10038 const options = me.options;
10039 const formats = options.time.displayFormats;
10040 const unit = me._unit;
10041 const majorUnit = me._majorUnit;
10042 const minorFormat = unit && formats[unit];
10043 const majorFormat = majorUnit && formats[majorUnit];
10044 const tick = ticks[index];
10045 const major = majorUnit && majorFormat && tick && tick.major;
10046 const label = me._adapter.format(time, format || (major ? majorFormat : minorFormat));
10047 const formatter = options.ticks.callback;
10048 return formatter ? callback(formatter, [label, index, ticks], me) : label;
10049 }
10050 generateTickLabels(ticks) {
10051 let i, ilen, tick;
10052 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
10053 tick = ticks[i];
10054 tick.label = this._tickFormatFunction(tick.value, i, ticks);
10055 }
10056 }
10057 getDecimalForValue(value) {
10058 const me = this;
10059 return value === null ? NaN : (value - me.min) / (me.max - me.min);
10060 }
10061 getPixelForValue(value) {
10062 const me = this;
10063 const offsets = me._offsets;
10064 const pos = me.getDecimalForValue(value);
10065 return me.getPixelForDecimal((offsets.start + pos) * offsets.factor);
10066 }
10067 getValueForPixel(pixel) {
10068 const me = this;
10069 const offsets = me._offsets;
10070 const pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
10071 return me.min + pos * (me.max - me.min);
10072 }
10073 _getLabelSize(label) {
10074 const me = this;
10075 const ticksOpts = me.options.ticks;
10076 const tickLabelWidth = me.ctx.measureText(label).width;
10077 const angle = toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
10078 const cosRotation = Math.cos(angle);
10079 const sinRotation = Math.sin(angle);
10080 const tickFontSize = me._resolveTickFontOptions(0).size;
10081 return {
10082 w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation),
10083 h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation)
10084 };
10085 }
10086 _getLabelCapacity(exampleTime) {
10087 const me = this;
10088 const timeOpts = me.options.time;
10089 const displayFormats = timeOpts.displayFormats;
10090 const format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
10091 const exampleLabel = me._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format);
10092 const size = me._getLabelSize(exampleLabel);
10093 const capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h) - 1;
10094 return capacity > 0 ? capacity : 1;
10095 }
10096 getDataTimestamps() {
10097 const me = this;
10098 let timestamps = me._cache.data || [];
10099 let i, ilen;
10100 if (timestamps.length) {
10101 return timestamps;
10102 }
10103 const metas = me.getMatchingVisibleMetas();
10104 if (me._normalized && metas.length) {
10105 return (me._cache.data = metas[0].controller.getAllParsedValues(me));
10106 }
10107 for (i = 0, ilen = metas.length; i < ilen; ++i) {
10108 timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(me));
10109 }
10110 return (me._cache.data = me.normalize(timestamps));
10111 }
10112 getLabelTimestamps() {
10113 const me = this;
10114 const timestamps = me._cache.labels || [];
10115 let i, ilen;
10116 if (timestamps.length) {
10117 return timestamps;
10118 }
10119 const labels = me.getLabels();
10120 for (i = 0, ilen = labels.length; i < ilen; ++i) {
10121 timestamps.push(parse(me, labels[i]));
10122 }
10123 return (me._cache.labels = me._normalized ? timestamps : me.normalize(timestamps));
10124 }
10125 normalize(values) {
10126 return _arrayUnique(values.sort(sorter));
10127 }
10128 }
10129 TimeScale.id = 'time';
10130 TimeScale.defaults = {
10131 bounds: 'data',
10132 adapters: {},
10133 time: {
10134 parser: false,
10135 unit: false,
10136 round: false,
10137 isoWeekday: false,
10138 minUnit: 'millisecond',
10139 displayFormats: {}
10140 },
10141 ticks: {
10142 source: 'auto',
10143 major: {
10144 enabled: false
10145 }
10146 }
10147 };
10148
10149 function interpolate(table, val, reverse) {
10150 let prevSource, nextSource, prevTarget, nextTarget;
10151 if (reverse) {
10152 prevSource = Math.floor(val);
10153 nextSource = Math.ceil(val);
10154 prevTarget = table[prevSource];
10155 nextTarget = table[nextSource];
10156 } else {
10157 const result = _lookup(table, val);
10158 prevTarget = result.lo;
10159 nextTarget = result.hi;
10160 prevSource = table[prevTarget];
10161 nextSource = table[nextTarget];
10162 }
10163 const span = nextSource - prevSource;
10164 return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;
10165 }
10166 class TimeSeriesScale extends TimeScale {
10167 constructor(props) {
10168 super(props);
10169 this._table = [];
10170 this._maxIndex = undefined;
10171 }
10172 initOffsets() {
10173 const me = this;
10174 const timestamps = me._getTimestampsForTable();
10175 me._table = me.buildLookupTable(timestamps);
10176 me._maxIndex = me._table.length - 1;
10177 super.initOffsets(timestamps);
10178 }
10179 buildLookupTable(timestamps) {
10180 const me = this;
10181 const {min, max} = me;
10182 if (!timestamps.length) {
10183 return [
10184 {time: min, pos: 0},
10185 {time: max, pos: 1}
10186 ];
10187 }
10188 const items = [min];
10189 let i, ilen, curr;
10190 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
10191 curr = timestamps[i];
10192 if (curr > min && curr < max) {
10193 items.push(curr);
10194 }
10195 }
10196 items.push(max);
10197 return items;
10198 }
10199 _getTimestampsForTable() {
10200 const me = this;
10201 let timestamps = me._cache.all || [];
10202 if (timestamps.length) {
10203 return timestamps;
10204 }
10205 const data = me.getDataTimestamps();
10206 const label = me.getLabelTimestamps();
10207 if (data.length && label.length) {
10208 timestamps = me.normalize(data.concat(label));
10209 } else {
10210 timestamps = data.length ? data : label;
10211 }
10212 timestamps = me._cache.all = timestamps;
10213 return timestamps;
10214 }
10215 getPixelForValue(value, index) {
10216 const me = this;
10217 const offsets = me._offsets;
10218 const pos = me._normalized && me._maxIndex > 0 && !isNullOrUndef(index)
10219 ? index / me._maxIndex : me.getDecimalForValue(value);
10220 return me.getPixelForDecimal((offsets.start + pos) * offsets.factor);
10221 }
10222 getDecimalForValue(value) {
10223 return interpolate(this._table, value) / this._maxIndex;
10224 }
10225 getValueForPixel(pixel) {
10226 const me = this;
10227 const offsets = me._offsets;
10228 const decimal = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
10229 return interpolate(me._table, decimal * this._maxIndex, true);
10230 }
10231 }
10232 TimeSeriesScale.id = 'timeseries';
10233 TimeSeriesScale.defaults = TimeScale.defaults;
10234
10235 var scales = /*#__PURE__*/Object.freeze({
10236 __proto__: null,
10237 CategoryScale: CategoryScale,
10238 LinearScale: LinearScale,
10239 LogarithmicScale: LogarithmicScale,
10240 RadialLinearScale: RadialLinearScale,
10241 TimeScale: TimeScale,
10242 TimeSeriesScale: TimeSeriesScale
10243 });
10244
10245 const registerables = [
10246 controllers,
10247 elements,
10248 plugins,
10249 scales,
10250 ];
10251
10252 export { Animation, Animations, ArcElement, BarController, BarElement, BasePlatform, BasicPlatform, BubbleController, CategoryScale, Chart, DatasetController, plugin_decimation as Decimation, DomPlatform, DoughnutController, Element, plugin_filler as Filler, Interaction, plugin_legend as Legend, LineController, LineElement, LinearScale, LogarithmicScale, PieController, PointElement, PolarAreaController, RadarController, RadialLinearScale, Scale, ScatterController, Ticks, TimeScale, TimeSeriesScale, plugin_title as Title, plugin_tooltip as Tooltip, adapters as _adapters, animator, controllers, elements, layouts, plugins, registerables, registry, scales };
10253